/* ============================================================
   هوشواره‌ی افق‌آبی — Design system («افق آبی» warm-cream v1)
   Tokens per docs/Redesign project files/Ofogh-Abi-Design-System:
   cream canvas (#FAF6EF) with a soft blue/terracotta halo, warm
   sand surfaces, brand blue #265FB0, terracotta accent #C96F4A.
   RTL-first; LTR is a guest.
   ============================================================ */

:root {
  /* Brand ramp — «آبیِ برند» #265FB0 family */
  --brand-50:  #eaf1fb;
  --brand-100: #d8e6f8;
  --brand-200: #b9d2f0;
  --brand-300: #8fb4e4;
  --brand-500: #3573c8;
  --brand-600: #265fb0;
  --brand-700: #1f4f93;
  --brand-900: #163a6d;

  /* Horizon accent — terracotta (sun on the horizon) */
  --accent-400: #c96f4a;
  --accent-500: #a85b3a;
  --accent-50:  #f8e9df;

  /* Editorial «زرشکی» — a deep berry used sparingly in blog prose alongside brand blue. */
  --blog-crimson: #9b2c48;
  --blog-crimson-dim: #c98198;
  --blog-crimson-border: #e2b6c1;
  --blog-crimson-soft: #f7e7ec;

  /* Warm-cream neutrals */
  --app-bg: #faf6ef;
  --app-surface: #ffffff;
  --app-surface-2: #f6f1e6;
  /* The "question" bubble (Chat + Studio user turn) — a warm-neutral shade visibly darker than
     the page canvas, deliberately decoupled from --app-primary-soft (which stays reserved for
     actual brand/selection accents elsewhere) so the user's own message reads as a neutral card,
     not a blue one. */
  --app-question-bg: #f1ebdd;
  --app-border: #ece3d4;
  --app-ink: #2a2118;
  --app-ink-2: #5d5347;
  --app-muted: #7c7163; /* ~4.6:1 on the cream canvas — keeps WCAG AA for secondary text */
  --scrollbar-thumb: rgba(42, 33, 24, .16);          /* faint at rest */
  --scrollbar-thumb-strong: rgba(42, 33, 24, .34);   /* on hover */

  /* ---- Sidebar surface ----
     The sidebar used to be a cold saturated navy (#0F1D3D) hard-coded across ~20 rules — the one
     element that did not belong to this warm-cream system, which is precisely why it read as heavy:
     a warm canvas meeting a cold slab, with an L* ~97 → ~11 cliff and no step between. It is now a
     QUIETER VERSION OF THE CANVAS instead of an inversion of it, expressed as tokens so the whole
     surface can be re-themed in one place (and so night mode gets a real variant, not an override).
     Side benefit: on a light ground the brand blue appears only where it means something — the
     active row — instead of dissolving into a sea of navy. */
  /* Lightened a step from the original #f2ebdd. This lands on --app-surface-2, a tone the design
     system already owns, so the sidebar is now the system's existing "warm sand surface" rather
     than a one-off mix. Everything below moves with it by the same amount, so the sidebar's INTERNAL
     relationships (raised fills, rules) are unchanged — only the overall lightness. --side-edge is
     deliberately NOT lightened: with the ground now closer to the canvas, the hairline is what keeps
     the seam legible. */
  --side-bg: #f6f1e6;              /* = --app-surface-2 */
  --side-bg-2: #f2ece0;            /* foot of the gradient */
  --side-edge: #e4dccd;            /* hairline against the canvas — held, see above */
  --side-ink: #2a2118;             /* = --app-ink; 14.0:1 on --side-bg */
  /* NOT --app-muted (#7c7163): that tone drops to ~4.2:1 here and below 4:1 on the raised fills,
     i.e. under AA for the section labels, group headings and account captions that use it. */
  --side-dim: #6b604e;             /* 5.5:1 on --side-bg, 5.0:1 on --side-raised */
  --side-hover: rgba(42, 33, 24, .07);
  --side-rule: #e2d9c7;
  --side-raised: #ebe4d5;          /* search field, «گفت‌وگوی جدید» rest state */
  --side-raised-line: #ded4c0;
  --side-active-bg: var(--brand-600);
  --side-active-ink: #ffffff;
  --side-foot-bg: rgba(42, 33, 24, .045);
  --side-scroll-thumb: rgba(42, 33, 24, .18);
  --side-scroll-thumb-strong: rgba(42, 33, 24, .34);

  /* Status */
  --ok-500: #2f9e6f;  --ok-50: #e7f4ee;
  --warn-500: #b8801f; --warn-50: #f7eed8;
  --bad-500: #c0432b;  --bad-50: #f6e4de;

  --app-primary: var(--brand-600);
  --app-primary-soft: var(--brand-50);
  --app-radius: 16px;
  --app-radius-lg: 22px;
  --shadow-1: 0 2px 10px rgba(74,58,38,.05);
  --shadow-2: 0 14px 34px rgba(74,58,38,.12);

  /* Bootstrap's own primary/link vars must speak the same blue, or components that
     read var(--bs-primary) (outlines, pills, focus tints) fall back to Bootstrap blue. */
  --bs-primary: #265fb0;
  --bs-primary-rgb: 38, 95, 176;
  --bs-link-color: #265fb0;
  --bs-link-color-rgb: 38, 95, 176;
  --bs-link-hover-color: #1f4f93;
  --bs-link-hover-color-rgb: 31, 79, 147;
}

/* ---------- Admin model-health control room ---------- */
.mh-stat-button {
  display: flex;
  min-height: 112px;
  flex-direction: column;
  align-items: flex-start;
  gap: .35rem;
  border: 1px solid var(--app-border);
  color: var(--app-ink);
}
.mh-stat-button:hover { border-color: var(--brand-300); box-shadow: var(--shadow-2); }
.mh-stat-button .stat-value { font-size: 1.55rem; line-height: 1.2; }
.mh-run-strip .progress { height: .65rem; }
.mh-filter-bar { position: sticky; top: 62px; z-index: 20; }
.mh-failure-filter { cursor: pointer; padding: .45rem .7rem; }
.mh-failure-filter.active { outline: 2px solid var(--brand-500); outline-offset: 2px; }
.mh-table { min-width: 1260px; }
.mh-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  white-space: nowrap;
  background: var(--app-surface-2);
}
.mh-table tbody td { vertical-align: middle; }
.mh-model-cell { min-width: 235px; }
.mh-reason-cell { min-width: 270px; max-width: 360px; }
.mh-error-preview {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow-wrap: anywhere;
}
.mh-mobile-cards { display: none; }
.mh-drawer { width: min(560px, 100vw) !important; }
.mh-drawer .offcanvas-header { position: sticky; top: 0; z-index: 3; background: var(--app-surface); border-bottom: 1px solid var(--app-border); }
.mh-drawer-footer { position: sticky; bottom: 0; z-index: 3; background: var(--app-surface); padding-bottom: max(1rem, env(safe-area-inset-bottom)) !important; }
.mh-error-box {
  max-height: 220px;
  overflow: auto;
  padding: .8rem;
  border: 1px solid var(--app-border);
  border-radius: 10px;
  background: var(--app-surface-2);
  color: var(--app-ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: .78rem;
}
.model-catalog-toolbar { position: sticky; top: 62px; z-index: 20; }
.model-catalog-table { min-width: 900px; }
.catalog-editor > summary { display: inline-flex; list-style: none; cursor: pointer; }
.catalog-editor > summary::-webkit-details-marker { display: none; }

@media (max-width: 767.98px) {
  .mh-filter-bar, .model-catalog-toolbar { position: static; }
  .mh-desktop-table { display: none; }
  .mh-mobile-cards { display: block; }
  .mh-mobile-cards .surface { overflow-wrap: anywhere; }
  .mh-mobile-cards .btn { min-height: 44px; }
  .mh-drawer { width: 100vw !important; }
  .mh-stats .stat-card { min-height: 98px; }
}

html { font-size: 14px; position: relative; min-height: 100%; scroll-behavior: smooth; }
@media (min-width: 768px) { html { font-size: 15.5px; } }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

body {
  font-family: 'Vazirmatn', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
  /* The «افق آبی» halo: two very soft radial washes — blue up top, warm terracotta below —
     over the cream canvas. Gives the page calm depth without weight (per the design system). */
  background:
    radial-gradient(1300px 680px at 78% -200px, rgba(38,95,176,.07), transparent 72%),
    radial-gradient(1200px 720px at 20% 96%, rgba(201,111,74,.05), transparent 70%),
    var(--app-bg);
  color: var(--app-ink);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
main { flex: 1 0 auto; }
/* The public layout wraps <main> in a .container, so flex-grow on <main> alone never
   pushed the footer down — the .container (not <main>) is body's flex child. Grow that
   wrapper so the footer sticks to the viewport bottom on short pages, instead of floating
   up and leaving a dead band below it (which reads as a "black void" in dark mode).
   Only the body's direct .container (the main wrapper) matches; navbar/footer .containers
   are nested deeper. The shell (panel/admin) layouts don't use this structure. */
body > .container { flex: 1 0 auto; }

/* Keep code / ids / prices LTR + monospace */
pre, code, kbd, samp, .ltr { font-family: ui-monospace, SFMono-Regular, 'Cascadia Code', Consolas, monospace; }
pre, code, kbd, samp, .ltr { direction: ltr; }
[dir="ltr"] { font-family: ui-monospace, SFMono-Regular, 'Cascadia Code', Consolas, monospace; }
/* …but the chat thread takes dir="ltr" for LTR *language* (English conversations),
   NOT because the text is a code/id fragment — so the rule above must not turn the
   whole conversation monospace. Restore the UI font on the chat surfaces. Real code
   blocks (pre/code) and inline dir="ltr" id/price fragments inside a reply keep mono
   via their own rules above (they match those selectors directly). */
.chat-msgs[dir="ltr"], .bubble[dir="ltr"], #chatInput[dir="ltr"], .edit-box textarea[dir="ltr"] {
  font-family: 'Vazirmatn', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
}

a { text-decoration: none; color: var(--brand-600); }
a:hover { color: var(--brand-700); }
h1,h2,h3,h4,h5 { font-weight: 700; }
::selection { background: var(--accent-400); color: #fff; }

/* ---------- Navbar (public) ---------- */
.app-navbar {
  background: rgba(250,246,239,.82);
  backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid #efe7d9;
}
.app-navbar .navbar-brand { color: var(--app-ink); font-size: 1.15rem; font-weight: 800; }
.app-navbar .brand-dot { color: var(--accent-500); }
.app-navbar .nav-link { color: var(--app-ink-2); border-radius: 9px; padding: .35rem .8rem; font-weight: 500; }
.app-navbar .nav-link:hover, .app-navbar .nav-link.active { background: var(--app-primary-soft); color: var(--brand-700); }
/* Pill CTA buttons in the public nav (per the mockup's rounded «شروع کنید»). */
.app-navbar .btn { border-radius: 999px; }

/* ---------- Brand logo ---------- */
.brand-logo { height: 2rem; width: auto; display: block; }
.app-navbar .brand-logo { height: 2.1rem; }
.app-footer .brand-logo { height: 1.9rem; }
/* (The sidebar used to carry the brandmark in a white rounded chip. The sidebar head shows the
   wordmark only now, so those two rules are gone — .brand-logo* below still serves the public
   navbar, the footer and the auth pages.) */

/* Theme-aware brandmark: the white-bg icon (logo.png) in light theme, the dark-bg icon
   (logo-dark.png) in dark theme. Pure CSS off data-bs-theme — set before first paint, so no
   flash — and it tracks the live theme toggle. Both imgs render; only the active one shows. */
.brand-logo-dark { display: none; }
[data-bs-theme="dark"] .brand-logo-light { display: none; }
[data-bs-theme="dark"] .brand-logo-dark { display: block; }

/* ---------- Cards / surfaces ---------- */
.card { border: 1px solid var(--app-border); border-radius: var(--app-radius); box-shadow: var(--shadow-1); }
.surface {
  background: var(--app-surface); border: 1px solid var(--app-border);
  border-radius: var(--app-radius); padding: 1.1rem 1.25rem; box-shadow: var(--shadow-1);
}
.surface-2 { background: var(--app-surface-2); }

/* ---------- Buttons / inputs ---------- */
.btn { border-radius: 10px; font-weight: 600; }
.btn-primary { background: var(--brand-600); border-color: var(--brand-600); }
.btn-primary:hover, .btn-primary:focus { background: var(--brand-700); border-color: var(--brand-700); }
.btn-outline-primary { color: var(--brand-600); border-color: var(--brand-300); }
.btn-outline-primary:hover { background: var(--brand-600); border-color: var(--brand-600); }
.btn-success { background: var(--ok-500); border-color: var(--ok-500); }
.form-control, .form-select { border-radius: 10px; border-color: var(--app-border); }
.form-control:focus, .form-select:focus, .form-check-input:focus {
  border-color: var(--brand-300); box-shadow: 0 0 0 .2rem rgba(38,95,176,.12);
}
.form-check-input:checked { background-color: var(--brand-600); border-color: var(--brand-600); }
.nav-tabs .nav-link { color: var(--app-ink-2); }
.nav-tabs .nav-link.active { color: var(--brand-700); font-weight: 600; }
label.form-label { font-weight: 600; font-size: .9rem; color: var(--app-ink-2); }
.input-hint { font-size: .78rem; color: var(--app-muted); }

/* ---------- Badges ---------- */
.badge-soft { border-radius: 999px; padding: .3em .75em; font-weight: 600; font-size: .76rem; }
.badge-soft.ok   { background: var(--ok-50);   color: #22795a; }
.badge-soft.warn { background: var(--warn-50); color: #7a5e1c; }
.badge-soft.bad  { background: var(--bad-50);  color: #a1361f; }
.badge-soft.info { background: var(--brand-50); color: var(--brand-700); }
.badge-soft.mute { background: var(--app-surface-2); color: var(--app-muted); }

/* In-chat "what is this model?" card under the picker — a collapsible <details>, closed by
   default so it stays small (one line) and doesn't crowd the chat, especially on mobile. */
.model-info {
  background: linear-gradient(180deg, #ffffff 0%, var(--brand-50) 140%);
  border: 1px solid var(--app-border);
  border-radius: 12px;
  padding: .4rem .7rem;
}
.model-info > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: .4rem; font-size: .9rem;
}
.model-info > summary::-webkit-details-marker { display: none; }
.model-info > summary::after {
  content: '▾'; margin-inline-start: auto; color: var(--app-muted); font-size: .8rem; transition: transform .15s;
}
.model-info[open] > summary::after { transform: rotate(180deg); }
.model-info .mi-body { margin-top: .5rem; }
.model-info .mi-badges { display: flex; flex-wrap: wrap; gap: .35rem; }
.model-info .mi-desc { color: var(--app-muted); font-size: .83rem; line-height: 1.75; margin-top: .45rem; }

/* "this model failed — continue with another?" offer under a retried error bubble */
.switch-offer { width: 100%; margin-top: .6rem; padding: .6rem .75rem; background: var(--warn-50); border: 1px solid var(--app-border); border-radius: 10px; }

/* per-reply "🤖 model · 🎭 assistant" tag, toggled by «🏷 نمایش مدل» */
.msg-model { font-size: .72rem; color: var(--app-muted); margin-top: .2rem; }
.hide-model-labels .msg-model { display: none; }

/* date · time: a compact element that sits on the same footer row as the action buttons (the
   buttons pack to the right; this gets pushed to the LEFT by margin-inline-start:auto) — directly
   under the bubble with no extra row/gap. */
.msg-time { width: auto; font-size: .66rem; color: var(--app-muted); margin-top: .25rem; opacity: .8; }
.cmsg.user .msg-time { margin-inline-start: auto; }   /* push the user's time to the left of the footer row */
.cmsg.assistant .msg-time { text-align: start; }
/* Assistant footer: model name + date/time on ONE line, pushed LEFT, sharing the footer row with the
   actions (which pack right). margin-inline-start:auto absorbs the free space on its right → left. */
.msg-foot { width: auto; margin-inline-start: auto; display: flex; flex-wrap: wrap; align-items: baseline;
  gap: .15rem .55rem; margin-top: .25rem; }
.msg-foot .msg-model, .msg-foot .msg-time { width: auto; margin-top: 0; margin-inline-start: 0; }

/* ---------- Landing (per the Ofogh-Abi Homepage mockup) ----------
   The hero sits directly on the cream canvas (no framed card); the halo comes
   from the body background. */
.home-hero { background: transparent; border: 0; border-radius: 0; overflow: visible; }
.hero-title { font-size: clamp(2rem, 4.8vw, 3.4rem); font-weight: 800; line-height: 1.3; letter-spacing: -.015em; color: var(--app-ink); }
/* Two-tone emphasis words inside the hero headline (blue + terracotta). */
.hero-title .t-blue { color: var(--brand-600); }
.hero-title .t-clay { color: var(--accent-400); }
.hero-title .grad {
  background: linear-gradient(90deg, var(--brand-700), var(--brand-500));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero-sub { color: var(--app-muted); font-size: 1.1rem; line-height: 1.95; max-width: 500px; }

/* Warm sand hero chips; the «افق آبی» one wears the terracotta sunrise tint. */
.hero-chip {
  display: inline-flex; align-items: center; gap: .35rem;
  font-size: .8rem; font-weight: 500; color: var(--app-ink-2);
  background: #f1ebe0; border: 1px solid #e6dccb;
  border-radius: 999px; padding: .34rem .8rem;
}
.hero-chip .ic { width: 1em; height: 1em; color: var(--brand-500); }
.hero-chip.chip-ofogh { color: var(--accent-500); background: var(--accent-50); border-color: #efd6c5; }
[data-bs-theme="dark"] .hero-chip { background: var(--app-surface); border-color: var(--app-border); }
[data-bs-theme="dark"] .hero-chip.chip-ofogh { background: rgba(201,111,74,.14); border-color: #7a4630; color: #e8a988; }

/* Pill CTAs in the hero */
.btn-pill { border-radius: 999px !important; }
.btn-hero-primary {
  background: var(--brand-600); border-color: var(--brand-600); color: #fff;
  border-radius: 999px; font-weight: 600; box-shadow: 0 6px 18px rgba(38,95,176,.25);
}
.btn-hero-primary:hover { background: var(--brand-700); border-color: var(--brand-700); color: #fff; }
.btn-hero-ghost {
  background: var(--app-surface); color: var(--brand-600); border: 1px solid #d8c9b3;
  border-radius: 999px; font-weight: 600;
}
.btn-hero-ghost:hover { color: var(--brand-700); background: var(--app-surface-2); border-color: #d8c9b3; }
[data-bs-theme="dark"] .btn-hero-ghost { border-color: var(--app-border); color: var(--brand-300); }

/* Hero stats: ONE contained card (per the mobile mockup) — three equal, centered
   columns split by hairlines, so the numbers read as a single confident unit
   instead of loose figures floating on the canvas. Digits are Latin + LTR (mono
   via the [dir="ltr"] rule), per the design system's numbers rule. */
.hero-stats {
  display: flex; align-items: stretch; max-width: 30rem;
  background: var(--app-surface); border: 1px solid var(--app-border);
  border-radius: 18px; padding: 1rem .35rem; box-shadow: var(--shadow-1);
}
.hero-stats .hs-cell { flex: 1 1 0; min-width: 0; text-align: center; padding: 0 .45rem; }
.hero-stats .hs-sep { width: 1px; align-self: center; height: 36px; background: var(--app-border); flex: none; }
.hero-stats .hs-num { font-size: 1.4rem; font-weight: 600; color: var(--brand-600); letter-spacing: -.01em; line-height: 1.2; }
.hero-stats .hs-cap { font-size: .74rem; color: var(--app-muted); margin-top: .2rem; line-height: 1.6; }

/* Live-chat demo card in the hero (static preview, no JS). */
.chat-demo {
  background: var(--app-surface); border: 1px solid var(--app-border); border-radius: 22px;
  box-shadow: 0 18px 44px rgba(74,58,38,.10); padding: 1.1rem;
}
.chat-demo .cd-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: .85rem; }
.chat-demo .cd-live { font-size: .8rem; color: var(--app-ink-2); display: inline-flex; align-items: center; gap: .45rem; }
.chat-demo .cd-live .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok-500); display: inline-block; }
.chat-demo .cd-model { font-size: .74rem; color: var(--brand-600); background: var(--brand-50); padding: .2rem .7rem; border-radius: 12px; font-weight: 600; }
.chat-demo .cd-q {
  background: var(--brand-600); color: #fff; font-size: .88rem; line-height: 1.7;
  padding: .7rem .9rem; border-radius: 16px 16px 5px 16px; margin-bottom: .6rem; max-width: 84%;
}
.chat-demo .cd-a {
  background: #f4efe6; color: #3a3228; font-size: .88rem; line-height: 1.85;
  padding: .75rem .9rem; border-radius: 16px 16px 16px 5px; max-width: 94%;
}
[data-bs-theme="dark"] .chat-demo .cd-a { background: var(--app-surface-2); color: var(--app-ink-2); }
.chat-demo .cd-caret { display: inline-block; width: 2px; height: .95em; background: var(--accent-400); vertical-align: -.15em; margin-inline-start: 3px; }
.chat-demo .cd-cost {
  display: flex; justify-content: space-between; align-items: center;
  margin-top: 1rem; padding-top: .8rem; border-top: 1px dashed #ece1cf;
}
[data-bs-theme="dark"] .chat-demo .cd-cost { border-top-color: var(--app-border); }
.chat-demo .cd-cost .lbl { font-size: .78rem; color: var(--accent-500); }
.chat-demo .cd-cost .val { font-size: .9rem; font-weight: 600; color: var(--app-ink); font-variant-numeric: tabular-nums; }
.chat-demo .cd-composer { display: flex; gap: .5rem; margin-top: .85rem; }
.chat-demo .cd-input {
  flex: 1; display: flex; align-items: center; background: var(--app-bg);
  border: 1px solid #efe7d9; border-radius: 14px; padding: .55rem .75rem;
  font-size: .78rem; color: var(--app-muted);
}
[data-bs-theme="dark"] .chat-demo .cd-input { border-color: var(--app-border); }
.chat-demo .cd-send {
  width: 42px; height: 42px; border-radius: 14px; background: var(--brand-600);
  display: grid; place-items: center; color: #fff; flex: none;
}
.chat-demo .cd-send .ic { width: 1.1rem; height: 1.1rem; }

/* Providers strip — a centered label line over a row of quiet maker chips, so the
   names read as a deliberate, contained band (not loose grey words at the edge). */
.providers-strip { text-align: center; }
.providers-strip .ps-label {
  display: block; font-size: .78rem; color: var(--app-muted); margin-bottom: .7rem;
}
.providers-strip .ps-row {
  display: flex; align-items: center; justify-content: center; gap: .5rem .6rem; flex-wrap: wrap;
}
.providers-strip .ps-name {
  direction: ltr; unicode-bidi: isolate;
  font-size: .82rem; font-weight: 700; color: var(--app-ink-2);
  background: var(--app-surface); border: 1px solid var(--app-border);
  border-radius: 999px; padding: .32rem .9rem;
}

/* Section eyebrow — small terracotta overline above section titles. */
.sec-eyebrow {
  font-size: .8rem; font-weight: 700; letter-spacing: .14em; color: var(--accent-400);
  text-transform: uppercase; margin-bottom: .6rem;
}

/* Full-bleed warm sand band (models / pricing sections). Breaks out of the page
   .container to span the viewport, with its own inner container. */
.band {
  margin-inline: calc(50% - 50vw); padding-block: 3.4rem;
  background: #f4eee3; border-block: 1px solid #efe7d9;
}
[data-bs-theme="dark"] .band { background: var(--app-surface-2); border-block-color: var(--app-border); }

/* Capability / lane cards */
.cap-card { background: var(--app-surface); border: 1px solid var(--app-border); border-radius: 20px; padding: 1.4rem; height: 100%; }
.cap-card .cc-icon { width: 46px; height: 46px; border-radius: 14px; display: grid; place-items: center; margin-bottom: .9rem; }
.cap-card .cc-title { font-size: 1.05rem; font-weight: 700; color: var(--app-ink); margin-bottom: .3rem; }
.cap-card .cc-desc { font-size: .85rem; line-height: 1.8; color: var(--app-muted); margin: 0; }

.lane-card {
  background: var(--app-surface); border: 1px solid var(--app-border); border-radius: 22px;
  padding: 1.6rem; height: 100%; display: flex; flex-direction: column;
}
.lane-card .lc-title { font-size: 1.2rem; font-weight: 800; color: var(--app-ink); }
.lane-card .lc-sub { font-size: .85rem; color: var(--app-muted); margin-top: .35rem; line-height: 1.8; }
.lane-card .lc-list { display: flex; flex-direction: column; gap: .65rem; margin-top: 1.1rem; font-size: .86rem; color: #3a3228; }
[data-bs-theme="dark"] .lane-card .lc-list { color: var(--app-ink-2); }
.lane-card .lc-list .ic { width: 1.05rem; height: 1.05rem; color: var(--brand-600); flex: none; }
.lane-card .lc-link { margin-top: auto; padding-top: 1.3rem; font-size: .88rem; font-weight: 600; color: var(--brand-600); }
.lane-card .lc-link:hover { color: var(--brand-700); }
/* The B2B lane is the filled blue card. */
.lane-card.lane-featured { background: var(--brand-600); border-color: #214f90; box-shadow: 0 14px 34px rgba(38,95,176,.28); }
.lane-card.lane-featured .lc-title { color: #fff; }
.lane-card.lane-featured .lc-sub { color: #d4e2f5; }
.lane-card.lane-featured .lc-list { color: #eef4fc; }
.lane-card.lane-featured .lc-list .ic { color: #9fc1ec; }
.lane-card.lane-featured .lc-link { color: #fff; }
.lane-card .lc-tag { font-size: .72rem; font-weight: 600; color: #fff; background: rgba(255,255,255,.18); padding: .15rem .6rem; border-radius: 10px; }
/* Dev lane code sample */
.lane-card .lc-code {
  background: #2a2118; border-radius: 14px; padding: .8rem .95rem; margin-top: 1rem; overflow: hidden;
}
.lane-card .lc-code pre { margin: 0; font-size: .74rem; line-height: 1.7; color: #e8ddc9; white-space: pre-wrap; word-break: break-all; background: transparent; padding: 0; }
.lane-card .lc-code .ck { color: var(--accent-400); }
.lane-card .lc-code .cv { color: #9fc1ec; }

/* ---------- Inline SVG icon set (Lucide) ---------- */
.ic {
  width: 1.35em; height: 1.35em; flex-shrink: 0; display: inline-block;
  vertical-align: -.2em; fill: none; stroke: currentColor; stroke-width: 2.25;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Icon sitting before a heading or inside a button */
h1 .ic, h2 .ic, h3 .ic, h4 .ic, h5 .ic, .btn .ic { vertical-align: -.15em; }
.ic-head { width: 1.15em; height: 1.15em; color: var(--brand-600); margin-inline-end: .3rem; }

/* Coloured icon tiles — each feature / trust item gets its own hue */
.icon-tile { display: grid; place-items: center; border-radius: 14px; flex-shrink: 0; }
.icon-tile .ic { stroke-width: 2.4; }
/* The warm palette speaks in four voices: brand blue, terracotta, amber and green.
   The old violet/cyan/pink hues are mapped onto those so every tile belongs. */
.tile-blue    { background: #eaf1fb; color: #265fb0; }
.tile-emerald { background: #e7f4ee; color: #2f9e6f; }
.tile-amber   { background: #f7eed8; color: #b8801f; }
.tile-violet  { background: #f8e9df; color: #c96f4a; }
.tile-cyan    { background: #eaf1fb; color: #1f4f93; }
.tile-pink    { background: #f8e9df; color: #a85b3a; }

/* Product preview card in the hero */
.hero-preview .preview-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: .6rem; }
.hero-preview .online { display: inline-flex; align-items: center; gap: .35rem; font-size: .76rem; color: #047857; }
.hero-preview .online .dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--ok-500); }
.hero-preview .models-row { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .7rem; }
.hero-preview .model-tag { font-size: .68rem; color: var(--app-muted); background: var(--app-surface-2); border: 1px solid var(--app-border); border-radius: 999px; padding: .12rem .5rem; }

/* ---------- Trust strip ---------- */
.trust-strip {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem;
  background: var(--app-surface); border: 1px solid var(--app-border);
  border-radius: var(--app-radius); padding: 1.1rem 1.3rem; box-shadow: var(--shadow-1);
}
.trust-item { display: flex; align-items: center; gap: .7rem; }
.trust-item .icon-tile { width: 44px; height: 44px; }
.trust-item .icon-tile .ic { width: 24px; height: 24px; }
.trust-item .t-main { font-weight: 700; font-size: .95rem; line-height: 1.2; }
.trust-item .t-sub { font-size: .74rem; color: var(--app-muted); }
@media (max-width: 767px) { .trust-strip { grid-template-columns: repeat(2, 1fr); gap: 1.1rem; } }

.feature-card { height: 100%; transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease; }
.feature-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-2); border-color: var(--brand-300); }
.feature-icon { width: 56px; height: 56px; margin-bottom: .9rem; }
.feature-icon .ic { width: 30px; height: 30px; }

.section-title { font-size: clamp(1.4rem, 2.6vw, 1.7rem); font-weight: 800; letter-spacing: -.01em; }
.section-sub { color: var(--app-muted); }

/* Pricing */
.price-card {
  position: relative; height: 100%; text-align: center; padding: 1.7rem 1.3rem;
  transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.price-card.plan { display: flex; flex-direction: column; }
.price-card.plan .btn { margin-top: auto; }      /* CTAs align across uneven cards */
.price-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-2); border-color: var(--brand-300); }
/* No overflow:hidden here — it was clipping the ribbon, which sits at top:-12px (above the
   card edge) and so rendered "half cut off". The top accent bar instead inherits the card's
   corner radius so it doesn't poke past the rounded corners. */
.price-card.featured { border: 2px solid var(--brand-500); box-shadow: var(--shadow-2); }
.price-card.featured::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 4px;
  background: linear-gradient(90deg, var(--brand-500), var(--brand-700));
  border-top-left-radius: inherit; border-top-right-radius: inherit;
}
.price-card .ribbon {
  position: absolute; top: -12px; left: 50%; right: auto; transform: translateX(-50%);
  background: var(--brand-600); color: #fff; font-size: .75rem; font-weight: 700;
  padding: .25rem .9rem; border-radius: 999px; white-space: nowrap;
}
.plan-tag {
  display: inline-block; font-size: .72rem; font-weight: 700;
  padding: .2rem .7rem; border-radius: 999px; margin-top: .15rem;
}
.plan-tag.sub { background: rgba(38,95,176,.12); color: var(--brand-700); }
.plan-tag.wallet { background: var(--app-surface-2); color: var(--app-ink-2); border: 1px solid var(--app-border); }
.plan-tag.free { background: var(--accent-50, #f8e9df); color: var(--accent-500); }
[data-bs-theme="dark"] .plan-tag.free { background: rgba(201,111,74,.16); color: #e8a988; }

/* "Renew current tier" shortcut card on the Buy page. */
.renew-card { border-color: var(--brand-100); background: var(--brand-50); }
[data-bs-theme="dark"] .renew-card { border-color: var(--brand-700); background: rgba(38,95,176,.10); }

/* «افق آبی» starter card — the 🌅 terracotta sunrise accent sets it apart from the blue paid plans. */
.price-card.free { border: 1.5px solid #e0b39e; background: linear-gradient(180deg, rgba(201,111,74,.07), transparent 60%); }
.price-card.free::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 4px;
  background: linear-gradient(90deg, var(--accent-400), var(--brand-500));
  border-top-left-radius: inherit; border-top-right-radius: inherit;
}
.price-card.free:hover { border-color: var(--accent-400); }
[data-bs-theme="dark"] .price-card.free { border-color: #7a4630; background: linear-gradient(180deg, rgba(201,111,74,.12), transparent 60%); }
.price-suffix { font-size: .82rem; font-weight: 600; color: var(--app-ink-2); margin-inline-start: .25rem; }
.price-models {
  display: inline-flex; align-items: center; gap: .4rem; font-weight: 700; font-size: .88rem;
  color: var(--brand-700); background: rgba(38,95,176,.10); padding: .28rem .8rem; border-radius: 999px;
}
.price-models .ic { width: 1.05em; height: 1.05em; }

/* Link to a package's "supported models" page (filtered catalog with search + filters). */
.pkg-models-link {
  display: inline-flex; align-items: center; gap: .35rem; font-weight: 600; font-size: .85rem;
  color: var(--brand-700); background: rgba(38,95,176,.10); padding: .28rem .8rem; border-radius: 999px;
  text-decoration: none; transition: background .15s ease;
}
.pkg-models-link:hover { background: rgba(38,95,176,.18); }
.pkg-models-link .ic { width: 1.05em; height: 1.05em; flex: 0 0 auto; }
[data-bs-theme="dark"] .pkg-models-link { color: var(--brand-300); background: rgba(143,180,228,.14); }

.price-divider { height: 1px; background: var(--app-border); margin: .9rem 0; }
.price-card .feat-ic { width: 1.1em; height: 1.1em; margin-top: .15em; flex: 0 0 auto; }
[data-bs-theme="dark"] .plan-tag.sub,
[data-bs-theme="dark"] .price-models { color: var(--brand-300); background: rgba(143,180,228,.14); }

/* ---------- Final call-to-action band (mockup: blue gradient + terracotta halo) ---------- */
.home-cta {
  position: relative; overflow: hidden;
  text-align: center; border-radius: 28px;
  background:
    radial-gradient(60% 120% at 80% -10%, rgba(201,111,74,.35), transparent 60%),
    linear-gradient(120deg, var(--brand-600), var(--brand-700));
  color: #fff;
}
.home-cta .section-title { color: #fff; }
.home-cta .section-sub { color: #d4e2f5; }
.home-cta .btn-light { background: #fff; color: var(--brand-600); font-weight: 700; border: none; border-radius: 999px; }
.home-cta .btn-light:hover { background: #f1f5ff; color: var(--brand-900); }
.home-cta .btn-ghost {
  background: rgba(255,255,255,.12); color: #fff; font-weight: 600; border: 1px solid rgba(255,255,255,.3); border-radius: 999px;
}
.home-cta .btn-ghost:hover { background: rgba(255,255,255,.2); color: #fff; }
.price-amount { font-size: 1.7rem; font-weight: 800; color: var(--brand-700); }
.price-credit { color: var(--app-ink-2); }

/* Power banner — "N powerful models from M top companies" */
.makers-banner {
  position: relative; overflow: hidden; border-radius: var(--app-radius-lg);
  padding: 2.4rem 1.5rem; color: #fff;
  background:
    radial-gradient(600px 280px at 50% -30%, rgba(255,255,255,.16), transparent 60%),
    linear-gradient(135deg, var(--brand-700) 0%, var(--brand-900) 100%);
}
.makers-banner-line { font-size: clamp(1.1rem, 2.6vw, 1.7rem); font-weight: 700; line-height: 1.9; margin: 0; }
.makers-banner .mk-num {
  font-size: clamp(1.9rem, 5vw, 3rem); font-weight: 900; line-height: 1; padding: 0 .12em;
  background: linear-gradient(180deg, #fff, #d4e2f5);
  -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
}
.makers-banner .mk-tail { font-size: clamp(1.25rem, 3vw, 2rem); font-weight: 800; }
.makers-banner .btn-light { background: #fff; color: var(--brand-700); font-weight: 700; border: none; }
.makers-banner .btn-light:hover { background: #f1f5ff; color: var(--brand-900); }

/* Public model catalog — company cards (logo tile + big name + count), expand to models */
.company-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr)); gap: .8rem; align-items: start; }
.company-card { padding: 0; overflow: hidden; transition: transform .15s ease, box-shadow .15s ease; }
.company-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-2); }
.company-card[open] { grid-column: 1 / -1; }            /* expanded company spans the full row */
.company-card > summary { list-style: none; cursor: pointer; padding: 1rem 1.1rem; display: flex; align-items: center; gap: .85rem; }
.company-card > summary::-webkit-details-marker { display: none; }
.maker-logo {
  width: 50px; height: 50px; border-radius: 14px; flex: 0 0 auto;
  display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 1.2rem;
  background: linear-gradient(135deg, hsl(var(--h, 210) 75% 56%), hsl(calc(var(--h, 210) + 35) 70% 46%));
  box-shadow: 0 4px 14px hsl(var(--h, 210) 60% 50% / .35);
}
.maker-name { font-size: 1.2rem; font-weight: 800; line-height: 1.2; }
.maker-count { font-size: .85rem; color: var(--app-ink-2); }
.company-card .maker-caret { margin-inline-start: auto; transition: transform .15s ease; color: var(--app-ink-2); font-size: 1.1rem; }
.company-card[open] .maker-caret { transform: rotate(90deg); }
.company-card .grp-body { padding: 0 1.1rem 1.1rem; }
.models-inner-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr)); gap: .6rem; }

/* Shared model filter bar (_ModelFilterBar.cshtml) + compare bar (_ModelCompareBar.cshtml).
   "mfb-" prefix is deliberate: the Chat picker already owns an unrelated .mp-row/.mp-locked pair,
   so filter-bar classes must not collide with that short prefix. */
.mfb-pill { border: none; cursor: pointer; display: inline-flex; align-items: center; gap: .3rem; }
.mfb-pill:hover { filter: brightness(.94); }
.mfb-comparebar {
  position: fixed; inset-inline: 0; bottom: 0; z-index: 1050;
  background: var(--app-surface); border-top: 1px solid var(--app-border);
  padding: .6rem 1rem; box-shadow: 0 -4px 16px rgba(0, 0, 0, .1);
}
@media (max-width: 575.98px) { .mfb-comparebar { padding: .5rem .65rem; } }

/* FAQ */
.accordion-item { border-color: var(--app-border); }
.accordion-button { font-weight: 600; }
.accordion-button:not(.collapsed) { background: var(--brand-50); color: var(--brand-700); box-shadow: none; }

/* ---------- App shell (panel + admin) ---------- */
.shell { display: flex; min-height: 100vh; }
.shell-side {
  width: 273px; flex-shrink: 0;   /* +10% over the old 248px, to give conversation titles more room */
  background: linear-gradient(180deg, var(--side-bg) 0%, var(--side-bg-2) 100%);
  color: var(--side-ink);
  border-inline-end: 1px solid var(--side-edge);
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh;
  /* The sidebar itself no longer scrolls — .side-scroll (nav + fold + recents) does, so the account
     block below it stays pinned. Clipping here keeps that contract and stops a stray second
     scrollbar appearing next to the inner one. */
  overflow: hidden;
}
/* The thumb stays visible AT REST so the scroll is discoverable without hovering — on touch /
   low-DPI a hover-only thumb read as "can't scroll" and users zoomed out to cope. */
.side-scroll {
  scrollbar-width: thin;                                  /* Firefox */
  scrollbar-color: var(--side-scroll-thumb) transparent;
}
.side-scroll:hover { scrollbar-color: var(--side-scroll-thumb-strong) transparent; }
.side-scroll::-webkit-scrollbar { width: 10px; }
.side-scroll::-webkit-scrollbar-track { background: transparent; }
.side-scroll::-webkit-scrollbar-thumb {
  background: var(--side-scroll-thumb); border-radius: 999px;
  border: 3px solid transparent; background-clip: padding-box;
}
.side-scroll:hover::-webkit-scrollbar-thumb, .side-scroll::-webkit-scrollbar-thumb:hover {
  background: var(--side-scroll-thumb-strong); background-clip: padding-box;
}
/* ---- One-sidebar shell (brand+search · scrolling nav+recents · pinned account) ----
   The panel used to have TWO sidebars: this nav column, plus a separate conversation column that
   only the chat page rendered (and that phones showed as a SECOND drawer with its own backdrop).
   They are merged here, ChatGPT-style. The sidebar is now a fixed-height flex column: only
   .side-scroll scrolls, so .side-account stays visible no matter how long the history gets. */
.shell-side .side-head {
  display: flex; align-items: center; gap: .25rem; padding-inline-end: .6rem; flex: 0 0 auto;
}
.shell-side .side-head .side-brand { flex: 1; min-width: 0; }
/* Search + theme toggle: one shared square so the pair reads as a set. The theme toggle arrives as
   a Bootstrap .btn (it also ships into light-surface headers elsewhere), so its chrome is stripped
   here rather than in the partial, which other pages still rely on. */
.side-head-btn,
.side-head .theme-toggle {
  flex: none; border: 0; background: transparent; color: var(--side-dim); cursor: pointer;
  width: 2rem; height: 2rem; padding: 0; border-radius: 8px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.side-head-btn:hover,
.side-head .theme-toggle:hover { background: var(--side-hover); color: var(--side-ink); }
.side-head-btn .ic { width: 1.05rem; height: 1.05rem; }
.side-head .theme-toggle .theme-ic { font-size: .95rem; line-height: 1; display: block; }
.side-search {
  flex: 0 0 auto; display: flex; align-items: center; gap: .45rem;
  margin: 0 .8rem .4rem; padding: .4rem .6rem; border-radius: 10px;
  background: var(--side-raised); border: 1px solid var(--side-raised-line);
}
.side-search .ic { width: .95rem; height: .95rem; color: var(--side-dim); flex: none; }
.side-search input {
  flex: 1; min-width: 0; border: 0; background: transparent; color: var(--side-ink);
  font-size: .85rem; font-family: inherit; outline: none;
}
.side-search input::placeholder { color: var(--side-dim); }
/* The ONLY scrolling region: nav + «بیشتر» + recents move together, exactly like the standard. */
.side-scroll {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column;
}
/* Recents takes the leftover height so a short history doesn't leave the account row floating
   mid-column, and a long one simply extends the shared scroll. */
.side-recents { flex: 1 0 auto; display: flex; flex-direction: column; min-height: 0; }
.side-recents.private-dim { opacity: .45; pointer-events: none; }
.shell-side .side-brand {
  display: flex; align-items: center; gap: .55rem; color: var(--side-ink); font-weight: 800;
  padding: 1.1rem 1.2rem .9rem; font-size: .98rem;
}
.shell-side .side-brand .brand-dot { color: var(--accent-400); }
/* Two-line brand: product name over a small role caption (dashboard mockup). */
.side-brand .sb-text { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.side-brand .sb-sub { font-size: .7rem; font-weight: 500; color: var(--side-dim); }
.side-section { font-size: .68rem; letter-spacing: .06em; color: var(--side-dim); padding: 1rem 1.3rem .35rem; }
.side-nav { list-style: none; margin: 0; padding: 0 .65rem; }
.side-nav a {
  display: flex; align-items: center; gap: .65rem;
  color: var(--side-ink); padding: .55rem .75rem; border-radius: 10px; font-size: .92rem; font-weight: 500;
}
.side-nav a:hover { background: var(--side-hover); }
.side-nav a.active {
  background: var(--side-active-bg); color: var(--side-active-ink);
  box-shadow: 0 4px 12px rgba(38,95,176,.28);
}
.side-nav .icon { width: 1.35rem; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.side-nav .icon .ic { width: 1.2rem; height: 1.2rem; }
.wallet-chip .ic { width: 1.05rem; height: 1.05rem; }
#shellToggle .ic { width: 1.2rem; height: 1.2rem; vertical-align: -.18em; }
.side-foot { margin-top: auto; padding: 1rem 1.2rem; font-size: .78rem; color: var(--side-dim); border-top: 1px solid var(--side-rule); }

/* «گفت‌وگوی جدید» — the sidebar's primary action, so it reads as a button rather than one more
   nav row, without leaving the .side-nav rhythm. */
.side-nav a.side-newchat {
  background: var(--side-raised); border: 1px solid var(--side-raised-line);
  color: var(--side-ink); font-weight: 700; margin-bottom: .35rem;
}
.side-nav a.side-newchat:hover {
  background: var(--side-active-bg); border-color: var(--side-active-bg); color: var(--side-active-ink);
}

/* Account block, pinned to the bottom (replaces the © line, and absorbs the bell/wallet/logout
   chips that used to crowd the top bar — on phones those were folded behind a ⋮ menu). */
.side-account {
  flex: 0 0 auto; padding: .6rem .8rem calc(.6rem + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--side-rule); background: var(--side-foot-bg);
  display: flex; flex-direction: column; gap: .45rem;
}
.side-account .sa-user {
  display: flex; align-items: center; gap: .55rem; min-width: 0;
  padding: .35rem .4rem; border-radius: 10px; color: var(--side-ink);
}
.side-account .sa-user:hover { background: var(--side-hover); }
/* Unscoped on purpose: the avatar has TWO hosts — the account row here and the collapsed rail. While
   this lived under `.side-account`, the rail's copy inherited none of it (no circle, no background,
   no centring — just a bare letter). Exactly the mistake the brand-tile variables made, so the base
   belongs on the class and only the size overrides belong on a host. */
.sa-avatar {
  flex: none; width: 2rem; height: 2rem; border-radius: 50%;
  background: var(--side-active-bg); color: var(--side-active-ink); font-weight: 700; font-size: .9rem;
  display: inline-flex; align-items: center; justify-content: center;
  /* `normal`, not the inherited 1.5 and not 1. The glyph is a single anonymous line box, and flex
     centres that BOX — so any line-height whose half-leading is split unevenly around the ink lands
     the letter off centre. Measured on the real font at 2rem: inherited 1.5 → 0.97px high, 1 →
     0.48px high, `normal` → 0.00px. `normal` is the only one that makes the box match the font's own
     ascent/descent, which is what the centring maths needs.
     overflow:hidden keeps a wide fallback glyph (the «؟» used when a display name is blank) inside
     the circle instead of bleeding past its edge. */
  line-height: normal; overflow: hidden;
}
.side-account .sa-text { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.side-account .sa-name {
  font-size: .84rem; font-weight: 600; color: var(--side-ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.side-account .sa-sub { font-size: .7rem; color: var(--side-dim); }
.side-account .sa-actions { display: flex; align-items: center; gap: .35rem; }
/* Bell and logout are fixed squares; the wallet takes the remaining width. All three centre their
   contents on both axes so the row reads as one strip rather than three differently-nudged chips. */
.side-account .sa-actions .wallet-chip { flex: 1; height: 2.1rem; justify-content: center; }
.side-account .bell-chip,
.side-account .sa-actions .btn {
  flex: none; width: 2.1rem; height: 2.1rem; padding: 0; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.side-account .sa-actions .ic { width: 1.05rem; height: 1.05rem; }
.side-account #logoutForm { display: flex; }
/* The chips ship a light-top-bar skin by default; inside the sidebar they follow its tokens. */
.side-account .bell-chip, .side-account .wallet-chip,
.side-account .btn-outline-secondary {
  background: var(--side-raised); border: 1px solid var(--side-raised-line); color: var(--side-ink);
}
.side-account .bell-chip:hover, .side-account .wallet-chip:hover,
.side-account .btn-outline-secondary:hover {
  background: var(--side-hover); border-color: var(--side-rule); color: var(--side-ink);
}
.side-account .wallet-chip.low { background: var(--bad-50); border-color: var(--bad-500); color: var(--bad-500); }

.shell-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* (The .shell-top bar lived here. It is gone: collapse/theme/search moved into the sidebar head,
   share moved into the chat composer, and the page title went with the bar.) */

/* Floating "bring the sidebar back". Visible ONLY while the sidebar is off screen — collapsed on
   desktop, drawer-closed on phones — because #shellToggle now lives INSIDE the sidebar and would
   otherwise be unreachable the moment it hides. It sits in the leading top corner, where the bar's
   menu button used to be, so the affordance stays in the same place it always was. */
.shell-reopen {
  position: fixed; top: calc(.7rem + env(safe-area-inset-top, 0px)); inset-inline-start: .7rem;
  z-index: 1035; display: none;
  width: 2.25rem; height: 2.25rem; padding: 0; line-height: 1;
  align-items: center; justify-content: center;
  border: 1px solid var(--app-border); border-radius: 11px;
  background: var(--app-surface); color: var(--app-ink); cursor: pointer;
  box-shadow: var(--shadow-1);
}
.shell-reopen:hover { background: var(--app-surface-2); }
.shell-reopen .ic { width: 1.15rem; height: 1.15rem; }
/* Desktop no longer uses the floating button at all — .shell-rail below carries the reopen control
   along with the rest of the primary actions. Phones keep it: there the sidebar is a drawer, the
   standard shows no rail, and this is the only way back. */
@media (min-width: 992px) {
  .shell.side-collapsed .shell-reopen { display: none; }
}
@media (max-width: 991.98px) {
  /* .shell-side precedes the button inside .shell, so a plain sibling combinator is enough — no
     :has() needed, which keeps this working on older mobile Safari too. */
  .shell-reopen { display: inline-flex; }
  .shell-side.open ~ .shell-reopen { display: none; }
}
.shell-content { padding: 1.25rem; }
@media (min-width: 992px) { .shell-content { padding: 1.1rem 1.4rem; } }

/* ---------- Collapsed-sidebar rail ---------- */
/* The narrow icon column that stands in for the sidebar while it is collapsed. A flex sibling of
   .shell-side, so it occupies real space in the shell row and content can never slide under it. */
.shell-rail {
  display: none;
  width: 3.5rem; flex: none;
  position: sticky; top: 0; height: 100vh;
  /* Same surface as the sidebar it replaces, so collapsing reads as the sidebar narrowing rather
     than as a different piece of chrome appearing. */
  background: var(--side-bg);
  border-inline-end: 1px solid var(--side-edge);
  flex-direction: column; align-items: center;
  gap: .35rem; padding: .6rem 0;
}
@media (min-width: 992px) { .shell.side-collapsed .shell-rail { display: flex; } }
.rail-btn {
  width: 2.4rem; height: 2.4rem; flex: none;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 11px; padding: 0;
  background: transparent; color: var(--side-dim); cursor: pointer;
  text-decoration: none; transition: background .15s ease, color .15s ease;
}
.rail-btn:hover { background: var(--side-hover); color: var(--side-ink); }
.rail-btn.active { background: var(--side-active-bg); color: var(--side-active-ink); }
.rail-btn .ic { width: 1.2rem; height: 1.2rem; }
/* The account sits at the far bottom, mirroring .side-account in the expanded sidebar. */
.rail-user { margin-top: auto; }
.rail-user .sa-avatar { width: 1.9rem; height: 1.9rem; font-size: .8rem; }

/* wallet chip in the top bar — a white card chip with a blue wallet icon (dashboard mockup) */
.wallet-chip {
  display: inline-flex; align-items: center; gap: .45rem;
  background: var(--app-surface); color: var(--app-ink);
  border: 1px solid #e4dccd; border-radius: 12px;
  padding: .32rem .8rem; font-weight: 700; font-size: .88rem; white-space: nowrap;
}
.wallet-chip .ic { color: var(--brand-600); }
.wallet-chip:hover { background: var(--app-surface-2); color: var(--app-ink); }
[data-bs-theme="dark"] .wallet-chip { border-color: var(--app-border); }
.wallet-chip.low { background: var(--bad-50); color: #a1361f; border-color: #eec5b7; }
.wallet-chip.low .ic { color: #a1361f; }

/* Desktop: the panel sidebar can be collapsed (via the ☰ button) to give the content
   its full width — handy while chatting. The state is remembered in localStorage. */
@media (min-width: 992px) {
  /* Both axes clipped: clipping X keeps the width-collapse animation clean, and Y must stay
     clipped too now that .side-scroll is the scroller — this rule used to set overflow-y:auto
     (back when the sidebar scrolled as one piece), which re-enabled a SECOND scrollbar over the
     inner one and let the pinned account block scroll out of view. */
  .shell-side { transition: width .18s ease, opacity .18s ease; overflow: hidden; }
  .shell.side-collapsed .shell-side {
    width: 0; min-width: 0; opacity: 0; pointer-events: none; border: 0;
  }
}

/* mobile: sidebar becomes an offcanvas drawer */
@media (max-width: 991.98px) {
  .shell-side {
    position: fixed; inset-inline-start: 0; top: 0; z-index: 1045;
    transform: translateX(100%); transition: transform .2s ease;
  }
  [dir="ltr"] .shell-side { transform: translateX(-100%); }
  .shell-side.open { transform: translateX(0); }
  .shell-backdrop {
    display: none; position: fixed; inset: 0; background: rgba(26,20,13,.5); z-index: 1040;
  }
  .shell-backdrop.show { display: block; }
}

/* ---------- Stat cards ---------- */
.stat-card { display: flex; align-items: center; gap: .9rem; }
.stat-icon {
  width: 44px; height: 44px; display: grid; place-items: center; flex-shrink: 0;
  border-radius: 12px; background: var(--brand-50); font-size: 1.25rem;
}
.stat-icon .ic { width: 1.5rem; height: 1.5rem; }
.stat-card .stat-label { font-size: .8rem; color: var(--app-muted); margin-bottom: .1rem; }
.stat-card .stat-value { font-size: 1.18rem; font-weight: 800; line-height: 1.3; }
.stat-card .stat-sub { font-size: .75rem; color: var(--app-muted); }

/* Semibold utility used across the panel (Bootstrap ships only fw-bold/fw-normal). */
.fw-600 { font-weight: 600; }

/* ---------- Dashboard plan ring ---------- */
/* Filled arc = subscription time REMAINING. Inline --pct (0..100) is set from the page.
   The ::before hole reuses --app-surface, so the ring works in dark mode with no overrides. */
.plan-ring {
  position: relative; width: 62px; height: 62px; border-radius: 50%; flex: none;
  background: conic-gradient(var(--brand-600) calc(var(--pct, 0) * 1%), var(--app-border) 0);
}
.plan-ring::before {
  content: ""; position: absolute; inset: 6px; border-radius: 50%; background: var(--app-surface);
}
.plan-ring > b {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 1.05rem; font-weight: 800; color: var(--app-ink); line-height: 1;
}
.plan-ring > b small { font-size: .6rem; font-weight: 600; color: var(--app-muted); }
.plan-ring > b .ic { width: 1.4rem; height: 1.4rem; color: var(--brand-600); }
/* A lapsed subscription paints the arc in the danger colour instead of brand blue. */
.plan-ring.expired { background: conic-gradient(var(--bad-500) calc(var(--pct, 0) * 1%), var(--app-border) 0); }

/* ---------- Tables ---------- */
.table { --bs-table-hover-bg: var(--app-bg); }
.table thead th {
  font-size: .78rem; color: var(--app-muted); font-weight: 600;
  border-bottom: 1px solid var(--app-border); background: var(--app-surface-2);
}
.table td { vertical-align: middle; }
.table-wrap { overflow-x: auto; border: 1px solid var(--app-border); border-radius: var(--app-radius); background: var(--app-surface); }
.table-wrap > .table { margin-bottom: 0; }

.empty-state { text-align: center; color: var(--app-muted); padding: 2.5rem 1rem; }
.empty-state .glyph { font-size: 2rem; display: block; margin-bottom: .5rem; }

/* Chat empty state — a calm, centered prompt: a flat icon in a soft brand badge, a clear title,
   and a quiet subline. Replaces the old emoji glyph; deliberately minimal (not busy). */
.chat-empty { display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 3rem 1.25rem; gap: 0; }
.chat-empty-ic { display: inline-flex; align-items: center; justify-content: center;
  width: 3.4rem; height: 3.4rem; border-radius: 50%; background: var(--brand-50); color: var(--brand-600);
  margin-bottom: 1rem; }
.chat-empty-ic .ic { width: 1.55rem; height: 1.55rem; }
.chat-empty-title { font-size: 1.05rem; font-weight: 700; color: var(--app-ink); margin: 0; }
.chat-empty-sub { font-size: .86rem; color: var(--app-muted); margin: .35rem 0 0; max-width: 23rem; line-height: 1.75; }

/* ---------- Chat ---------- */
.chat-layout { display: flex; gap: 1rem; align-items: stretch; }
.chat-layout .chat-wrap { flex: 1; min-width: 0; }

/* The thread-history bar: shared by Chat (above .chat-msgs) and Studio (above .img-thread) —
   ONE class both pages render literally the same way, not two hand-matched copies. Self-contained
   (display/wrap/align baked in here, not left to a caller's Bootstrap utility classes) so neither
   page can drift by forgetting to attach the same utilities. */
.thread-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-bottom: .6rem; }

/* Switches in RTL: label on the RIGHT, toggle on its LEFT (trailing) — the Persian/iOS
   convention. Bootstrap's .form-switch floats the toggle to the leading (right) edge, which
   reads backwards in Persian. Flex + order flips it cleanly without touching the markup. Scoped
   to .pop (not .thread-toolbar, which only ever holds history/new-thread now) since every switch
   lives inside the settings popover. */
.pop .form-check.form-switch {
  display: inline-flex; align-items: center; gap: .4rem; padding: 0; min-height: 0;
}
.pop .form-switch .form-check-input { float: none; margin: 0; order: 2; flex: none; }
.pop .form-switch .form-check-label { order: 1; margin: 0; cursor: pointer; display: inline-flex; align-items: center; gap: .4rem; }
/* Flat, muted icon before each switch label (replaces the old emoji glyphs) — uniform with the
   download buttons' icons. */
.pop .form-switch .form-check-label .ic { width: 1rem; height: 1rem; color: var(--app-muted); flex: none; }

/* ---- Custom model picker: a styled dropdown over the hidden native #chatModel ----
   Fixes the native <select>'s broken RTL: English model names render LTR, Persian RTL, the
   icon marks live in a fixed isolated column (no bidi scrambling), and the size is a clean
   trailing badge. The <select> stays in the DOM (hidden) as the value + JS source of truth.
   Trigger button is a plain .tool-btn now (Studio's shared shell — bg/border/padding/hover come
   from that rule); .model-picker itself still styles Studio's OWN _StudioModelPicker card-grid
   wrapper (unrelated, unchanged). .mp-native is unscoped (not `.model-picker .mp-native`) since
   the hidden <select> no longer lives inside a `.model-picker` — it's the ONLY thing that class
   is for, so hiding it unconditionally is correct regardless of parent. */
.model-picker { position: relative; display: inline-flex; }
.mp-native { display: none; }
/* The trigger's label carries a cloned name+size pair (unlike a plain-text .tb-val), so it needs
   its own internal flex layout — the size badge stays fully visible (flex:none) while .mp-name
   (below) does its own per-element ellipsis truncation. */
.mp-content { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
.mp-content .mp-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* .tb-val's own ellipsis-on-text trio would fight that flex layout instead of letting .mp-name do
   its own truncation — neutralize those three specifically for this combination; .tb-val's
   max-width cap is untouched and still applies to the whole label. */
.tb-val.mp-content { white-space: normal; overflow: visible; text-overflow: clip; }
.mp-marks { direction: ltr; unicode-bidi: isolate; display: inline-flex; gap: 2px; flex: none; }
.mp-size {
  direction: ltr; unicode-bidi: isolate; flex: none;
  background: var(--brand-50); color: var(--brand-700);
  border-radius: 6px; font-size: .72rem; font-weight: 600; padding: 0 .4rem;
}
/* .model-menu (its own position/width/show-class shell) is gone — model + assistant popovers are
   .pop/.pop-model/.pop-assistant now (site.css ~1970, ~1967, ~1969), which already provide
   position/width/shadow/visibility. .model-search/.model-list/.mp-* below still style the SAME
   content (search box, list, rows) regardless of which shell wraps it — unscoped by design. */
.model-search { display: flex; align-items: center; gap: 6px; padding: .5rem .6rem; border-bottom: 1px solid var(--app-border); color: var(--app-muted); }
.model-search .ic { flex: none; width: 1rem; height: 1rem; }
.model-search input { border: 0; outline: 0; background: transparent; font: inherit; font-size: .85rem; width: 100%; color: var(--app-ink); }
.model-list { overflow-y: auto; max-height: 60vh; padding: .25rem 0; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
/* group header + a divider line between maker groups (first one has no line above it). */
.mp-group { font-size: .72rem; font-weight: 700; color: var(--app-muted); padding: .6rem .85rem .2rem;
  border-top: 1px solid var(--app-border); margin-top: .25rem; }
.model-list > .mp-group:first-child { border-top: 0; margin-top: 0; }
/* flex-wrap so a row that no longer fits (.pop-model is 480px vs. the old dropdown's 672px)
   drops its .mp-tags to their own line instead of squeezing .mp-name unreadably thin — needs
   .mp-name to have an actual floor (below) or flex-shrink would absorb the overflow silently
   and this would never trigger. At the old 672px width nothing here changes: everything still
   fits comfortably on one line. */
.mp-row { display: flex; flex-wrap: wrap; align-items: center; gap: 9px; padding: .55rem .8rem; cursor: pointer; border-inline-start: 3px solid transparent; }
.mp-row:hover { background: var(--app-surface-2); }
.mp-row.sel { background: var(--brand-50); border-inline-start-color: var(--brand-600); }
.mp-row .mp-name { flex: 1; min-width: 6rem; font-size: .86rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--app-ink-2); }
.mp-row.sel .mp-name { color: var(--brand-700); font-weight: 600; }
.mp-row.mp-locked { opacity: .6; cursor: not-allowed; }
.mp-empty { padding: 1rem .7rem; text-align: center; color: var(--app-muted); font-size: .82rem; }
/* Per-model entitlement source (leading, "from which package/group") + capability tags (trailing) —
   Persian TEXT labels instead of emoji icons. */
.mp-source { flex: none; font-size: .68rem; font-weight: 600; color: var(--brand-700); background: var(--brand-50);
  border: 1px solid var(--brand-100); border-radius: 6px; padding: .05rem .45rem; white-space: nowrap;
  max-width: 10rem; overflow: hidden; text-overflow: ellipsis; }
.mp-source-locked { color: var(--app-muted); background: var(--app-surface-2); border-color: var(--app-border); }
.mp-tags { flex: none; display: inline-flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end; }
.mp-tag { font-size: .66rem; color: var(--app-muted); background: var(--app-surface-2);
  border: 1px solid var(--app-border); border-radius: 5px; padding: .05rem .4rem; white-space: nowrap; }
.mp-tag.tag-feat { color: var(--brand-700); background: var(--brand-50); border-color: var(--brand-100); }
/* «برگزیده‌های افق آبی»: the free bucket's heading carries a flat spark icon; its hand-picked models
   wear a solid «برگزیده» pill so they read as curated highlights (theme-safe brand colors). */
.mp-group.mp-group-ofogh { color: var(--brand-700); display: flex; align-items: center; gap: .35rem; }
.mp-group-ofogh .ic { width: .95rem; height: .95rem; }
.mp-tag.tag-ofogh { color: #fff; background: var(--brand-600); border-color: var(--brand-600); font-weight: 700; }
/* In-menu upgrade teaser at the bottom of the picker (shown to non-subscribers under the locked group). */
.mp-upsell { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: .6rem .8rem;
  border-top: 1px solid var(--app-border); background: var(--app-surface-2); font-size: .78rem; color: var(--app-ink-2); }
.mp-upsell:hover { background: var(--brand-50); }
.mp-upsell .mp-upsell-btn { flex: none; background: var(--brand-600); color: #fff; border-radius: 8px; padding: .2rem .75rem; font-weight: 700; font-size: .74rem; }

/* ---- Assistant (persona) picker: trigger is a .tool-btn (icon + active-persona name(s), same
   convention as the model tool-btn), menu is a .pop.pop-assistant reusing the model picker's list
   chrome (.model-menu / .mp-*), with richer two-line rows — emoji + name + a one-line description.
   Same search, grouping, hover + selected states as the model list; just a leading emoji column. ---- */
/* Multi-select picker: ✓ + order badge on selected rows; the rest dim once the cap is reached. */
.asst-row .asst-mark { margin-inline-start: auto; display: inline-flex; align-items: center; gap: .3rem; color: var(--brand-600); font-size: .85rem; flex: none; }
.asst-row .asst-mark .asst-order { background: var(--brand-50); color: var(--brand-700); border-radius: 6px; padding: 0 .35rem; font-size: .72rem; font-variant-numeric: tabular-nums; }
.asst-row.at-cap { opacity: .45; }
.asst-hint { padding: .45rem .7rem; font-size: .72rem; color: var(--app-muted); border-bottom: 1px solid var(--app-border); }
.asst-active { padding: .4rem .7rem; font-size: .72rem; color: var(--brand-700); border-bottom: 1px solid var(--app-border); }
.asst-row { align-items: flex-start; }
.asst-emoji { flex: none; width: 1.6rem; text-align: center; font-size: 1.15rem; line-height: 1.4;
  direction: ltr; unicode-bidi: isolate; }
.asst-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.asst-text .mp-name { flex: none; }
.asst-desc { font-size: .73rem; color: var(--app-muted); line-height: 1.45;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* (Focus / fullscreen mode lived here. Removed: with no top bar and a collapsible sidebar the
   thread already fills the viewport, so a separate full-screen state earned nothing.) */

/* Share, beside ارسال in the composer. Matches .attach-plus-btn's scale at the other end of the
   row so the composer reads as one strip with a control at each end. */
.composer-icon-btn {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 2.4rem; height: 2.4rem; padding: 0; align-self: end;
  border: 1px solid var(--app-border); border-radius: 12px;
  background: var(--app-surface); color: var(--app-ink-2); cursor: pointer;
}
.composer-icon-btn:hover { background: var(--app-surface-2); color: var(--app-ink); }
.composer-icon-btn .ic { width: 1.15rem; height: 1.15rem; }

/* ---- Conversation list ----
   TWO homes, one markup contract. STUDIO still renders it as its own light-surface .conv-sidebar
   drawer (see _StudioCanvas), so the base rules below stay authored for --app-surface. CHAT's copy
   was merged into the dark shell sidebar; it re-skins the same classes in the .shell-side block
   further down instead of forking the markup. Touch the base only if Studio should change too. */
.conv-sidebar {
  width: 250px; flex-shrink: 0; display: flex; flex-direction: column;
  height: calc(100vh - 150px); min-height: 420px;
}
.conv-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: .25rem; }
.conv-empty { color: var(--app-muted); font-size: .85rem; text-align: center; padding: 1.5rem .5rem; }
.conv-item {
  display: flex; align-items: center; gap: .25rem;
  border-radius: 10px; padding: .45rem .6rem; cursor: pointer;
  border: 1px solid transparent; transition: background .12s ease;
}
.conv-item:hover { background: var(--app-surface-2); border-color: var(--app-border); }
.conv-item.active { background: var(--brand-50); border-color: var(--brand-200); }
.conv-item .conv-title {
  flex: 1; min-width: 0; font-size: .86rem; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; color: var(--app-ink-2);
}
.conv-item.active .conv-title { color: var(--brand-700); font-weight: 600; }
.conv-time { font-size: .68rem; color: var(--app-muted); white-space: nowrap; flex-shrink: 0; }
.conv-item:hover .conv-time, .conv-item.active .conv-time { display: none; }   /* swap with actions on hover */
.conv-actions { display: none; gap: .1rem; }
.conv-item:hover .conv-actions, .conv-item.active .conv-actions { display: flex; }
.conv-btn {
  border: none; background: transparent; cursor: pointer; font-size: .8rem;
  padding: .1rem .3rem; border-radius: 6px; color: var(--app-muted); line-height: 1;
}
.conv-btn:hover { background: var(--app-border); color: var(--app-ink); }
/* Pinned threads: a subtle brand accent + a faded pin action so it reads as "on". */
.conv-item.pinned { background: var(--brand-50); border-color: var(--brand-200); }
.conv-item.pinned .conv-title { color: var(--brand-700); }
.conv-btn.pin { opacity: .45; }            /* unpinned: faded pin affordance */
.conv-btn.pin.on { opacity: 1; }           /* pinned: solid */

/* date-grouped list — the group HEADER is a real SECTION HEADING, so each section is set off by
   spacing + a hairline, and there is no row-like hover background. */
.conv-group { margin-bottom: .1rem; }
.conv-group + .conv-group { margin-top: .65rem; padding-top: .55rem; border-top: 1px solid var(--app-border); }
.conv-group-head {
  display: flex; align-items: center; gap: .4rem; cursor: pointer; user-select: none;
  padding: .2rem .35rem .35rem; font-size: .95rem;
}
.conv-group-head:hover .conv-group-title { color: var(--brand-700); }
.conv-group-caret { width: .9rem; text-align: center; font-size: .68rem; color: var(--app-muted); opacity: .7; }
.conv-group-title { flex: 1; font-weight: 700; color: var(--app-ink); }
.conv-group-count { background: transparent; color: var(--app-muted); padding: 0; font-size: .72rem; font-weight: 400; }
.conv-group-body { display: flex; flex-direction: column; gap: .15rem; padding-top: .15rem; }
.conv-group-del {
  border: none; background: transparent; cursor: pointer; font-size: .8rem; line-height: 1;
  padding: .1rem .35rem; border-radius: 6px; color: var(--app-muted);
  opacity: 0; transition: opacity .12s, background .12s;
}
.conv-group-head:hover .conv-group-del { opacity: 1; }
.conv-group-del:hover { background: var(--bad-50); color: #b91c1c; }

/* Drawer backdrop is inert until the mobile media query (and .show) activate it. Studio-only now —
   chat's history is the shell sidebar, which has its own #shellBackdrop. */
.conv-backdrop { display: none; }

@media (max-width: 991.98px) {
  /* Off-canvas drawer. Physical props (not logical) so RTL/LTR docking is unambiguous:
     RTL → dock to the physical RIGHT and hide fully off the right edge. The earlier
     logical-property version resolved inset-inline-end to left:0 in RTL, so the panel
     only slid partway and a sliver bled back onto the header. */
  .conv-sidebar {
    position: fixed; top: 0; bottom: 0; z-index: 1050;
    width: 270px; max-width: 86vw; background: var(--app-surface); box-shadow: var(--shadow-2);
    padding: 1rem; height: 100vh; height: 100dvh; transition: transform .2s ease;
    right: 0; left: auto; transform: translateX(110%);
  }
  [dir="ltr"] .conv-sidebar { right: auto; left: 0; transform: translateX(-110%); }
  .conv-sidebar.open { transform: translateX(0); }
  .conv-backdrop {
    position: fixed; inset: 0; background: rgba(26,20,13,.5);
    z-index: 1049; display: none;
  }
  .conv-backdrop.show { display: block; }
}

/* ---- The same list, re-skinned for its home in the shell sidebar ----
   Scoped by ancestor so Studio's own drawer above is untouched. Only colour + density change; the
   structure and every class name stay identical. Everything here reads the --side-* tokens, so the
   list follows the sidebar's theme automatically instead of pinning its own colours. */
.shell-side .conv-list { flex: none; overflow: visible; gap: .1rem; padding: 0 .65rem .5rem; }
.shell-side .conv-empty { color: var(--side-dim); font-size: .8rem; padding: 1rem .5rem; }
.shell-side .conv-item { border-radius: 8px; padding: .4rem .55rem; }
.shell-side .conv-item:hover { background: var(--side-hover); border-color: transparent; }
.shell-side .conv-item.active { background: var(--side-active-bg); border-color: transparent; }
/* No ellipsis here: conv-sidebar.js trims each title to the last word that fully fits, so CSS must
   not also slice one mid-word and append «…». Studio's copy keeps the ellipsis (its own renderer
   does no trimming), which is why this is scoped rather than changed on the base rule. */
.shell-side .conv-item .conv-title { font-size: .84rem; color: var(--side-ink); text-overflow: clip; }
.shell-side .conv-item:hover .conv-title { color: var(--side-ink); }
.shell-side .conv-item.active .conv-title { color: var(--side-active-ink); }
.shell-side .conv-time { font-size: .66rem; color: var(--side-dim); }
.shell-side .conv-btn { color: var(--side-dim); font-size: .78rem; padding: .1rem .28rem; }
.shell-side .conv-btn:hover { background: var(--side-hover); color: var(--side-ink); }
.shell-side .conv-item.pinned { background: var(--side-raised); border-color: var(--side-raised-line); }
.shell-side .conv-item.pinned .conv-title { color: var(--side-ink); }
.shell-side .conv-group + .conv-group { margin-top: .5rem; padding-top: .45rem; border-top: 1px solid var(--side-rule); }
.shell-side .conv-group-head { font-size: .8rem; padding: .2rem .35rem .3rem; }
.shell-side .conv-group-head:hover .conv-group-title { color: var(--side-ink); }
.shell-side .conv-group-caret { color: var(--side-dim); font-size: .64rem; opacity: .8; }
.shell-side .conv-group-title { color: var(--side-dim); }
.shell-side .conv-group-count { color: var(--side-dim); font-size: .7rem; }
.shell-side .conv-group-body { gap: .1rem; padding-top: .1rem; }
.shell-side .conv-group-del { color: var(--side-dim); font-size: .78rem; }
.shell-side .conv-group-del:hover { background: var(--bad-50); color: var(--bad-500); }

.chat-wrap { position: relative; display: flex; flex-direction: column; height: calc(100vh - 150px); min-height: 420px; }

/* ---------- The reading column ---------- */
/* One centred measure for everything the reader looks at — messages, the empty state, the tool row,
   the composer and its hint — so they share an edge instead of each stretching to whatever the
   window happens to be. 60% of the available width, capped at 48rem: the percentage is what makes
   it read as a column on a laptop, and the cap is what stops a line of text running the full width
   of an ultrawide monitor, where 60% would be ~1150px of unreadable prose.
   Full width below 992px — a phone has no width to give away, and the standard does not either. */
.chat-wrap { --chat-measure: 100%; }
@media (min-width: 992px) { .chat-wrap { --chat-measure: min(60%, 48rem); } }

/* #msgs itself is deliberately NOT narrowed: it is the scroller, so it must stay full-width or its
   scrollbar would float in the middle of the page instead of sitting at the edge. The measure goes
   on its CHILDREN. Everything else under .chat-scroll is narrowed directly.
   `.cmsg` and not `*`: the EMPTY STATE is the one thing here that is not prose, and it wants the
   room — see below. */
.chat-scroll > :not(.chat-msgs),
.chat-msgs > .cmsg,
.chat-wrap > .thread-toolbar {
  width: var(--chat-measure);
  margin-inline: auto;
}

/* The empty state spans the FULL width, and only the conversation itself falls back to the reading
   column. The two have opposite needs: a column is what makes an answer readable, but the six brand
   tiles are a picker — squeezing them into the same 60% is what was cutting «Gemini 3.6 Flash» down
   to «Gemini 3.…». Nothing moves when the first message arrives, because the empty state is gone by
   then and the thread was never the thing being widened. */
.chat-msgs > .chat-empty { width: 100%; }
/* The chat is a self-contained app screen: it fills the viewport (its own .conv-list / .chat-msgs /
   .chat-scroll do the scrolling), so the WINDOW must NEVER get its own scrollbar — otherwise the PAGE
   scrolls and a big empty void opens below the chat. Suppress it on the chat page only, matched TWO ways
   so it works with or without a rebuild: body.nav-chat (server-set class, needs a rebuild) AND
   :has(.chat-layout) (pure CSS — applies on a plain refresh). Also drop the chat region's min-height so
   it can never force itself taller than the viewport. Kept as separate rules so a browser without :has()
   still honours the .nav-chat ones. */
body.nav-chat { overflow: hidden; }
body.nav-chat .chat-wrap { min-height: 0; }
body:has(.chat-layout) { overflow: hidden; }
body:has(.chat-layout) .chat-wrap { min-height: 0; }
/* Standard chat layout (ChatGPT-style): the composer is PINNED at the bottom and ONLY #msgs scrolls, so
   the composer is always in view. .chat-scroll is just a non-scrolling flex column filling the space below
   the toolbar (which stays above it so its dropdowns aren't clipped); #msgs is the bounded scroller and
   the composer sits pinned beneath it. Nothing here can scroll the page. */
.chat-scroll { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.chat-msgs {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 1rem 0; background: transparent;
  /* The history is a fixed scroll region. Isolate its large layout/paint tree so resizing the composer
     does not invalidate every rendered answer on each keystroke. */
  contain: layout paint style;
}
/* The scroller keeps its full width (so its scrollbar sits at the edge of the page, as it should),
   which means the bar eats into the CONTENT box on ONE side — shifting the centred column off from
   the composer beneath it. Reserving the gutter on both edges keeps the box symmetrical, whether or
   not the thread is long enough to scroll.
   Applied at every width on purpose: the property is a no-op wherever the platform draws OVERLAY
   scrollbars (phones), so it costs nothing there and still fixes the case that bit — a narrow window
   on a machine with classic scrollbars, where the column was landing 10px off centre. */
.chat-msgs { scrollbar-gutter: stable both-edges; }

/* Chromium can skip layout/paint for old turns outside the scroll viewport. `auto` remembers each
   turn's real size after first render, avoiding scrollbar jumps while keeping long chats responsive. */
@supports (content-visibility: auto) {
  .chat-msgs > .cmsg {
    content-visibility: auto;
    contain-intrinsic-block-size: auto 180px;
  }
}

/* Subtle, ChatGPT-style scrollbars on the chat scrollers + the history list: a thin thumb that's faint at
   rest and firms up when you hover the area (or grab the thumb). The thumb's 3px transparent border +
   background-clip keeps the visible bar slim (~4px). Theme-aware via --scrollbar-thumb. */
.chat-msgs, .conv-list, #chatInput {
  scrollbar-width: thin;                                       /* Firefox */
  scrollbar-color: var(--scrollbar-thumb) transparent;
}
.chat-msgs:hover, .conv-list:hover, #chatInput:hover, #chatInput:focus {
  scrollbar-color: var(--scrollbar-thumb-strong) transparent;
}
.chat-msgs::-webkit-scrollbar, .conv-list::-webkit-scrollbar, #chatInput::-webkit-scrollbar { width: 10px; height: 10px; }
.chat-msgs::-webkit-scrollbar-track, .conv-list::-webkit-scrollbar-track, #chatInput::-webkit-scrollbar-track { background: transparent; }
.chat-msgs::-webkit-scrollbar-thumb, .conv-list::-webkit-scrollbar-thumb, #chatInput::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb); border-radius: 999px;
  border: 3px solid transparent; background-clip: padding-box;
}
.chat-msgs:hover::-webkit-scrollbar-thumb, .conv-list:hover::-webkit-scrollbar-thumb,
#chatInput:hover::-webkit-scrollbar-thumb, #chatInput:focus::-webkit-scrollbar-thumb,
.chat-msgs::-webkit-scrollbar-thumb:hover, .conv-list::-webkit-scrollbar-thumb:hover, #chatInput::-webkit-scrollbar-thumb:hover {
  background: var(--scrollbar-thumb-strong); background-clip: padding-box;
}
.cmsg { display: flex; margin-bottom: .8rem; }
/* Q&A stack vertically and span the full chat area (not user-right / assistant-left). */
.cmsg .bubble {
  padding: .6rem .9rem; border-radius: 14px; width: 100%; max-width: 100%;
  line-height: 1.9; overflow-wrap: anywhere; box-shadow: var(--shadow-1);
}
.cmsg.user { justify-content: flex-start; }
.cmsg.assistant { justify-content: flex-start; }
/* Preserve the line breaks the user typed (textContent collapses them without this). Only the
   USER bubble — the assistant bubble is rendered markdown and must keep normal white-space. */
.cmsg.user .bubble { background: var(--app-question-bg); color: var(--app-ink); border-end-start-radius: 4px; white-space: pre-wrap; }
/* The assistant ANSWER is fully flat — no box, no background, no border/shadow/padding — so it reads
   as plain content on the page; only the user QUESTION keeps a coloured bubble. */
.cmsg.assistant .bubble { background: transparent; border: none; box-shadow: none; padding: 0; border-radius: 0; }
.cmsg .meta { font-size: .7rem; color: var(--app-muted); margin-top: .35rem; }
.cmsg.user .meta { color: var(--app-muted); }
/* A replied-to message, shown as a distinct quoted block inside the user bubble (a side bar + a
   nested surface + an «در پاسخ به» label) so the user's own question never blurs into the quote. */
.cmsg.user .bubble .reply-quoted {
  background: var(--app-surface); border-inline-start: 3px solid var(--brand-500);
  border-radius: 6px; padding: .35rem .6rem; margin-bottom: .55rem;
}
.reply-quoted-label { font-size: .7rem; color: var(--app-muted); margin-bottom: .12rem; }
.reply-quoted-text { font-size: .92em; color: var(--app-ink-2); white-space: pre-wrap; line-height: 1.7; }
/* Show/hide questions & answers (⋯-menu toggles) — applies to the live thread; the export reads the
   same toggles, so a hidden role is dropped from the PDF/HTML too. */
.chat-msgs.hide-questions .cmsg.user { display: none; }
.chat-msgs.hide-answers .cmsg.assistant { display: none; }

/* «جمع‌کردن» — two independent switches (questions + answers), each admin-gated. JS marks long
   blocks of a finished reply with .clp, and folds each HEADING's section into a .clp-sec-body
   sibling (nested tree). While .clp-on is active on the thread, unopened blocks show only their
   first line with a fade + a clear round chevron chip. Collapsed styles live under :not(.open) so
   an expanded block keeps ALL its natural styles. Exports are never affected. */
.chat-msgs.clp-on .clp,
.chat-msgs.clp-on .clp-h { position: relative; cursor: pointer; padding-left: 1.9rem; }
.chat-msgs.clp-on .clp:not(.open) { max-height: 2.1em; overflow: hidden; }
.chat-msgs.clp-on .clp:not(.open)::before {
  content: ""; position: absolute; inset-inline: 0; bottom: 0; height: 1em;
  background: linear-gradient(180deg, transparent, var(--app-bg)); pointer-events: none;
}
/* QUESTIONS fold when their switch is on: past ~4 lines the bubble shows its first two lines with
   a fade matching its own tint, and expands on tap. */
.cmsg.user .bubble.clp-q { position: relative; cursor: pointer; padding-left: 1.9rem; }
.cmsg.user .bubble.clp-q:not(.open) { max-height: 4.6em; overflow: hidden; }
.cmsg.user .bubble.clp-q:not(.open)::before {
  content: ""; position: absolute; inset-inline: 0; bottom: 0; height: 1.4em;
  background: linear-gradient(180deg, transparent, var(--app-question-bg)); pointer-events: none;
}
.cmsg.user .bubble.clp-q::after {
  content: "▾"; position: absolute; top: .55em; left: .35rem;
  width: 1.3em; height: 1.3em; display: grid; place-items: center;
  background: var(--app-surface); border: 1px solid var(--brand-200); border-radius: 50%;
  color: var(--brand-600); font-size: .72em; line-height: 1;
}
.cmsg.user .bubble.clp-q.open::after { content: "▴"; border-color: var(--app-border); color: var(--app-muted); }
.cmsg.user .bubble.clp-q:not(.open):hover::after { background: var(--brand-100); color: var(--brand-700); }
/* clear, chip-style expander: a small round badge with the chevron, brand-tinted.
   PHYSICAL left in both directions — for Persian (RTL) that's the same edge as before;
   for English-mode chats the chip no longer flips to the right (user feedback). */
.chat-msgs.clp-on .clp::after,
.chat-msgs.clp-on .clp-h::after {
  content: "▾"; position: absolute; top: .1em; left: .1rem;
  width: 1.3em; height: 1.3em; display: grid; place-items: center;
  background: var(--brand-50); border: 1px solid var(--brand-200); border-radius: 50%;
  color: var(--brand-600); font-size: .72em; line-height: 1;
}
.chat-msgs.clp-on .clp.open::after { content: "▴"; background: var(--app-surface-2); border-color: var(--app-border); color: var(--app-muted); }
.chat-msgs.clp-on .clp:not(.open):hover::after,
.chat-msgs.clp-on .clp-h.closed:hover::after { background: var(--brand-100); color: var(--brand-700); }
/* headings: the chevron chip sits on the heading; its folded section body hides entirely */
.chat-msgs.clp-on .clp-h::after { top: .18em; }
.chat-msgs.clp-on .clp-h:not(.closed)::after { content: "▴"; background: var(--app-surface-2); border-color: var(--app-border); color: var(--app-muted); }
.chat-msgs.clp-on .clp-sec-body.closed { display: none; }

/* per-message actions (copy / regenerate / edit) under each message */
.cmsg.assistant, .cmsg.user { flex-wrap: wrap; }
.msg-actions { width: auto; display: flex; gap: .3rem; margin-top: .25rem;
  opacity: 0; transition: opacity .12s ease; }
.cmsg.assistant .msg-actions { justify-content: flex-start; } /* actions on the right (start), matching the RTL message */
.cmsg.user .msg-actions { justify-content: flex-start; }    /* under user bubble (right in RTL) */
.cmsg:hover .msg-actions { opacity: 1; }
.msg-act { border: 1px solid var(--app-border); background: var(--app-surface); border-radius: 8px;
  font-size: .72rem; padding: .14rem .55rem; cursor: pointer; color: var(--app-muted); line-height: 1.4; }
.msg-act:hover { background: var(--app-surface-2); color: var(--app-ink); border-color: var(--app-border); }
/* 👍/👎 reply feedback: the chosen thumb stays lit; the inline note box is compact and quiet. */
.msg-act.fb-on { background: var(--brand-50); border-color: var(--brand-200); color: var(--brand-700); opacity: 1; }
.msg-act.fb-btn:disabled:not(.fb-on) { opacity: .4; cursor: default; }
.fb-note { width: 100%; max-width: 34rem; margin-top: .4rem; display: flex; flex-direction: column; gap: .15rem; }
.fb-note .fb-hint { font-size: .72rem; color: var(--app-muted); margin-top: .2rem; }
/* "answer cut at the length cap" notice + continue button, shown under a truncated reply. */
.trunc-note { width: 100%; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin-top: .35rem; font-size: .8rem; color: var(--app-muted); }
.cmsg.assistant .trunc-note { justify-content: flex-start; }

/* inline edit box for a user message */
.edit-box { width: 100%; display: flex; flex-direction: column; gap: .4rem; }
.edit-box textarea { resize: vertical; }
.edit-btns { display: flex; gap: .4rem; justify-content: flex-start; }
.bubble.md pre { background: #2a2118; color: #e8ddc9; padding: .7rem .9rem; border-radius: 10px; overflow: auto; font-size: var(--fs-code, 14px); }
/* Per-code-block copy button (top-right, away from where the code text begins). */
.bubble.md .code-wrap { position: relative; }
.bubble.md .code-wrap pre { margin: 0; }
.code-copy { position: absolute; top: .4rem; right: .4rem; border: 1px solid rgba(255,255,255,.18);
  background: rgba(255,255,255,.08); color: #e2e8f0; border-radius: 8px; font-size: .72rem;
  padding: .12rem .5rem; cursor: pointer; line-height: 1.5; opacity: .55;
  font-family: Vazirmatn, Tahoma, sans-serif; transition: opacity .12s ease, background .12s ease; }
.code-copy:hover { background: rgba(255,255,255,.18); }
.bubble.md .code-wrap:hover .code-copy, .code-copy:focus-visible { opacity: 1; }
.bubble.md code { background: rgba(38,95,176,.08); border-radius: 4px; padding: .1em .35em; }
.bubble.md pre code { background: transparent; padding: 0; }

/* ---- Reader font sizes: defaults on the chat root; the ⋯-menu sliders override these vars on #msgs
   (persisted in localStorage), so every part of the answer re-flows live. ---- */
.chat-msgs { --fs-body: 16px; --fs-title: 22px; --fs-subtitle: 18px; --fs-code: 14px; }
.cmsg .bubble { font-size: var(--fs-body, 16px); }
.bubble.md h1, .bubble.md h2 { font-size: var(--fs-title, 22px); line-height: 1.4; }
.bubble.md h3, .bubble.md h4, .bubble.md h5, .bubble.md h6 { font-size: var(--fs-subtitle, 18px); line-height: 1.45; }
/* Mixed-language answers: each block carries dir="auto" (set at render), so it aligns to its OWN
   side — Persian right, English/formula left. Math (KaTeX) and code always render left-to-right so
   superscripts/exponents and operators keep their correct side even inside an RTL paragraph. */
.bubble.md [dir="auto"] { text-align: start; }
.bubble.md .katex, .bubble.md .katex-display { direction: ltr; }
.bubble.md pre { direction: ltr; }

/* Font-size panel inside the ⋯ menu — a full-width vertical group, so it fits BOTH the desktop popover
   column and the mobile wrap-card without the sliders getting cramped. */
.fs-panel { flex-basis: 100%; display: flex; flex-direction: column; gap: .4rem; }
.fs-panel-head { display: flex; align-items: center; gap: .4rem; font-size: .78rem; font-weight: 600; color: var(--app-ink-2); }
.fs-panel-head .ic { width: .95rem; height: .95rem; color: var(--app-muted); flex: none; }
.fs-reset { margin-inline-start: auto; font-size: .72rem; background: none; border: none; color: var(--brand-600); cursor: pointer; padding: 0; }
.fs-reset:hover { color: var(--brand-700); text-decoration: underline; }
.fs-row { display: grid; grid-template-columns: 3rem 1fr 2rem; align-items: center; gap: .5rem; margin: 0; font-size: .8rem; color: var(--app-ink-2); }
.fs-row input[type="range"] { width: 100%; margin: 0; }
.fs-row output { text-align: end; font-variant-numeric: tabular-nums; color: var(--app-muted); font-size: .78rem; }
/* Wide tables scroll horizontally instead of squeezing columns (so e.g. "۱۹۷۹" never breaks). */
.bubble.md .table-scroll { overflow-x: auto; margin: .5rem 0; max-width: 100%; -webkit-overflow-scrolling: touch; }
.bubble.md .table-scroll table { margin: 0; }
.bubble.md table { width: 100%; margin: .5rem 0; border-collapse: collapse; }
.bubble.md th, .bubble.md td { border: 1px solid var(--app-border); padding: .3rem .55rem; white-space: nowrap; }
/* The model's «---» section dividers: a clean, subtle hairline — not the browser's heavy default rule. */
.bubble.md hr { border: none; height: 1px; background: var(--app-border); margin: 1.2rem 0; }
/* Real charts (```chart JSON → Chart.js). White card so it's readable on either theme. */
.bubble.md .chart-box { position: relative; height: var(--chart-h, 320px); max-width: 100%; margin: .6rem 0; background: #fff;
  border: 1px solid var(--app-border); border-radius: 10px; padding: .6rem .7rem; }
/* Download-as-PNG button overlaid on each rendered chart (the inline-end top corner, away from the
   legend which sits inline-start). Subtle on the white card, solid on hover. */
.chart-dl { position: absolute; top: .45rem; inset-inline-end: .45rem; z-index: 2;
  display: inline-flex; align-items: center; cursor: pointer; opacity: .6;
  border: 1px solid var(--app-border); background: rgba(255,255,255,.82); color: var(--app-muted);
  border-radius: 8px; padding: .2rem .32rem; transition: opacity .12s ease, color .12s ease, background .12s ease; }
.chart-dl:hover, .chart-dl:focus-visible { opacity: 1; color: var(--brand-700); background: #fff; }
.chart-dl .ic { width: 1.05rem; height: 1.05rem; }
.blink::after { content: "▋"; animation: blink 1s steps(2) infinite; }
@keyframes blink { 50% { opacity: 0; } }

/* Model reasoning (<think>…</think>): a collapsed, expandable block at the top of a reply.
   Collapsed it shows only the first thought line; click to reveal the full chain of thought.
   While the reasoning is still streaming it pulses «در حال فکر کردن…». */
.bubble.md .think {
  margin: 0 0 .65rem; padding: .35rem .6rem;
  background: var(--brand-50); border: 1px dashed var(--app-border); border-radius: 10px;
}
.bubble.md .think > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: .4rem;
  color: var(--app-muted); font-size: .82rem;
}
.bubble.md .think > summary::-webkit-details-marker { display: none; }
.bubble.md .think > summary::after {
  content: '▾'; margin-inline-start: auto; font-size: .8rem; transition: transform .15s; flex: none;
}
.bubble.md .think[open] > summary::after { transform: rotate(180deg); }
.bubble.md .think .think-ico { flex: none; }
.bubble.md .think .think-line { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.bubble.md .think .think-body {
  margin-top: .5rem; padding-top: .5rem; border-top: 1px solid var(--app-border);
  color: var(--app-muted); font-size: .92em; line-height: 1.85;
}
.bubble.md .think-live > summary { color: var(--brand-600); }
.bubble.md .think-live .dots::after { content: ""; animation: thinking-dots 1.2s steps(4) infinite; }

.thinking { color: var(--app-muted); font-size: .9rem; }
.thinking .dots::after { content: "…"; animation: thinking-dots 1.2s steps(4) infinite; }
@keyframes thinking-dots { 0% { content: ""; } 25% { content: "."; } 50% { content: ".."; } 75% { content: "..."; } }

/* "thinking" typing indicator (three bouncing dots) */
.typing { display: inline-flex; gap: 5px; align-items: center; height: 1.4em; padding: .1rem 0; }
.typing span { width: 7px; height: 7px; border-radius: 50%; background: var(--app-muted); animation: typing-bounce 1.2s infinite ease-in-out both; }
.typing span:nth-child(2) { animation-delay: .15s; }
.typing span:nth-child(3) { animation-delay: .3s; }
@keyframes typing-bounce { 0%, 70%, 100% { transform: translateY(0); opacity: .35; } 35% { transform: translateY(-5px); opacity: 1; } }

/* LTR layout pins ارسال to the physical LEFT (the textarea fills the rest and grows); the
   textarea's own dir=rtl keeps typed text right-to-left. #attachBtn lives in .composer-tools now,
   not in here. */
.chat-composer { display: flex; gap: .6rem; margin-top: .7rem; align-items: flex-end; direction: ltr; }
.chat-composer textarea { resize: none; flex: 1 1 auto; }
.attach-plus-btn {
  flex: 0 0 2.7rem; width: 2.7rem; height: 2.7rem; padding: 0; border-radius: 14px;
  border: 1px solid var(--app-border); background: var(--app-surface); color: var(--brand-700);
  display: inline-grid; place-items: center; cursor: pointer;
  transition: background .12s, border-color .12s, transform .12s;
}
.attach-plus-btn:hover { background: var(--brand-50); border-color: var(--brand-300); }
.attach-plus-btn:active { transform: scale(.96); }
.attach-plus-btn .ic { width: 1.2rem; height: 1.2rem; }
.send-btn.attach-plus-btn { background: var(--app-surface); color: var(--brand-700); border: 1px solid var(--app-border); }
.send-btn.attach-plus-btn:hover { background: var(--brand-50); border-color: var(--brand-300); }

/* ChatGPT-style composer: auto-grows with its content (via JS) up to a cap (~30% of the viewport); past
   the cap it scrolls INTERNALLY. Pinned at the bottom, so it is always in view and never pushes the page. */
#chatInput { resize: none; overflow-y: auto; min-height: 2.6rem; max-height: 30vh; }

/* attachments */
.attach-bar { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .6rem; }
.attach-chip {
  display: inline-flex; align-items: center; gap: .35rem;
  background: var(--brand-50); border: 1px solid var(--brand-100);
  color: var(--brand-700); border-radius: 999px;
  padding: .25rem .7rem; font-size: .8rem; max-width: 100%;
}
.attach-x {
  border: none; background: transparent; cursor: pointer;
  color: var(--app-muted); font-size: .8rem; padding: 0 .15rem; line-height: 1;
}
.attach-x:hover { color: var(--bad-500); }
.attach-image-card {
  position: relative; display: grid; grid-template-rows: 7rem auto; gap: .35rem;
  width: 9rem; padding: .35rem; border-radius: .8rem; overflow: hidden;
  background: var(--app-surface); border-color: var(--app-border); color: var(--app-ink);
}
.attach-image-preview {
  display: block; width: 100%; height: 7rem; object-fit: contain; object-position: center;
  border-radius: .55rem; background: var(--app-surface-2);
}
.attach-image-meta { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: .35rem; padding-inline: .15rem; }
.attach-image-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .74rem; }
.attach-image-size { flex: 0 0 auto; color: var(--app-muted); font-size: .68rem; direction: ltr; }
.attach-image-remove {
  position: absolute; top: .55rem; inset-inline-end: .55rem; width: 1.55rem; height: 1.55rem;
  display: grid; place-items: center; padding: 0; border-radius: 50%;
  background: rgba(20,25,35,.72); color: #fff; font-size: 1.05rem;
}
.attach-image-remove:hover { background: var(--bad-500); color: #fff; }

/* "reply to a message" context bar above the composer */
.reply-bar {
  display: flex; align-items: center; gap: .5rem; margin-top: .6rem;
  padding: .4rem .65rem; border-inline-start: 3px solid var(--brand-600);
  background: var(--brand-50); border-radius: 8px; font-size: .82rem;
}
.reply-bar .reply-icon { color: var(--brand-600); flex-shrink: 0; }
.reply-bar .reply-quote { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--app-ink-2); }
.reply-bar .reply-x { border: none; background: transparent; cursor: pointer; color: var(--app-muted); font-size: 1rem; line-height: 1; padding: 0 .15rem; }
.reply-bar .reply-x:hover { color: var(--bad-500); }
.bubble-img { display: block; max-width: 220px; border-radius: 10px; margin-top: .5rem; border: 1px solid var(--app-border); }

/* private mode — chat dims .side-recents in the shell sidebar (see that block); this stays for
   Studio, whose history is still its own .conv-sidebar. */
.conv-sidebar.private-dim { opacity: .45; pointer-events: none; }

.gen-out {
  min-height: 120px; background: var(--app-surface-2);
  border: 1px dashed var(--app-border); border-radius: var(--app-radius); padding: 1rem;
}
.gen-out img, .gen-out video { max-width: 100%; border-radius: 10px; border: 1px solid var(--app-border); }

/* cost meta line under generations */
.cost-line { font-size: .78rem; color: var(--app-muted); }

/* ---------- Progress / wallet bar ---------- */
.progress { background: var(--brand-50); border-radius: 999px; }
.progress-bar { background-color: var(--brand-600); }

/* ---------- Auth ---------- */
.auth-card { max-width: 430px; margin: 2.5rem auto; }
.auth-card .surface { padding: 1.8rem 1.7rem; }
.auth-logo { font-weight: 800; color: var(--brand-700); font-size: 1.25rem; }
.auth-logo-img { height: 92px; width: auto; display: block; margin: 0 auto .25rem; }

/* ---------- Footer (public) ----------
   Deep warm-ink footer per the mockup: cream headings, sand links, hairline top border. */
.app-footer {
  flex-shrink: 0; border-top: 1px solid #3d3527;
  padding: 2.6rem 0 1.6rem; margin-top: 3rem; font-size: .88rem;
  background: #2a2118; color: #cabfae;
}
.app-footer .foot-title { font-weight: 700; color: #faf6ef; margin-bottom: .6rem; }
.app-footer a { color: #cabfae; }
.app-footer a:hover { color: #fff; }
.app-footer .text-muted-2 { color: #9a8e7d; }
.app-footer .divider { border-top-color: #3d3527; }
/* The light-bg logo needs a soft chip on the dark footer (same idea as the sidebar). */
.app-footer .brand-logo-light { background: #fff; border-radius: 8px; padding: 2px; }
/* Refund-guarantee trust chip in the footer (links to the policy on /Contact#refund). */
.app-footer .foot-refund {
  font-size: .8rem; font-weight: 600; color: #e8ddc9;
  background: rgba(255,255,255,.06); border: 1px solid #3d3527;
  border-radius: 999px; padding: .35rem .9rem;
}
.app-footer .foot-refund:hover { color: #fff; background: rgba(255,255,255,.1); }
.app-footer .foot-refund .ic { width: 1rem; height: 1rem; color: #7fd4ae; }
[data-bs-theme="dark"] .app-footer { background: #100c08; border-top-color: var(--app-border); color: var(--app-ink-2); }
[data-bs-theme="dark"] .app-footer .foot-title { color: var(--app-ink); }
[data-bs-theme="dark"] .app-footer a { color: var(--app-ink-2); }
[data-bs-theme="dark"] .app-footer a:hover { color: var(--brand-300); }

/* ---------- Helpers ---------- */
.text-muted-2 { color: var(--app-muted); }
.fw-800 { font-weight: 800; }
.mono { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; direction: ltr; unicode-bidi: embed; }
.card-num {
  font-family: ui-monospace, Consolas, monospace; direction: ltr; unicode-bidi: embed;
  letter-spacing: .12em; font-size: 1.15rem; font-weight: 700; color: var(--brand-700);
}
.divider { border-top: 1px solid var(--app-border); margin: 1.1rem 0; }
.step-dot {
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
  background: var(--brand-600); color: #fff; display: grid; place-items: center;
  font-size: .85rem; font-weight: 700;
}

/* Required-agreement marker + submit-gate hint on the payment page. The asterisk sits inline
   before a consent checkbox's label — large, red, bold so the obligation is unmistakable. */
.req-star {
  color: #dc3545; font-size: 1.3em; font-weight: 800; line-height: 0;
  margin-inline-end: .15em; vertical-align: middle;
}
.agree-hint {
  color: #b02a37; font-size: .85rem; font-weight: 600; line-height: 1.4;
  display: none; /* shown by JS only while a required agreement is still unchecked */
}

/* RTL placeholder alignment for floating labels */
.form-floating > .form-control-plaintext::placeholder,
.form-floating > .form-control::placeholder { color: var(--app-muted); text-align: end; }
.form-floating > .form-control-plaintext:focus::placeholder,
.form-floating > .form-control:focus::placeholder { text-align: start; }

/* focus visibility */
:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 2px; }

/* ============================================================
   MOBILE OPTIMIZATION
   Touch ergonomics, dynamic viewport heights, iOS zoom guard.
   ============================================================ */

/* Stop iOS Safari from auto-inflating text; tame the tap flash. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
* { -webkit-tap-highlight-color: rgba(38,95,176,.15); }

/* Use the dynamic viewport unit so the mobile URL bar doesn't clip layouts.
   (Falls back to vh on browsers without dvh support.) */
@supports (height: 100dvh) {
  .shell-side { height: 100dvh; }
  .chat-wrap { height: calc(100dvh - 150px); }
  /* desktop-only: on mobile the conv-sidebar is a full-height fixed drawer */
  @media (min-width: 992px) { .conv-sidebar { height: calc(100dvh - 150px); } }
}

/* The off-canvas drawers are parked off the right/left edge; clip the horizontal
   overflow so that never creates a horizontal scrollbar.
   Use `clip`, NOT `hidden`: `overflow-x: hidden` forces overflow-y to compute to
   `auto`, turning html/body into scroll containers — which breaks single-finger
   vertical scrolling on mobile (it ends up needing two fingers). `clip` clips
   without establishing a scroll container, so normal touch scrolling is preserved. */
html, body { overflow-x: clip; max-width: 100%; }

/* ---- Touch devices: no hover, so reveal hover-only affordances ---- */
@media (hover: none) {
  /* per-message copy / edit / regenerate are always visible on touch */
  .msg-actions { opacity: 1; }
  /* sidebar row: show actions, drop the swap-on-hover clock */
  .conv-item .conv-time { display: none; }
  .conv-actions { display: flex; }
  .conv-group-del { opacity: 1; }   /* show the per-day delete on touch (no hover) */
  /* bigger, finger-friendly hit areas for the tiny icon buttons */
  .conv-btn { padding: .35rem .5rem; font-size: .95rem; }
  .msg-act { padding: .3rem .7rem; font-size: .8rem; }
  .attach-x { padding: .2rem .4rem; font-size: 1rem; }
}

/* ---- Phones / small tablets ---- */
@media (max-width: 575.98px) {
  /* tighten the page frame so content gets the width */
  .shell-content { padding: .85rem .8rem; }

  /* Block padding only. Inline padding here used to inset every message ~11px from the composer
     below it, so the two sides of the reading column no longer lined up — .shell-content already
     provides the page gutter at this width. */
  .chat-msgs { padding: .8rem 0; }
  .cmsg .bubble { max-width: 100%; line-height: 1.85; }

  /* 16px+ on real inputs prevents iOS from zooming in on focus */
  .chat-composer textarea,
  #chatInput,
  .form-control, .form-select { font-size: 16px; }

  /* composer: keep send as a solid, thumb-sized target. #attachBtn moved into .composer-tools
     (a .tool-btn.tb-attach now) — its 2.1rem sizing is shared with Studio's identical button and
     deliberately doesn't get a mobile override, matching Studio (which has none either). */
  .chat-composer { gap: .45rem; }
  #sendBtn {
    min-height: 46px; display: inline-flex; align-items: center; justify-content: center; padding-inline: 1.1rem;
  }
  .chat-composer .attach-plus-btn { flex-basis: 46px; width: 46px; height: 46px; }
  .attach-image-card { width: 7.5rem; grid-template-rows: 5.5rem auto; }
  .attach-image-preview { height: 5.5rem; }

  /* Model/assistant/settings all used to have bespoke mobile positioning here (a fixed
     full-width sheet for the model/assistant dropdowns, an inline-row card for settings) — all
     deleted along with #modelPicker/.model-trigger/.more-wrap/.toolbar-more themselves. Every
     popover is a .pop now (opens upward via inset-block-end, Studio's mechanism), identical at
     every width — no mobile-specific override needed for any of them. */

  /* wider drawer on phones so titles aren't cramped */
  .conv-sidebar { width: 84vw; max-width: 320px; }
}

/* iOS rubber-band & momentum scrolling inside the chat + lists */
.chat-msgs, .chat-scroll, .conv-list { -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }

/* Respect notch / home-indicator safe areas when running full-screen (PWA) */
@supports (padding: env(safe-area-inset-bottom)) {
  @media (max-width: 991.98px) {
    /* The top bar used to absorb the notch inset; with it gone the content itself must, or the
       first rows of a page sit under the status bar on a full-screen PWA. */
    .shell-content { padding-top: calc(.85rem + env(safe-area-inset-top)); }
    .shell-content { padding-bottom: calc(.85rem + env(safe-area-inset-bottom)); }
    .shell-side { padding-bottom: env(safe-area-inset-bottom); }
  }
}

/* ---- Mobile chat fills the viewport via flex (placed last to win the cascade) ----
   Use a DEFINITE height (not min-height) so .chat-msgs becomes a real bounded scroller.
   With min-height the message list grew with its content, never scrolled itself, and
   overscroll-behavior:contain then trapped single-finger drags — so the page only moved
   with two fingers. A definite height makes flex:1 give .chat-msgs a fixed box to scroll
   inside, and touch-action:pan-y declares it as a vertical scroller. */
@media (max-width: 991.98px) {
  .chat-layout { height: calc(100dvh - 76px); min-height: 0; align-items: stretch; }
  .chat-wrap { height: 100%; flex: 1 1 auto; min-height: 0; }
  .chat-msgs, .chat-scroll { min-height: 0; touch-action: pan-y; }
}

/* Installed PWA (display:standalone + viewport-fit=cover): 100dvh spans EDGE-TO-EDGE — behind the status
   bar AND the bottom home/nav bar. The base height above only accounts for the header, so the pinned
   composer ends up behind the bottom system bar and is clipped. Subtract BOTH safe-area insets so the
   chat fits the *visible* area. env() is 0 in a normal browser tab, so this is a no-op there. Placed after
   the base rule so it wins the cascade. */
@supports (padding: env(safe-area-inset-bottom)) {
  @media (max-width: 991.98px) {
    .chat-layout { height: calc(100dvh - 76px - env(safe-area-inset-top) - env(safe-area-inset-bottom)); }
  }
}

/* ============================================================
   CHAT CHROME — compact mobile redesign
   Reclaims vertical space for the conversation: one tidy control
   bar with uniform 42px touch targets, a low-profile model caption
   instead of a heavy card, and a single subtle file hint. Placed
   after the base mobile rules so it wins the cascade.
   ============================================================ */
@media (max-width: 575.98px) {
  /* .thread-toolbar only ever holds history + new-thread now — model/assistant/settings all
     moved into .composer-tools, which needs no mobile override of its own (see above). */
  .thread-toolbar { gap: .4rem; margin-bottom: .45rem; }
  .thread-toolbar .btn { min-height: 42px; }

  /* "What is this model?" reads as a quiet caption under the picker. */
  #modelInfo .mi-summary { font-size: .82rem; }
  #dailyQuota { font-size: .72rem; }

  /* Composer hugs the thread; the file hint is one subtle, truncating line
     (the full sentence stays in its title= tooltip). */
  .chat-composer { margin-top: .5rem; }
  .input-hint {
    font-size: .72rem;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
}

/* ============================================================
   CHAT MESSAGES — compact mobile redesign
   Tighter turn rhythm + a single-line reply caption: the model
   name and timestamp share one muted baseline (separated by a
   dot that hides with the label when «نمایش مدل» is off) instead
   of stacking on three rows. The cost/token meta stays inside the
   bubble. Placed after the base rules so it wins the cascade.
   ============================================================ */
@media (max-width: 575.98px) {
  /* tighter rhythm between turns + a touch less bubble padding */
  .cmsg { margin-bottom: .55rem; }
  .cmsg .bubble { padding: .55rem .8rem; }

  /* cost/tokens line inside the bubble: lighter, snug */
  .cmsg .meta { font-size: .66rem; margin-top: .3rem; line-height: 1.6; }

  /* compact, always-visible (touch) action buttons */
  .msg-actions { gap: .25rem; margin-top: .25rem; }
  .msg-act { font-size: .7rem; padding: .2rem .5rem; }

  /* model + time collapse onto ONE caption line (msg-time drops its 100%
     width so it sits beside the label). The "·" rides on the label, so it
     disappears together with it when model labels are toggled off. */
  .msg-time { width: auto; margin-top: .2rem; }
  .msg-model { margin-top: .2rem; }
  .msg-model::after { content: "·"; margin-inline: .4rem; opacity: .5; }
}

/* ============================================================
   NIGHT THEME (dark mode) — DESIGN_SYSTEM.md §12
   Activated by data-bs-theme="dark" on <html> (set early, before paint,
   by the _ThemeHead partial). Bootstrap 5.3 reads the same attribute for
   its own components; we remap the app tokens + a few Bootstrap surface
   vars to a deep-navy "Blue Horizon at night" palette and fix the handful
   of components that hard-code light colors. Filled --brand-600/700 (white
   text on them) are left untouched; only brand-as-TEXT spots are lightened.
   ============================================================ */
[data-bs-theme="dark"] {
  /* neutrals → «شبِ گرم»: the cream canvas dimmed to a deep warm ink, so the night
     theme is the same warm identity after sunset — not an unrelated navy app. */
  --app-bg: #16110c;
  --app-surface: #211a14;
  --app-surface-2: #1b1510;
  /* Same de-blued question bubble as light mode — reuses the next elevation tier rather than a
     literal "darker than bg" tone, since darker-than-near-black would be nearly invisible here. */
  --app-question-bg: var(--app-surface-2);
  --app-border: #3a3126;
  --app-ink: #f3ecdf;
  --app-ink-2: #cfc3b0;
  --app-muted: #a3957f;
  --scrollbar-thumb: rgba(243, 236, 223, .14);
  --scrollbar-thumb-strong: rgba(243, 236, 223, .28);

  /* soft brand/status tints (hover fills, pills, selection, badges) → dark.
     Brand fills keep the BLUE hue (selected rows, user bubble) — blue is the brand
     accent on the warm night canvas, exactly like terracotta is by day. */
  --brand-50:  #1d2a41;
  --brand-100: #283a57;
  --brand-200: #34517a;
  --app-primary-soft: #1d2a41;
  --accent-50: #33221a;
  /* «زرشکی» after sunset — muted berry that stays legible on the warm-night canvas. */
  --blog-crimson: #e7899f;
  --blog-crimson-dim: #7a3a49;
  --blog-crimson-border: #5a2634;
  --blog-crimson-soft: #2c1620;
  --ok-50:   #17281e;
  --warn-50: #2b2213;
  --bad-50:  #331a12;

  /* shadows read stronger on a dark canvas */
  --shadow-1: 0 1px 2px rgba(0,0,0,.5), 0 2px 8px rgba(0,0,0,.38);
  --shadow-2: 0 12px 32px rgba(0,0,0,.55);

  /* map Bootstrap's own surface/border vars onto the warm-night palette so
     cards, inputs, dropdowns, modals & tables match (not Bootstrap grey) */
  --bs-body-bg: var(--app-bg);
  --bs-body-color: var(--app-ink);
  --bs-emphasis-color: var(--app-ink);
  --bs-secondary-bg: var(--app-surface);
  --bs-tertiary-bg: var(--app-surface-2);
  --bs-border-color: var(--app-border);
  --bs-secondary-color: var(--app-muted);
  --bs-tertiary-color: var(--app-muted);
}

[data-bs-theme="dark"] body { background: var(--app-bg); color: var(--app-ink); }
[data-bs-theme="dark"] .card { --bs-card-bg: var(--app-surface); --bs-card-border-color: var(--app-border); }
[data-bs-theme="dark"] .dropdown-menu { --bs-dropdown-bg: var(--app-surface); --bs-dropdown-border-color: var(--app-border); --bs-dropdown-link-color: var(--app-ink); }
[data-bs-theme="dark"] .modal-content, [data-bs-theme="dark"] .offcanvas { background: var(--app-surface); }
[data-bs-theme="dark"] .list-group { --bs-list-group-bg: var(--app-surface); }

/* Links + selection a touch brighter for AA contrast on the dark canvas */
[data-bs-theme="dark"] a { color: var(--brand-300); }
[data-bs-theme="dark"] a:hover { color: #c3dbff; }
[data-bs-theme="dark"] ::selection { background: var(--brand-100); color: var(--app-ink); }

/* Translucent navbar → warm dark glass */
[data-bs-theme="dark"] .app-navbar { background: rgba(22,17,12,.82); border-bottom-color: var(--app-border); }
[data-bs-theme="dark"] .app-navbar .navbar-brand { color: var(--app-ink); }

/* Brand colour used as TEXT on a (now dark) surface → lighten to stay legible.
   (Filled buttons/bubbles that put white text ON --brand-600/700 are untouched.) */
[data-bs-theme="dark"] .app-navbar .navbar-brand,
[data-bs-theme="dark"] .app-navbar .nav-link:hover,
[data-bs-theme="dark"] .app-navbar .nav-link.active,
[data-bs-theme="dark"] .nav-tabs .nav-link.active,
[data-bs-theme="dark"] .wallet-chip,
[data-bs-theme="dark"] .hero-chip,
[data-bs-theme="dark"] .accordion-button:not(.collapsed),
[data-bs-theme="dark"] .price-amount,
[data-bs-theme="dark"] .badge-soft.info { color: var(--brand-300); }
[data-bs-theme="dark"] .btn-outline-primary { color: var(--brand-300); border-color: var(--brand-300); }
/* New landing components: brand/terracotta used as TEXT lightens for AA on the night canvas */
[data-bs-theme="dark"] .hero-stats .hs-num,
[data-bs-theme="dark"] .lane-card .lc-link,
[data-bs-theme="dark"] .hero-title .t-blue,
[data-bs-theme="dark"] .chat-demo .cd-model { color: var(--brand-300); }
[data-bs-theme="dark"] .lane-card.lane-featured .lc-link { color: #fff; }
[data-bs-theme="dark"] .sec-eyebrow,
[data-bs-theme="dark"] .chat-demo .cd-cost .lbl { color: #e8a988; }
[data-bs-theme="dark"] .hero-title .t-clay { color: #e0906a; }
[data-bs-theme="dark"] .app-footer a:hover { color: var(--brand-300); }
[data-bs-theme="dark"] .hero-title .grad { background: linear-gradient(90deg, var(--brand-300), var(--brand-500)); -webkit-background-clip: text; background-clip: text; }

/* Soft badges: keep the dark tint, lift the text for contrast (warm-night hues) */
[data-bs-theme="dark"] .badge-soft.ok   { color: #7fd4ae; }
[data-bs-theme="dark"] .badge-soft.warn { color: #e7b967; }
[data-bs-theme="dark"] .turn-unsaved { color: #e7b967; }
[data-bs-theme="dark"] .badge-soft.bad  { color: #ff9d8a; }
[data-bs-theme="dark"] .badge-soft.mute { background: #2a2318; color: var(--app-muted); }
[data-bs-theme="dark"] .wallet-chip.low { color: #ff9d8a; border-color: #55301f; }

/* Feature / category icon tiles → dark translucent version of each (warm-palette) hue */
[data-bs-theme="dark"] .tile-blue    { background: rgba(38,95,176,.20);  color: #8fb4e4; }
[data-bs-theme="dark"] .tile-emerald { background: rgba(47,158,111,.18); color: #7fd4ae; }
[data-bs-theme="dark"] .tile-amber   { background: rgba(184,128,31,.18); color: #e5b45c; }
[data-bs-theme="dark"] .tile-violet  { background: rgba(201,111,74,.18); color: #e8a988; }
[data-bs-theme="dark"] .tile-cyan    { background: rgba(38,95,176,.16);  color: #a9c6ec; }
[data-bs-theme="dark"] .tile-pink    { background: rgba(168,91,58,.20);  color: #dfa084; }
[data-bs-theme="dark"] .hero-preview .online { color: #7fd4ae; }

/* Gradients that hard-coded light colours → warm-night-friendly */
[data-bs-theme="dark"] .home-hero { background: transparent; }
/* Night sidebar: the same "quieter version of the canvas" idea as by day, one step RAISED from the
   #16110c night canvas rather than a colder slab dropped onto it. Re-pointing the --side-* tokens
   is the whole change — every rule in the sidebar reads them, so nothing else needs a dark variant.
   (This replaced a hard-coded navy gradient, which was the one cold element left in a warm system.) */
[data-bs-theme="dark"] {
  /* At night, panel-vs-canvas separation is carried by the BORDER, not the fill — the app's own
     --app-surface sits only 1.09:1 above --app-bg while --app-border reads 1.47:1. These follow that
     same convention instead of inventing a brighter slab, so the sidebar is elevated exactly the way
     every other panel in the night theme is. */
  --side-bg: #221b13;              /* 1.10:1 above the night canvas — matches --app-surface's step */
  --side-bg-2: #261e15;
  --side-edge: #3a3126;            /* = --app-border at night */
  --side-ink: #f0e8da;               /* 13.4:1 on --side-bg */
  --side-dim: #a3957f;               /* = --app-muted; 5.1:1 on --side-bg */
  --side-hover: rgba(243, 236, 223, .07);
  --side-rule: rgba(243, 236, 223, .11);
  --side-raised: rgba(243, 236, 223, .06);
  --side-raised-line: rgba(243, 236, 223, .12);
  --side-active-bg: #2f6ec4;         /* lifted off brand-600 so it separates from the dark ground */
  --side-active-ink: #ffffff;
  --side-foot-bg: rgba(0, 0, 0, .22);
  --side-scroll-thumb: rgba(243, 236, 223, .16);
  --side-scroll-thumb-strong: rgba(243, 236, 223, .32);
}
[data-bs-theme="dark"] .lane-card .lc-code { border: 1px solid var(--app-border); }
[data-bs-theme="dark"] body {
  background:
    radial-gradient(1300px 680px at 78% -200px, rgba(38,95,176,.14), transparent 72%),
    radial-gradient(1200px 720px at 20% 96%, rgba(201,111,74,.07), transparent 70%),
    var(--app-bg);
}
[data-bs-theme="dark"] .model-info { background: linear-gradient(180deg, var(--app-surface) 0%, var(--app-surface-2) 140%); }
[data-bs-theme="dark"] .bubble.md code { background: rgba(143,180,228,.16); }

/* The logo PNGs carry a baked white background — on a dark card that reads as a harsh
   white rectangle. Present it as a deliberate rounded white badge instead (same idea as
   the sidebar's .brand-logo-chip). Applies to the auth pages + the dark sidebar chip. */
[data-bs-theme="dark"] .auth-logo-img {
  background: #fff;
  padding: 8px 12px;
  border-radius: 16px;
  box-shadow: 0 4px 16px rgba(0,0,0,.45);
}

/* ---------- Theme toggle button (in every layout header) ---------- */
.theme-toggle { line-height: 1; }
.theme-toggle .theme-ic { font-size: 1rem; display: inline-block; }

/* ---------- Local-provider («گروه تاریک») online/offline indicator ---------- */
.status-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: 0 0 auto; }
.status-dot.online { background: #16a34a; box-shadow: 0 0 0 3px rgba(22,163,74,.18); }
.status-dot.offline { background: #9ca3af; box-shadow: 0 0 0 3px rgba(156,163,175,.18); }
.local-status-badge { display: inline-flex; align-items: center; gap: .35rem; font-size: .8rem; padding: .1rem .5rem; border-radius: 999px; border: 1px solid var(--app-border); }
.local-status-badge.online { color: #16a34a; }
.local-status-badge.offline { color: #b45309; }
.local-status-badge.unknown { color: var(--app-muted); }
/* Still selectable — the server can come back between the probe and the send — but visibly dimmed
   so the row reads as "not right now" instead of looking identical to a working model. */
.mp-row.mp-row-offline .mp-name { opacity: .6; }

/* ---------- Local-model generation queue notice (in the pending reply bubble) ---------- */
.queue-wait { display: inline-block; line-height: 1.7; color: var(--app-muted); }
.queue-wait strong { color: #b45309; font-size: 1.05em; }

/* ============================================================
   Studio — image generation / editing / video / music
   ============================================================ */
/* The page column. No hero here: the sticky topbar already renders the page title (a second
   heading was the duplicated «استودیوی تولید»). On desktop this column is stretched to the
   viewport by the body:has(.studio-body) chain further down, exactly like chat's .chat-layout. */
.studio-layout { display: flex; flex-direction: column; gap: .75rem; min-height: 0; }

/* --- segmented section switcher: a compact pill strip, not three hero cards --- */
.studio-segments { display: flex; flex-wrap: wrap; gap: .4rem; margin-block-end: 0; }
.seg {
  display: inline-flex; align-items: center; gap: .5rem; text-align: start;
  min-height: 0; padding: .4rem .85rem; border: 1px solid var(--app-border); border-radius: 999px;
  background: var(--app-surface); color: var(--app-ink); cursor: pointer;
  transition: border-color .15s, box-shadow .15s, background .15s;
}
.seg:hover { border-color: var(--brand-300); background: var(--app-surface-2); }
.seg > .ic { width: 1.1rem; height: 1.1rem; color: var(--app-muted); flex: 0 0 auto; }
.seg-title { font-weight: 600; font-size: .9rem; }
.seg-count {
  align-self: center; font-size: .8rem; font-weight: 600; min-width: 1.7rem; text-align: center;
  padding: .15rem .5rem; border-radius: 999px; background: var(--app-surface-2);
  color: var(--app-muted); border: 1px solid var(--app-border);
}
.seg.is-active {
  border-color: var(--brand-600); background: var(--brand-50);
  box-shadow: 0 0 0 1px var(--brand-600) inset;
}
.seg.is-active > .ic, .seg.is-active .seg-title { color: var(--brand-700); }
.seg.is-active .seg-count { background: var(--brand-600); color: #fff; border-color: var(--brand-600); }

/* ---- The same section switcher, as sidebar sub-navigation ----
   Scoped by ancestor: the pill strip above is what Studio still uses at page level nowhere else,
   but these rules must win inside the sidebar, where the buttons have to read as indented nav rows
   under «عکس، فیلم، صدا» rather than as free-floating pills. */
.side-subnav { list-style: none; }
.side-subnav .studio-segments { display: block; list-style: none; margin: 0; padding: 0; }
.side-subnav .studio-segments > li { margin: 0; }
.side-subnav .seg {
  display: flex; width: 100%; gap: .55rem; align-items: center;
  /* Indented past the parent row's icon column so the nesting is legible without a rule or guide.
     Logical, not physical: in RTL the indent belongs on the right, which `padding-left` would put
     on the wrong side entirely. */
  padding: .4rem .7rem;
  padding-inline-start: 2.05rem;
  border: 0; border-radius: 9px; background: transparent; color: var(--side-ink);
  font-family: inherit; font-size: .86rem; text-align: start;
}
.side-subnav .seg:hover { background: var(--side-hover); border: 0; }
.side-subnav .seg > .ic { width: 1rem; height: 1rem; color: var(--side-dim); }
.side-subnav .seg-title { font-weight: 500; font-size: .86rem; flex: 1; min-width: 0; }
.side-subnav .seg-count {
  align-self: center; min-width: 0; padding: 0; border: 0; background: transparent;
  color: var(--side-dim); font-size: .72rem; font-weight: 500;
}
.side-subnav .seg.is-active {
  background: var(--side-active-bg); box-shadow: none; border: 0;
}
.side-subnav .seg.is-active > .ic,
.side-subnav .seg.is-active .seg-title,
.side-subnav .seg.is-active .seg-count { color: var(--side-active-ink); }

/* ---- Studio's per-section history, relocated into the sidebar's recents region ---- */
.studio-recents { display: flex; flex-direction: column; }
.studio-recents-group[hidden] { display: none; }
.studio-recents-group { display: flex; flex-direction: column; gap: .4rem; padding: 0 .65rem; }
.studio-recents-group .conv-list { flex: none; overflow: visible; padding: 0; }
/* The emptied .conv-sidebar shell stays in .studio-body (initStudio queries it live) but must not
   take space or paint once its contents have moved out. */
body.nav-studio .studio-body > .conv-sidebar,
body.nav-studio .studio-body > .conv-backdrop { display: none; }
/* Chat's own recents heading + list are inert on this page — Studio owns the region here. */
body.nav-studio .side-recents > .side-section,
body.nav-studio #convList { display: none; }

/* --- panels --- */
.studio-panel { display: none; }
.studio-panel.is-active { display: block; animation: studioFade .2s ease; }
@keyframes studioFade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.studio-grid { display: grid; grid-template-columns: minmax(320px, 380px) 1fr; gap: 1rem; align-items: start; }
.studio-controls { display: flex; flex-direction: column; gap: 1rem; }
.ctl-block { display: flex; flex-direction: column; gap: .45rem; }
.ctl-label { font-size: .82rem; font-weight: 600; color: var(--app-ink-2); }

/* --- model card picker --- */
.mp-search { position: relative; margin-block-end: .5rem; }
.mp-search .ic { position: absolute; inset-inline-start: .6rem; top: 50%; transform: translateY(-50%); width: 1rem; height: 1rem; color: var(--app-muted); }
.mp-search-input {
  width: 100%; padding: .4rem .6rem; padding-inline-start: 2rem; font-size: .85rem;
  border: 1px solid var(--app-border); border-radius: 10px; background: var(--app-surface-2); color: var(--app-ink);
}
.mp-search-input:focus { outline: none; border-color: var(--brand-300); box-shadow: 0 0 0 3px rgba(38,95,176,.18); }
.mp-grid { display: flex; flex-direction: column; gap: .4rem; max-height: 248px; overflow-y: auto; padding-inline-end: .2rem; }
/* Studio model PICKER card (a clickable button). Scoped to .mp-grid so this row
   layout never leaks onto the catalog cards in .models-inner-grid. */
.mp-grid .model-card {
  display: flex; align-items: center; gap: .6rem; width: 100%; text-align: start;
  padding: .5rem .6rem; border: 1px solid var(--app-border); border-radius: 11px;
  background: var(--app-surface); color: var(--app-ink); cursor: pointer;
  transition: border-color .12s, background .12s, box-shadow .12s;
}
.mp-grid .model-card:hover { border-color: var(--brand-300); background: var(--app-surface-2); }
.mp-grid .model-card.is-selected { border-color: var(--brand-600); background: var(--brand-50); box-shadow: 0 0 0 1px var(--brand-600) inset; }
/* Studio picker: render the card list fully left-to-right (Latin model names) — monogram on
   the left, text left-aligned — and let names wrap to as many lines as needed so the FULL
   name is always visible (never truncated). Scoped to .mp-grid so the (Persian) search box
   above keeps its RTL direction. The model popover itself is widened (see .pop-model). */
.mp-grid { direction: ltr; }
.mp-grid .mc-name { white-space: normal; line-height: 1.35; }

/* Catalog card (public /Models, in-panel guide): a vertical stack — title on one
   line, then capability badges, then the description. */
.models-inner-grid .model-card { display: flex; flex-direction: column; gap: .35rem; }
.models-inner-grid .model-card .mc-title { min-width: 0; }      /* lets the title truncate to one line */
.models-inner-grid .model-card .badge { white-space: nowrap; }
.mc-mono {
  flex: 0 0 auto; width: 2rem; height: 2rem; border-radius: 8px; display: grid; place-items: center;
  font-weight: 700; font-size: .9rem; color: #fff; background: linear-gradient(135deg, var(--brand-500), var(--brand-700));
}
.mc-mono.is-free { background: linear-gradient(135deg, var(--accent-400), var(--accent-500)); }
.mc-body { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.mc-name { font-size: .9rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mc-maker { font-size: .72rem; color: var(--app-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mc-badge { flex: 0 0 auto; font-size: .68rem; font-weight: 600; padding: .08rem .45rem; border-radius: 999px; }
.mc-badge.is-free { color: #047857; background: var(--ok-50); border: 1px solid #a7f3d0; }
.mc-badge.is-paid { color: var(--app-muted); background: var(--app-surface-2); border: 1px solid var(--app-border); }

/* --- chips (aspect / count) --- */
.chip-group { display: flex; flex-wrap: wrap; gap: .4rem; }
.chip {
  padding: .35rem .7rem; font-size: .82rem; font-weight: 600; cursor: pointer;
  border: 1px solid var(--app-border); border-radius: 999px; background: var(--app-surface); color: var(--app-ink-2);
  transition: border-color .12s, background .12s, color .12s;
}
.chip:hover { border-color: var(--brand-300); }
.chip.is-selected { border-color: var(--brand-600); background: var(--brand-600); color: #fff; }

/* --- sample prompt chips --- */
.sample-prompts { display: flex; flex-wrap: wrap; gap: .35rem; margin-block-start: .15rem; }
.sample {
  font-size: .76rem; color: var(--app-ink-2); cursor: pointer; text-align: start; line-height: 1.5;
  padding: .25rem .6rem; border: 1px dashed var(--app-border); border-radius: 999px; background: transparent;
  max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sample:hover { border-color: var(--brand-300); color: var(--brand-700); background: var(--brand-50); border-style: solid; }

/* --- advanced disclosure --- */
.opt-advanced summary { cursor: pointer; font-size: .82rem; font-weight: 600; color: var(--app-muted); list-style: none; }
.opt-advanced summary::-webkit-details-marker { display: none; }
.opt-advanced summary::before { content: "▸ "; }
.opt-advanced[open] summary::before { content: "▾ "; }
.opt-advanced[open] { display: flex; flex-direction: column; gap: .6rem; }
.opt-advanced .ctl-block { margin-block-start: .5rem; }

/* --- image-edit upload zone --- */
.upload-zone {
  border: 1.5px dashed var(--app-border); border-radius: var(--app-radius); background: var(--app-surface-2);
  padding: 1rem; text-align: center; cursor: pointer; transition: border-color .15s, background .15s;
}
.upload-zone:hover, .upload-zone:focus-visible, .upload-zone.is-drag { border-color: var(--brand-500); background: var(--brand-50); outline: none; }
.uz-empty { display: flex; flex-direction: column; align-items: center; gap: .4rem; color: var(--app-muted); font-size: .85rem; }
.uz-empty .ic-lg { width: 1.8rem; height: 1.8rem; color: var(--brand-500); }
.uz-preview { display: flex; align-items: center; justify-content: center; gap: .8rem; }
.uz-preview img { height: 84px; border-radius: 10px; border: 1px solid var(--app-border); }

/* --- run button + hint --- */
.btn-run { display: inline-flex; align-items: center; justify-content: center; gap: .4rem; width: 100%; padding-block: .6rem; font-size: .98rem; }
.btn-run.is-busy { position: relative; color: transparent; pointer-events: none; }
.btn-run.is-busy::after {
  content: ""; position: absolute; width: 1.1rem; height: 1.1rem; border-radius: 50%;
  border: 2px solid rgba(255,255,255,.5); border-top-color: #fff; animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.run-hint { font-size: .8rem; color: var(--bad-500); min-height: 1em; }
.run-hint:empty { display: none; }

/* --- generation canvas / output gallery --- */
.studio-canvas { display: flex; flex-direction: column; gap: .6rem; min-height: 360px; }
.gen-stage { flex: 1 1 auto; display: flex; }
.stage-placeholder {
  flex: 1 1 auto; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .5rem; color: var(--app-muted); border: 1px dashed var(--app-border); border-radius: var(--app-radius);
  background: var(--app-surface-2); min-height: 320px; font-size: .9rem;
}
.stage-placeholder .glyph { font-size: 2.2rem; }
.gen-gallery { flex: 1 1 auto; display: grid; grid-template-columns: 1fr; gap: .7rem; width: 100%; }
.gen-gallery.is-multi { grid-template-columns: repeat(2, 1fr); }
.gen-item {
  position: relative; border: 1px solid var(--app-border); border-radius: var(--app-radius);
  background: var(--app-surface-2); overflow: hidden; min-height: 160px;
  display: flex; align-items: center; justify-content: center;
}
.gen-item img, .gen-item video { width: 100%; height: auto; display: block; }
.gen-item audio { width: 100%; padding: .8rem; }
.gen-item.is-loading, .gen-item.is-error { min-height: 200px; }
.gi-spin { display: grid; place-items: center; color: var(--brand-600); }
.gi-err { padding: 1rem; color: var(--bad-500); font-size: .85rem; text-align: center; }
.gi-text { padding: .8rem; font-size: .9rem; line-height: 1.7; }
.gen-dl {
  position: absolute; inset-block-start: .5rem; inset-inline-end: .5rem;
  width: 2rem; height: 2rem; display: grid; place-items: center; border-radius: 8px;
  background: rgba(15,23,42,.55); color: #fff; backdrop-filter: blur(4px); opacity: 0; transition: opacity .15s;
}
.gen-item:hover .gen-dl { opacity: 1; }
.gen-dl .ic { width: 1.05rem; height: 1.05rem; }

@media (max-width: 900px) {
  /* .studio-segments is a wrapping pill row now — it needs no responsive column overrides. */
  .studio-grid { grid-template-columns: 1fr; }
}
@media (max-width: 520px) {
  .gen-gallery.is-multi { grid-template-columns: 1fr; }
}

/* ============================================================
   Studio · image — conversational canvas (generate + iterate)
   ============================================================ */

/* Thread sidebar + canvas, the Studio twin of chat's .chat-layout. Deliberately a SEPARATE
   class: body:has(.chat-layout) locks page scroll and forces the full-viewport app shell,
   which would break Studio's normally-scrolling page. Reuses every .conv-* rule (item, group,
   backdrop, mobile off-canvas drawer) — those are scoped to .conv-sidebar, not to chat. */
.studio-body { display: flex; gap: 1rem; align-items: stretch; }
.studio-body .img-studio { flex: 1 1 auto; min-width: 0; }
/* Match the canvas's fixed height exactly. It must stay DEFINITE (not auto): .conv-list is
   flex:1 + overflow-y:auto, so without a bounded parent a long thread list would grow the
   sidebar past the canvas and scroll the page instead of the list. */
.studio-body .conv-sidebar { height: min(72vh, 640px); min-height: 0; }
/* Hairline between the history column and the canvas. This used to be an unscoped .conv-sidebar
   rule shared with chat; chat's column is gone (merged into the shell sidebar), so it is Studio's
   alone now and is scoped accordingly. */
@media (min-width: 992px) {
  .studio-body .conv-sidebar { border-inline-end: 1px solid var(--app-border); padding-inline-end: 1rem; }
}
/* (A «گفت‌وگوی جدید» toolbar twin used to live here, surfaced whenever Studio's own history column
   was hidden. That column is gone — the history and its new-thread button are permanently in the
   shell sidebar — so the twin, and the collapse states it existed for, went with it.) */

.img-studio {
  position: relative; display: flex; flex-direction: column;
  min-height: min(72vh, 640px); height: min(72vh, 640px); padding: 0; overflow: hidden;
  /* Chat and Studio use the same reading measure. Keep the scroller full-width so its scrollbar
     remains at the page edge, then narrow only the content inside it. */
  --studio-measure: 100%;
}
@media (min-width: 992px) {
  .img-studio { --studio-measure: min(60%, 48rem); }
}

/* --- thread --- */
.img-thread {
  flex: 1 1 auto; overflow-y: auto; padding: 1.1rem 0; display: flex; flex-direction: column; gap: 1.1rem;
  scroll-behavior: smooth; scrollbar-gutter: stable both-edges;
}
.img-thread > .turn {
  width: var(--studio-measure);
  margin-inline: auto;
}
.thread-empty {
  margin: auto; max-width: 32rem; text-align: center; color: var(--app-muted);
  display: flex; flex-direction: column; align-items: center; gap: .6rem;
}
.thread-empty .glyph { font-size: 2.6rem; }
.thread-empty h2 { margin: 0; font-size: 1.25rem; font-weight: 700; color: var(--app-ink); }
.thread-empty p { margin: 0; font-size: .9rem; line-height: 1.8; }
.thread-empty .sample-prompts { justify-content: center; margin-block-start: .4rem; }

/* --- turns --- */
/* Studio thread turns — deliberately mirror the chat page's .cmsg/.bubble pattern (full-width
   stacked Q&A, soft-tinted user bubble with the 4px "tail" corner, flat borderless assistant
   block, muted .msg-foot-style footer) so استودیو reads exactly like گفت‌وگو. Bubble values are
   copied from .cmsg .bubble / .cmsg.user .bubble above — keep the two in sync. */
.turn { display: flex; flex-direction: column; gap: .5rem; margin-bottom: .8rem; animation: studioFade .2s ease; }
.turn-bubble {
  max-width: 100%; padding: .6rem .9rem; border-radius: 14px; border-end-start-radius: 4px;
  background: var(--app-question-bg); color: var(--app-ink); font-size: .95rem; line-height: 1.9;
  white-space: pre-wrap; overflow-wrap: anywhere; box-shadow: var(--shadow-1);
}
.turn-bubble .muted { opacity: .8; font-style: italic; }
.turn-refs { display: flex; flex-wrap: wrap; gap: .4rem; }
.turn-refs img { height: 64px; width: 64px; object-fit: cover; border-radius: 10px; border: 1px solid var(--app-border); }
.turn-notice {
  align-self: flex-start; font-size: .8rem; color: var(--brand-700); background: var(--brand-50);
  border: 1px solid var(--brand-200); border-radius: 999px; padding: .25rem .7rem;
}
/* A generation that billed successfully but whose history-save silently failed (see
   appendAssistantTurn's setUnsaved doc comment) — amber, not the brand-blue .turn-notice above,
   since this needs to read as "act now" rather than routine info. */
.turn-unsaved {
  align-self: flex-start; font-size: .8rem; color: #7a5e1c; background: var(--warn-50);
  border: 1px solid var(--warn-500); border-radius: 999px; padding: .25rem .7rem;
}
.turn-assistant {
  /* Chat's assistant reply starts at the logical reading edge instead of becoming a centred island.
     Keep Studio's children stretched across the same reading measure, then let each child choose
     the same right-actions / left-footer placement used by .cmsg.assistant. */
  align-items: stretch;
}
.turn-gallery {
  display: grid; grid-template-columns: minmax(0, 420px); justify-content: start;
  gap: .7rem; width: 100%; margin-inline-start: 0; margin-inline-end: auto;
}
.turn-gallery.is-multi {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  max-width: 640px;
}
.turn-assistant.is-processing { position: relative; min-height: 220px; }
.media-generation-eta {
  /* The result card begins at the RTL reading edge. Anchor the status to that card's centre:
     210px for a desktop single card, 50% whenever the reading column/card is narrower. */
  position: absolute; z-index: 3; right: min(210px, 50%); left: auto; top: 50%;
  transform: translate(50%, -50%); pointer-events: none;
  width: max-content; max-width: min(calc(100% - 1.5rem), 25rem);
  white-space: normal; text-align: center; line-height: 1.55;
  padding: .48rem .8rem; border: 1px solid color-mix(in srgb, var(--brand-500) 32%, transparent);
  border-radius: 999px; background: color-mix(in srgb, var(--app-surface) 88%, transparent);
  color: var(--brand-700); box-shadow: var(--shadow-1); backdrop-filter: blur(8px);
  font-size: .78rem; font-weight: 650; direction: rtl;
}
.turn-assistant:has(.turn-gallery.is-multi) .media-generation-eta { right: min(320px, 50%); }
.media-generation-eta.is-overdue { color: #8a5a00; border-color: color-mix(in srgb, var(--warn-500) 45%, transparent); }
/* Footer row under an assistant turn (model · cost · time): the same bottom-left placement as
   Chat's .msg-foot, while the action row stays at the RTL reading edge on the right. */
.turn-meta {
  width: auto; margin-inline-start: auto; display: flex; flex-wrap: wrap; align-items: baseline;
  gap: .15rem .55rem; font-size: .72rem; color: var(--app-muted);
}
.turn-meta:empty { display: none; }
.turn-assistant > .msg-actions {
  width: auto; align-self: flex-start; justify-content: flex-start;
}
/* The new per-result .msg-actions row (copy/delete/feedback — reuses chat's exact .msg-actions/
   .msg-act chrome unchanged) needs its OWN reveal-on-hover rule: chat's is `.cmsg:hover
   .msg-actions{opacity:1}` (site.css ~1062), scoped to a class Studio's DOM never has. Without
   this the row would sit at opacity:0 permanently on desktop — same scoping problem Studio's own
   .shot-actions already solved once (`.shot:hover .shot-actions`, ~1876); same fix here. */
.turn-assistant:hover .msg-actions, .turn-assistant:focus-within .msg-actions { opacity: 1; }

/* --- result tile --- */
.shot {
  position: relative; border: 1px solid var(--app-border); border-radius: var(--app-radius);
  background: var(--app-surface-2); overflow: hidden; min-height: 160px;
  display: flex; align-items: center; justify-content: center;
}
.shot img, .shot video { width: 100%; height: auto; display: block; }
/* Video/audio controls must own their full row. The old absolutely-positioned action buttons sat
   over the native audio play button (and could intercept the click); media actions now live in a
   labelled, always-visible toolbar below the player. */
.shot.shot-media {
  min-height: auto; display: grid; grid-template-columns: minmax(0, 1fr);
  align-items: stretch; background: var(--app-surface);
}
.shot.shot-media audio { width: 100%; display: block; padding: .7rem; }
.shot.shot-media video { width: 100%; }
.shot.shot-media .shot-media-actions {
  position: static; opacity: 1; justify-content: flex-start;
  padding: 0 .7rem .7rem; direction: rtl;
}
/* The native audio player's own controls also follow the page's RTL reading order. */
.shot.shot-audio audio { direction: rtl; }
.shot.shot-media .shot-media-act {
  width: auto; min-width: 2.5rem; height: 2.35rem; padding-inline: .65rem;
  display: inline-flex; align-items: center; justify-content: center; gap: .35rem;
  border: 1px solid var(--app-border); background: var(--app-surface-2);
  color: var(--app-ink-2); backdrop-filter: none; font-size: .75rem; font-weight: 650;
}
.shot.shot-media .shot-media-act:hover {
  border-color: var(--brand-300); background: var(--brand-50); color: var(--brand-700);
}
.shot.shot-media .shot-media-act:focus-visible {
  outline: 2px solid var(--brand-500); outline-offset: 2px;
}
.shot.is-skel { min-height: 220px; background:
  linear-gradient(100deg, var(--app-surface-2) 30%, var(--brand-50) 50%, var(--app-surface-2) 70%);
  background-size: 200% 100%; animation: shotShimmer 1.3s ease-in-out infinite; }
@keyframes shotShimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
.shot.is-error { min-height: 120px; }
.shot.shot-text { min-height: auto; }
.shot .gi-text { padding: .8rem; font-size: .9rem; line-height: 1.7; }
.shot-actions {
  position: absolute; inset-block-start: .5rem; inset-inline-start: .5rem; inset-inline-end: auto;
  display: flex; gap: .35rem;
  opacity: 0; transition: opacity .15s;
}
.shot:hover .shot-actions, .shot:focus-within .shot-actions { opacity: 1; }
.shot-act {
  width: 2rem; height: 2rem; display: grid; place-items: center; border-radius: 8px; cursor: pointer;
  background: rgba(15,23,42,.58); color: #fff; border: none; backdrop-filter: blur(4px); text-decoration: none;
}
.shot-act:hover { background: var(--brand-600); }
.shot-act .ic { width: 1.05rem; height: 1.05rem; }

/* --- composer --- */
.composer {
  flex: 0 0 auto; border-block-start: 1px solid var(--app-border); background: var(--app-surface);
  padding: .7rem .9rem .8rem; display: flex; flex-direction: column; gap: .55rem;
}
.img-studio:not(.editor-workspace) > .composer {
  width: var(--studio-measure); margin-inline: auto;
  border-block-start: 0; background: transparent;
}
.img-studio:not(.editor-workspace) > .composer > .composer-tools {
  /* In this RTL flex row, flex-end is the physical LEFT edge. Keep the model/settings pair there
     while the conversation column itself remains centred. */
  width: 100%; flex-wrap: nowrap; justify-content: flex-end;
}
.img-studio:not(.editor-workspace) > .composer > .composer-tools > .pop-wrap {
  flex: 1 1 0; min-width: 0; max-width: 14.4rem;
}
.img-studio:not(.editor-workspace) > .composer > .composer-tools > .pop-wrap > .tool-btn {
  width: 100%;
}
.canvas-pill {
  align-self: flex-start; display: inline-flex; align-items: center; gap: .4rem;
  font-size: .8rem; color: var(--brand-700); background: var(--brand-50);
  border: 1px solid var(--brand-200); border-radius: 999px; padding: .25rem .35rem .25rem .7rem;
}
.canvas-pill .ic { width: 1rem; height: 1rem; }
.canvas-pill .cp-x {
  border: none; background: transparent; color: var(--brand-700); cursor: pointer; font-size: .85rem;
  width: 1.3rem; height: 1.3rem; border-radius: 50%; line-height: 1;
}
.canvas-pill .cp-x:hover { background: var(--brand-200); }
.attach-row { display: flex; flex-wrap: wrap; gap: .5rem; }
.video-frame-row { display: flex; gap: .65rem; flex-wrap: wrap; margin-bottom: .55rem; }
.video-frame-row[hidden], .video-frame-slot[hidden], .drop-overlay[hidden] { display: none !important; }
.video-composer {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "frames tools" "hint hint" "input input";
  align-items: end;
}
.video-composer > .video-frame-row { grid-area: frames; margin: 0; align-self: end; }
.video-composer > .composer-tools { grid-area: tools; align-self: end; }
.video-composer > .run-hint { grid-area: hint; }
.video-composer > .composer-input { grid-area: input; }
.video-frame-slot {
  position: relative; min-width: 9rem; min-height: 4.8rem; padding: .45rem;
  border: 1px dashed var(--app-border); border-radius: .75rem; background: var(--app-surface);
  color: var(--app-ink); display: grid; grid-template-columns: 3.5rem 1fr; gap: .5rem;
  align-items: center; text-align: start;
}
.video-frame-slot:hover { border-color: var(--brand-500); background: var(--brand-50); }
.video-frame-slot img { width: 3.5rem; height: 3.5rem; object-fit: cover; border-radius: .5rem; grid-row: 1 / span 2; }
.video-frame-slot .vf-label { font-weight: 700; font-size: .82rem; }
.video-frame-slot .vf-empty { color: var(--app-muted); font-size: .75rem; }
.video-frame-slot .vf-remove { position: absolute; inset-inline-end: .25rem; top: .15rem; font-size: 1.1rem; }
.attach-chip { position: relative; }
.attach-chip img { height: 52px; width: 52px; object-fit: cover; border-radius: 9px; border: 1px solid var(--app-border); display: block; }
.attach-chip .ac-x {
  position: absolute; inset-block-start: -.45rem; inset-inline-end: -.45rem; width: 1.25rem; height: 1.25rem;
  border-radius: 50%; border: none; background: var(--bad-500); color: #fff; font-size: .65rem; cursor: pointer; line-height: 1;
}
.composer-tools { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: .5rem; }
.pop-wrap { position: relative; }
.tool-btn {
  display: inline-flex; align-items: center; gap: .4rem; padding: .35rem .7rem; font-size: .82rem; font-weight: 600;
  border: 1px solid var(--app-border); border-radius: 999px; background: var(--app-surface); color: var(--app-ink-2); cursor: pointer;
  transition: border-color .12s, background .12s;
}
.tool-btn:hover { border-color: var(--brand-300); background: var(--app-surface-2); }
.tool-btn .ic { width: 1rem; height: 1rem; color: var(--brand-600); }
.tool-btn .tb-caret { color: var(--app-muted); font-size: .7rem; }
.tool-btn.tb-attach { padding: .35rem; width: 2.1rem; justify-content: center; }
.tb-val { max-width: min(20rem, 46vw); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Studio's composer tools (model + settings) are the two decisions that define the run and what it
   costs, but they rendered as small pills — the same chrome as a secondary chat toggle. Make them
   blocks the exact height of the frame slots they sit beside (4.8rem) and three times that wide, so
   the composer's bottom row reads as one band of equal-height boxes. They keep their place in the
   row; only the box grows. The width also has to hold the settings label, which now spells out
   every choice (duration · resolution · aspect · audio · seed) rather than just the first two.

   Laid out as a proper control card rather than centred text in a heavy outline: a tinted icon
   badge on the inline-start, a muted caption naming the control ("مدل" / "تنظیمات" — without it the
   box was just an anonymous value), the value under it, and the chevron centred on the trailing
   edge where a select's affordance belongs. Centred text with the chevron floating at the bottom
   read as unfinished, and the 2px border made two of these dominate the composer.

   The editor panel is excluded: it's a compact in-place tool whose stage needs the vertical space
   and it has one trigger, not a pair. */
.img-studio .composer:not(.editor-command-panel) > .composer-tools .tool-btn {
  /* Explicit box, not aspect-ratio: as a shrink-to-fit inline-flex the button takes its label's
     max-content width, so aspect-ratio sized each button off its own text and the two boxes came
     out different (71px vs 90px). A fixed size keeps them identical and matched to a frame slot. */
  width: 14.4rem; height: 4.8rem;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "icon cap caret" "icon val caret";
  align-items: center; column-gap: .5rem; row-gap: .1rem;
  padding: .45rem .6rem;
  border: 1px solid var(--app-border); border-radius: .75rem;
  background: var(--app-surface);
  /* RTL-only panel: `start` would resolve against each span's OWN direction, so the dir="ltr" model
     name would hug the left edge while the caption above it hugs the right. */
  text-align: right;
  transition: border-color .12s, background .12s, box-shadow .12s;
}
.img-studio .composer:not(.editor-command-panel) > .composer-tools .tool-btn:hover {
  border-color: var(--brand-300); background: var(--app-surface-2); box-shadow: var(--shadow-1);
}
/* Open popover = active control. */
.img-studio .composer:not(.editor-command-panel) > .composer-tools .tool-btn[aria-expanded="true"] {
  border-color: var(--brand-500); background: var(--brand-50);
}
.img-studio .composer:not(.editor-command-panel) > .composer-tools .tool-btn .ic {
  grid-area: icon; width: 2rem; height: 2rem; padding: .45rem; box-sizing: border-box;
  border-radius: .5rem; background: var(--brand-50); color: var(--brand-600);
}
.img-studio .composer:not(.editor-command-panel) > .composer-tools .tool-btn .tb-cap {
  grid-area: cap; align-self: end;
  /* Floor at 10px: the root drops to 14px under 768px, where a bare .66rem caption lands at 9.2px
     and Persian stops being comfortably legible. */
  font-size: max(.66rem, 10px); font-weight: 600; line-height: 1.2; color: var(--app-muted);
}
.img-studio .composer:not(.editor-command-panel) > .composer-tools .tool-btn .tb-caret {
  grid-area: caret; align-self: center; font-size: .7rem; color: var(--app-muted);
}
/* Wrap inside the box instead of ellipsis-cutting at the 20rem desktop / 7rem ≤620px caps — but cap
   at two lines so an unusually long model name can't grow past the fixed box height again. */
.img-studio .composer:not(.editor-command-panel) > .composer-tools .tool-btn .tb-val {
  grid-area: val; align-self: start;
  font-size: .8rem; font-weight: 700; line-height: 1.3; color: var(--app-ink);
  max-width: none; min-width: 0; white-space: normal;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
/* The button grid is RTL because its caption is Persian, but provider model/voice identifiers are
   LTR. Align only that value line to the physical left; the caption and chevron keep their places. */
.img-studio .composer:not(.editor-command-panel) > .composer-tools .tool-btn .tb-val[dir="ltr"] {
  text-align: left; unicode-bidi: plaintext;
}

/* --- popovers --- */
/* Popovers anchor to their trigger and open upward inside the bounded composer. On mobile they
   become top-layer bottom sheets in the responsive block below. */
.pop {
  position: absolute; inset-block-end: calc(100% + .5rem); inset-inline-start: auto; inset-inline-end: 0; z-index: 30;
  min-width: 17rem; max-width: 22rem; padding: .8rem; border: 1px solid var(--app-border);
  border-radius: var(--app-radius); background: var(--app-surface); box-shadow: var(--shadow-2);
  /* Cap to the viewport and scroll INSIDE it — a popover with enough content (chat's settings:
     ~13 controls + a font-size slider panel + 3 download buttons) can overflow past the bottom of
     a short window, and the page can't scroll an absolutely-positioned popover. Was chat-only
     (its old .toolbar-more had this); moved here so every .pop gets it, closing the same latent
     gap in Studio's own .pop-opts (untested until now, same risk). */
  max-height: min(80vh, calc(100vh - 6rem)); overflow-y: auto; overscroll-behavior: contain;
}
.pop.pop-opts { display: flex; flex-direction: column; gap: .8rem; }
.pop .model-picker { display: block; width: 100%; margin: 0; }
/* The model popover is wide so full (long) model names are visible top-to-bottom.
   Class selector (matches the markup's class="pop pop-model" — there's no id="pop-model"
   anywhere; this used to be written as an ID selector, so it never actually applied and the
   popover silently fell back to the base .pop's narrower 17-22rem width). */
.pop-model { width: min(30rem, 92vw); max-width: 92vw; min-width: 0; inset-inline-start: auto; inset-inline-end: 0; }
/* Centre the wide model catalog over its trigger. This stays inside the centred reading column on
   desktop and avoids making the toolbar appear visually weighted to either side. */
@media (min-width: 992px) {
  .img-studio .pop-model { left: 50%; right: auto; transform: translateX(-50%); }
}
/* Assistant popover needs the same wide treatment — two-line .asst-desc rows get cramped at the
   base .pop's 17-22rem (this used to be a dedicated `.assistant-picker .model-menu{width:30rem}`
   rule, tied to a class this reshell drops; same value, kept as its own name since it isn't a
   model list). */
.pop-assistant { width: min(30rem, 92vw); max-width: 92vw; min-width: 0; inset-inline-start: auto; inset-inline-end: 0; }

/* --- composer input row --- */
.composer-input { display: flex; align-items: flex-end; gap: .5rem; }
.composer-input textarea {
  flex: 1 1 auto; resize: none; max-height: 200px; min-height: 2.6rem; padding: .65rem .85rem;
  font: inherit; font-size: .95rem; line-height: 1.6; color: var(--app-ink);
  border: 1px solid var(--app-border); border-radius: 14px; background: var(--app-surface-2);
}
.composer-input textarea:focus { outline: none; border-color: var(--brand-300); box-shadow: 0 0 0 3px rgba(38,95,176,.16); background: var(--app-surface); }
.send-btn {
  flex: 0 0 auto; width: 2.7rem; height: 2.7rem; border-radius: 14px; border: none; cursor: pointer;
  background: var(--brand-600); color: #fff; display: grid; place-items: center; position: relative;
  transition: background .12s, transform .12s;
}
.send-btn:hover { background: var(--brand-700); }
.send-btn:active { transform: scale(.96); }
.send-btn .ic { width: 1.2rem; height: 1.2rem; }
.send-btn.is-busy { color: transparent; pointer-events: none; }
.send-btn.is-busy::after {
  content: ""; position: absolute; width: 1.1rem; height: 1.1rem; border-radius: 50%;
  border: 2px solid rgba(255,255,255,.5); border-top-color: #fff; animation: spin .7s linear infinite;
}

/* --- drag-drop overlay --- */
.drop-overlay {
  position: absolute; inset: 0; z-index: 40; display: grid; place-items: center;
  background: rgba(38,95,176,.10); backdrop-filter: blur(2px);
}
.drop-overlay .do-inner {
  display: flex; flex-direction: column; align-items: center; gap: .5rem; color: var(--brand-700); font-weight: 600;
  border: 2px dashed var(--brand-500); border-radius: var(--app-radius-lg); background: var(--app-surface);
  padding: 1.4rem 2.2rem; box-shadow: var(--shadow-2);
}
.drop-overlay .ic-lg { width: 2rem; height: 2rem; color: var(--brand-500); }

/* --- lightbox --- */
.lightbox {
  position: fixed; inset: 0; z-index: 1080; display: grid; place-items: center;
  background: rgba(8,12,22,.82); padding: 2rem;
}
.lightbox img, .lightbox video { max-width: 92vw; max-height: 88vh; border-radius: var(--app-radius); box-shadow: var(--shadow-2); }
.lightbox .lb-close {
  position: absolute; inset-block-start: 1rem; inset-inline-end: 1.2rem; width: 2.4rem; height: 2.4rem;
  border-radius: 50%; border: none; background: rgba(255,255,255,.14); color: #fff; font-size: 1.1rem; cursor: pointer;
}
.lightbox .lb-close:hover { background: rgba(255,255,255,.26); }

@media (max-width: 620px) {
  .img-studio { height: auto; min-height: 70vh; }
  /* Bubbles are full-width at every breakpoint now (chat parity) — only tighten the rhythm,
     the same way the chat page's compact-mobile rules do for .cmsg. */
  .turn { margin-bottom: .55rem; }
  .turn-bubble { padding: .55rem .8rem; line-height: 1.85; }
  .turn-gallery, .turn-gallery.is-multi { grid-template-columns: 1fr; }
  /* Scoped to Studio: its composer-tools row has more buttons crammed into one line than Chat's,
     so it needs a tighter cap. Unscoped, this would ALSO silently apply to Chat's model trigger
     (575.98px ⊂ 620px) and replace its today-unconstrained mobile label with a much harsher 112px
     cut — an accidental regression, not a deliberate choice, so keep it Studio-only. */
  .img-studio .tb-val { max-width: 7rem; }
}

/* Studio (desktop): fill the viewport exactly like Chat — the page itself stops scrolling, only
   .img-thread scrolls, and the composer stays pinned. Byte-for-byte the same chain chat uses for
   .chat-layout (see `body:has(.chat-layout)` further down).

   Keyed on .studio-body — NOT .studio-layout — because the layout column also renders for an
   inactive account and for kinds with no models, where the locked-model upsell card must still be
   able to scroll. .studio-body only exists when at least one kind has a usable canvas, and :has()
   sees it even inside the two display:none panels (DOM presence, not rendering).

   Every rule is prefixed with body:has(.studio-body) so it wins on SPECIFICITY (0,3,x) over
   `.studio-body .conv-sidebar{height:min(72vh,640px)}` and `.conv-sidebar{min-height:420px}` —
   nothing here depends on source order. Scoped to >=992px so the `@media(max-width:620px)`
   `.img-studio{height:auto}` rule above is never clobbered: below 992px the page keeps scrolling
   and the sidebar stays an off-canvas drawer, exactly as today.

   The min-height:0 on each link of the chain is mandatory — omit one and a flex item refuses to
   shrink below its content, so the PAGE scrolls instead of the thread. (.img-thread needs none:
   its own overflow-y:auto already zeroes its automatic minimum size.) */
@media (min-width: 992px) {
  body:has(.studio-body) { overflow: hidden; }
  body:has(.studio-body) .shell { height: 100vh; height: 100dvh; }
  body:has(.studio-body) .shell-main { min-height: 0; }
  body:has(.studio-body) .shell-content { flex: 1 1 auto; min-height: 0; }
  body:has(.studio-body) .studio-layout { height: 100%; }
  body:has(.studio-body) .studio-panel.is-active {
    display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0;
  }
  body:has(.studio-body) .studio-body { height: 100%; min-height: 0; }
  body:has(.studio-body) .studio-body .img-studio { height: 100%; min-height: 0; }
  body:has(.studio-body) .studio-body .conv-sidebar { height: 100%; min-height: 0; }
  /* A kind with no models renders a plain .surface card instead of .studio-body — let that tab
     scroll on its own, since the page can't. */
  body:has(.studio-body) .studio-panel.is-active:has(> .surface) { overflow-y: auto; }
}

/* ============================================================
   Locked model showcase — the «افق آبی» free trial reveals the
   full catalog to non-subscribers, disabled, with an upgrade CTA.
   (Chat picker uses native <option disabled>; this styles the
   studio empty-state teaser.)
   ============================================================ */
.locked-showcase { margin-top: 1rem; text-align: center; }
.locked-showcase .ls-head { font-size: .9rem; color: var(--app-muted); margin-bottom: .6rem; }
.locked-showcase .ls-grid {
  display: flex; flex-wrap: wrap; gap: .4rem; justify-content: center; max-width: 38rem; margin: 0 auto;
}
.locked-showcase .ls-chip {
  font-size: .8rem; padding: .25rem .6rem; border-radius: 999px;
  background: var(--app-surface-2); border: 1px solid var(--app-border);
  color: var(--app-muted); opacity: .85;
}

/* ============================================================
   Admin → user announcements inbox (dashboard card).
   Unread rows carry a brand accent on the inline-start edge and a
   tinted background; read rows fade back to neutral.
   ============================================================ */
.ann-item {
  border: 1px solid var(--app-border); border-radius: .6rem;
  padding: .7rem .85rem; background: var(--app-surface-2);
  border-inline-start: 3px solid transparent;
}
.ann-item.is-unread { border-inline-start-color: var(--brand-600, #2563eb); background: var(--brand-50, #eff6ff); }
.ann-item.is-read { opacity: .82; }
.ann-item .ann-body { white-space: pre-wrap; word-break: break-word; font-size: .9rem; }
.ann-item .ann-actions { display: flex; flex-direction: column; align-items: flex-end; gap: .25rem; }

/* Notification bell in the panel top bar, with an unread-count pip. */
.bell-chip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.1rem; height: 2.1rem; border-radius: 999px;
  color: var(--app-muted); border: 1px solid var(--app-border);
  background: var(--app-surface); text-decoration: none;
}
.bell-chip:hover { color: var(--brand-700, #1d4ed8); border-color: var(--brand-600, #2563eb); }
.bell-badge {
  position: absolute; top: -.35rem; inset-inline-end: -.35rem; min-width: 1.05rem; height: 1.05rem;
  padding: 0 .25rem; border-radius: 999px; background: var(--bad-600, #dc2626); color: #fff;
  font-size: .68rem; font-weight: 700; line-height: 1.05rem; text-align: center;
}

/* (The mobile-only "⋮" account menu lived here. Bell/wallet/logout now have a permanent home in
   .side-account at the bottom of the merged sidebar, so there is nothing left to fold away.) */

/* ---------- Blog ---------- */
/* Article hero: a distinct, professionally-framed panel that stands apart from the article card and
   shows the WHOLE image at its natural ratio (no cropping, whatever aspect the admin uploads). */
.blog-hero {
  margin: 0 0 1.25rem;
  padding: .5rem;
  background: var(--app-surface);
  border: 1px solid var(--app-border);
  border-radius: 18px;
  box-shadow: var(--shadow-1);
  overflow: hidden;
}
.blog-hero img {
  display: block;
  width: 100%;
  height: auto;            /* full image, never cropped */
  border-radius: 12px;
}
.blog-card-cover {
  display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
  border-radius: 12px; margin-block-end: .85rem; border: 1px solid var(--app-border);
}
/* Professional byline: avatar chip + author + date + reading time. */
.blog-byline { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.blog-byline .avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.4rem; height: 2.4rem; border-radius: 999px; flex: none;
  background: var(--brand-600, #2563eb); color: #fff; font-weight: 800; font-size: .95rem;
}
.blog-byline .who { font-weight: 700; }
.blog-byline .dot { opacity: .5; }

/* Readable long-form typography over the shared surface. */
.blog-body { line-height: 2; font-size: 1.02rem; }
.blog-body > *:first-child { margin-block-start: 0; }
.blog-body p { margin-block: 0 1.1rem; }
.blog-body p.lead { font-size: 1.14rem; font-weight: 500; color: var(--text-2, #334155); }
.blog-body h2 { font-size: 1.4rem; font-weight: 800; margin-block: 2rem .75rem; line-height: 1.5; }
.blog-body h3 { font-size: 1.12rem; font-weight: 700; margin-block: 1.5rem .5rem; }
.blog-body ul, .blog-body ol { margin-block: 0 1.1rem; padding-inline-start: 1.4rem; }
.blog-body li { margin-block: .35rem; }
.blog-body a { color: var(--brand-700, #1d4ed8); text-underline-offset: .2em; }
/* Pull-quote / callout — a clearly-visible tinted block so the article doesn't read as one flat
   slab. Blue by default; add class "crimson" for the زرشکی variant (alternate them for rhythm). */
.blog-body blockquote {
  position: relative; margin: 1.7rem 0; padding: 1.1rem 1.35rem 1.1rem 1.35rem;
  border: 1px solid var(--brand-200, #b9d2f0);
  border-inline-start: 5px solid var(--brand-600, #265fb0);
  background: var(--brand-50, #eaf1fb); border-radius: 0 14px 14px 0;
  font-size: 1.08rem; font-weight: 500; line-height: 1.95; color: var(--app-ink, #2a2118);
}
.blog-body blockquote::before {
  content: "”"; position: absolute; inset-block-start: -.2rem; inset-inline-start: .6rem;
  font-family: Georgia, serif; font-size: 2.6rem; line-height: 1; color: var(--brand-300, #8fb4e4);
}
.blog-body blockquote p { margin-block: 0 .6rem; }
.blog-body blockquote p:last-child { margin-block-end: 0; }
.blog-body blockquote cite {
  display: block; margin-block-start: .6rem; font-size: .92rem; font-style: normal;
  font-weight: 700; color: var(--brand-700, #1f4f93);
}
.blog-body blockquote.crimson {
  border-color: var(--blog-crimson-border, #e2b6c1);
  border-inline-start-color: var(--blog-crimson, #9b2c48);
  background: var(--blog-crimson-soft, #f7e7ec);
}
.blog-body blockquote.crimson::before { color: var(--blog-crimson-dim, #c98198); }
.blog-body blockquote.crimson cite { color: var(--blog-crimson, #9b2c48); }
/* Sparse professional color accents — use on a FEW key terms only, never whole paragraphs. */
.blog-body .tc-blue { color: var(--brand-700, #1f4f93); }
.blog-body .tc-crimson { color: var(--blog-crimson, #9b2c48); }

/* Admin blog editor: guarantee the tall form scrolls inside the modal (footer/Save stays reachable),
   independent of Bootstrap's scrollable-class quirks. The `min-height:0` is the load-bearing bit —
   without it a flex-column child (.modal-body) refuses to shrink, so overflow-y never engages and the
   whole dialog spills past the viewport. `#blogModal { overflow:auto }` is a belt: even if the body
   somehow doesn't scroll, the overlay itself can. */
#blogModal { overflow-y: auto; }
#blogModal .modal-dialog { max-height: calc(100dvh - 1rem); margin-block: .5rem; }
#blogModal .modal-content { max-height: calc(100dvh - 1rem); display: flex; flex-direction: column; }
#blogModal .modal-header, #blogModal .modal-footer { flex: 0 0 auto; }
#blogModal .modal-body { overflow-y: auto; min-height: 0; }
.blog-body figure { margin: 1.5rem 0; }
.blog-body figure img { width: 100%; border-radius: 12px; border: 1px solid var(--app-border); }
.blog-body figure figcaption {
  margin-block-start: .5rem; font-size: .88rem; color: var(--app-muted); text-align: center;
}

/* ===================================================================================
   «مسیر خوش‌فرجام» (feedback v4): quick-start cards, «انتخاب سریع» slots, two-level
   sidebar, welcome page, homepage sample prompts, dashboard first-steps checklist.
   Uses the REAL app tokens (--app-surface/--app-border/--app-muted), which the night
   theme remaps — plus explicit dark lightening for brand-as-TEXT spots (§12).
   =================================================================================== */

/* Chat empty-state brand tiles — tap switches the chat to that brand's top model in the user's
   package. Logo badge on the inline-start, brand name, then the model it actually resolves to.

   Each card carries its own brand accent (--bc-*), which drives the badge tint, the logo colour and
   the hover border, so the four tiles read as four products rather than four identical grey boxes. */
/* Everything below is 1.5x the first sizing pass. The container grew with it, otherwise two 1.5x
   cards would just squeeze their own text. */
.brand-cards {
  /* Explicit column counts, not auto-fit: there are exactly SIX tiles, and every breakpoint here
     divides 6 evenly (3 / 2 / 1). auto-fit picked 4 columns on a ~760px chat area, which left a
     ragged 4 + 2 last row and squeezed the model line into an ellipsis. */
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  /* Raised from 900px now that the empty state gets the full width: at 900 the three columns left
     each card ~106px of text, which is less than «Gemini 3.6 Flash» needs at any readable size.
     `width: 100%` is load-bearing, not decoration. The cards are query containers, so their
     contents no longer contribute to intrinsic width — and .chat-empty centres its children rather
     than stretching them, so without a definite width here the grid resolved to max-content of
     nothing and the whole row collapsed to ~157px. */
  gap: 1.05rem; margin-block-start: 1.25rem;
  width: 100%; max-width: min(100%, 64rem); margin-inline: auto;
  /* The markup also carries dir="ltr"; this keeps the LTR order if the attribute is ever dropped. */
  direction: ltr; text-align: left;
}
/* Neutral defaults for EVERY surface that renders a brand badge. Declared on both hosts, not just
   .brand-card: the landing tiles inherited none of these, so `--bc-fill: var(--bc-ink)` on the
   ChatGPT/Grok rules resolved against an undefined variable and their icons silently vanished there
   while Gemini/Claude — which set --bc-ink inline — kept working. */
.brand-card, .bs-tile, .studio-brand-card {
  /* ChatGPT and Grok are BLACK brands per the icon set's own brand notes, so they keep the
     ink/surface pair — which also means they follow light/dark automatically via --app-ink. */
  --bc-ink: var(--app-ink);
  --bc-tint: var(--app-surface-2);
  --bc-accent: var(--brand-300);
  --bc-mask: none;
  --bc-fill: transparent;
  --bc-mono: '';
}
.brand-card {
  /* Query container for the fluid type on .bc-brand / .bc-model below. Safe on a grid item: the
     grid already gives the card a definite width, so the size containment this implies costs
     nothing. */
  container-type: inline-size;
  display: flex; align-items: center; gap: 1.05rem; text-align: left;
  padding: 1.125rem 1.275rem; border-radius: 21px; cursor: pointer;
  border: 1px solid var(--app-border); background: var(--app-surface);
  color: inherit; text-decoration: none; box-shadow: var(--shadow-1);
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.brand-card:hover {
  border-color: var(--bc-accent); box-shadow: var(--shadow-2); transform: translateY(-1px);
}
.brand-card:active { transform: translateY(0); }

/* The logo is a CSS mask over a solid fill rather than an <img>: the icon set's -mono files are
   authored with fill="currentColor", and a mask lets the black brands invert for dark mode instead
   of turning into a black square on a dark card. ::before carries the mask so the badge's own tint
   background isn't masked away with it. */
.bc-logo {
  position: relative; flex: none; width: 3.6rem; height: 3.6rem;
  border-radius: 16px; background: var(--bc-tint);
  display: grid; place-items: center; color: var(--bc-ink);
  font-weight: 800; font-size: 1.35rem; line-height: 1;
}
/* Monogram fallback for a brand with no icon file yet. Empty string for icon brands, so the
   pseudo-element renders nothing and only the masked layer below shows. */
.bc-logo::after { content: var(--bc-mono, ''); }
/* --bc-fill, not --bc-ink, is what paints the masked layer. A brand with `--bc-mask: none` would
   otherwise render an UNMASKED solid block filling the whole badge; keeping the fill transparent
   by default means a brand without an icon degrades to its monogram instead of a coloured square. */
.bc-logo::before {
  content: ''; position: absolute; inset: 0; background: var(--bc-fill);
  -webkit-mask: var(--bc-mask) center / 2.025rem 2.025rem no-repeat;
          mask: var(--bc-mask) center / 2.025rem 2.025rem no-repeat;
}
/* Brand identity is shared by the chat cards and the landing-page tiles — keyed on .bc-* alone so
   both surfaces stay in step. */
.bc-chatgpt { --bc-mask: url('/img/ai-icons/svg/chatgpt-mono.svg'); --bc-fill: var(--bc-ink); }
.bc-grok    { --bc-mask: url('/img/ai-icons/svg/grok-mono.svg');    --bc-fill: var(--bc-ink); }
.bc-gemini  {
  --bc-mask: url('/img/ai-icons/svg/gemini-mono.svg'); --bc-fill: var(--bc-ink);
  --bc-ink: #1c7dff; --bc-tint: rgba(28, 125, 255, .12); --bc-accent: #1c7dff;
}
.bc-claude  {
  --bc-mask: url('/img/ai-icons/svg/claude-mono.svg'); --bc-fill: var(--bc-ink);
  --bc-ink: #d97757; --bc-tint: rgba(217, 119, 87, .14); --bc-accent: #d97757;
}
/* Both are BLACK brands per the icon set's own brand notes, exactly like ChatGPT and Grok, so they
   take the same ink/surface pair and invert for dark mode on their own. */
.bc-kimi { --bc-mask: url('/img/ai-icons/svg/kimi-mono.svg'); --bc-fill: var(--bc-ink); }
.bc-zai  { --bc-mask: url('/img/ai-icons/svg/zai-mono.svg');  --bc-fill: var(--bc-ink); }
/* Studio product/maker marks live in the same local set as the chat brands. Mono masks keep every
   mark crisp and let black brands follow dark mode without maintaining duplicate white assets. */
.bc-nanobanana {
  --bc-mask: url('/img/ai-icons/svg/nanobanana-mono.svg'); --bc-fill: var(--bc-ink);
  --bc-ink: #b67800; --bc-tint: rgba(249, 194, 60, .17); --bc-accent: #d99b16;
}
.bc-openai { --bc-mask: url('/img/ai-icons/svg/openai-mono.svg'); --bc-fill: var(--bc-ink); }
.bc-google {
  --bc-mask: url('/img/ai-icons/svg/google-mono.svg'); --bc-fill: var(--bc-ink);
  --bc-ink: #4285f4; --bc-tint: rgba(66, 133, 244, .12); --bc-accent: #4285f4;
}
.bc-sora {
  --bc-mask: url('/img/ai-icons/svg/sora-mono.svg'); --bc-fill: var(--bc-ink);
  --bc-ink: #0968da; --bc-tint: rgba(9, 104, 218, .12); --bc-accent: #0968da;
}
.bc-bytedance {
  --bc-mask: url('/img/ai-icons/svg/bytedance-mono.svg'); --bc-fill: var(--bc-ink);
  --bc-ink: #325ab4; --bc-tint: rgba(50, 90, 180, .12); --bc-accent: #3c8cff;
}
.bc-kling {
  --bc-mask: url('/img/ai-icons/svg/kling-mono.svg'); --bc-fill: var(--bc-ink);
  --bc-ink: #087ed0; --bc-tint: rgba(11, 242, 249, .12); --bc-accent: #04a6f0;
}
.bc-alibaba {
  --bc-mask: url('/img/ai-icons/svg/alibaba-mono.svg'); --bc-fill: var(--bc-ink);
  --bc-ink: #e85400; --bc-tint: rgba(255, 96, 3, .12); --bc-accent: #ff6003;
}
.bc-flux { --bc-mask: url('/img/ai-icons/svg/flux-mono.svg'); --bc-fill: var(--bc-ink); }
.bc-recraft { --bc-mask: url('/img/ai-icons/svg/recraft-mono.svg'); --bc-fill: var(--bc-ink); --bc-ink: #e44a7b; --bc-tint: rgba(228, 74, 123, .11); --bc-accent: #e44a7b; }
.bc-krea { --bc-mask: url('/img/ai-icons/svg/krea-mono.svg'); --bc-fill: var(--bc-ink); --bc-ink: #635bff; --bc-tint: rgba(99, 91, 255, .11); --bc-accent: #635bff; }
.bc-microsoft { --bc-mask: url('/img/ai-icons/svg/microsoft-mono.svg'); --bc-fill: var(--bc-ink); --bc-ink: #1473e6; --bc-tint: rgba(20, 115, 230, .11); --bc-accent: #1473e6; }
.bc-mistral { --bc-mask: url('/img/ai-icons/svg/mistral-mono.svg'); --bc-fill: var(--bc-ink); --bc-ink: #f97316; --bc-tint: rgba(249, 115, 22, .12); --bc-accent: #f97316; }
.bc-qwen { --bc-mask: url('/img/ai-icons/svg/qwen-mono.svg'); --bc-fill: var(--bc-ink); --bc-ink: #615ced; --bc-tint: rgba(97, 92, 237, .11); --bc-accent: #615ced; }
.bc-minimax { --bc-mask: url('/img/ai-icons/svg/minimax-mono.svg'); --bc-fill: var(--bc-ink); --bc-ink: #7c3aed; --bc-tint: rgba(124, 58, 237, .11); --bc-accent: #7c3aed; }
.bc-deepgram { --bc-mono: 'D'; --bc-ink: #13a36f; --bc-tint: rgba(19, 163, 111, .11); --bc-accent: #13a36f; }
.bc-sourceful { --bc-mono: 'S'; --bc-ink: #3568d4; --bc-tint: rgba(53, 104, 212, .11); --bc-accent: #3568d4; }
.bc-generic { --bc-mono: 'AI'; }

/* Provider-specific TTS voice lists can be long. A native select gives mobile users the platform
   search/scroll affordance instead of rendering dozens of tiny wrapped chips. */
.studio-voice-select {
  width: 100%; min-height: 2.65rem; border-color: var(--app-border);
  border-radius: 12px; background-color: var(--app-surface); color: var(--app-ink);
  font-size: .82rem;
}

/* min-width:0 so a long model name ellipsises instead of stretching the grid track. */
.bc-text { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
/* Both lines size themselves against the CARD, not the page, so a narrow card shrinks its type
   instead of amputating a model name. `cqi` is 1% of the card's own inline size, which is why the
   card declares itself a query container just above — a vw-based clamp would react to the window
   and get it exactly wrong here, since the card is narrowest when the window is widest but the
   sidebar is open. The clamp floor keeps it legible; the ceiling is the original size, so a roomy
   card looks exactly as it did before. Ellipsis stays as the last resort for a name no size can
   fit, but at these floors it effectively never fires. */
.bc-brand {
  font-size: clamp(1rem, 8.3cqi, 1.5rem);
  font-weight: 800; color: var(--app-ink); line-height: 1.25;
}
/* The model line is the quiet half — it names the tier, the brand above is what you scan for.
   Fluid sizing alone cannot guarantee a fit: cqi tracks the CARD, while overflowing depends on the
   NAME, and the catalog really does hand out things like «GPT-3.5 Turbo (older v0613)» (27 chars) —
   measured at 54px past the edge even in a roomy card. So the line is allowed to wrap, and only a
   name that still will not fit in TWO lines is ever cut. The grid stretches every card to the
   tallest, so a wrapped tile does not leave a ragged row. */
.bc-model {
  font-size: clamp(.72rem, 5.9cqi, 1.11rem);
  font-weight: 500; color: var(--app-muted); line-height: 1.35;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; overflow-wrap: anywhere;
}
/* A brand the package doesn't reach: dimmed teaser that links to the upgrade page. */
.brand-card.is-locked { opacity: .6; box-shadow: none; }
.brand-card.is-locked:hover { opacity: 1; }

/* Selected tile vs. the rest, on the empty state. The five that step back are DE-EMPHASISED, never
   disabled: they keep the pointer cursor and come fully back on hover, because switching brand by
   clicking one of them is the whole point of the row. Greyscale on the badge (not just opacity)
   is what actually reads as «خاکستری» — Gemini's blue and Claude's terracotta survive a fade. */
.brand-card.is-current {
  border-color: var(--bc-accent);
  box-shadow: var(--shadow-2), inset 0 0 0 1px var(--bc-accent);
}
.brand-card.is-dim { opacity: .6; box-shadow: none; }
.brand-card.is-dim .bc-logo { filter: grayscale(1); }
.brand-card.is-dim:hover { opacity: 1; box-shadow: var(--shadow-1); }
.brand-card.is-dim:hover .bc-logo { filter: none; }
/* minmax(0, 1fr), NOT a bare 1fr: `1fr` is minmax(auto, 1fr), and that auto minimum is min-content —
   which the nowrap model name pushes past the viewport (a 390px screen was getting a 493px card). */
/* 6 tiles: three across on a roomy chat area, two when it narrows, one on a phone. */
@media (max-width: 991.98px) { .brand-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 575.98px) { .brand-cards { grid-template-columns: minmax(0, 1fr); } }

/* ---------- Studio brand/model shortcuts ---------- */
/* Studio uses the same card scale as Chat so the two primary entry points feel like one product.
   The cards only select an already-gated picker row. */
.thread-empty.has-brand-cards {
  width: 100%; max-width: 64rem; padding-block: .35rem;
}
.thread-empty.has-brand-cards > p { max-width: 42rem; }
.studio-brand-picker { width: 100%; min-width: 0; margin-block-start: .45rem; }
/* One/two/four-card catalogs keep Chat's ~20rem tile width instead of stretching across a three
   column shell. Four audio models therefore read as an intentional 2×2 block, not a ragged 3+1. */
.studio-brand-picker.pick-count-1 { max-width: 20.65rem; margin-inline: auto; }
.studio-brand-picker.pick-count-2,
.studio-brand-picker.pick-count-4 { max-width: 42rem; margin-inline: auto; }
.studio-brand-picker.pick-count-1 .studio-brand-cards { grid-template-columns: minmax(0, 1fr); }
.studio-brand-picker.pick-count-2 .studio-brand-cards,
.studio-brand-picker.pick-count-4 .studio-brand-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.studio-brand-head {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  margin-block-end: .55rem; padding-inline: .1rem; direction: rtl;
  color: var(--app-ink-2); font-size: .78rem; font-weight: 650; text-align: start;
}
.studio-all-models {
  display: inline-flex; align-items: center; gap: .3rem; flex: none;
  border: 0; background: transparent; color: var(--brand-600); cursor: pointer;
  padding: .2rem .25rem; font: inherit; font-size: .74rem; font-weight: 650;
}
.studio-all-models:hover { color: var(--brand-700); }
.studio-brand-cards {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.05rem;
  width: 100%; min-width: 0; direction: ltr; text-align: left;
}
.studio-brand-card {
  position: relative; min-width: 0; container-type: inline-size;
  display: flex; align-items: center; gap: 1.05rem; padding: 1.125rem 1.275rem;
  border: 1px solid var(--app-border); border-radius: 21px;
  background: var(--app-surface); color: inherit; cursor: pointer; text-align: left;
  text-decoration: none; font: inherit;
  box-shadow: var(--shadow-1);
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease, background .15s ease;
}
.studio-brand-card:hover {
  border-color: var(--bc-accent); box-shadow: var(--shadow-2); transform: translateY(-1px);
}
.studio-brand-card:active { transform: translateY(0); }
.studio-brand-card:focus-visible { outline-color: var(--bc-accent); }
.studio-brand-card .bc-logo {
  width: 3.6rem; height: 3.6rem; border-radius: 16px; font-size: 1.35rem;
}
.studio-brand-card .bc-logo::before {
  -webkit-mask-size: 2.025rem 2.025rem;
          mask-size: 2.025rem 2.025rem;
}
.studio-brand-copy {
  display: flex; flex: 1 1 auto; min-width: 0; flex-direction: column; gap: .22rem;
}
.studio-brand-top { display: flex; align-items: center; gap: .4rem; min-width: 0; }
.studio-brand-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--app-ink); font-size: clamp(1rem, 8.3cqi, 1.5rem); font-weight: 800; line-height: 1.25;
}
.studio-brand-badge {
  flex: none; border-radius: 999px; padding: .08rem .38rem; direction: rtl;
  background: var(--app-surface-2); color: var(--app-muted);
  border: 1px solid var(--app-border); font-size: .61rem; font-weight: 650; line-height: 1.45;
}
.studio-brand-badge.is-recommended {
  color: var(--brand-700); background: var(--brand-50); border-color: var(--brand-100);
}
.studio-brand-badge.is-access-state {
  color: var(--app-muted); background: var(--app-surface-2); border-color: var(--app-border);
}
.studio-brand-model {
  color: var(--app-muted); font-size: clamp(.72rem, 5.9cqi, 1.11rem); font-weight: 500; line-height: 1.35;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; overflow-wrap: anywhere;
}
/* Audio tab only: says whether this card composes music or speaks. Its own line rather than the
   badge slot, because the badge becomes «پیشنهاد ما» on the recommended card and this is the one
   fact that must survive that. Speech is tinted so the two families separate at a glance. */
.studio-brand-kind {
  align-self: flex-start; flex: none; margin-block-start: .12rem;
  border-radius: 999px; padding: .06rem .42rem; direction: rtl;
  background: var(--app-surface-2); color: var(--app-muted);
  border: 1px solid var(--app-border); font-size: .61rem; font-weight: 650; line-height: 1.45;
}
.studio-brand-kind[data-audio-kind="speech"] {
  color: var(--brand-700); background: var(--brand-50); border-color: var(--brand-100);
}
.studio-brand-check {
  position: absolute; inset-block-end: .7rem; inset-inline-start: 3.55rem;
  width: 1.15rem; height: 1.15rem; display: grid; place-items: center;
  border-radius: 50%; background: var(--bc-accent); color: #fff;
  font-size: .66rem; font-weight: 800; opacity: 0; transform: scale(.75);
  transition: opacity .15s ease, transform .15s ease;
}
.studio-brand-card.is-current {
  border-color: var(--bc-accent);
  background: color-mix(in srgb, var(--bc-tint) 58%, var(--app-surface));
  box-shadow: var(--shadow-2), inset 0 0 0 1px var(--bc-accent);
}
.studio-brand-card.is-current .studio-brand-check { opacity: 1; transform: scale(1); }
.studio-brand-card.is-locked,
.studio-brand-card.is-needs-selection {
  opacity: .72; box-shadow: none;
}
.studio-brand-card.is-locked:hover,
.studio-brand-card.is-needs-selection:hover {
  opacity: 1; color: inherit; text-decoration: none;
}
.studio-brand-card.is-locked .bc-logo,
.studio-brand-card.is-needs-selection .bc-logo { filter: grayscale(.45); }
.studio-brand-card.is-locked:hover .bc-logo,
.studio-brand-card.is-needs-selection:hover .bc-logo { filter: none; }
.studio-brand-lock {
  position: absolute; inset-block-end: .72rem; inset-inline-start: .8rem;
  display: grid; place-items: center; width: 1.25rem; height: 1.25rem;
  border-radius: 50%; background: var(--app-surface-2); color: var(--app-muted);
  border: 1px solid var(--app-border); font-size: .62rem; line-height: 1;
}
.sample-prompts-label {
  margin-block-start: .15rem; color: var(--app-muted); font-size: .72rem; font-weight: 650;
}

/* In the dedicated editor the upload stage remains the first decision. Once an image is loaded,
   the choices become a compact single-row strip so they do not steal height from the canvas. */
.editor-workspace:not(.has-edit-source) .is-editor-picker { display: none; }
.studio-brand-picker.is-compact { flex: 0 0 auto; margin: 0; }
.studio-brand-picker.is-compact .studio-brand-head { margin-block-end: .35rem; }
.studio-brand-picker.is-compact .studio-brand-cards {
  display: flex; gap: .5rem; overflow-x: auto; padding-block: .05rem .2rem;
  scrollbar-width: none; overscroll-behavior-inline: contain;
}
.studio-brand-picker.is-compact .studio-brand-cards::-webkit-scrollbar { display: none; }
.studio-brand-picker.is-compact .studio-brand-card {
  flex: 0 0 13.25rem; min-height: 4.25rem; padding: .55rem .65rem;
}
.studio-brand-picker.is-compact .bc-logo { width: 2.35rem; height: 2.35rem; border-radius: 11px; }
.studio-brand-picker.is-compact .bc-logo::before {
  -webkit-mask-size: 1.3rem 1.3rem;
          mask-size: 1.3rem 1.3rem;
}

@media (max-width: 991.98px) {
  .studio-brand-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 575.98px) {
  .thread-empty.has-brand-cards {
    max-width: none; align-items: stretch; gap: .45rem; padding-block: .2rem; text-align: center;
  }
  .thread-empty.has-brand-cards > .glyph,
  .thread-empty.has-brand-cards > h2,
  .thread-empty.has-brand-cards > p,
  .thread-empty.has-brand-cards > .sample-prompts-label,
  .thread-empty.has-brand-cards > .sample-prompts { align-self: center; }
  .thread-empty.has-brand-cards > .glyph { font-size: 2.2rem; }
  .thread-empty.has-brand-cards > p { font-size: .82rem; line-height: 1.65; }
  .studio-brand-head { margin-block-end: .35rem; }
  /* One tactile row with a deliberate next-card peek. It keeps six models discoverable without
     turning a 390px screen into six stacked cards above the pinned composer. */
  .studio-brand-cards {
    display: grid; grid-template-columns: none; grid-auto-flow: column;
    grid-auto-columns: minmax(17rem, 86%); gap: .7rem;
    overflow-x: auto; overflow-y: hidden; padding: .12rem .08rem .5rem;
    scroll-snap-type: x mandatory; scroll-padding-inline: .08rem;
    overscroll-behavior-inline: contain; -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  .studio-brand-picker[class*="pick-count-"] .studio-brand-cards { grid-template-columns: none; }
  .studio-brand-cards::-webkit-scrollbar { display: none; }
  .studio-brand-card { scroll-snap-align: start; }
  .sample-prompts-label { margin-block-start: 0; }
  .thread-empty.has-brand-cards .sample-prompts {
    flex-wrap: nowrap; justify-content: flex-start; max-width: 100%; overflow-x: auto;
    padding-block-end: .15rem; scrollbar-width: none;
  }
  .thread-empty.has-brand-cards .sample-prompts::-webkit-scrollbar { display: none; }
  .thread-empty.has-brand-cards .sample { flex: 0 0 auto; max-width: min(18rem, 82vw); }

  .studio-brand-picker.is-compact .studio-brand-cards {
    display: flex; padding-block-end: .2rem; scroll-snap-type: x proximity;
  }
  .studio-brand-picker.is-compact .studio-brand-card { flex-basis: min(13.25rem, 78vw); }
}

/* (.qs-cards/.qs-card lived here for the empty state's prompt cards. The brand tiles above replaced
   them and no other surface used the classes — the homepage's prompt strip is .hero-prompt — so the
   rules are gone. `QuickStart` the SERVICE is still live: the homepage and the welcome page read it.) */

/* «انتخاب سریع» rows in the chat model picker. */
.mp-group-quick { color: var(--brand-700); font-weight: 700; }
.mp-quick .mp-quick-label {
  flex: 0 0 auto; font-size: .74rem; font-weight: 700; padding: .1rem .45rem;
  border-radius: 999px; background: var(--brand-100); color: var(--brand-700);
}
.mp-tag.tag-rec { background: var(--brand-700); color: #fff; }

/* «از کجا شروع کنم؟» strip on the model catalog pages. */
.qp-strip { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.qp-strip-label { font-size: .88rem; font-weight: 700; color: var(--app-muted); }
.qp-chip {
  display: inline-flex; align-items: center; gap: .45rem; padding: .35rem .7rem;
  border-radius: 999px; border: 1px solid var(--app-border); background: var(--app-surface);
  color: inherit; text-decoration: none; transition: border-color .15s ease;
}
a.qp-chip:hover { border-color: var(--brand-300); }
.qp-chip-label { font-size: .76rem; font-weight: 700; color: var(--brand-700); }
.qp-chip-name { font-size: .84rem; }

/* Two-level sidebar: the «بیشتر» fold.
   It used to be styled as a .side-section CAPTION (tiny, tracked-out, muted) because it labelled a
   group. It is now a real destination sitting in the nav run — the single door to everything that
   isn't the core journey — so it reads as a nav ROW, and heavier than its siblings so the door is
   obvious rather than looking like a heading nobody clicks. */
.side-adv-toggle {
  display: flex; align-items: center; gap: .65rem;
  width: calc(100% - 1.3rem); margin: .15rem .65rem;
  background: none; border: 0; cursor: pointer; text-align: start; font-family: inherit;
  color: var(--side-ink); font-size: .95rem; font-weight: 800; letter-spacing: 0;
  padding: .55rem .75rem; border-radius: 10px;
}
.side-adv-toggle:hover { background: var(--side-hover); }
.side-adv-toggle .icon { width: 1.35rem; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.side-adv-toggle .icon .ic { width: 1.2rem; height: 1.2rem; }
.side-adv-caret { transition: transform .15s ease; font-size: .7rem; margin-inline-start: auto; }
.side-adv.open .side-adv-caret { transform: rotate(180deg); }
.side-adv .side-adv-list { display: none; }
.side-adv.open .side-adv-list { display: block; }

/* First-login welcome page. */
.welcome-wrap { max-width: 720px; padding: 2rem 1.5rem; }
.welcome-hero-emoji { font-size: 2.4rem; margin-block-end: .35rem; }
.welcome-card {
  display: flex; flex-direction: column; gap: .3rem; width: 100%; height: 100%;
  text-align: start; padding: 1rem 1.1rem; border-radius: 14px; cursor: pointer;
  background: var(--app-surface); border: 1px solid var(--app-border);
  color: inherit; transition: border-color .15s ease, box-shadow .15s ease;
}
.welcome-card:hover { border-color: var(--brand-300); box-shadow: var(--shadow-1); }
.welcome-card .wc-emoji { font-size: 1.5rem; line-height: 1; }
.welcome-card .wc-title { font-weight: 700; }
.welcome-card .wc-desc { font-size: .86rem; color: var(--app-muted); }

/* Homepage «همین حالا امتحان کن» sample-prompt chips (link-styled hero chips). */
.hero-prompts { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.hp-label { font-size: .88rem; color: var(--app-muted); }
.hero-prompt {
  display: inline-flex; align-items: center; gap: .3rem; padding: .35rem .75rem;
  border-radius: 999px; border: 1px solid var(--app-border); background: var(--app-surface);
  color: inherit; text-decoration: none; font-size: .88rem;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.hero-prompt:hover { border-color: var(--brand-300); box-shadow: var(--shadow-1); }

/* Dashboard «اولین قدم‌ها» checklist. */
.fs-steps { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .6rem; }
.fs-step {
  display: flex; align-items: flex-start; gap: .6rem; padding: .65rem .8rem;
  border-radius: 12px; border: 1px solid var(--app-border);
  color: inherit; text-decoration: none; transition: border-color .15s ease;
}
.fs-step:hover { border-color: var(--brand-300); }
.fs-check {
  flex: 0 0 auto; width: 1.5rem; height: 1.5rem; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .82rem; font-weight: 700;
  background: var(--brand-100); color: var(--brand-700);
}
.fs-step.done { opacity: .72; }
.fs-step.done .fs-check { background: var(--brand-700); color: #fff; }
.fs-text { display: flex; flex-direction: column; gap: .1rem; }
.fs-text b { font-size: .92rem; }
.fs-text small { color: var(--app-muted); }
@media (max-width: 575.98px) { .fs-steps { grid-template-columns: 1fr; } }

/* Night theme: brand colour used as TEXT on the (now dark) surfaces → lighten (§12).
   --brand-100 tint backgrounds already remap to their dark values; filled
   --brand-700 chips with white text (tag-rec, done-check) stay untouched. */
[data-bs-theme="dark"] .mp-group-quick,
[data-bs-theme="dark"] .mp-quick .mp-quick-label,
[data-bs-theme="dark"] .qp-chip-label { color: var(--brand-300); }

/* ------------------------------------------------------------------------------
   UI polish round (post-QA user feedback): professional grouped ⋯ settings menu,
   a BOLD first-steps card, a hairline divider before the chat history column, and
   an exact-fill chat page (no leftover canvas strip under the composer).
   ------------------------------------------------------------------------------ */

/* ⋯ settings popover: titled sections with iOS-style full-width rows. Lives in a plain (non-flex)
   .pop, so its .tm-section children just stack via normal block flow — no flex-basis tricks
   needed at any width (the old mobile-inline-row override that needed one is gone with it). */
.pop .tm-section { display: flex; flex-direction: column; gap: .1rem; }
.pop .tm-section + .tm-section { border-top: 1px solid var(--app-border); margin-top: .55rem; padding-top: .6rem; }
.tm-head {
  display: flex; align-items: center; gap: .4rem;
  font-size: .74rem; font-weight: 700; color: var(--app-muted); margin-bottom: .3rem; padding-inline: .15rem;
}
.tm-head .ic { width: .95rem; height: .95rem; flex: none; }
.pop .tm-row {
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
  padding: .3rem .45rem; border-radius: 10px; margin: 0; font-size: .85rem; cursor: pointer;
}
.tm-row-label { display: inline-flex; align-items: center; gap: .45rem; color: var(--app-ink-2); white-space: nowrap; }
.tm-row-label .ic { width: 1rem; height: 1rem; color: var(--app-muted); flex: none; }
.pop .tm-dir { width: auto; max-width: 11rem; }
/* Switch rows: label (with icon) on the reading edge, the toggle pushed to the far edge. */
.pop .form-check.form-switch { width: 100%; justify-content: space-between; padding: .42rem .45rem; border-radius: 10px; }
.pop .tm-row:hover, .pop .form-check.form-switch:hover { background: var(--app-surface-2); }
.pop .form-check-label { display: inline-flex; align-items: center; gap: .45rem; color: var(--app-ink-2); }
.pop .form-check-label .ic { width: 1rem; height: 1rem; color: var(--app-muted); flex: none; }
.tm-dl-row { display: flex; gap: .5rem; }
.tm-dl-row .btn { flex: 1 1 0; justify-content: center; }

/* «اولین قدم‌ها» — promoted to a real hero card: brand-tinted, accent-edged, with a
   progress pill and filled step chips, so it cannot be mistaken for one more stat box. */
.fs-card {
  background: linear-gradient(180deg, var(--brand-50), var(--app-surface) 75%);
  border: 1px solid var(--brand-200);
  border-inline-start: 4px solid var(--brand-600);
  box-shadow: var(--shadow-1);
}
.fs-card-title { font-size: 1.15rem; font-weight: 800; }
.fs-progress {
  font-size: .78rem; font-weight: 700; padding: .25rem .75rem; border-radius: 999px;
  background: var(--brand-600); color: #fff; white-space: nowrap;
}
.fs-card .fs-step { background: var(--app-surface); }
.fs-check { width: 1.75rem; height: 1.75rem; font-size: .9rem; background: var(--brand-600); color: #fff; }
.fs-step.done { opacity: 1; }
.fs-step.done .fs-check { background: var(--ok-500); color: #fff; }
.fs-step.done .fs-text { opacity: .6; }
.fs-step.fs-guide .fs-check { background: var(--brand-100); color: var(--brand-700); font-size: 1rem; }
[data-bs-theme="dark"] .fs-step.fs-guide .fs-check { color: var(--brand-300); }

/* Chat page (desktop): fill the viewport EXACTLY instead of the old fixed
   calc(100vh - 150px) guess, which left a dead canvas strip under the composer.
   The shell becomes a bounded column and the chat stretches to the content box.
   Matched both ways (server class + :has) like the existing overflow rules. */
@media (min-width: 992px) {
  body.nav-chat .shell, body:has(.chat-layout) .shell { height: 100vh; height: 100dvh; }
  body.nav-chat .shell-main, body:has(.chat-layout) .shell-main { min-height: 0; }
  body.nav-chat .shell-content, body:has(.chat-layout) .shell-content { flex: 1 1 auto; min-height: 0; }
  body.nav-chat .chat-layout, body:has(.chat-layout) .chat-layout { height: 100%; }
  body.nav-chat .chat-wrap, body:has(.chat-layout) .chat-wrap { height: 100%; min-height: 0; }
}

/* ============================================================
   MOBILE APP-SCREEN PARITY
   Chat and Studio are bounded app screens on phones too. Their
   thread is the scroller; controls never fall below the viewport.
   ============================================================ */
@media (max-width: 991.98px) {
  /* Replace the old guessed `100dvh - 76px` chat height with the same complete flex chain used
     on desktop. This remains correct when the shell header wraps, browser chrome changes height,
     or a device has safe-area insets. */
  body.nav-chat .shell,
  body:has(.chat-layout) .shell,
  body:has(.studio-body) .shell {
    height: 100vh;
    height: 100dvh;
    min-height: 0;
  }
  body.nav-chat .shell-main,
  body:has(.chat-layout) .shell-main,
  body:has(.studio-body) .shell-main { min-height: 0; }
  body.nav-chat .shell-content,
  body:has(.chat-layout) .shell-content,
  body:has(.studio-body) .shell-content {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
  }
  body.nav-chat .chat-layout,
  body:has(.chat-layout) .chat-layout {
    height: 100%;
    min-height: 0;
  }

  /* The tool strip is the first row of the chat work area on mobile. Keeping the same DOM node
     means every desktop action/listener stays available; only its visual order changes. */
  .chat-scroll > .composer-tools {
    order: -1;
    flex: 0 0 auto;
    padding: .1rem .25rem .5rem;
  }

  /* All tool rows stay on one horizontally scrollable line. The model control therefore remains
     visible instead of being wrapped underneath secondary actions on narrow phones. */
  .composer-tools {
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    overscroll-behavior-inline: contain;
    -webkit-overflow-scrolling: touch;
  }
  .composer-tools::-webkit-scrollbar { display: none; }
  .composer-tools > *, .composer-tools .pop-wrap { flex: 0 0 auto; }

  /* Chat JS relocates the thread-toolbar's LIVE controls into this strip on phones — the emptied
     row above only held desktop widgets besides them, so it's hidden and its vertical space goes
     to the thread. The quota chip must not wrap the single-line strip. */
  .composer-tools #dailyQuota { white-space: nowrap; align-self: center; }

  /* Popovers used to open upward inside overflow:hidden chat/canvas containers and were clipped.
     On mobile they become a viewport-bound bottom sheet, while retaining their exact content and
     interaction code. The home-indicator inset is included for installed PWAs. */
  .pop {
    position: fixed;
    inset-inline: .75rem;
    inset-block-start: auto;
    inset-block-end: calc(.75rem + env(safe-area-inset-bottom, 0px));
    width: auto;
    min-width: 0;
    max-width: none;
    max-height: min(72dvh, 38rem);
    z-index: 1070;
    border-radius: 18px;
    box-shadow: 0 -8px 32px rgba(15, 29, 61, .22);
  }
  /* The sheet is promoted into the TOP LAYER when the browser has the popover API (see the chat
     script's openPop) — that is what makes it immune to .chat-scroll's overflow:hidden and to the
     tool strip's own overflow, whatever ancestors later grow. The [popover] UA sheet brings its own
     inset/margin defaults along with it; every inset is already restated by the rule above, so only
     the margin needs neutralising. Promoting a menu must change WHERE it paints and nothing else. */
  .pop[popover] { margin: 0; }
  .pop-model, .pop-assistant {
    inset-inline: .75rem;
    width: auto;
    min-width: 0;
    max-width: none;
    /* ONE scroller, not two. The sheet is capped at 72dvh but .model-list's own 60vh cap uses the
       LARGE viewport (vh ignores mobile browser chrome), so the list's box could outgrow the sheet:
       its tail rows sat clipped below the sheet's edge while overscroll-behavior:contain kept the
       outer scroller unreachable — the catalog "stuck" before its last groups (e.g. گروه تاریک).
       The sheet is now a non-scrolling flex column and the list flexes into exactly the visible
       space, so scrolling the list always reaches the final row. */
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  /* Studio's picker keeps an extra .model-picker wrapper between the sheet and the search/list —
     it must pass the flex chain through, or the list inside it falls back to its natural height. */
  .pop-model .model-picker {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
  }
  .pop-model .model-search, .pop-assistant .model-search { flex: 0 0 auto; }
  .pop-model .model-list, .pop-assistant .model-list {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
  }
  /* Studio now follows Chat's bounded layout on phones: the generated-content thread scrolls,
     while its composer and complete desktop tool set stay in view. */
  body:has(.studio-body) { overflow: hidden; }
  body:has(.studio-body) .studio-layout { height: 100%; }
  body:has(.studio-body) .studio-panel.is-active {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
  }
  body:has(.studio-body) .studio-body {
    flex: 1 1 auto;
    height: 100%;
    min-height: 0;
  }
  body:has(.studio-body) .studio-body .img-studio {
    height: 100%;
    min-height: 0;
  }
  body:has(.studio-body) .studio-panel.is-active:has(> .surface) { overflow-y: auto; }

  /* Put model/options before previews and frame slots in Studio. Large attachment collections may
     scroll inside the composer, but can no longer bury the primary controls. */
  .img-studio .composer {
    max-height: min(58dvh, 30rem);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .img-studio .composer-tools {
    order: -1;
    flex: 0 0 auto;
  }
  .img-studio .img-thread { min-height: 0; touch-action: pan-y; }

  /* (The Studio tab strip used to need horizontal-scroll handling here. The sections are sidebar
     rows now — a normal vertical list — so none of it applies.) */
}

@media (max-width: 575.98px) {
  /* The round chevron was the reported returning expander. The whole folded block remains tappable
     (desktop/mobile behaviour parity), but the intrusive chip and its wasted left padding are gone. */
  .chat-msgs.clp-on .clp,
  .chat-msgs.clp-on .clp-h { padding-left: 0; }
  .cmsg.user .bubble.clp-q { padding-left: .8rem; }
  .chat-msgs.clp-on .clp::after,
  .chat-msgs.clp-on .clp-h::after,
  .cmsg.user .bubble.clp-q::after { display: none; }

  .img-studio .composer { padding: .55rem .65rem .65rem; }
  .composer-input textarea { font-size: 16px; }

  /* Two 14.4rem boxes want ~410px, but a 390px phone leaves the composer ~372px — so the pair
     spilled into the tool strip's horizontal scroll and the second box sat half off-screen.

     The strip has to fill the row before it can be split. Below 992px `.video-composer` reverts to
     the base flex COLUMN (see the `display: flex` override further down), and there the desktop
     grid's `align-self: end` on the tools stops meaning "bottom of the grid row" and starts meaning
     "don't stretch across the cross axis" — which pinned the strip to its own content width (~329px)
     no matter how wide the phone was. Stretch it back. */
  .img-studio .composer:not(.editor-command-panel) > .composer-tools { align-self: stretch; }
  /* Then split that row: each box takes half, capped at the desktop width so a roomier phone
     doesn't stretch them PAST three squares. Driven off .pop-wrap (the flex item) rather than the
     button, to override the strip's `flex: 0 0 auto`. */
  .img-studio .composer:not(.editor-command-panel) > .composer-tools .pop-wrap {
    flex: 1 1 0; min-width: 0; max-width: 14.4rem;
  }
  .img-studio .composer:not(.editor-command-panel) > .composer-tools .tool-btn { width: 100%; }
}

/* ============================================================
   Studio model catalog + dedicated image editor
   ============================================================ */
.studio-model-search { position: sticky; top: 0; z-index: 2; background: var(--app-surface); padding-bottom: .55rem; }
.studio-model-list { max-height: min(68vh, 34rem); }
.studio-model-row {
  width: 100%; min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; gap: .28rem .65rem; padding: .65rem .75rem;
  border: 0; border-inline-start: 3px solid transparent; border-radius: 0;
  background: transparent; text-align: start; direction: ltr; font: inherit; color: inherit;
}
.studio-model-row .studio-model-name {
  grid-column: 1 / -1; grid-row: 1; min-width: 0; display: flex; flex-direction: column;
  align-items: flex-start; gap: .1rem; white-space: normal; overflow: hidden;
}
.studio-model-row .mc-name {
  display: block; width: 100%; font-weight: 650; line-height: 1.4;
  white-space: normal; overflow: hidden; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
}
.studio-model-maker { color: var(--app-muted); font-size: .7rem; font-weight: 400; }
.studio-capabilities {
  grid-column: 1 / -1; grid-row: 2; width: 100%; display: flex; flex-wrap: wrap;
  justify-content: flex-start; gap: .28rem; padding: 0;
}
.studio-capabilities .mp-tag { white-space: nowrap; }
.studio-model-specs {
  grid-column: 1 / -1; grid-row: 3; min-width: 0; display: flex; flex-wrap: wrap;
  align-items: center; gap: .15rem .4rem; direction: ltr; color: var(--app-muted);
  font-size: .68rem; font-weight: 400; line-height: 1.5;
}
.studio-model-specs > span + span::before { content: "·"; margin-inline-end: .4rem; color: var(--app-muted); }
.studio-model-row.sel .studio-capabilities .mp-tag { border-color: var(--brand-200); }
.locked-showcase .ls-chip { display: inline-flex; flex-direction: column; gap: .15rem; align-items: flex-start; }
.locked-showcase .ls-chip small { color: var(--app-muted); font-size: .67rem; }

.studio-body-editor { display: block; min-width: 0; }
.editor-workspace {
  gap: .65rem; padding: .8rem; background: var(--app-bg); overflow: visible;
}
.editor-history-sink { display: none !important; }
.editor-workspace-head {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .15rem .15rem 0;
}
.editor-workspace-head h2 { margin: 0; color: var(--app-ink); font-size: 1.05rem; font-weight: 750; }
.editor-workspace-head p { margin: .15rem 0 0; color: var(--app-muted); font-size: .76rem; }
.editor-head-actions { display: inline-flex; align-items: center; gap: .4rem; }
.editor-replace {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: .4rem; min-height: 2.35rem;
  padding: .4rem .75rem; border: 1px solid var(--app-border); border-radius: 11px;
  background: var(--app-surface); color: var(--app-ink-2); font: inherit; font-size: .8rem; cursor: pointer;
}
.editor-replace:hover { border-color: var(--brand-400); color: var(--brand-700); background: var(--brand-50); }
.editor-replace .ic { width: 1rem; height: 1rem; color: var(--brand-600); }
.image-editor {
  flex: 1 1 auto; min-height: 0; overflow: hidden; display: flex; flex-direction: column; gap: .55rem;
  padding: .55rem; border: 1px solid var(--app-border); border-radius: 16px;
  background: var(--app-surface); box-shadow: var(--shadow-1);
}
.image-editor-toolbar {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center; gap: .45rem;
  min-width: 0; overflow-x: auto; padding: .05rem; scrollbar-width: none;
}
.image-editor-toolbar::-webkit-scrollbar { display: none; }
.editor-tool-group, .editor-action-group {
  flex: 0 0 auto; display: inline-flex; align-items: center; padding: .18rem;
  border: 1px solid var(--app-border); border-radius: 11px; background: var(--app-surface-2);
}
.editor-tool, .editor-size, .editor-color {
  flex: 0 0 auto; min-height: 2rem; display: inline-flex; align-items: center; justify-content: center; gap: .32rem;
  padding: .28rem .55rem; border: 0; border-radius: 8px;
  background: transparent; color: var(--app-ink-2); font: inherit; font-size: .76rem;
}
button.editor-tool { cursor: pointer; }
button.editor-tool:hover { background: var(--app-surface); color: var(--brand-700); }
button.editor-tool.is-active { background: var(--app-surface); color: var(--brand-700); box-shadow: var(--shadow-1); }
.editor-icon-tool { width: 2rem; padding: 0; font-size: .95rem; }
.editor-size, .editor-color { border: 1px solid var(--app-border); background: var(--app-surface); border-radius: 11px; }
.editor-size input { width: clamp(4.5rem, 8vw, 7rem); accent-color: var(--brand-600); }
.editor-color input { width: 1.55rem; height: 1.35rem; padding: 0; border: 0; background: none; cursor: pointer; }
.image-editor-stage {
  position: relative; flex: 1 1 auto; min-height: 14rem; width: 100%; max-width: 980px; margin-inline: auto;
  display: grid; place-items: center; overflow: hidden; border: 1px dashed var(--app-border);
  border-radius: 13px; background:
    linear-gradient(45deg, var(--app-surface) 25%, transparent 25%),
    linear-gradient(-45deg, var(--app-surface) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--app-surface) 75%),
    linear-gradient(-45deg, transparent 75%, var(--app-surface) 75%), var(--app-surface-2);
  background-size: 18px 18px; background-position: 0 0, 0 9px, 9px -9px, -9px 0;
  outline: none;
}
.image-editor-stage:focus-within { border-color: var(--brand-400); box-shadow: 0 0 0 3px rgba(38,95,176,.10); }
.image-editor-stage > img {
  position: relative; z-index: 1; display: block; max-width: 100%; max-height: min(52vh, 570px);
  width: auto; height: auto; user-select: none; pointer-events: none;
}
.image-editor-stage > canvas { position: absolute; z-index: 2; inset: auto; cursor: crosshair; touch-action: none; }
.image-editor-empty {
  min-height: 14rem; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .42rem; color: var(--app-muted); cursor: pointer; text-align: center; padding: 1rem;
}
.editor-empty-icon {
  width: 3rem; height: 3rem; display: grid; place-items: center; margin-bottom: .2rem;
  border-radius: 14px; color: var(--brand-600); background: var(--brand-50); border: 1px solid var(--brand-100);
}
.image-editor-empty .ic { width: 1.4rem; height: 1.4rem; }
.image-editor-empty b { color: var(--app-ink); font-size: .95rem; }
.image-editor-empty span:last-child { font-size: .76rem; }
.image-editor-empty[hidden], .image-editor-stage > img[hidden], .image-editor-stage > canvas[hidden],
.editor-result-layer[hidden] { display: none !important; }
.editor-result-layer {
  position: absolute; z-index: 4; inset: 0; display: grid; place-items: center;
  padding: .35rem; background: var(--app-surface-2);
}
.editor-result-turn, .editor-result-turn .turn-gallery {
  width: 100%; height: 100%; min-width: 0; min-height: 0; margin: 0; overflow: hidden;
}
.editor-result-turn.is-processing { min-height: 0; position: relative; }
.editor-result-turn .turn-gallery,
.editor-result-turn .turn-gallery.is-multi {
  grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr);
  max-width: none; place-items: stretch;
}
.editor-result-turn .shot {
  width: 100%; height: 100%; min-width: 0; min-height: 0; border: 0; border-radius: 10px;
  display: flex; align-items: center; justify-content: center; overflow: hidden; background: var(--app-surface-2);
}
/* Match the source-image stage exactly: keep a smaller result at its natural size in the centre,
   but shrink an oversized result on either axis until the whole image fits without cropping. */
.editor-result-turn .shot img {
  display: block; width: auto; height: auto; max-width: 100%; max-height: 100%;
  object-fit: contain; object-position: center;
}
.editor-result-layer:has(.shot.is-skel) { padding: 0; }
.editor-result-turn .shot.is-skel {
  min-height: 100%; border-radius: 12px;
  background: linear-gradient(100deg, var(--app-surface-2) 30%, var(--brand-50) 50%, var(--app-surface-2) 70%);
  background-size: 200% 100%; animation: shotShimmer 1.3s ease-in-out infinite;
}
.editor-result-turn .shot-actions { opacity: 1; }
.editor-stage-status { flex: 0 0 auto; min-height: 1.1rem; color: var(--app-muted); font-size: .72rem; text-align: center; }
.editor-command-panel {
  border: 1px solid var(--app-border); border-radius: 15px; padding: .55rem .65rem .65rem;
  background: var(--app-surface); box-shadow: var(--shadow-1);
}
.editor-command-panel .composer-tools { justify-content: flex-end; }
.editor-model-trigger { max-width: min(28rem, 70vw); }
.editor-command-input { align-items: stretch; }
.editor-command-input textarea { min-height: 2.75rem; background: var(--app-surface); }
.editor-apply { width: auto; min-width: 7.5rem; height: auto; min-height: 2.75rem; padding-inline: .85rem; display: inline-flex; gap: .4rem; }
.editor-apply span { font-size: .82rem; font-weight: 650; }

@media (max-width: 620px) {
  .editor-workspace { padding: .45rem; gap: .45rem; }
  .editor-workspace-head { padding-inline: .1rem; }
  .editor-workspace-head p { display: none; }
  .editor-replace span { display: none; }
  .editor-replace { width: 2.35rem; padding: 0; justify-content: center; }
  .editor-head-actions { gap: .3rem; }
  .image-editor { min-height: 0; padding: .4rem; }
  .image-editor-toolbar { justify-content: flex-start; margin-inline: -.05rem; }
  .editor-tool, .editor-size, .editor-color { min-height: 2.25rem; }
  .editor-tool span, .editor-size span, .editor-color span { display: none; }
  .image-editor-stage, .image-editor-empty { min-height: 10rem; }
  .image-editor-stage > img { max-height: 42dvh; }
  .editor-command-panel { max-height: none !important; overflow: visible !important; padding: .45rem; }
  .editor-command-panel .composer-tools { overflow: visible; }
  .editor-command-input { gap: .35rem; }
  .editor-apply { min-width: 2.75rem; width: 2.75rem; padding: 0; justify-content: center; }
  .editor-apply span { display: none; }
  .studio-capabilities { padding-inline-start: 0; }
}

@media (max-width: 991.98px) {
  .video-composer { display: flex; }
  .video-composer > .video-frame-row { margin: 0; }
}

/* ---------- Support tickets (پشتیبانی) ---------- */
/* Standard ticket-system thread: full-width message cards with an author header row, a staff
   accent on support replies, and an attachment strip. Tokens only — dark mode follows for free. */
.ticket-thread { display: flex; flex-direction: column; gap: .75rem; }
.ticket-msg {
  border: 1px solid var(--app-border); border-radius: var(--app-radius);
  background: var(--app-surface); overflow: hidden;
}
.ticket-msg-head {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  padding: .5rem .85rem; background: var(--app-surface-2);
  border-bottom: 1px solid var(--app-border); font-size: .8rem;
}
.ticket-author { display: inline-flex; align-items: center; gap: .35rem; font-weight: 600; }
.ticket-author .ic { width: 1rem; height: 1rem; }
.ticket-msg.staff { border-inline-start: 3px solid #265fb0; }
.ticket-msg.staff .ticket-author.staff { color: #265fb0; }
[data-bs-theme="dark"] .ticket-msg.staff { border-inline-start-color: #6ea8fe; }
[data-bs-theme="dark"] .ticket-msg.staff .ticket-author.staff { color: #6ea8fe; }
.ticket-msg.note { border-inline-start: 3px solid var(--bs-warning); }
.ticket-msg.note .ticket-msg-head { background: var(--bs-warning-bg-subtle); }
.ticket-msg-body { padding: .7rem .85rem; white-space: pre-wrap; word-break: break-word; font-size: .92rem; }
.ticket-att-list {
  display: flex; flex-wrap: wrap; gap: .5rem; align-items: center;
  padding: .55rem .85rem .7rem; border-top: 1px dashed var(--app-border);
}
.ticket-att-img img {
  max-height: 140px; max-width: 220px; display: block;
  border: 1px solid var(--app-border); border-radius: .5rem;
}
.ticket-att-file {
  display: inline-flex; align-items: center; gap: .4rem; max-width: 100%;
  padding: .3rem .65rem; border: 1px solid var(--app-border); border-radius: 999px;
  background: var(--app-surface-2); font-size: .8rem; color: var(--app-ink); text-decoration: none;
}
.ticket-att-file:hover { border-color: #265fb0; color: #265fb0; }
.ticket-att-file .ic { width: .95rem; height: .95rem; flex-shrink: 0; }
.ticket-att-file .att-name { max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Details list on the ticket sidebar */
.ticket-meta { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; font-size: .85rem; }
.ticket-meta li { display: flex; justify-content: space-between; align-items: center; gap: 1rem; }
.ticket-meta .k { color: var(--app-muted); flex-shrink: 0; }

/* List tabs (فعال / آرشیو) + queue filter chips */
.ticket-tabs { display: inline-flex; gap: .25rem; padding: .2rem; border: 1px solid var(--app-border); border-radius: 999px; background: var(--app-surface-2); }
.ticket-tab { padding: .25rem .8rem; border-radius: 999px; font-size: .82rem; color: var(--app-muted); text-decoration: none; }
.ticket-tab.active { background: var(--app-surface); color: var(--app-ink); font-weight: 600; box-shadow: 0 1px 4px rgba(0,0,0,.08); }
.ticket-chip {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .3rem .75rem; border: 1px solid var(--app-border); border-radius: 999px;
  background: var(--app-surface); font-size: .82rem; color: var(--app-ink); text-decoration: none;
}
.ticket-chip:hover { background: var(--app-surface-2); color: var(--app-ink); }
.ticket-chip.active { background: #265fb0; border-color: #265fb0; color: #fff; }
.tt-count { font-size: .72rem; opacity: .75; }
.ticket-subject { color: var(--app-ink); text-decoration: none; }
.ticket-subject:hover { color: #265fb0; }

/* Unread-ticket count in the sidebar nav */
.side-badge {
  margin-inline-start: auto; background: var(--bs-danger); color: #fff;
  border-radius: 999px; font-size: .68rem; font-weight: 700; line-height: 1;
  padding: .22rem .45rem; min-width: 1.15rem; text-align: center;
}

/* ---------- «اشتراک‌گذاری عمومی گفت‌وگو» dialog (chat thread → public /share/{token} link) ---------- */
.share-overlay {
  position: fixed; inset: 0; z-index: 1080; background: rgba(15, 23, 42, .45);
  display: flex; align-items: center; justify-content: center; padding: 1rem;
}
.share-box {
  width: 100%; max-width: 460px; background: var(--app-surface);
  border: 1px solid var(--app-border); border-radius: 16px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .25); padding: 1rem 1.1rem;
}
.share-box-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin-bottom: .6rem; }
.share-close {
  border: none; background: transparent; color: var(--app-muted);
  font-size: 1rem; line-height: 1; padding: .2rem .45rem; border-radius: 8px;
}
.share-close:hover { background: var(--app-surface-2); color: var(--app-ink); }
.share-p { font-size: .85rem; line-height: 1.9; color: var(--app-ink-2, var(--app-ink)); }
.share-url-row { display: flex; gap: .4rem; margin-bottom: .5rem; }
.share-url-row input { direction: ltr; font-size: .78rem; }
.share-meta { font-size: .75rem; color: var(--app-muted); margin-bottom: .7rem; }
.share-actions { display: flex; gap: .5rem; flex-wrap: wrap; }

/* «قدرت استدلال» (reasoning effort) — reuses the model picker's exact chrome: a .tool-btn trigger
   and a .pop menu with .mp-row rows. Only the wrapper's inline display is toggled by JS (shown for
   reasoning-capable models). The menu is narrower than the model catalog since its rows are short. */
.rz-tool { display: inline-flex; align-items: center; }
.pop-reasoning { min-width: 13rem; max-width: 18rem; }

/* ---------- /Admin/PackageModels — the dedicated per-package model-set editor ---------- */
/* Replaces the checklist that lived inside the package/group edit modal. Maker <details> sections
   keep the 440-row catalog cheap on phones: closed sections skip layout entirely. */
.pm-toolbar { position: sticky; top: 0; z-index: 20; }
.pm-chips { display: flex; flex-wrap: wrap; gap: .35rem; align-items: center; }
.pm-section { padding: 0; margin-bottom: .65rem; overflow: hidden; }
.pm-section > summary {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  padding: .6rem .85rem; cursor: pointer; list-style: none;
}
.pm-section > summary::-webkit-details-marker { display: none; }
.pm-section > summary::before { content: '◂'; color: var(--app-muted); font-size: .8rem; transition: transform .12s; }
.pm-section[open] > summary::before { transform: rotate(-90deg); }
.pm-sec-name { font-weight: 700; }
.pm-sec-count { font-size: .78rem; color: var(--app-muted); direction: ltr; }
.pm-sec-actions { margin-inline-start: auto; display: inline-flex; gap: .3rem; }
.pm-rows { border-top: 1px solid var(--app-border); }
.pm-row {
  display: flex; align-items: center; gap: .6rem; padding: .45rem .85rem; margin: 0; cursor: pointer;
  border-bottom: 1px solid var(--app-border);
}
.pm-row:last-child { border-bottom: none; }
.pm-row:hover { background: var(--app-surface-2); }
.pm-row input[type=checkbox] { flex: none; width: 1.05rem; height: 1.05rem; }
.pm-row-main { min-width: 0; display: flex; flex-direction: column; gap: .05rem; }
.pm-row-name { font-weight: 600; font-size: .88rem; }
.pm-row-id { font-family: var(--bs-font-monospace, monospace); font-size: .72rem; color: var(--app-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pm-row-badges { margin-inline-start: auto; display: inline-flex; gap: .3rem; align-items: center; flex: none; }
.pm-badge-size { font-size: .7rem; color: var(--app-muted); border: 1px solid var(--app-border);
  border-radius: 999px; padding: 0 .4rem; }
/* A row barred by the ExcludeContains pattern (rule mode): visibly out, checkbox disabled. */
.pm-pattern-out { opacity: .5; }
.pm-pattern-out .pm-row-name { text-decoration: line-through; }
.pm-actionbar {
  position: sticky; bottom: .5rem; z-index: 20; display: flex; align-items: center; gap: .6rem;
  padding: .6rem .85rem; margin-top: .75rem; box-shadow: var(--shadow-2);
}
@media (max-width: 575.98px) {
  .pm-sec-actions .btn { padding: .2rem .45rem; font-size: .72rem; }
  .pm-row-badges .badge-soft { display: none; }  /* keep rows one-line on phones; flags stay filterable */
}

/* Change-preview dialog on /Admin/PackageModels: exactly what saving will add/remove, plus the
   rule that is being persisted (as opposed to the toolbar chips, which only filter the view). */
.pm-diff-dlg {
  width: min(56rem, 94vw); max-height: 86vh; border: 1px solid var(--app-border);
  border-radius: var(--app-radius); background: var(--app-surface); color: var(--app-ink);
  padding: 0; box-shadow: var(--shadow-2);
}
.pm-diff-dlg::backdrop { background: rgba(15, 23, 42, .45); }
.pm-diff-head { display: flex; align-items: center; gap: .5rem; padding: .7rem .9rem;
  border-bottom: 1px solid var(--app-border); }
.pm-diff-note { padding: .55rem .9rem 0; }
.pm-diff-rule { padding: .55rem .9rem 0; }
.pm-diff-body { display: grid; grid-template-columns: 1fr 1fr; gap: .9rem; padding: .7rem .9rem .9rem; }
.pm-diff-title { font-weight: 700; font-size: .85rem; margin-bottom: .3rem; }
.pm-diff-title.added { color: var(--ok-600, #1a7f5a); }
.pm-diff-title.removed { color: var(--bad-500); }
.pm-diff-list { list-style: none; margin: 0; padding: 0; max-height: 42vh; overflow-y: auto; }
.pm-diff-list > li { display: flex; flex-direction: column; gap: .05rem;
  padding: .3rem .1rem; border-bottom: 1px solid var(--app-border); font-size: .8rem; }
.pm-diff-list > li:last-child { border-bottom: none; }
.pm-diff-rule .pm-diff-list > li { display: list-item; list-style: disc; margin-inline-start: 1.1rem; }
.pm-diff-name { font-weight: 600; }
.pm-diff-id { font-family: var(--bs-font-monospace, monospace); font-size: .72rem; color: var(--app-muted); }
.pm-diff-maker { font-size: .72rem; color: var(--app-muted); }
@media (max-width: 767.98px) { .pm-diff-body { grid-template-columns: 1fr; } }

/* ---------- Landing page brand showcase ---------- */
/* Replaces the old plain company-name chips. A titled box that takes grouped rows: chat, image,
   video and audio all use the same product-card language without repeating tiers of one family.
   Shares the .bc-* brand identity variables with the chat cards, so the two never disagree. */
.brand-showcase {
  border: 1px solid var(--app-border); border-radius: var(--app-radius);
  /* Tinted box, plain-surface tiles: the reverse read flat, because tile and box shared a colour. */
  background: var(--app-surface-2);
  padding: 1.35rem 1.25rem 1.45rem;
}
/* Centred header, matching the landing page's other section headings (eyebrow over title) rather
   than the inline label the old provider strip used. */
.bs-head { text-align: center; margin-bottom: 1.1rem; }
.bs-title { display: block; font-weight: 800; font-size: 1.12rem; color: var(--app-ink); }
.bs-sub { display: block; font-size: .82rem; color: var(--app-muted); margin-top: .2rem; }
.bs-group + .bs-group {
  margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--app-border);
}
.bs-group-title {
  display: flex; align-items: center; justify-content: flex-start; gap: .45rem;
  margin: 0 0 .6rem; direction: rtl; color: var(--app-ink-2);
  font-size: .82rem; font-weight: 750; line-height: 1.4;
}
.bs-group-title .ic { width: 1rem; height: 1rem; color: var(--brand-600); }
/* Six across on desktop; auto-fit keeps them even as it steps down to 3 / 2 on narrower screens
   rather than leaving one orphan on its own row. */
.bs-row {
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: .6rem;
}
/* A category with fewer than six unique product families keeps the exact same tile width and is
   centred, instead of stretching two audio products to half the showcase each. */
.bs-row.is-short {
  display: flex; flex-wrap: wrap; justify-content: center;
}
.bs-row.is-short .bs-tile { flex: 0 1 10.5rem; width: min(10.5rem, 100%); }
/* Stacked, centred tiles — the same shape as the «چهار رسانه» cards further down the page. At six
   across, a logo-left row left ~140px for two lines of text and read cramped. */
.bs-tile {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: .55rem; min-width: 0;
  padding: 1.05rem .7rem 1rem; border-radius: 16px;
  border: 1px solid var(--app-border); background: var(--app-surface);
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
  /* Each tile is an <a> into the chat with that model preselected — reset the link chrome so it
     still reads as a card, and keep the inherited ink so .bs-brand/.bs-model keep their own colours. */
  color: inherit; text-decoration: none;
  /* Query container for the fluid type below — same treatment as the chat's .brand-card. These
     tiles are SIX across rather than three, so they run out of room sooner, not later. */
  container-type: inline-size;
}
.bs-tile:hover { border-color: var(--bc-accent); box-shadow: var(--shadow-1); transform: translateY(-2px); }
.bs-tile .bc-logo { width: 3rem; height: 3rem; border-radius: 14px; font-size: 1.15rem; }
.bs-tile .bc-logo::before {
  -webkit-mask-size: 1.7rem 1.7rem;
          mask-size: 1.7rem 1.7rem;
}
.bs-text { display: flex; flex-direction: column; gap: .12rem; min-width: 0; width: 100%; }
/* Same rule as the chat tiles, for the same reason: the type sizes against the TILE so a narrow one
   shrinks its text instead of amputating a model name, and wrapping — not an ellipsis — is what
   catches a name no size can fit. Six across means these are the tighter of the two sets. */
.bs-brand {
  font-weight: 800; font-size: clamp(.8rem, 9.9cqi, .95rem);
  color: var(--app-ink); line-height: 1.25;
}
.bs-model {
  font-size: clamp(.62rem, 7.7cqi, .74rem); color: var(--app-muted); line-height: 1.3;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; overflow-wrap: anywhere;
}
@media (max-width: 1199.98px) { .bs-row { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 767.98px)  { .bs-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 767.98px)  {
  .bs-row.is-short .bs-tile { flex-basis: calc(50% - .3rem); max-width: 10.5rem; }
  /* Three added media catalogs stay one tactile row each on phones/tablets. Chat preserves its
     familiar 2-column grid; media gets horizontal snap so the homepage does not become 16 cards tall. */
  .bs-group-media .bs-row,
  .bs-group-media .bs-row.is-short {
    display: grid; grid-template-columns: none; grid-auto-flow: column;
    grid-auto-columns: 10.5rem; justify-content: start;
    overflow-x: auto; overflow-y: hidden; padding-block-end: .35rem;
    scroll-snap-type: x mandatory; overscroll-behavior-inline: contain;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .bs-group-media .bs-row::-webkit-scrollbar { display: none; }
  .bs-group-media .bs-tile,
  .bs-group-media .bs-row.is-short .bs-tile {
    width: 100%; max-width: none; scroll-snap-align: start;
  }
}
@media (max-width: 399.98px)  {
  .bs-row { grid-template-columns: minmax(0, 1fr); }
  .bs-row.is-short .bs-tile { flex-basis: 100%; max-width: 100%; }
}
