﻿/* ============================================================================
   Firas AI — Definitive Design System (mature Claude.ai aesthetic)
   One stylesheet serves LTR + RTL via CSS logical properties.
   Light = default; [data-theme="dark"] = warm charcoal.
   Pure static, no build step. All JS hooks (ids/classes/data-attrs) preserved.
   ========================================================================== */

/* ----------------------------------------------------------------------------
   1. Design Tokens
---------------------------------------------------------------------------- */
:root {
  /* ---------- COLOR · LIGHT ----------
     Restored to the pre-redesign neutrals. The identity green was designed against THESE
     surfaces; on the cream I had swapped in, #237A68 fell to 4.47:1 and #2E7D5B to 4.31:1 —
     below AA. Putting the colour back without putting its ground back would have shipped an
     accessibility regression disguised as a restoration. */
  --color-bg:            #FAF9F5;
  --color-bg-subtle:     #F0EEE6;
  --color-surface:       #FFFFFF;
  /* Referenced by 5 presentation-deck rules but never defined anywhere, so those
     backgrounds resolved to `transparent` and the deck card lost every sunken panel. */
  --color-surface-sunken:#F0EEE6;
  --color-sidebar-bg:    #F5F4EE;

  --color-text-primary:  #1A1A18;
  --color-text-secondary:#6B6A63;
  /* The ONE value not restored verbatim. The original #908E85 measures 3.12:1 on #FAF9F5 —
     a confirmed AA failure, and this token colours every ::placeholder in the app. #6E6C64
     is the same tone one step darker and clears 4.5:1. Restoring the look must not mean
     restoring a bug the audit already caught. */
  --color-text-muted:    #6E6C64;

  --color-border-hairline:#E6E4DA;
  --color-border-strong:  #D8D6CB;
  /* ALIAS — 22 rules (most of Firas Code's chrome: toolbar buttons, the project
     card, template tiles, the diff card, the AI input) were written against a
     plain `--color-border` that was never defined anywhere. `border: 1px solid
     var(--color-border)` is then invalid-at-computed-value-time, so the WHOLE
     shorthand resets and border-style becomes `none` — those controls rendered
     as borderless labels floating on a same-coloured surface, which is what made
     the workspace look unfinished. Defined via the hairline token so it keeps
     following the theme (the dark block redefines that token below). */
  --color-border: var(--color-border-hairline);

  /* THE IDENTITY COLOUR — green tea. Restored to the exact pre-redesign values after a
     brass/gold detour: the owner's mark is this green and it is not up for reinterpretation.
     Verified against `git show HEAD:styles.css`. */
  --color-accent:        #237A68;
  --color-accent-hover:  #1A6253;
  --color-accent-soft:   rgba(44,138,120,0.08);
  --color-accent-ring:   rgba(44,138,120,0.40);
  --color-on-accent:     #FFFFFF;
  /* The mark's second tone. The Beam's lower arm sits a step deeper than the primary so the
     letter reads as mass with light falling across it rather than one flat silhouette. */
  --color-accent-deep:   #14544A;

  --color-success:       #2E7D5B;
  --color-error:         #B3261E;
  /* Quick Fix consumes both of these with no fallback, so an undefined name leaves the
     chip uncoloured and — inside color-mix() — drops the border declaration entirely.
     danger is error under a second name; the stylesheet already treats them as one. */
  --color-warn:          #8A5A0F;   /* light — dark enough to read as text on a white surface */
  --color-danger:        var(--color-error);

  /* ---------- HAIRLINES ----------
     Named for their FUNCTION (a drawn rule), not for a material. The previous names said
     "brass", which stopped being true the moment the palette moved — a token whose name
     encodes a colour has to be renamed every time the brand shifts. These follow the accent. */
  --color-line:          var(--color-accent);
  --color-line-bright:   var(--color-accent-hover);
  --grain-opacity:       0.030;

  /* ---------- SPACING (4px base) ---------- */
  --space-0:  0;
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  /* --space-7 was MISSING while `.landing` referenced it. One unresolvable var()
     invalidates the whole `padding` shorthand, so the logged-out landing screen —
     the first thing every visitor sees — was rendering with padding:0 on all four
     sides, edge-to-edge, with the safe-area inset for notched phones silently lost. */
  --space-7:  28px;
  --space-8:  32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;

  /* ---------- RADII ----------
     An engraved instrument is cut, not moulded. Corners tighten to a machined
     edge; only genuinely circular parts (the ring, the rotor, avatars) stay round.
     Tightening these six values sharpens every panel in the app at once. */
  --radius-xs:  2px;
  --radius-sm:  3px;
  --radius-md:  5px;
  --radius-lg:  7px;
  --radius-xl:  9px;
  --radius-pill: 999px;

  /* ---------- BORDER WIDTHS ---------- */
  --border-hairline: 1px;
  --border-strong:   1.5px;

  /* ---------- SHADOWS (very soft, no glow) ---------- */
  --shadow-xs:    0 1px 2px rgba(0,0,0,0.04);
  --shadow-sm:    0 1px 2px rgba(0,0,0,0.04), 0 2px 6px rgba(0,0,0,0.05);
  --shadow-md:    0 4px 12px rgba(0,0,0,0.06);
  --shadow-lg:    0 8px 24px rgba(0,0,0,0.06);
  --shadow-focus: 0 0 0 3px var(--color-accent-ring);
  --shadow-composer: 0 1px 2px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.05);

  /* ---------- TYPOGRAPHY · FAMILIES ----------
     Display is Kufic because Kufic is what was actually cut into astrolabe plates —
     the display voice is the object's own lettering. Body is a workhorse: this is a
     surface people read in for hours, and Arabic legibility outranks character there.
     `--font-serif` is kept as a name because ~40 rules reference it, but it now points
     at the display stack; headings render Kufic in Arabic and Archivo in Latin. */
  /* Family order is per-glyph, and that is load-bearing here: Latin resolves to the
     first face that has it, Arabic falls through to the first face that does. Putting
     the Arabic face BEFORE system-ui is what stops Windows quietly serving Segoe UI
     for every Arabic character and erasing the type decision. */
  --font-display: "Archivo", "Reem Kufi", "Segoe UI", sans-serif;
  --font-serif: var(--font-display);
  --font-sans:  "Archivo", "Noto Sans Arabic", system-ui, -apple-system, sans-serif;
  --font-arabic:"Noto Sans Arabic", "Segoe UI", Tahoma, sans-serif;
  /* Arabic has its OWN display voice, and it needs its own token: 166 rules in this
     file point Arabic at --font-arabic, including one that captures every h1/h2/h3.
     Without this token the Kufic display face is never reached in the primary language —
     it would download and be used for nothing. Body text deliberately stays on the
     workhorse; Kufi is a display cut and gets unpleasant at 13px. */
  --font-arabic-display: "Reem Kufi", "Noto Sans Arabic", "Segoe UI", Tahoma, sans-serif;
  /* Mono is for code, data and measurement only — never as a costume for "technical". */
  --font-mono:  "JetBrains Mono", "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace;

  /* ---------- TYPE SCALE (size / line-height) ---------- */
  --text-xs:   12px;  --leading-xs:  16px;
  --text-sm:   13px;  --leading-sm:  20px;
  --text-base: 15px;  --leading-base:24px;
  --text-md:   16px;  --leading-md:  26px;
  --text-lg:   17px;  --leading-lg:  29px;
  --text-xl:   21px;  --leading-xl:  30px;
  --text-2xl:  26px;  --leading-2xl: 34px;
  --text-3xl:  32px;  --leading-3xl: 40px;

  --weight-regular: 400;
  --weight-medium:  500;
  --weight-semibold:600;
  /* Referenced by the Max-tier "beta" badge but never defined. font-weight inherits,
     so the badge was silently rendering at its parent button's weight — at 9px, weight
     was the only thing making it legible. */
  --weight-bold:    700;
  --tracking-wide:  0.06em;
  --tracking-tight: -0.01em;

  /* ---------- Z-INDEX ---------- */
  --z-base:        0;
  --z-sticky:      100;
  --z-scrollbtn:   200;
  --z-dropdown:    300;
  --z-drawer:      400;
  --z-overlay:     390;
  --z-toast:       500;

  /* ---------- MOTION ---------- */
  --dur-fast:   120ms;
  --dur-base:   160ms;
  --dur-slow:   220ms;
  --ease-out:   cubic-bezier(0.22, 1, 0.36, 1);
  --ease-inout: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-ios:   cubic-bezier(0.32, 0.72, 0, 1); /* the private iOS keyboard curve */
  /* Was an elastic overshoot curve (a >1 control point, so it travelled past its target
     and sprang back). A machined part does not bounce past its detent; it decelerates hard
     and stops. Retuned to ease-out-expo, which corrects every tap in the app from this one
     line. The old value is described rather than quoted so design scanners do not keep
     reporting it as still present. */
  --spring:     cubic-bezier(0.16, 1, 0.30, 1);
  --kb-dur:     0.30s; /* matches the native iOS keyboard slide */

  /* ---------- NATIVE SHELL (set by MainActivity on Android; env() defaults elsewhere) ---------- */
  --safe-top:    env(safe-area-inset-top, 0px);
  --safe-right:  env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left:   env(safe-area-inset-left, 0px);
  --kb: 0px;       /* iOS keyboard height, driven by app.js */
  --kb-inset: 0px; /* Android IME height, driven by MainActivity */

  /* ---------- LAYOUT ---------- */
  --sidebar-w: 268px;
  --topbar-h:  56px;
  --column-w:  744px;
}

/* ---------- COLOR · DARK — THE IDENTITY ----------
   The use scene decided this, not the category: an Arabic reader at night, phone or
   laptop, one lamp or none, checking an answer against a page before an exam. So the
   ground is the patina an unlit bronze instrument actually sits in — warm, slightly
   green, never neutral charcoal and never the blue-black every AI product ships.
   Light comes only from the engraving. */
[data-theme="dark"] {
  --color-bg:            #262624;
  --color-bg-subtle:     #1F1E1D;
  --color-surface:       #30302E;
  --color-surface-sunken:#1F1E1D;
  --color-sidebar-bg:    #1F1E1D;

  --color-text-primary:  #ECEAE3;
  --color-text-secondary:#A6A39A;
  /* Same exception as the light theme: the original #76746C measures 3.24:1 on #262624 and
     3.55:1 on the sidebar — both below AA, on the token that paints every placeholder.
     #9A978E is the same tone one step lighter and clears it. */
  --color-text-muted:    #9A978E;

  --color-border-hairline:#3A3A36;
  --color-border-strong:  #46453F;

  /* Green tea — the identity colour, restored exactly as it was. */
  --color-accent:        #57AE9C;
  --color-accent-hover:  #6BC0AE;
  --color-accent-soft:   rgba(87,174,156,0.14);
  --color-accent-ring:   rgba(87,174,156,0.45);
  --color-on-accent:     #1F1E1D;
  --color-accent-deep:   #2F6F62;

  --color-success:       #4BA784;
  --color-error:         #E06A60;
  /* Quick Fix consumes both of these with no fallback, so an undefined name leaves the
     chip uncoloured and — inside color-mix() — drops the border declaration entirely.
     danger is error under a second name; the stylesheet already treats them as one. */
  --color-warn:          #E0A33A;   /* dark */
  --color-danger:        var(--color-error);

  --color-line:          var(--color-accent);
  --color-line-bright:   var(--color-accent-hover);
  --grain-opacity:       0.042;

  /* Metal sits on its surface; it does not hover above it. Offsets stay short and
     the blur stays tight, so panels read as machined plates rather than floating cards. */
  --shadow-xs:    0 1px 1px rgba(0,0,0,0.45);
  --shadow-sm:    0 1px 2px rgba(0,0,0,0.50), 0 2px 4px rgba(0,0,0,0.35);
  --shadow-md:    0 2px 8px rgba(0,0,0,0.50);
  --shadow-lg:    0 6px 20px rgba(0,0,0,0.58);
  --shadow-composer: 0 1px 2px rgba(0,0,0,0.50), 0 6px 18px rgba(0,0,0,0.45);
}

/* ── Extra themes ────────────────────────────────────────────────────────────
   Each theme redefines the SAME token names with different values, so all ~6,400
   lines of CSS below inherit it at once — the architecture the redesign was built
   on. Every one of these is dark-family on purpose: previewTheme() in app.js reads
   any theme that is not "light" as dark, and the product's binding constraint is
   that it stays dark. Adding a LIGHT-family theme means teaching that function
   first — see THEMES in app.js.
   Contrast: every --color-text-muted below was checked against both its own --color-bg
   and its --color-surface, because muted is the token that paints every placeholder. */

/* PURE BLACK — for OLED panels, where black pixels are actually off. */
[data-theme="black"] {
  --color-bg:            #000000;
  --color-bg-subtle:     #0A0A0A;
  --color-surface:       #161616;
  --color-surface-sunken:#0A0A0A;
  --color-sidebar-bg:    #000000;

  --color-text-primary:  #F2F2F0;
  --color-text-secondary:#ABABA6;
  --color-text-muted:    #8C8C87;

  --color-border-hairline:#232323;
  --color-border-strong:  #343432;

  --color-accent:        #5FBBA7;
  --color-accent-hover:  #74CFBB;
  --color-accent-soft:   rgba(95,187,167,0.15);
  --color-accent-ring:   rgba(95,187,167,0.45);
  --color-on-accent:     #000000;
  --color-accent-deep:   #2F6F62;

  --color-success:       #4BA784;
  --color-error:         #E06A60;
  /* Quick Fix consumes both of these with no fallback, so an undefined name leaves the
     chip uncoloured and — inside color-mix() — drops the border declaration entirely.
     danger is error under a second name; the stylesheet already treats them as one. */
  --color-warn:          #E0A33A;   /* black */
  --color-danger:        var(--color-error);

  --color-line:          var(--color-accent);
  --color-line-bright:   var(--color-accent-hover);
  /* The grain is a metal tooth at 4% white — on a true black ground it lifts #000 to a
     flat charcoal and throws away the entire reason to pick this theme. Off, not faint. */
  --grain-opacity:       0;

  --shadow-xs:    0 1px 1px rgba(0,0,0,0.7);
  --shadow-sm:    0 1px 2px rgba(0,0,0,0.75), 0 2px 4px rgba(0,0,0,0.5);
  --shadow-md:    0 2px 8px rgba(0,0,0,0.7);
  --shadow-lg:    0 6px 20px rgba(0,0,0,0.8);
  --shadow-composer: 0 1px 2px rgba(0,0,0,0.75), 0 6px 18px rgba(0,0,0,0.6);
}

/* MIDNIGHT — deep navy ground, cool blue signal. */
[data-theme="midnight"] {
  --color-bg:            #0F1522;
  --color-bg-subtle:     #0A0E19;
  --color-surface:       #182133;
  --color-surface-sunken:#0A0E19;
  --color-sidebar-bg:    #0A0E19;

  --color-text-primary:  #E6ECF5;
  --color-text-secondary:#9FACC2;
  --color-text-muted:    #8695AE;

  --color-border-hairline:#232E44;
  --color-border-strong:  #33405A;

  --color-accent:        #5FBBA7;
  --color-accent-hover:  #74CFBB;
  --color-accent-soft:   rgba(95,187,167,0.15);
  --color-accent-ring:   rgba(95,187,167,0.45);
  --color-on-accent:     #0A0E19;
  --color-accent-deep:   #2F6F62;

  --color-success:       #4BA784;
  --color-error:         #E06A60;
  /* Quick Fix consumes both of these with no fallback, so an undefined name leaves the
     chip uncoloured and — inside color-mix() — drops the border declaration entirely.
     danger is error under a second name; the stylesheet already treats them as one. */
  --color-warn:          #E0A33A;   /* midnight */
  --color-danger:        var(--color-error);

  --color-line:          var(--color-accent);
  --color-line-bright:   var(--color-accent-hover);
  --grain-opacity:       0.035;

  --shadow-xs:    0 1px 1px rgba(0,0,0,0.5);
  --shadow-sm:    0 1px 2px rgba(0,0,0,0.55), 0 2px 4px rgba(0,0,0,0.4);
  --shadow-md:    0 2px 8px rgba(0,0,0,0.55);
  --shadow-lg:    0 6px 20px rgba(0,0,0,0.62);
  --shadow-composer: 0 1px 2px rgba(0,0,0,0.55), 0 6px 18px rgba(0,0,0,0.5);
}

/* GRAPHITE — neutral grey, no colour cast anywhere in the ground. */
[data-theme="graphite"] {
  --color-bg:            #171719;
  --color-bg-subtle:     #101012;
  --color-surface:       #202023;
  --color-surface-sunken:#101012;
  --color-sidebar-bg:    #101012;

  --color-text-primary:  #ECECEE;
  --color-text-secondary:#A5A5AA;
  --color-text-muted:    #8B8B90;

  --color-border-hairline:#2A2A2E;
  --color-border-strong:  #3A3A3F;

  --color-accent:        #57AE9C;
  --color-accent-hover:  #6BC0AE;
  --color-accent-soft:   rgba(87,174,156,0.14);
  --color-accent-ring:   rgba(87,174,156,0.45);
  --color-on-accent:     #101012;
  --color-accent-deep:   #2F6F62;

  --color-success:       #4BA784;
  --color-error:         #E06A60;
  /* Quick Fix consumes both of these with no fallback, so an undefined name leaves the
     chip uncoloured and — inside color-mix() — drops the border declaration entirely.
     danger is error under a second name; the stylesheet already treats them as one. */
  --color-warn:          #E0A33A;   /* graphite */
  --color-danger:        var(--color-error);

  --color-line:          var(--color-accent);
  --color-line-bright:   var(--color-accent-hover);
  --grain-opacity:       0.03;

  --shadow-xs:    0 1px 1px rgba(0,0,0,0.5);
  --shadow-sm:    0 1px 2px rgba(0,0,0,0.55), 0 2px 4px rgba(0,0,0,0.4);
  --shadow-md:    0 2px 8px rgba(0,0,0,0.55);
  --shadow-lg:    0 6px 20px rgba(0,0,0,0.62);
  --shadow-composer: 0 1px 2px rgba(0,0,0,0.55), 0 6px 18px rgba(0,0,0,0.5);
}

/* AMBER — warm ground, brass signal. The astrolabe world after dark. */
[data-theme="amber"] {
  --color-bg:            #1B1713;
  --color-bg-subtle:     #141110;
  --color-surface:       #241F19;
  --color-surface-sunken:#141110;
  --color-sidebar-bg:    #141110;

  --color-text-primary:  #F0E7D8;
  --color-text-secondary:#B3A793;
  --color-text-muted:    #9C907C;

  --color-border-hairline:#332C23;
  --color-border-strong:  #453C30;

  --color-accent:        #D9A05B;
  --color-accent-hover:  #E8B475;
  --color-accent-soft:   rgba(217,160,91,0.15);
  --color-accent-ring:   rgba(217,160,91,0.45);
  --color-on-accent:     #1B1713;
  --color-accent-deep:   #8A6234;

  --color-success:       #8FBF6F;
  --color-error:         #E06A60;
  /* Quick Fix consumes both of these with no fallback, so an undefined name leaves the
     chip uncoloured and — inside color-mix() — drops the border declaration entirely.
     danger is error under a second name; the stylesheet already treats them as one. */
  --color-warn:          #F0A83C;   /* amber — separated from this theme's own #D9A05B accent */
  --color-danger:        var(--color-error);

  --color-line:          var(--color-accent);
  --color-line-bright:   var(--color-accent-hover);
  --grain-opacity:       0.05;

  --shadow-xs:    0 1px 1px rgba(0,0,0,0.5);
  --shadow-sm:    0 1px 2px rgba(0,0,0,0.55), 0 2px 4px rgba(0,0,0,0.4);
  --shadow-md:    0 2px 8px rgba(0,0,0,0.55);
  --shadow-lg:    0 6px 20px rgba(0,0,0,0.62);
  --shadow-composer: 0 1px 2px rgba(0,0,0,0.55), 0 6px 18px rgba(0,0,0,0.5);
}


/* ----------------------------------------------------------------------------
   2. Base + Motion + Accessibility
---------------------------------------------------------------------------- */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }

body {
  min-height: 100dvh;
  background: var(--color-bg);
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-base);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;
  transition: background var(--dur-slow) var(--ease-inout), color var(--dur-slow) var(--ease-inout);
}

/* The ground is a metal surface, so it has tooth. One fixed layer of fractal noise at
   a few percent — it never repaints and never scrolls.
   This replaces a corner radial-gradient glow: a coloured halo bleeding out of one
   corner is decoration standing in for depth, and it is the single most recognizable
   tell of the look this world refuses. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  opacity: var(--grain-opacity);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

button, input, textarea { font-family: inherit; color: inherit; }
button { cursor: pointer; border: none; background: none; }
a { color: var(--color-accent); text-decoration: none; }
ul, ol { margin: 0; }
h1, h2, h3 { margin: 0; font-family: var(--font-serif); font-weight: var(--weight-semibold); }

::selection { background: var(--color-accent-soft); }

/* Scrollbars */
* { scrollbar-width: thin; scrollbar-color: var(--color-border-strong) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-thumb { background: var(--color-border-strong); border-radius: var(--radius-pill); }
*::-webkit-scrollbar-track { background: transparent; }

:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  border-radius: var(--radius-sm);
}

@keyframes msg-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes caret-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.25; } }

.message-enter { animation: msg-in var(--dur-slow) var(--ease-out); }

/* Reduced motion, meant rather than merely obeyed.
   The blanket rule this replaces also set `animation-iteration-count: 1`, which stops every
   SPINNER after a single turn. A frozen spinner is not a calmer spinner — it reads as a hung
   app, so the setting made the product less usable for the people it exists to protect.

   What reduced motion actually asks for is no vestibular movement: no travel, no scaling, no
   parallax. It does not ask for state to stop being reported. So movement goes, and anything
   whose job is to say "still working" keeps working — re-expressed as an opacity pulse, which
   carries the same meaning with nothing moving. */
@keyframes rm-working { 0%, 100% { opacity: 0.32; } 50% { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  body::before { display: none; }

  /* Busy + progress indicators keep reporting. */
  .landing__cta.is-busy::after,
  .auth__submit.is-busy::after,
  .cw-bar__btn--primary.is-busy .cw-bar__btn-ico,
  .cw-live__spin,
  .cw-plan__spin,
  .cw-status__save.is-saving::before,
  .thread-loading__dot,
  .firas-ask-loading__dots > span,
  .skeleton,
  .stream-caret::after {
    animation-name: rm-working !important;
    animation-duration: 1.5s !important;
    animation-iteration-count: infinite !important;
    animation-timing-function: var(--ease-inout) !important;
    transform: none !important;   /* the pulse replaces the rotation entirely */
  }
}

/* ----------------------------------------------------------------------------
   Arabic font swap (mandatory) — Plex Arabic replaces both serif & sans roles
---------------------------------------------------------------------------- */
[dir="rtl"], [lang="ar"], .is-arabic { font-family: var(--font-arabic); }

/* Long-form Arabic — assistant answers and rendered markdown — stays on the workhorse.
   This is reading material, sometimes for an hour, and legibility beats character. */
[dir="rtl"] .assistant-prose, [dir="rtl"] .md { font-family: var(--font-arabic); }

/* The product's own voice — headings, the wordmark, the welcome title — takes the Kufic
   display cut. Kufic is the script astrolabes were actually engraved in, so this is the
   one place the type carries the world rather than just carrying the words. */
/* h1/h2 only. Kufic is a display cut with tight counters — at the 15-16px an h3 card
   title actually renders, it stops being characterful and starts being hard to read.
   h3 keeps the workhorse. */
[dir="rtl"] .wordmark, [dir="rtl"] h1, [dir="rtl"] h2,
[lang="ar"] h1, [lang="ar"] h2,
[dir="rtl"] .welcome__title {
  font-family: var(--font-arabic-display);
  letter-spacing: 0;
  /* Reem Kufi ships a very narrow word-space — at display sizes "لماذا فِراس" closes up
     and reads as a single word. Opening the space is the difference between a Kufic
     heading and an unreadable one. */
  word-spacing: 0.16em;
}
/* …but NOT inside answers: a heading the model wrote is content, not chrome. */
[dir="rtl"] .md h1, [dir="rtl"] .md h2, [dir="rtl"] .md h3 { font-family: var(--font-arabic); }

/* ----------------------------------------------------------------------------
   Firas mark — a distinct rounded-square app-icon glyph (NOT a Claude sunburst).
   A confident, flat teal tile containing a crisp inline-SVG signal/node glyph.
   Used everywhere as the brand mark: topbar, welcome, avatar, auth, favicon.
---------------------------------------------------------------------------- */
/* A brass disc, not a rounded-square app tile. The astrolabe is a disc; making the
   mark one is the cheapest, truest identity move available, and it distinguishes the
   product instantly from every squircle in the category. The shallow gradient is the
   sheen real metal has under one light — it is material, not decoration. */
.nib {
  position: relative;
  display: inline-grid; place-items: center;
  inline-size: 24px; block-size: 24px;
  border-radius: 50%;
  background: linear-gradient(150deg, var(--color-accent-hover), var(--color-accent) 62%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.22), inset 0 -1px 0 rgba(0,0,0,0.18);
  flex: 0 0 auto;
  direction: ltr;
  overflow: hidden;
}
/* The geometric glyph is rendered as an inline SVG injected by JS (.fmark).
   A serif "F" fallback (.glyph) shows if SVG injection hasn't run. */
.nib .glyph {
  font-family: var(--font-serif);
  color: var(--color-on-accent);
  font-weight: var(--weight-semibold);
  font-size: 13px;
  line-height: 1;
}
/* 72%: the engraving runs nearly to the rim on a real plate. */
.nib .fmark { inline-size: 72%; block-size: 72%; display: block; color: var(--color-on-accent); }
.nib .fmark + .glyph { display: none; }
.nib[data-size="sm"] { inline-size: 20px; block-size: 20px; }
.nib[data-size="sm"] .glyph { font-size: 11px; }
.nib[data-size="lg"] { inline-size: 44px; block-size: 44px; }
.nib[data-size="lg"] .glyph { font-size: 24px; }
.nib[data-size="xl"] { inline-size: 76px; block-size: 76px; }
.nib[data-size="xl"] .glyph { font-size: 40px; }
.nib.is-aurora { background: linear-gradient(150deg, var(--color-accent-hover), var(--color-accent) 62%); }

/* Wordmark — serif, quiet, always LTR */
.wordmark {
  font-family: var(--font-serif);
  font-weight: var(--weight-semibold);
  font-size: 20px;
  letter-spacing: var(--tracking-tight);
  color: var(--color-text-primary);
  direction: ltr; unicode-bidi: isolate;
  white-space: nowrap;
}
.wordmark .firas { color: var(--color-text-primary); }
.wordmark .ai { color: var(--color-text-primary); }

.logo-lockup {
  display: inline-flex; align-items: center; gap: var(--space-2);
  direction: ltr; text-decoration: none;
}
.logo-lockup:hover { text-decoration: none; }

/* ----------------------------------------------------------------------------
   3. App Shell
---------------------------------------------------------------------------- */
.app {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  block-size: 100dvh;
}

/* ----- Sidebar ----------------------------------------------------------- */
.sidebar {
  display: flex; flex-direction: column;
  block-size: 100dvh;
  padding: var(--space-3);
  gap: var(--space-2);
  background: var(--color-sidebar-bg);
  border-inline-end: var(--border-hairline) solid var(--color-border-hairline);
  z-index: 30;
}
.sidebar__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--space-2) var(--space-2) var(--space-3);
}
.sidebar .sidebar__close { display: none; } /* hidden on desktop; .sidebar bump beats .icon-btn */

.btn-newchat {
  display: flex; align-items: center; gap: var(--space-2);
  inline-size: 100%;
  block-size: 40px;
  padding-inline: var(--space-3);
  border-radius: var(--radius-sm);
  border: var(--border-hairline) solid var(--color-border-hairline);
  background: transparent;
  color: var(--color-text-primary);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.btn-newchat svg { inline-size: 16px; block-size: 16px; color: var(--color-text-secondary); flex: 0 0 auto; }
.btn-newchat:hover { background: var(--color-surface); border-color: var(--color-border-strong); }

/* Search */
.sidebar__search { position: relative; margin-block: var(--space-2) var(--space-1); }
.sidebar__search svg {
  position: absolute; inset-block-start: 50%; inset-inline-start: var(--space-3);
  transform: translateY(-50%);
  inline-size: 15px; block-size: 15px; color: var(--color-text-muted); pointer-events: none;
}
.sidebar__search input {
  inline-size: 100%;
  block-size: 36px;
  padding-inline: 36px var(--space-3);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-sm);
  color: var(--color-text-primary);
  font-size: var(--text-sm);
  transition: border-color var(--dur-fast) var(--ease-out);
}
.sidebar__search input::placeholder { color: var(--color-text-muted); }
.sidebar__search input:focus { outline: none; border-color: var(--color-border-strong); }

/* History */
.history { flex: 1; overflow-y: auto; margin-inline: calc(var(--space-1) * -1); padding-inline: var(--space-1); }
.history__group-label {
  padding: var(--space-3) var(--space-3) var(--space-1);
  font-size: var(--text-xs); line-height: var(--leading-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase; letter-spacing: var(--tracking-wide);
  color: var(--color-text-muted);
}
.history__empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--space-2); padding: var(--space-10) var(--space-4);
  color: var(--color-text-muted); font-size: var(--text-sm);
}
.history__empty-ic { display: grid; place-items: center; opacity: 0.6; }

.chat-item {
  position: relative;
  display: flex; align-items: center; gap: var(--space-2);
  block-size: 36px;
  padding-inline: var(--space-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
  color: var(--color-text-secondary);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.chat-item:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.chat-item.is-active { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.chat-item.is-active .chat-item__title { font-weight: var(--weight-medium); }
.chat-item__title {
  flex: 1; min-inline-size: 0;
  font-size: var(--text-sm);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat-item__title input {
  inline-size: 100%; background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-strong); border-radius: var(--radius-xs);
  padding: 2px 6px; font-size: var(--text-sm); color: var(--color-text-primary);
}
.chat-item__actions { display: none; gap: 2px; flex: 0 0 auto; }
.chat-item.is-active .chat-item__actions { display: flex; }
/* HOVER-REVEAL ONLY WHERE HOVER IS REAL. A touch screen SYNTHESISES :hover on the first tap,
   which flipped this from none to flex and reflowed the row while the finger was still down —
   the click then landed on shifted geometry and the chat only opened on the SECOND tap. That
   is the reported "المحادثات مرتين اضغط حتى تفتح", and it is a layout change mid-gesture, not
   a slow handler: nothing in JS was ever wrong. */
@media (hover: hover) and (pointer: fine) {
  .chat-item:hover .chat-item__actions { display: flex; }
}
/* Touch: always present, so the row's geometry is identical before and after the tap. */
@media (hover: none) {
  .chat-item__actions { display: flex; }
}
.chat-item__actions button {
  display: grid; place-items: center;
  inline-size: 26px; block-size: 26px;
  border-radius: var(--radius-xs); color: var(--color-text-muted);
  transition: background var(--dur-fast), color var(--dur-fast);
}
.chat-item__actions button:hover { background: var(--color-surface); color: var(--color-text-secondary); }
.chat-item__actions svg { inline-size: 14px; block-size: 14px; }

/* Account pill */
.account-pill {
  display: flex; align-items: center; gap: var(--space-3);
  margin-top: var(--space-2); padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  border: var(--border-hairline) solid transparent;
  transition: background var(--dur-fast) var(--ease-out);
}
.account-pill:hover { background: var(--color-bg-subtle); }
.account-pill__avatar {
  inline-size: 28px; block-size: 28px; border-radius: var(--radius-pill);
  display: grid; place-items: center; flex: 0 0 auto;
  background: var(--color-accent); color: var(--color-on-accent);
  font-weight: var(--weight-semibold); font-size: var(--text-xs);
}
.account-pill__name { flex: 1; min-inline-size: 0; font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--color-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.account-pill__logout {
  display: grid; place-items: center; flex: 0 0 auto;
  inline-size: 30px; block-size: 30px; border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.account-pill__logout:hover { background: var(--color-surface); color: var(--color-text-primary); }
.account-pill__logout svg { inline-size: 16px; block-size: 16px; }
.account-pill__settings {
  display: grid; place-items: center; flex: 0 0 auto;
  inline-size: 30px; block-size: 30px; border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.account-pill__settings:hover { background: var(--color-surface); color: var(--color-text-primary); }
.account-pill__settings svg { inline-size: 16px; block-size: 16px; }

/* ----------------------------------------------------------------------------
   4. Main column
---------------------------------------------------------------------------- */
.main { position: relative; display: flex; flex-direction: column; block-size: 100dvh; min-inline-size: 0; }

/* Top bar */
.topbar {
  display: flex; align-items: center; gap: var(--space-3);
  block-size: var(--topbar-h);
  min-block-size: var(--topbar-h);
  padding-inline: var(--space-4);
  background: var(--color-bg);
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
  position: sticky; top: 0;
  z-index: var(--z-sticky);
}
.topbar .topbar__mobile-only { display: none; } /* hidden on desktop; .topbar bump beats .icon-btn */
.topbar__spacer { flex: 1; }

.topbar__desktop-mark .wordmark { font-size: 20px; }

/* ----- Tier switcher (segmented control) ----- */
.tier-switch {
  display: inline-flex; align-items: center;
  block-size: 32px;
  padding: 2px;
  gap: 2px;
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-sm);
}
.tier-switch__btn {
  display: inline-flex; align-items: center; gap: 5px;
  block-size: 26px;
  padding-inline: var(--space-3);
  border-radius: 6px;
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  color: var(--color-text-secondary);
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.tier-switch__btn .tier-ico { display: none; }      /* spark handled via ::before for ultra */
.tier-switch__btn:hover { color: var(--color-text-primary); }
.tier-switch__btn.is-active {
  background: var(--color-surface);
  color: var(--color-text-primary);
  box-shadow: var(--shadow-xs);
}
/* Ultra gets a small teal node dot before its label (not a Claude spark) */
.tier-switch__btn[data-tier="ultra"] .tier-name::before {
  content: "";
  display: inline-block;
  inline-size: 6px; block-size: 6px;
  margin-inline-end: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  vertical-align: 1px;
}
.tier-switch__btn[data-tier="ultra"].is-active {
  background: var(--color-accent-soft);
  color: var(--color-accent);
  font-weight: var(--weight-semibold);
  box-shadow: none;
}
.tier-switch__btn[data-tier="ultra"].is-active .tier-name::before { background: var(--color-accent); }

/* Max gets a PURPLE node dot + a premium glow when active */
.tier-switch__btn[data-tier="max"] .tier-name::before {
  content: ""; display: inline-block;
  inline-size: 6px; block-size: 6px; margin-inline-end: 6px;
  border-radius: var(--radius-pill); background: #8b5cf6; vertical-align: 1px;
}
.tier-switch__btn[data-tier="max"]:hover { color: #7c3aed; }
.tier-switch__btn[data-tier="max"].is-active {
  background: rgba(139, 92, 246, 0.13);
  color: #7c3aed; font-weight: var(--weight-semibold); box-shadow: none;
}
.tier-switch__btn[data-tier="max"].is-active .tier-name::before {
  background: #8b5cf6; animation: maxDotPulse 1.8s ease-in-out infinite;
}
@keyframes maxDotPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(139, 92, 246, 0.5); }
  50%      { box-shadow: 0 0 0 5px rgba(139, 92, 246, 0); }
}
/* "beta" badge next to Max */
.tier-switch__btn[data-tier="max"] .tier-beta {
  display: inline-flex; align-items: center;
  margin-inline-start: 5px;
  padding: 1px 5px;
  font-size: 9px; font-weight: var(--weight-bold);
  letter-spacing: 0.04em; text-transform: uppercase;
  line-height: 1.4;
  color: #a78bfa;
  background: rgba(139, 92, 246, 0.16);
  border: 1px solid rgba(139, 92, 246, 0.35);
  border-radius: var(--radius-pill);
}
.tier-switch__btn[data-tier="max"].is-active .tier-beta {
  color: #7c3aed; background: rgba(139, 92, 246, 0.22);
}

/* Professional "switch" pop on whichever tier was just activated */
.tier-switch__btn.just-activated { animation: tierPop 0.42s var(--ease-out); }
@keyframes tierPop {
  0%   { transform: scale(0.9); }
  45%  { transform: scale(1.07); }
  100% { transform: scale(1); }
}
.tier-switch__btn[data-tier="max"].just-activated {
  animation: tierPop 0.42s var(--ease-out), maxGlow 0.65s var(--ease-out);
}
@keyframes maxGlow {
  0%   { box-shadow: 0 0 0 0 rgba(139, 92, 246, 0.55); }
  100% { box-shadow: 0 0 0 16px rgba(139, 92, 246, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .tier-switch__btn.just-activated,
  .tier-switch__btn[data-tier="max"].is-active .tier-name::before { animation: none; }
}

/* Icon button */
.icon-btn {
  display: grid; place-items: center;
  inline-size: 36px; block-size: 36px;
  min-inline-size: 36px;
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.icon-btn:hover { background: var(--color-bg-subtle); }
.icon-btn:active { background: var(--color-border-hairline); }
.icon-btn svg { inline-size: 20px; block-size: 20px; }

.topbar__newchat {
  display: inline-flex; align-items: center; gap: var(--space-2);
  block-size: 36px;
  padding-inline: var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary); font-size: var(--text-sm); font-weight: var(--weight-medium);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.topbar__newchat:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.topbar__newchat svg { inline-size: 16px; block-size: 16px; }

/* Theme toggle — minimal icon button */
.theme-toggle {
  position: relative;
  display: grid; place-items: center;
  inline-size: 36px; block-size: 36px; min-inline-size: 36px;
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
  background: transparent;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.theme-toggle:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.theme-toggle__knob { display: grid; place-items: center; }
.theme-toggle__knob svg { inline-size: 19px; block-size: 19px; }
.theme-toggle__knob .ico-sun { display: none; }
[data-theme="dark"] .theme-toggle__knob .ico-sun { display: block; }
[data-theme="dark"] .theme-toggle__knob .ico-moon { display: none; }

/* ----------------------------------------------------------------------------
   5. Chat scroll region
---------------------------------------------------------------------------- */
.chat-scroll {
  flex: 1; overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain;
  /* NOTE: no scroll-behavior:smooth here — during streaming the app re-pins to the bottom every
     frame, and smooth turns each pin into a restarted animation (view forever chasing the bottom).
     The scroll-to-bottom BUTTON does its own smooth scroll in JS. */
  /* Was a flat 150px. The composer is not a fixed height — two rows, a growing prompt and an
     attachment tray all change it — so the reserve is now measured from the real element by
     watchComposerRoom() in app.js. The 150px stays as the fallback for the first frame, before
     the observer has reported. */
  padding-bottom: var(--composer-room, 150px);
}
.chat-inner {
  max-inline-size: var(--column-w);
  margin-inline: auto;
  padding: var(--space-8) var(--space-6) 0;
}

/* ----- Welcome / hero ---------------------------------------------------- */
.welcome {
  max-inline-size: var(--column-w);
  margin-inline: auto;
  padding: var(--space-16) var(--space-6) 0;
  display: flex; flex-direction: column;
}
.welcome__mark { margin-bottom: var(--space-5); }
.welcome__title {
  font-family: var(--font-serif);
  font-size: var(--text-3xl); line-height: var(--leading-3xl);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-tight);
  color: var(--color-text-primary);
}
[dir="rtl"] .welcome__title { line-height: 1.3; }

/* ----------------------------------------------------------------------------
   6. Messages
---------------------------------------------------------------------------- */
.turn {
  animation: msg-in var(--dur-slow) var(--ease-out);
  margin-bottom: var(--space-8);
  /* Long-thread smoothness: off-screen turns skip layout/paint entirely; the browser
     remembers each turn's real size after first render so scroll math stays accurate. */
  content-visibility: auto;
  contain-intrinsic-size: auto 320px;
}
/* The actively-streaming turn must stay fully rendered. ":last-child" was the whole rule, on
   the assumption that the live turn is always last — and in Firas Brain it is NOT: a sweep
   appends a "Sweeping the document… 5/40" notice AFTER it. The answer then lost the exemption,
   collapsed from its real 5814px to the 320px intrinsic estimate, the thread stopped
   overflowing at all, and the browser clamped scrollTop to 0 — throwing the reader back to the
   top of the prompt on every batch. `.is-live` states the intent instead of inferring it. */
.turn:last-child,
.turn.is-live { content-visibility: visible; }

/* ----- User bubble — aligned inline-end, quiet ----- */
.msg-user { display: flex; justify-content: flex-end; }
.msg-user__bubble {
  max-inline-size: 85%;
  /* 14px block padding buys the bubble its height: a one-line message lands ~58px tall,
     safely past where the 20px radius could close it into a capsule. The 20px inline padding
     keeps the first and last glyph off the corner arcs. */
  padding: 14px var(--space-5);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--soft-radius);
  box-shadow: var(--shadow-xs);
  /* Long-press (any device) opens ONLY our Copy menu — never the OS callout and never a
     text selection. The user copies via the button, not by selecting the text. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  font-family: var(--font-sans);
  font-size: var(--text-base); line-height: var(--leading-base);
  color: var(--color-text-primary);
  white-space: pre-wrap;
  word-wrap: break-word; overflow-wrap: anywhere;
}
/* YOUR OWN QUESTION IS SELECTABLE. This was `user-select: none` on EVERY child, which made the
   sentence the reader typed impossible to copy — on a phone there was no way to get it back at
   all, as the "copy your own question" block further down this file says in its own words while
   solving it for one product only.
   The reason behind the old rule was real but much narrower than the rule: KaTeX ships a HIDDEN
   MathML twin of each formula for screen readers, and selecting across one pastes the equation
   twice. So the twin keeps selection off, and the words do not. */
.msg-user__bubble, .msg-user__bubble * { -webkit-user-select: text; user-select: text; }
.msg-user__bubble .katex-mathml { -webkit-user-select: none; user-select: none; }
.msg-user__bubble[dir="rtl"] { font-family: var(--font-arabic); }

/* ----- Assistant prose — no bubble, document on bg ----- */
.msg-ai { position: relative; }
.msg-ai__head { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); }
.msg-ai__avatar {
  inline-size: 24px; block-size: 24px; border-radius: 26%;
  display: grid; place-items: center; flex: 0 0 auto;
  overflow: hidden;
  background: linear-gradient(145deg, var(--color-accent-hover), var(--color-accent));
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.14);
}
.msg-ai__avatar .fmark { inline-size: 64%; block-size: 64%; display: block; color: var(--color-on-accent); }
.msg-ai__avatar .fmark + .glyph { display: none; }
.msg-ai__avatar .glyph {
  font-family: var(--font-serif); font-weight: var(--weight-semibold);
  font-size: 13px; line-height: 1; color: var(--color-on-accent);
}
.msg-ai__name {
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  color: var(--color-text-primary);
}

/* Per-message tier badge */
.msg-ai__badge {
  display: inline-flex; align-items: center; gap: 4px;
  margin-inline-start: var(--space-2);
  font-size: var(--text-xs); line-height: var(--leading-xs);
  letter-spacing: var(--tracking-wide); font-weight: var(--weight-semibold);
  text-transform: uppercase;
  color: var(--color-text-muted);
  direction: ltr; unicode-bidi: isolate;
}
.msg-ai__badge .dot { display: none; }
.msg-ai__badge[data-tier="ultra"] {
  color: var(--color-accent);
  border: var(--border-hairline) solid var(--color-accent);
  border-radius: var(--radius-pill);
  padding: 1px var(--space-2);
  background: var(--color-accent-soft);
}
.msg-ai__badge[data-tier="ultra"]::before {
  content: ""; inline-size: 6px; block-size: 6px;
  border-radius: var(--radius-pill); background: var(--color-accent);
  display: inline-block;
}
/* Max = the purple tier — its per-message badge gets a purple ring (was plain like Pro) */
.msg-ai__badge[data-tier="max"] {
  color: #7c3aed;
  border: var(--border-hairline) solid #7c3aed;
  border-radius: var(--radius-pill);
  padding: 1px var(--space-2);
  background: color-mix(in srgb, #7c3aed 10%, transparent);
}
.msg-ai__badge[data-tier="max"]::before {
  content: ""; inline-size: 6px; block-size: 6px;
  border-radius: var(--radius-pill); background: #7c3aed;
  display: inline-block;
}
[data-theme="dark"] .msg-ai__badge[data-tier="max"] { color: #a78bfa; border-color: #a78bfa; }
[data-theme="dark"] .msg-ai__badge[data-tier="max"]::before { background: #a78bfa; }

/* Prose body */
.msg-ai__body {
  position: relative;
  font-family: var(--font-serif);
  font-size: var(--text-lg); line-height: var(--leading-lg);
  font-weight: var(--weight-regular);
  color: var(--color-text-primary);
}
.msg-ai__body[dir="rtl"] { font-family: var(--font-arabic); line-height: 1.9; }

/* Streaming caret */
.stream-caret::after {
  content: ""; display: inline-block;
  inline-size: 2px; block-size: 1.05em;
  background: var(--color-accent);
  margin-inline-start: 2px;
  vertical-align: text-bottom;
  animation: caret-pulse 1s ease-in-out infinite;
}

/* Markdown prose */
/* WORD INTEGRITY.
   This used to be `overflow-wrap: anywhere; word-break: break-word`, which is why a word
   could split mid-letter — "مرحبا" coming out as "مرحب" on one line and "ا" on the next,
   worst of all inside table cells. Two separate reasons:

     · `anywhere` permits a break at ANY character whenever the line is tight, not only when
       the word cannot fit at all;
     · `anywhere` also shrinks the element's min-content width, so a table column is allowed
       to collapse to one character and then every word in it breaks.

   `break-word` breaks a word ONLY when that word alone cannot fit on a line of its own, and
   it leaves min-content sizing intact — so columns size to whole words. A monstrous URL
   still wraps instead of overflowing; ordinary Arabic and English never split. */
.md {
  font-family: inherit; font-size: inherit; line-height: inherit;
  color: var(--color-text-primary);
  overflow-wrap: break-word;
  word-break: normal;
  hyphens: none;                 /* never insert a hyphen mid-word */
}
/* Long unbroken tokens (URLs, hashes, paths) in inline code must wrap, not clip — code
   BLOCKS (pre) keep their own horizontal scroll. */
.md :not(pre) > code { overflow-wrap: anywhere; }
.md > *:first-child { margin-top: 0; }
.md > *:last-child { margin-bottom: 0; }
.md p { margin: 0 0 var(--space-4); }
.md h1, .md h2, .md h3 {
  font-family: var(--font-serif); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
  margin: var(--space-6) 0 var(--space-3); line-height: 1.25;
}
[dir="rtl"] .md h1, [dir="rtl"] .md h2, [dir="rtl"] .md h3 { font-family: var(--font-arabic); }
.md h1 { font-size: var(--text-2xl); line-height: var(--leading-2xl); }
.md h2 { font-size: var(--text-2xl); line-height: var(--leading-2xl); }
.md h3 { font-size: var(--text-xl); line-height: var(--leading-xl); }
.md ul, .md ol { margin: 0 0 var(--space-4); padding-inline-start: var(--space-6); }
/* An RTL ordered list renders "1." as ".1". The digit is a strong LTR character and the period
   is NEUTRAL, so the paragraph's right-to-left direction pulls the period to the left of the
   number it belongs to. Isolating the marker in its own left-to-right run keeps the pair in the
   order every reader expects, in Arabic exactly as in English. ::marker accepts `direction` and
   `unicode-bidi` precisely for this; it is a no-op on a list that is already LTR. */
.md ol > li::marker { direction: ltr; unicode-bidi: isolate; }
.md li { margin: var(--space-2) 0; }
.md a {
  color: var(--color-accent);
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px;
}
.md a:hover { color: var(--color-accent-hover); }
.md strong { font-weight: var(--weight-semibold); }
.md blockquote {
  margin: 0 0 var(--space-4); padding-inline-start: var(--space-4);
  border-inline-start: var(--border-strong) solid var(--color-border-strong);
  color: var(--color-text-secondary);
}
.md hr { border: none; border-top: var(--border-hairline) solid var(--color-border-hairline); margin: var(--space-6) 0; }
.md table {
  inline-size: 100%; border-collapse: collapse; margin: 0 0 var(--space-4);
  font-family: var(--font-sans); font-size: var(--text-sm);
  display: block; overflow-x: auto;
}
.md th, .md td {
  border: var(--border-hairline) solid var(--color-border-hairline);
  padding: var(--space-2) var(--space-3);
  text-align: start;
  /* Restated at cell level so no later era of this stylesheet can re-introduce a mid-word
     break here — a table is the narrowest context in the app and the first place it shows. */
  overflow-wrap: break-word;
  word-break: normal;
  hyphens: none;
}
/* A cell should rather widen (the table scrolls) than chop its words to fit. */
.md table { table-layout: auto; }
.md th { background: var(--color-bg-subtle); font-weight: var(--weight-semibold); }

/* Inline code */
.md :not(pre) > code {
  font-family: var(--font-mono); font-size: var(--text-sm);
  background: var(--color-bg-subtle);
  border-radius: var(--radius-xs);
  padding: 2px 5px;
  direction: ltr; unicode-bidi: isolate;
}

/* Code blocks */
.code-block {
  margin: var(--space-4) 0;
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  overflow: hidden;
  direction: ltr; unicode-bidi: isolate; text-align: start;
}
.code-block__head {
  display: flex; align-items: center; justify-content: space-between;
  block-size: 36px;
  padding-inline: var(--space-3);
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
}
.code-block__lang {
  font-family: var(--font-sans);
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-wide);
  color: var(--color-text-muted);
}
.code-block__copy {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-sans);
  font-size: var(--text-xs); color: var(--color-text-secondary);
  padding: 4px 8px; border-radius: var(--radius-xs);
  transition: color var(--dur-fast), background var(--dur-fast);
}
.code-block__copy:hover { color: var(--color-text-primary); background: var(--color-bg); }
.code-block__copy svg { inline-size: 14px; block-size: 14px; }
.code-block pre { margin: 0; padding: var(--space-4); overflow-x: auto; }
/* Sketch from LaTeX — rendered TikZ figures (TikZJax) */
.tikz-figure { margin: var(--space-4) 0; text-align: center; overflow-x: auto; }
.tikz-figure svg { max-width: 100%; height: auto; }
.tikz-figure .tikz-svg { display: block; margin-inline: auto; background: #fff; border: 1px solid var(--color-border-hairline, #e5e7eb); border-radius: 8px; }
.tikz-svg text.tikz-lbl { font-family: var(--font-sans); font-size: 13px; fill: #1f2937; }
.plot-figure { max-width: 480px; margin-inline: auto; }
.plot-figure { --plot-c1: var(--color-accent, #2563eb); --plot-c2: #3b82f6; --plot-c3: #ef4444; --plot-c4: #10b981; --plot-c5: #a855f7; }
.plot-figure svg { width: 100%; height: auto; font-family: var(--font-sans); }
.plot-legend { position: absolute; top: 8px; inset-inline-end: 10px; display: flex; flex-direction: column; gap: 2px;
  padding: 4px 9px; border-radius: 8px; pointer-events: none; max-inline-size: 66%;
  background: var(--color-bg-subtle, #fff); background: color-mix(in srgb, var(--color-bg-subtle, #fff) 86%, transparent);
  border: 1px solid var(--color-border-hairline, #e5e7eb); }
.plot-legend__row { display: flex; align-items: center; gap: 7px; line-height: 1.15; }
.plot-legend__sw { inline-size: 15px; block-size: 3px; border-radius: 2px; flex: 0 0 auto; }
.plot-legend__lb { color: var(--color-text-secondary, #374151); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.plot-legend__lb .katex { font-size: 0.98em; }
/* Right-click / long-press copy menu (user messages) */
.msg-menu {
  position: fixed; z-index: 1200; min-inline-size: 130px; padding: 5px;
  border-radius: 11px; background: var(--color-surface, var(--color-bg, #fff));
  border: var(--border-hairline, 1px) solid var(--color-border-hairline, #e5e7eb);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.3); animation: msgMenuIn 0.12s var(--ease-out, ease-out);
}
.msg-menu__item {
  display: flex; align-items: center; gap: 9px; inline-size: 100%; padding: 8px 12px;
  border: 0; background: none; border-radius: 7px; cursor: pointer; font: inherit;
  font-size: var(--text-base, 14px); color: var(--color-text-primary, #111); text-align: start;
}
.msg-menu__item:hover { background: var(--color-bg-subtle, rgba(0, 0, 0, 0.05)); }
.msg-menu__item svg { inline-size: 16px; block-size: 16px; flex: 0 0 auto; }
@keyframes msgMenuIn { from { opacity: 0; transform: translateY(-5px) scale(0.96); } to { opacity: 1; transform: none; } }
/* Select a span in an answer → "Ask about this".
   Sits above the copy menu's z-index band because it can appear while that menu is being
   dismissed, and it is the thing the user is reaching for. Accent-filled rather than
   surface-coloured: it floats ON TOP of the sentence it refers to, and a surface-on-surface
   pill would disappear into the answer behind it. */
.sel-ask {
  position: fixed; z-index: 1250;
  display: inline-flex; align-items: center; gap: 7px;
  max-inline-size: min(72vw, 300px);
  padding: 7px 13px;
  border: 0; border-radius: var(--radius-pill, 999px);
  background: var(--color-accent, #237A68);
  color: var(--color-on-accent, #fff);
  font-family: var(--font-sans);
  font-size: var(--text-sm, 13px);
  font-weight: var(--weight-semibold, 600);
  line-height: 1.2; white-space: nowrap;
  cursor: pointer;
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(0, 0, 0, 0.28));
  /* The chip must never become part of the selection it is offering to quote. */
  -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
  animation: selAskIn var(--dur-fast, 120ms) var(--ease-out, ease-out);
}
/* It holds Arabic half the time and the shell is hard LTR, so it stamps its own dir — this
   only picks up the matching face. */
.sel-ask[dir="rtl"] { font-family: var(--font-arabic, var(--font-sans)); }
.sel-ask__lb { overflow: hidden; text-overflow: ellipsis; }
.sel-ask svg { inline-size: 15px; block-size: 15px; flex: 0 0 auto; }
.sel-ask:hover { background: var(--color-accent-hover, var(--color-accent, #237A68)); }
.sel-ask:active { transform: scale(0.97); }
.sel-ask:focus-visible { outline: 2px solid var(--color-accent-deep, #14544A); outline-offset: 2px; }
@keyframes selAskIn { from { opacity: 0; transform: translateY(4px) scale(0.94); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .sel-ask { animation: none; }
  .sel-ask:active { transform: none; }
}
/* ── Public shared-chat page (/?share=<id>) ── */
/* .share-page IS ITS OWN SCROLLER, because nothing else on this surface is one.

   styles.css sets body{overflow:hidden} on purpose — its own note says "every scroller declares its
   own overflow" — and no rule sets overflow on <html>, so that value reaches the viewport. This
   page never declared the scroller that rule assumes, so it could not be scrolled at all: measured
   live, 5113px of answer in a 383px viewport left 4730px — 92% — unreachable by wheel or touch.

   It matters more here than anywhere else in the product. A share link is the one page that goes to
   people with no account and no other route to the content; they saw a heading, a paragraph, and a
   page that appeared broken.

   The sticky header is not harmed by this — it is REPAIRED by it. position: sticky needs a
   scrolling ancestor and had none, so it was pinned to a viewport that never moved. */
.share-page {
  position: fixed; inset: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch;   /* a share link is opened on a phone */
  background: var(--color-bg); color: var(--color-text-primary);
}
.share-page__head {
  position: sticky; top: 0; z-index: 5; display: flex; align-items: center; justify-content: space-between;
  padding: 12px clamp(16px, 4vw, 40px); background: var(--color-bg);
  border-bottom: var(--border-hairline) solid var(--color-border-hairline);
}
.share-page__brand { font-family: var(--font-serif, serif); font-weight: 700; font-size: 20px; }
.share-page__cta {
  padding: 9px 18px; border-radius: 999px; background: var(--color-accent); color: #fff;
  text-decoration: none; font-family: var(--font-sans); font-size: 14px; font-weight: 600;
}
.share-page__cta:hover { filter: brightness(1.06); }
.share-page__col { max-inline-size: var(--column-w, 760px); margin-inline: auto; padding: 28px clamp(14px, 4vw, 24px) 80px; }
.share-page__title { font-size: var(--text-2xl, 26px); margin: 0 0 22px; }
.share-page__user { display: flex; justify-content: flex-end; margin: 22px 0 14px; }
.share-page__user .msg-user__bubble { max-inline-size: 85%; }
.share-page__ai { margin-bottom: 10px; }
.share-page__missing { text-align: center; opacity: .7; padding: 60px 0; font-family: var(--font-sans); }
/* The caption on a shared ANSWER. That snapshot carries no title on purpose, so this line is the
   only thing telling the reader why there is no question above the answer — a caption, not a
   heading, so it stays quiet. Tracking and uppercase are LATIN-ONLY: letter-spacing pulls Arabic
   letters out of their joins and text-transform does nothing to them but cost a repaint. */
.share-page__one {
  margin: 0 0 18px;
  text-align: center;
  font-family: var(--font-sans);
  font-size: 12px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
}
.share-page__one[dir="rtl"] {
  font-family: var(--font-arabic);
  letter-spacing: 0;
  text-transform: none;
}

/* ── Product switcher (Firas AI / Agent / Code) ── */
.product-switch {
  display: flex; align-items: center; gap: 8px; margin: 2px 14px 10px; padding: 8px 12px;
  border-radius: 10px; cursor: pointer; font-family: var(--font-sans); font-size: 13px; font-weight: 600;
  color: var(--color-text-secondary); background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--color-border-hairline);
  transition: border-color .15s, color .15s;
}
.product-switch:hover { color: var(--color-text-primary); border-color: var(--color-border-strong, var(--color-border-hairline)); }
.product-switch__dot { inline-size: 8px; block-size: 8px; border-radius: 50%; background: var(--color-accent); flex: 0 0 auto; }
.product-switch__name { flex: 1 1 auto; text-align: start; }
.product-switch__chev { inline-size: 14px; block-size: 14px; opacity: .6; }
.product-menu {
  position: fixed; z-index: 1300; min-inline-size: 230px; padding: 6px;
  border-radius: 13px; background: var(--color-surface, var(--color-bg));
  border: var(--border-hairline) solid var(--color-border-hairline);
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.35); animation: msgMenuIn .12s var(--ease-out, ease-out);
}
.product-menu__item {
  display: grid; grid-template-columns: 1fr auto; grid-template-rows: auto auto; column-gap: 10px;
  inline-size: 100%; padding: 10px 12px; border: 0; background: none; border-radius: 9px; cursor: pointer;
  text-align: start; font-family: var(--font-sans); color: var(--color-text-primary);
}
.product-menu__item:hover { background: var(--color-bg-subtle); }
.product-menu__name { font-size: 14px; font-weight: 650; }
.product-menu__tag { grid-column: 1; font-size: 11.5px; color: var(--color-text-muted); margin-top: 2px; }
.product-menu__check { grid-row: 1 / span 2; align-self: center; color: var(--color-accent); font-weight: 700; }
.product-menu__soon { font-size: 10px; font-weight: 700; letter-spacing: .04em; color: var(--color-text-muted); border: 1px solid var(--color-border-hairline); border-radius: 999px; padding: 1px 7px; margin-inline-start: 6px; vertical-align: 2px; }
.product-menu__item.is-locked { opacity: .5; }
.product-menu__item.is-locked .product-menu__name { filter: grayscale(1); }
/* Under-development modal (Firas Code) */
.dev-modal { position: fixed; inset: 0; z-index: 1400; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,.55); animation: lbFade .15s ease-out; }
.dev-modal__card { max-inline-size: 340px; margin: 20px; padding: 26px 24px 20px; border-radius: 16px; text-align: center; background: var(--color-surface, var(--color-bg)); border: var(--border-hairline) solid var(--color-border-hairline); box-shadow: 0 20px 60px rgba(0,0,0,.4); font-family: var(--font-sans); }
.dev-modal__icon { font-size: 34px; }
.dev-modal__title { margin: 10px 0 6px; font-size: 18px; color: var(--color-text-primary); }
.dev-modal__body { margin: 0 0 18px; font-size: 13.5px; line-height: 1.7; color: var(--color-text-secondary); }
.dev-modal__ok { padding: 9px 26px; border: 0; border-radius: 999px; cursor: pointer; background: var(--color-accent); color: #fff; font: 600 13.5px var(--font-sans); }
/* ── Firas Agent environment: same palette, elevated feel ── */
.product-agent .nib, .product-agent .product-switch__dot { background: linear-gradient(135deg, var(--color-accent) 20%, #5b8def 115%); }
.product-agent .chat-scroll { background-image: radial-gradient(640px 220px at 50% -60px, color-mix(in srgb, var(--color-accent) 9%, transparent), transparent); }
.product-agent .composer { border-color: color-mix(in srgb, var(--color-accent) 45%, var(--color-border-hairline)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-accent) 18%, transparent), var(--shadow-sm, 0 1px 3px rgba(0,0,0,.2)); }
.product-agent .product-switch { border-color: color-mix(in srgb, var(--color-accent) 40%, var(--color-border-hairline)); color: var(--color-text-primary); }
/* Agent runs on ONE permanent engine — no tier choice, anywhere */
.product-agent .tier-switch { display: none !important; }
/* The Agent thinks, plans and web-searches on its OWN — hide the thinking/web-search "+" menu and the
   Auto/Plan mode switch in the composer. Only attach-files, attach-images and send remain. */
.product-agent #modeSwitch,
.product-agent #toolsMenu { display: none !important; }
/* …and UI 2.0’s model pill, for the same reason the tier strip just above it is hidden: the
   mission runs on Max whatever this said. csSyncBar() already refuses to build it here — this
   makes its absence a property of the Agent surface, the way the three selectors above it
   already are, rather than of one function remembering to ask. */
.product-agent #csModelPill { display: none !important; }
.msg-ai__badge--agent { background: linear-gradient(100deg, color-mix(in srgb, var(--color-accent) 24%, transparent), color-mix(in srgb, #5b8def 24%, transparent)); border: 1px solid color-mix(in srgb, var(--color-accent) 45%, transparent); color: var(--color-text-primary); }
.msg-ai__badge--agent .dot { background: linear-gradient(135deg, var(--color-accent), #5b8def); }
.msg-ai__avatar--agent { background: linear-gradient(135deg, var(--color-accent) 20%, #5b8def 115%); }
/* Solid, not gradient-clipped. Emphasis comes from weight and size; gradient text is
   decoration standing in for hierarchy, and the #5b8def it faded into was a foreign blue
   that belonged to no part of this palette. */
.agent-welcome__grad { color: var(--color-accent); font-weight: var(--weight-bold); }
.agent-welcome__sub { max-inline-size: 460px; margin: 10px auto 22px; font: 400 14.5px/1.8 var(--font-sans); color: var(--color-text-secondary); text-align: center; }
.agent-welcome__starters { display: flex; flex-wrap: wrap; gap: 9px; justify-content: center; max-inline-size: 560px; margin-inline: auto; }
.agent-starter {
  padding: 9px 14px; border-radius: 999px; cursor: pointer; font: 500 12.5px var(--font-sans);
  color: var(--color-text-secondary); background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--color-border-hairline);
  transition: border-color .15s, color .15s, transform .15s;
}
.agent-starter:hover { color: var(--color-text-primary); border-color: color-mix(in srgb, var(--color-accent) 55%, var(--color-border-hairline)); transform: translateY(-1px); }
/* ── Firas Agent run card (live plan + steps + result) ── */
.agent-card {
  border: 1px solid color-mix(in srgb, var(--color-accent) 32%, var(--color-border-hairline));
  border-radius: 14px; padding: 14px 16px 12px; background: var(--color-surface, var(--color-bg-subtle));
  font-family: var(--font-sans);
}
.agent-card__head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.agent-card__bolt { font-size: 15px; }
.agent-card__brand { font-weight: 700; font-size: 13.5px; letter-spacing: .02em; }
.agent-card__phase { margin-inline-start: auto; font-size: 11.5px; font-weight: 600; color: var(--color-text-muted); padding: 3px 10px; border-radius: 999px; border: 1px solid var(--color-border-hairline); }
.agent-card__phase.is-live { color: var(--color-accent); border-color: color-mix(in srgb, var(--color-accent) 50%, transparent); animation: agentPulse 1.6s ease-in-out infinite; }
@keyframes agentPulse { 50% { opacity: .55; } }
.agent-card__title { font-size: 15px; font-weight: 650; margin: 2px 0 10px; color: var(--color-text-primary); }
.agent-card__steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.agent-step { display: flex; align-items: flex-start; gap: 9px; padding: 5px 2px; font-size: 13.5px; color: var(--color-text-secondary); }
.agent-step__ic {
  flex: 0 0 auto; inline-size: 19px; block-size: 19px; margin-top: 1px; border-radius: 6px;
  /* grid + line-height:1 for the same reason as .insp-step__mark: inline-flex centred the line
     box, and the inherited leading left the tick sitting below the middle of it. */
  display: grid; place-items: center; line-height: 1;
  font-size: 12px; font-weight: 700; border: 1.4px solid var(--color-border-strong, var(--color-border-hairline));
  color: transparent;
}
.agent-step.is-done > .agent-step__ic, .agent-step.is-done summary .agent-step__ic { background: var(--color-accent); border-color: var(--color-accent); color: #fff; }
.agent-step.is-fail > .agent-step__ic { border-color: #dc2626; color: #dc2626; }
.agent-step.is-run > .agent-step__ic { border-color: var(--color-accent); }
.agent-step.is-done { color: var(--color-text-primary); }
.agent-step.is-run { color: var(--color-accent); font-weight: 600; }
.agent-step__spin { inline-size: 9px; block-size: 9px; border-radius: 50%; border: 2px solid var(--color-accent); border-top-color: transparent; animation: tikz-spin 0.9s linear infinite; }
.agent-step__det { flex: 1 1 auto; min-inline-size: 0; }
.agent-step__det > summary { display: flex; align-items: flex-start; gap: 9px; cursor: pointer; list-style: none; }
.agent-step__det > summary::-webkit-details-marker { display: none; }
.agent-step__det[open] > summary .agent-step__t { color: var(--color-accent); }
.agent-step__out { margin: 8px 0 6px 28px; padding: 10px 14px; border-inline-start: 2.5px solid color-mix(in srgb, var(--color-accent) 45%, transparent); background: var(--color-bg-subtle); border-radius: 8px; font-size: 13.5px; }
[dir="rtl"] .agent-step__out { margin: 8px 28px 6px 0; }
.agent-card__final { margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--color-border-hairline); }
.agent-card__final-label { font-size: 12.5px; font-weight: 700; color: var(--color-text-muted); margin-bottom: 6px; }
.agent-card__prog { display: flex; align-items: center; gap: 10px; margin: 2px 0 12px; }
.agent-card__prog-bar { flex: 1 1 auto; block-size: 6px; border-radius: 999px; background: var(--color-bg-subtle); overflow: hidden; }
.agent-card__prog-fill { block-size: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--color-accent), #5b8def); transition: width .5s var(--ease-out, ease-out); }
.agent-card__prog-n { font-size: 11.5px; font-weight: 700; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.agent-step__kind { margin-inline-end: 6px; font-size: 12.5px; }
.agent-card__report { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--color-border-hairline); }
.agent-card__report-label { font-size: 11.5px; font-weight: 700; color: var(--color-text-muted); margin-inline-end: 4px; }
.agent-report-chip { font-size: 11.5px; font-weight: 600; padding: 4px 10px; border-radius: 999px; background: var(--color-bg-subtle); border: 1px solid var(--color-border-hairline); color: var(--color-text-secondary); font-variant-numeric: tabular-nums; }
.agent-live-preview { margin-top: 12px; border: 1px solid color-mix(in srgb, var(--color-accent) 32%, var(--color-border-hairline)); border-radius: 12px; overflow: hidden; background: #fff; }
.agent-live-preview__bar { display: flex; align-items: center; gap: 8px; padding: 8px 13px; font: 600 11.5px var(--font-sans); color: var(--color-text-secondary); background: var(--color-bg-subtle); border-bottom: 1px solid var(--color-border-hairline); }
.agent-live-preview__dot { inline-size: 8px; block-size: 8px; border-radius: 50%; background: var(--color-accent); animation: maxDotPulse 1.4s ease-in-out infinite; }
.agent-live-preview iframe { display: block; width: 100%; height: 420px; border: 0; background: #fff; }
.agent-card__resume { margin-top: 12px; padding: 9px 18px; border: 0; border-radius: 999px; cursor: pointer; background: linear-gradient(100deg, var(--color-accent), #5b8def); color: #fff; font: 600 13px var(--font-sans); }
.agent-card__resume:hover { filter: brightness(1.07); }
.agent-card__resume:disabled { opacity: .5; cursor: default; }
/* "Fix what's left" sits on a DONE mission, next to a delivered result — an amber repair
   affordance, not the blue "carry on" one, so the two never read as the same action. It can
   appear alongside nothing else, so it also carries its own top margin. */
.agent-card__fixrest { background: linear-gradient(100deg, #c98a2b, #b3622a); margin-inline-start: 8px; }
/* EXPORT — the finished mission as one .md. Quiet on purpose: it stands beside "Resume" and
   "Fix what's left", which both CHANGE the run, and a third filled pill would read as a third
   thing the agent is about to go and do. The start margin is on the SIBLING selector, not on the
   class, so when this is the only button on the card it still lines up with the card's edge. */
.agent-card__mdx { background: none; color: var(--color-text-secondary); border: 1px solid var(--color-border-hairline); padding: 8px 16px; }
.agent-card__mdx:hover { filter: none; background: var(--color-bg-subtle); color: var(--color-text-primary); }
.agent-card__resume + .agent-card__mdx { margin-inline-start: 8px; }
/* ── START FROM A SAVED PLAN ────────────────────────────────────────────
   Under the Agent home's one line of pitch, where a mission is about to be described. Quiet by
   construction: it is only ever on screen for someone who saved a plan themselves, so it never
   competes with the wordmark for a first-time reader. Every inset is logical, because a chip
   carries the direction of the name typed into it and the shell around it does not. */
.agent-tpl { max-inline-size: 560px; margin: 0 auto; }
.agent-tpl__head { margin-bottom: 9px; font: 600 11.5px var(--font-sans); letter-spacing: .02em; color: var(--color-text-muted); text-align: center; }
.agent-tpl__list { display: flex; flex-wrap: wrap; gap: 9px; justify-content: center; }
/* The pill is the ITEM, not the button, because two controls live in it: the plan and its
   removal. Nesting a button inside a button would be neither. */
.agent-tpl__item {
  display: inline-flex; align-items: center; border-radius: 999px;
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--color-border-hairline);
  transition: border-color .15s, transform .15s;
}
.agent-tpl__item:hover { border-color: color-mix(in srgb, var(--color-accent) 55%, var(--color-border-hairline)); transform: translateY(-1px); }
.agent-tpl__chip {
  display: inline-flex; align-items: center; gap: 7px; min-inline-size: 0;
  padding-block: 8px; padding-inline: 14px 6px; border: 0; background: none; cursor: pointer;
  font: 500 12.5px var(--font-sans); color: var(--color-text-secondary);
}
.agent-tpl__chip:hover { color: var(--color-text-primary); }
.agent-tpl__n { max-inline-size: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.agent-tpl__c { padding: 1px 7px; border-radius: 999px; border: var(--border-hairline) solid var(--color-border-hairline); font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--color-text-muted); }
.agent-tpl__x { padding-block: 8px; padding-inline: 4px 12px; border: 0; background: none; cursor: pointer; font: 400 15px/1 var(--font-sans); color: var(--color-text-muted); }
.agent-tpl__x:hover { color: #dc2626; }
.agent-step__file { font-size: 11px; padding: 1px 7px; border-radius: 6px; background: var(--color-bg-subtle); border: 1px solid var(--color-border-hairline); color: var(--color-text-muted); margin-inline-start: 6px; }
/* ── STEER A RUNNING MISSION ──────────────────────────────────────────────────────────────
   Sits between the step list and the report, on the same dashed rule the report uses, so it
   reads as part of the live run rather than as another delivery affordance. The note spans a
   full row of its own (flex-basis 100%) because a queued correction is the user's own words
   and must be allowed to wrap instead of squeezing the input. */
.agent-steer { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--color-border-hairline); }
.agent-steer__in { flex: 1 1 180px; min-inline-size: 0; padding: 8px 14px; border-radius: 999px; border: 1px solid var(--color-border-hairline); background: var(--color-bg-subtle); color: var(--color-text-primary); font: 500 12.5px var(--font-sans); }
.agent-steer__in::placeholder { color: var(--color-text-muted); }
.agent-steer__in:focus { outline: none; border-color: color-mix(in srgb, var(--color-accent) 55%, transparent); }
.agent-steer__in:disabled { opacity: .55; cursor: not-allowed; }
.agent-steer__go { flex: 0 0 auto; padding: 8px 17px; border: 0; border-radius: 999px; cursor: pointer; background: var(--color-accent); color: #fff; font: 600 12.5px var(--font-sans); transition: filter var(--dur-fast, .15s) var(--ease-out, ease-out); }
.agent-steer__go:hover { filter: brightness(1.08); }
.agent-steer__go:disabled { opacity: .5; cursor: default; filter: none; }
.agent-steer__note { flex: 1 1 100%; display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px; font-size: 11.5px; line-height: 1.5; color: var(--color-text-muted); }
.agent-steer__note:empty { display: none; }
.agent-steer.has-queue .agent-steer__note-t { color: var(--color-accent); font-weight: 600; overflow-wrap: anywhere; }
/* Each half of the note carries its own dir — the label follows the run's language, the quoted
   correction follows whatever the user actually typed — so neither re-orders the other. */
.agent-steer__in[dir="rtl"], .agent-steer__note [dir="rtl"] { font-family: var(--font-arabic); }
/* The receipt: a correction that was consumed, chipped onto the step it shaped. */
.agent-step__fix, .tc-fix { display: inline-block; max-inline-size: 15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: baseline; font-size: 10.5px; font-weight: 600; padding: 1px 8px; border-radius: 999px; background: color-mix(in srgb, var(--color-accent) 13%, transparent); border: 1px solid color-mix(in srgb, var(--color-accent) 38%, transparent); color: var(--color-accent); }
.agent-step__fix { margin-inline-start: 6px; }
.tc-fix { margin-block-start: 5px; }
.agent-step__fix[dir="rtl"], .tc-fix[dir="rtl"] { font-family: var(--font-arabic); }
/* ── A `table` STEP'S GRID ───────────────────────────────────────────────────
   Deliberately NOT `.md table`: that one lives inside a rendered answer and inherits the answer's
   type scale, which is far too large for a 40-row grid tucked into a step card. This gets its own
   smaller scale, its own scroll container (the card must never be the thing that widens the page)
   and a sticky header row — without it a long table scrolls its own headers away and the columns
   stop meaning anything. */
.agent-tbl { margin: 8px 0 6px 28px; border: 1px solid var(--color-border-hairline); border-radius: 10px; background: var(--color-bg-subtle); overflow: hidden; }
[dir="rtl"] .agent-tbl { margin: 8px 28px 6px 0; }
.agent-tbl__bar { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 10px; border-bottom: 1px solid var(--color-border-hairline); }
.agent-tbl__dims { font: 600 11px var(--font-sans); color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.agent-tbl__copy { flex: 0 0 auto; padding: 4px 12px; border: 1px solid var(--color-border-hairline); border-radius: 999px; cursor: pointer; background: var(--color-surface, transparent); color: var(--color-text-secondary); font: 600 11px var(--font-sans); transition: color var(--dur-fast, .15s) var(--ease-out, ease-out), border-color var(--dur-fast, .15s) var(--ease-out, ease-out); }
.agent-tbl__copy:hover { color: var(--color-accent); border-color: color-mix(in srgb, var(--color-accent) 45%, transparent); }
.agent-tbl__copy[dir="rtl"] { font-family: var(--font-arabic); }
.agent-tbl__scroll { max-block-size: 380px; overflow: auto; }
.agent-tbl table { inline-size: 100%; border-collapse: collapse; font: 12.5px/1.55 var(--font-sans); }
/* A cell widens (and the container scrolls) rather than chopping a word in half — the same trade
   `.md table` makes, restated here because this table shares none of its rules. */
.agent-tbl th, .agent-tbl td { padding: 6px 11px; text-align: start; border-bottom: 1px solid var(--color-border-hairline); overflow-wrap: break-word; word-break: normal; hyphens: none; }
/* The box-shadow IS the header rule, not decoration. Under `border-collapse: collapse` the border
   belongs to the TABLE and not to the cell, so it stays behind while the sticky cell travels: scroll
   a long grid and the header loses its underline and merges into the rows sliding beneath it. An
   inset shadow is painted by the cell itself, so it comes along. */
.agent-tbl th { position: sticky; inset-block-start: 0; z-index: 1; background: var(--color-bg-subtle); color: var(--color-text-primary); font-weight: 700; white-space: nowrap; box-shadow: inset 0 -1px 0 var(--color-border-hairline); }
.agent-tbl td { color: var(--color-text-secondary); }
.agent-tbl tbody tr:last-child td { border-bottom: 0; }
/* Each cell carries its own dir, so each cell picks its own face and its own digit alignment. */
.agent-tbl th[dir="rtl"], .agent-tbl td[dir="rtl"] { font-family: var(--font-arabic); }
.agent-tbl td[dir="ltr"] { font-variant-numeric: tabular-nums; }
.agent-tbl__note { margin: 0; padding: 6px 11px; border-top: 1px solid var(--color-border-hairline); font: 11.5px/1.55 var(--font-sans); color: var(--color-text-muted); }
.agent-tbl__note[dir="rtl"] { font-family: var(--font-arabic); }
/* Inside a UI 2.0 tool-call row the detail column is narrower and already sits at 11px, so the grid
   drops the legacy list's 28px indent and matches that scale instead. Both directions restated:
   the RTL indent above has the same specificity and would otherwise win on source order. */
.tc-detail .agent-tbl, [dir="rtl"] .tc-detail .agent-tbl { margin: 0; }
.tc-detail .agent-tbl table { font-size: 11.5px; }
.tc-detail .agent-tbl__scroll { max-block-size: 300px; }
/* ── Project (folder) card: file viewer + ZIP download ── */
.proj-card { border: 1px solid color-mix(in srgb, var(--color-accent) 32%, var(--color-border-hairline)); border-radius: 14px; background: var(--color-surface, var(--color-bg-subtle)); font-family: var(--font-sans); overflow: hidden; }
.proj-card__head { display: flex; align-items: center; gap: 11px; padding: 13px 15px; border-bottom: 1px solid var(--color-border-hairline); flex-wrap: wrap; }
.proj-card__ic { font-size: 22px; }
.proj-card__meta { flex: 1 1 auto; min-inline-size: 0; }
.proj-card__name { font-weight: 700; font-size: 14.5px; color: var(--color-text-primary); overflow-wrap: anywhere; }
.proj-card__sub { font-size: 11.5px; color: var(--color-text-muted); margin-top: 1px; }
.proj-card__zip { padding: 8px 15px; border: 0; border-radius: 999px; cursor: pointer; background: var(--color-accent); color: #fff; font: 600 12.5px var(--font-sans); white-space: nowrap; }
.proj-card__zip:hover { filter: brightness(1.07); }
.proj-card__preview { background: transparent; color: var(--color-accent); border: 1px solid color-mix(in srgb, var(--color-accent) 55%, transparent); }
.proj-card__preview:hover { background: color-mix(in srgb, var(--color-accent) 12%, transparent); }
.proj-card__files { display: flex; flex-direction: column; }
.proj-file + .proj-file { border-top: 1px solid var(--color-border-hairline); }
.proj-file__row { display: flex; align-items: center; gap: 9px; inline-size: 100%; padding: 10px 15px; border: 0; background: none; cursor: pointer; font: 500 13px var(--font-mono, monospace); color: var(--color-text-secondary); text-align: start; }
.proj-file__row:hover { background: var(--color-bg-subtle); }
.proj-file__path { flex: 1 1 auto; direction: ltr; text-align: start; overflow-wrap: anywhere; }
.proj-file__size { font-size: 11px; color: var(--color-text-muted); font-family: var(--font-sans); }
.proj-file__chev { font-size: 10px; opacity: .55; transition: transform .15s; }
.proj-file.is-open .proj-file__chev { transform: rotate(180deg); }
.proj-file__body { max-block-size: 420px; overflow: auto; border-top: 1px dashed var(--color-border-hairline); }
.proj-file__body pre { margin: 0; padding: 12px 15px; font-size: 12px; line-height: 1.6; direction: ltr; text-align: left; }
@media (max-width: 560px) { .proj-card__zip { inline-size: 100%; } }
@media (max-width: 560px) {
  .agent-card { padding: 12px 12px 10px; }
  .agent-step__out { margin-inline-start: 12px; }
  [dir="rtl"] .agent-step__out { margin: 8px 12px 6px 0; }
}
.plot-figure .plot-bg { fill: var(--color-bg-subtle, #fff); stroke: var(--color-border-hairline, #e5e7eb); stroke-width: 1; }
/* Grid = graph-paper lines: keyed to text-muted (NOT the hairline border) so they stay clearly
   visible in the LIGHT theme too (hairline #E6E4DA at .5 was invisible on the white paper). */
.plot-figure .plot-grid { stroke: var(--color-text-muted, #6b7280); opacity: .34; stroke-width: 1; }
.plot-figure .plot-axis { stroke: var(--color-text-secondary, #4b5563); stroke-width: 1.4; }
.plot-figure .plot-arrow { fill: var(--color-text-secondary, #4b5563); }
.plot-figure .plot-tick { fill: var(--color-text-muted, #6b7280); font-size: 9px; }
.plot-figure .plot-axislabel { fill: var(--color-text-secondary, #4b5563); font-size: 10.5px; font-style: italic; }
.plot-figure .plot-curve { fill: none; stroke-width: 2.2; stroke-linejoin: round; stroke-linecap: round; }
.plot-figure .plot-legend-bg { fill: var(--color-bg, #fff); opacity: .85; stroke: var(--color-border-hairline, #e5e7eb); stroke-width: .5; }
/* Interactive plot: drag to pan, wheel/pinch to zoom, ⟲ to reset */
.plot-figure { position: relative; }
.plot-interactive svg { cursor: grab; touch-action: none; -webkit-user-select: none; user-select: none; }
.plot-interactive.plot-grabbing svg { cursor: grabbing; }
.plot-reset {
  position: absolute; top: 8px; inset-inline-start: 8px; inline-size: 26px; block-size: 26px;
  display: flex; align-items: center; justify-content: center; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--color-border-hairline, #e5e7eb); background: var(--color-bg, #fff);
  color: var(--color-text-secondary, #4b5563); font-size: 15px; line-height: 1; padding: 0;
  opacity: 0; transition: opacity var(--dur-fast, .15s);
}
.plot-figure:hover .plot-reset, .plot-interactive.plot-grabbing .plot-reset { opacity: .92; }
.plot-reset:hover { color: var(--color-text-primary, #111); border-color: var(--color-text-muted, #9ca3af); }
.tikz-figure__spin { display: inline-block; padding: var(--space-3) var(--space-4); opacity: .55; font-size: 1.4em; animation: tikz-spin 1.1s linear infinite; }
@keyframes tikz-spin { to { transform: rotate(360deg); } }
.code-block code {
  font-family: var(--font-mono); font-size: var(--text-sm); line-height: 20px;
  color: var(--color-text-primary); background: none; padding: 0;
}

/* ----- Thinking disclosure ----------------------------------------------- */
.thinking { margin-bottom: var(--space-3); }
.thinking__head {
  display: inline-flex; align-items: center; gap: var(--space-2);
  block-size: 28px;
  padding-inline: var(--space-2);
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  font-family: var(--font-serif); font-size: var(--text-sm); font-weight: var(--weight-medium);
  transition: background var(--dur-fast);
}
[dir="rtl"] .thinking__head { font-family: var(--font-arabic); }
.thinking__head:hover { background: var(--color-bg-subtle); }
.thinking__head .nib { display: none; } /* quiet caret-only disclosure */
.thinking__chevron {
  order: -1;
  inline-size: 12px; block-size: 12px; color: var(--color-text-muted);
  transition: transform var(--dur-fast) var(--ease-out);
}
.thinking.is-open .thinking__chevron { transform: rotate(90deg); }
[dir="rtl"] .thinking__chevron { transform: rotate(180deg); }
[dir="rtl"] .thinking.is-open .thinking__chevron { transform: rotate(90deg); }
.thinking__body {
  overflow: hidden; max-block-size: 0; opacity: 0;
  transition: max-block-size var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out), margin var(--dur-base) var(--ease-out);
}
.thinking.is-open .thinking__body { max-block-size: 600px; opacity: 1; margin-block-start: var(--space-2); }
.thinking__inner {
  font-family: var(--font-mono); font-size: var(--text-sm); line-height: 20px;
  color: var(--color-text-secondary);
  background: var(--color-bg-subtle);
  border-inline-start: var(--border-strong) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  white-space: pre-wrap; word-break: break-word;
  max-block-size: 320px; overflow-y: auto;
  direction: ltr; unicode-bidi: plaintext;
}

/* ----- Message actions --------------------------------------------------- */
.msg-actions {
  display: flex; flex-wrap: wrap; gap: 2px; margin-block-start: var(--space-3);
  opacity: 1;                       /* always visible (copy / regenerate never disappear) */
  transition: opacity var(--dur-fast);
}
.msg-action {
  display: inline-flex; align-items: center; gap: 5px;
  block-size: 32px;
  padding-inline: var(--space-2);
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-size: var(--text-sm); color: var(--color-text-muted);
  transition: background var(--dur-fast), color var(--dur-fast);
}
.msg-action:hover { background: var(--color-bg-subtle); color: var(--color-text-secondary); }
.msg-action svg { inline-size: 15px; block-size: 15px; }
.msg-action.is-ultra { color: var(--color-accent); }
.msg-action.is-ultra svg { color: var(--color-accent); }
.msg-action.is-ultra:hover { color: var(--color-accent-hover); background: var(--color-accent-soft); }
.msg-action .ok { color: var(--color-success); display: inline-flex; }
.msg-action .ok svg { color: var(--color-success); }

/* ----- The folded tail of the action row --------------------------------- */
/* Eleven verbs under every answer wrapped onto two lines and read as clutter. The first four stay;
   the rest live here and come back when «عرض المزيد» is pressed. `.msg-actions` is a flex row, so
   the container has to be one too or the returned buttons would stack. */
.msg-actions__rest {
  display: flex; flex-wrap: wrap; gap: 2px;
  inline-size: 100%;
  margin-block-start: 2px;
}
.msg-actions__rest[hidden] { display: none; }
/* Quieter than the four it hides behind: it is a way to see more, not a thing to do. */
.msg-action--more { color: var(--color-text-muted); opacity: .8; }
.msg-action--more svg { transition: transform var(--dur-fast) var(--ease-out); }
.msg-action--more.is-open svg { transform: rotate(180deg); }

/* ----- Continue a cut-off answer ----------------------------------------- */
/* Tinted, unlike its neighbours: it appears ONLY when an answer stopped mid-way, so it has
   to read as the thing to press — while staying an action-row button, not a call to action. */
.msg-action--continue { color: var(--color-accent); }
.msg-action--continue svg { color: var(--color-accent); }
.msg-action--continue:hover { background: var(--color-accent-soft); color: var(--color-accent-hover); }
.msg-action--continue[disabled] { cursor: default; }
.msg-action--continue.is-busy { background: var(--color-accent-soft); }
.msg-action--continue.is-busy svg { animation: msg-continue-pulse 1.1s var(--ease-inout) infinite; }
@keyframes msg-continue-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) {
  .msg-action--continue.is-busy svg { animation: none; opacity: 0.6; }
}

/* ----- Answer versions (the 1/2 pager above a regenerated turn) ---------- */
/* CHROME, NOT CONTENT. It sits above the head at caption weight so a turn with two answers
   still opens with the answer, not with an argument about which answer. Rendered only when a
   turn actually has more than one version, so there is nothing to hide on a normal turn and
   no rule here can affect one. Logical properties throughout — the row flips with the dir
   ansVersionsEl stamps on it, and the arrow GLYPHS are chosen there for the same reason. */
.ans-vers {
  display: flex; align-items: center; gap: 2px;
  margin-block-end: var(--space-2);
  font-family: var(--font-sans); font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.ans-vers__lbl { margin-inline-end: var(--space-1); }
.ans-vers__n {
  min-inline-size: 38px; text-align: center;
  /* Tabular figures: 1 / 2 → 2 / 2 must not nudge the arrow you are about to click again. */
  font-variant-numeric: tabular-nums;
  color: var(--color-text-secondary);
}
.ans-vers__nav {
  inline-size: 24px; block-size: 24px; line-height: 1; font-size: 16px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm); color: inherit;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.ans-vers__nav:hover:not([disabled]) { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.ans-vers__nav[disabled] { opacity: .35; cursor: default; }
.ans-vers__keep {
  margin-inline-start: var(--space-2);
  block-size: 24px; padding-inline: var(--space-2);
  border-radius: var(--radius-sm);
  font-family: inherit; font-size: var(--text-sm); color: var(--color-accent);
  transition: background var(--dur-fast), color var(--dur-fast);
}
.ans-vers__keep:hover { background: var(--color-accent-soft); color: var(--color-accent-hover); }
.ans-vers__diff {
  margin-inline-start: var(--space-2);
  block-size: 24px; padding-inline: var(--space-2);
  border-radius: var(--radius-sm);
  font-family: inherit; font-size: var(--text-sm); color: inherit;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.ans-vers__diff:hover:not([disabled]) { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.ans-vers__diff[disabled] { opacity: .35; cursor: default; }
.ans-vers__diff[aria-expanded="true"] { color: var(--color-accent); background: var(--color-accent-soft); }
/* PHONE. The row stays caption-sized chrome; the two things you actually press do not —
   these arrows are the only way back to the answer you were reading a second ago. Same
   query the rest of the phone layer uses. The diff toggle joins them: it is the third control
   on the row and the one whose label is a word rather than a glyph. */
@media (pointer: coarse), (max-width: 560px) {
  .ans-vers { gap: 4px; }
  .ans-vers__nav { inline-size: 32px; block-size: 32px; }
  .ans-vers__keep { block-size: 32px; }
  .ans-vers__diff { block-size: 32px; }
}

/* ----- What changed (the word-by-word panel under the pager) -------------- */
/* CHROME AROUND CONTENT: a framed well so the reader can tell at a glance where the comparison
   ends and the answer itself begins — those two are the same words in the same type, one above
   the other, and without a frame the panel reads as a duplicated first paragraph. Logical
   properties throughout: ansDiffPanelEl stamps the ANSWER's dir on the root, so every inset here
   flips with it. Rendered only for a turn that has a previous version to compare against, so
   there is nothing to hide on a normal turn and no rule here can reach one. */
.ans-diff {
  margin-block-end: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-bg-subtle);
  font-family: var(--font-sans);
  overflow: hidden;
}
.ans-diff[dir="rtl"] { font-family: var(--font-arabic); }
.ans-diff__hd {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-2) var(--space-3);
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
  font-size: var(--text-xs); color: var(--color-text-secondary);
}
.ans-diff__pair, .ans-diff__c {
  /* Tabular figures: +9 → +10 must not shove the chip beside it sideways. */
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
}
.ans-diff__chip { display: inline-flex; align-items: center; gap: 5px; }
/* The swatch is an ::before rather than a span, so the count element stays the ONE thing in the
   chip carrying its own dir — a third child would be a third direction to keep straight. */
.ans-diff__chip::before {
  content: ""; inline-size: 8px; block-size: 8px; border-radius: 2px; flex: 0 0 auto;
}
.ans-diff__chip.is-ins::before { background: var(--color-success); }
.ans-diff__chip.is-del::before { background: var(--color-error); }
.ans-diff__bd {
  /* Capped and scrolled: this panel sits ABOVE the answer, and an uncapped diff of a long
     re-roll would push the answer itself off the bottom of the screen. */
  max-block-size: 300px; overflow: auto;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm); line-height: var(--leading-sm);
  color: var(--color-text-primary);
}
.ans-diff__note { margin: 0; color: var(--color-text-secondary); }
/* pre-wrap, because the newlines and the indentation inside a rewritten hunk are part of what
   changed — a markdown list that lost its nesting is a real difference and collapsing the
   whitespace would hide it. */
.ans-diff__ln { margin-block: 2px; white-space: pre-wrap; overflow-wrap: anywhere; }
.ans-diff__ln.is-same { color: var(--color-text-muted); }
.ans-diff__gap {
  margin-block: 6px; font-size: var(--text-xs); color: var(--color-text-muted); opacity: .8;
}
.ans-diff ins {
  text-decoration: none; border-radius: 2px; padding-inline: 1px;
  background: color-mix(in srgb, var(--color-success) 20%, transparent);
  color: var(--color-text-primary);
}
.ans-diff del {
  text-decoration: line-through; text-decoration-thickness: 1px;
  border-radius: 2px; padding-inline: 1px;
  background: color-mix(in srgb, var(--color-error) 16%, transparent);
  color: var(--color-text-secondary);
}
@media (pointer: coarse), (max-width: 560px) {
  /* Shorter on a phone for the same reason it is capped at all: the answer has to stay in
     reach. The panel scrolls inside itself, so nothing is lost by the smaller window. */
  .ans-diff__bd { max-block-size: 220px; }
}

/* ----- Error notice ------------------------------------------------------ */
.msg-error {
  margin-block-start: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-bg-subtle);
  border-radius: var(--radius-md);
  color: var(--color-error); font-family: var(--font-sans); font-size: var(--text-sm);
  display: flex; align-items: center; gap: var(--space-2);
}
.msg-error svg { inline-size: 16px; block-size: 16px; flex: 0 0 auto; }
.msg-error button { color: var(--color-accent); text-decoration: underline; font-size: inherit; margin-inline-start: auto; white-space: nowrap; }
.msg-error button:hover { color: var(--color-accent-hover); }

/* ----- Plan-mode "Start" quick-action --------------------------------------
   A premium teal pill shown under a plan-mode reply: one tap approves the plan
   and executes it through the normal send path. */
.plan-start {
  margin-block-start: var(--space-4);
  display: flex; justify-content: flex-start;
}
.plan-start__btn {
  display: inline-flex; align-items: center; gap: var(--space-2);
  block-size: 38px;
  padding-inline: var(--space-5);
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  box-shadow: var(--shadow-xs);
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .plan-start__btn { font-family: var(--font-arabic); }
.plan-start__ico { display: inline-flex; }
.plan-start__ico svg { inline-size: 16px; block-size: 16px; }
.plan-start__btn:hover { background: var(--color-accent-hover); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.plan-start__btn:active { transform: translateY(0); }
.plan-start__btn:disabled { opacity: .6; pointer-events: none; }

/* ----- Quick replies (follow-up chips) -------------------------------------
   Quiet outlines, not another call to action: the answer is the thing on the page, and this
   row is an offer. It only ever tints on hover/focus. */
.qreply {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin-block-start: var(--space-4);
}
/* THE LATEST TURN ONLY, DECIDED IN CSS. The row is built into every assistant turn, and it has
   to be: turnNodeFor memoizes a turn's node by the fields it was rendered from, and "am I the
   last message" is not one of them — so a JS gate would leave a stale row of chips under a turn
   the instant the next message arrived, and only a cache invalidation would clear it. A
   structural selector re-decides on every insertion, for free. #chatThread holds exactly one
   child per message (renderThread trims to chat.messages.length), so :last-child is the newest
   turn — and the moment the user sends, THEIR bubble is last and the chips are gone. */
#chatThread > .turn:not(:last-child) .qreply { display: none; }
/* While the answer is still arriving there is nothing to follow up on yet. */
.msg-ai.is-generating .qreply { display: none; }
.qreply__chip {
  /* inline-FLEX with a shrinkable label, not inline-block with the clipping on the button: the
     ellipsis has to sit on the element that carries the subject's own dir, or it is placed at
     the wrong end of a Latin label inside an Arabic answer. min-inline-size:0 is the half that
     makes it work at all — a flex item's automatic minimum is its content width, so without it
     max-inline-size:100% loses and a long chip pushes the column sideways on a phone. */
  display: inline-flex; align-items: center;
  max-inline-size: 100%; min-inline-size: 0;
  padding-block: 6px; padding-inline: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-secondary);
  font-family: var(--font-sans);
  font-size: var(--text-sm); line-height: var(--leading-sm);
  text-align: start;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.qreply__tx {
  min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The LABEL carries its own dir (an English term inside an Arabic answer keeps it LTR), so the
   Arabic face is selected from THAT and not from the shell — which is permanently LTR. */
.qreply__tx[dir="rtl"] { font-family: var(--font-arabic); }
.qreply__chip:hover { background: var(--color-accent-soft); border-color: var(--color-accent-ring); color: var(--color-accent); }
.qreply__chip:active { transform: scale(0.98); }
.qreply__chip:focus-visible { outline: 2px solid var(--color-accent-deep); outline-offset: 2px; }
@media (pointer: coarse) { .qreply__chip { padding-block: 8px; } }
@media (prefers-reduced-motion: reduce) { .qreply__chip { transition: none; } .qreply__chip:active { transform: none; } }


/* ══ ASKAG · ask the same question with one word different ═════════════════════
   A block, not a popover. The panel is inserted as a SIBLING of the action row rather than
   positioned over the page, so it needs no viewport clamp, no scroll listener and no teardown —
   the three things the select-to-ask chip needs and pays for. It lives under exactly one answer
   at a time, because the button that opens it is also the one that removes it.

   Direction is split on purpose: the panel is CHROME and takes the interface language, while the
   question line and every word chip carry their own dir. That is why the font-family rules below
   hang off [dir] on those two and are inherited everywhere else — an Arabic panel holding an
   English question must not set the Arabic face on it, and a "React" chip inside an Arabic
   question must not inherit one either. */
.askag {
  margin-block-start: var(--space-2);
  padding: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-bg-subtle);
}
.askag[dir="rtl"] { font-family: var(--font-arabic); }
.askag__t {
  font-size: var(--text-sm); line-height: var(--leading-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-secondary);
}
/* Two lines of the question and no more: it is here to REMIND, and the answer above it is often a
   screen and a half tall. min-inline-size:0 is what lets it clamp inside the panel at all. */
.askag__q {
  margin-block-start: 3px;
  min-inline-size: 0;
  font-size: var(--text-sm); line-height: var(--leading-sm);
  color: var(--color-text-muted);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.askag__q[dir="rtl"] { font-family: var(--font-arabic); }
.askag__q[dir="ltr"] { font-family: var(--font-sans); }
.askag__ws {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin-block-start: var(--space-3);
}
.askag__w {
  max-inline-size: 100%;
  padding-block: 5px; padding-inline: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-sm); line-height: var(--leading-sm);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.askag__w[dir="rtl"] { font-family: var(--font-arabic); }
.askag__w:hover { background: var(--color-accent-soft); border-color: var(--color-accent-ring); color: var(--color-accent); }
.askag__w:active { transform: scale(0.98); }
.askag__w:focus-visible { outline: 2px solid var(--color-accent-deep); outline-offset: 2px; }
.askag__h {
  margin-block-start: var(--space-2);
  font-size: var(--text-xs); line-height: var(--leading-sm);
  color: var(--color-text-muted);
}
/* The panel is suppressed under 560px in JS and not here: below that width the gesture it exists
   for (a word arriving selected, typed straight over) is taken apart by the soft keyboard. This
   rule is only the tablet band, where the chips still need a thumb-sized row. */
@media (pointer: coarse) { .askag__w { padding-block: 8px; } }
/* The motion attribute is read off :root as the LEFTMOST compound, because :root IS <html> — put
   any ancestor selector in front of it and the rule can never match anything. */
@media (prefers-reduced-motion: reduce) {
  .askag__w { transition: none; }
  .askag__w:active { transform: none; }
}
:root[data-motion="off"] .askag__w { transition: none; }

/* ══ SIMPLIFY · the same answer, said at a level the reader can take ═══════════
   A sibling of the action row, like the panel above it and for the same reason: that row is a
   wrapping flex line of 32px buttons, and a block dropped into it becomes one more thing on that
   line. The whole panel is CHROME and takes the interface language — there is no model text in
   it, so unlike the askag panel above there is no per-child [dir] here to font-match. */
.simpl {
  margin-block-start: var(--space-2);
  padding: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-bg-subtle);
}
.simpl[dir="rtl"] { font-family: var(--font-arabic); }
.simpl__t {
  font-size: var(--text-sm); line-height: var(--leading-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-secondary);
}
.simpl__opts {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin-block-start: var(--space-3);
}
/* Each level is a BLOCK inside the wrapping row — its name over its own one line of explanation
   — so a phone stacks three full-width cards and a wide thread lays them side by side, without
   either needing a media query. min-inline-size:0 is what lets a long Arabic line wrap inside the
   card instead of pushing it past the panel. */
.simpl__o {
  flex: 1 1 12rem; min-inline-size: 0;
  text-align: start;
  padding-block: var(--space-2); padding-inline: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-family: inherit;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.simpl__o:hover { background: var(--color-accent-soft); border-color: var(--color-accent-ring); color: var(--color-accent); }
.simpl__o:active { transform: scale(0.98); }
.simpl__o:focus-visible { outline: 2px solid var(--color-accent-deep); outline-offset: 2px; }
.simpl__ol {
  display: block;
  font-size: var(--text-sm); line-height: var(--leading-sm);
  font-weight: var(--weight-semibold);
}
.simpl__od {
  display: block; margin-block-start: 2px;
  font-size: var(--text-xs); line-height: var(--leading-sm);
  color: var(--color-text-muted);
}
/* The explanation is the QUIET half of the card and must not light up with it: inheriting the
   accent on hover makes the two lines stop being two lines. */
.simpl__o:hover .simpl__od { color: inherit; }
.simpl__h {
  margin-block-start: var(--space-2);
  font-size: var(--text-xs); line-height: var(--leading-sm);
  color: var(--color-text-muted);
}
@media (pointer: coarse) { .simpl__o { padding-block: var(--space-3); } }
/* The motion attribute is read off :root as the LEFTMOST compound, because :root IS <html> — put
   any ancestor selector in front of it and the rule can never match anything. */
@media (prefers-reduced-motion: reduce) {
  .simpl__o { transition: none; }
  .simpl__o:active { transform: none; }
}
:root[data-motion="off"] .simpl__o { transition: none; }

/* The busy state of the row button while the rewrite streams. Its OWN rule rather than a shared
   `.msg-action.is-busy`: Continue's is tinted because Continue itself is tinted, and this is an
   ordinary button in the row. It borrows the keyframes that row already defines — one animation,
   two callers — including the reduced-motion answer to it. */
.msg-action--simpl[disabled] { cursor: default; }
.msg-action--simpl.is-busy { background: var(--color-bg-subtle); }
.msg-action--simpl.is-busy svg { animation: msg-continue-pulse 1.1s var(--ease-inout) infinite; }
@media (prefers-reduced-motion: reduce) {
  .msg-action--simpl.is-busy svg { animation: none; opacity: 0.6; }
}

/* ══ ASKX · the same question, put to a different product ═════════════════════
   The same block-under-the-row shape .askag uses directly above, and for its reasons: a sibling of
   the action row rather than a popover, so it needs no viewport clamp, no scroll listener and no
   teardown. What differs is what sits inside it — two destinations, each a name, a sentence and
   (for Brain) the count of files it would answer from — so the targets are a grid that folds to
   one column the moment two will not fit, which is every phone and every open sidebar.

   Direction is split the same way: the panel is CHROME and takes the interface language, while the
   question line carries its own. That is why the Arabic face below hangs off [dir] on that line and
   is inherited everywhere else — an Arabic panel holding an English question must not set the
   Arabic face on it. */
.askx {
  margin-block-start: var(--space-2);
  padding: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-bg-subtle);
}
.askx[dir="rtl"] { font-family: var(--font-arabic); }
.askx__t {
  font-size: var(--text-sm); line-height: var(--leading-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-secondary);
}
/* Two lines of the question and no more: it is here to say WHAT is about to be sent, and the
   answer above it is often a screen and a half tall. min-inline-size:0 is what lets it clamp
   inside the panel at all. */
.askx__q {
  margin-block-start: 3px;
  min-inline-size: 0;
  font-size: var(--text-sm); line-height: var(--leading-sm);
  color: var(--color-text-muted);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.askx__q[dir="rtl"] { font-family: var(--font-arabic); }
.askx__q[dir="ltr"] { font-family: var(--font-sans); }
.askx__ts {
  display: grid; gap: var(--space-2);
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  margin-block-start: var(--space-3);
}
/* font-family is INHERITED rather than set: the panel root already carries the Arabic face in an
   Arabic shell, and a button is the one element that would otherwise fall back to the system UI
   font and print the description in a face nothing else on the page uses. */
.askx__go {
  display: flex; align-items: center; gap: var(--space-2);
  min-inline-size: 0;
  padding: var(--space-2) var(--space-3);
  text-align: start;
  font-family: inherit;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text-primary);
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.askx__ic { flex: 0 0 auto; display: inline-flex; color: var(--color-text-muted); }
.askx__ic svg { inline-size: 18px; block-size: 18px; }
/* The only column allowed to shrink. Without min-inline-size:0 a long sentence pushes the count
   off the end of the card instead of wrapping inside it. */
.askx__tx { flex: 1 1 auto; min-inline-size: 0; }
.askx__n {
  display: block;
  font-family: var(--font-sans);
  font-size: var(--text-sm); line-height: var(--leading-sm);
  font-weight: var(--weight-semibold);
}
.askx__d {
  display: block; margin-block-start: 2px;
  font-size: var(--text-xs); line-height: var(--leading-sm);
  color: var(--color-text-muted);
}
.askx__c {
  flex: 0 0 auto;
  font-size: var(--text-xs); line-height: var(--leading-sm);
  color: var(--color-text-muted);
}
.askx__go:hover { background: var(--color-accent-soft); border-color: var(--color-accent-ring); color: var(--color-accent); }
/* The two muted runs come up to the card's colour on hover; left as they were they read as
   disabled text on an accented ground. */
.askx__go:hover .askx__ic,
.askx__go:hover .askx__d,
.askx__go:hover .askx__c { color: inherit; }
.askx__go:active { transform: scale(0.99); }
.askx__go:focus-visible { outline: 2px solid var(--color-accent-deep); outline-offset: 2px; }
/* The one wait in the panel: Brain's library is fetched before the question can be aimed at it,
   and BOTH cards go down for it — a handoff that is already running must not be raced by the
   other destination. */
.askx__go:disabled { opacity: 0.55; }
.askx__h {
  margin-block-start: var(--space-2);
  font-size: var(--text-xs); line-height: var(--leading-sm);
  color: var(--color-text-muted);
}
@media (pointer: coarse) { .askx__go { padding-block: 10px; } }
/* The motion attribute is read off :root as the LEFTMOST compound, because :root IS <html> — put
   any ancestor selector in front of it and the rule can never match anything. */
@media (prefers-reduced-motion: reduce) {
  .askx__go { transition: none; }
  .askx__go:active { transform: none; }
}
:root[data-motion="off"] .askx__go { transition: none; }

/* ----------------------------------------------------------------------------
   Plan-mode interactive CHOICE LISTS (the firas-ask panel)
   Selectable radio/checkbox cards with a soft "Recommended" badge + Continue.
   Teal-accented, RTL-aware (logical props), light + dark, keyboard-accessible.
---------------------------------------------------------------------------- */
/* Plan-mode: calm placeholder shown while the raw firas-ask JSON streams. The
   user never sees the JSON — only this pill, then the cards on finalize. */
.firas-ask-loading {
  display: inline-flex; align-items: center; gap: var(--space-2);
  margin-block: var(--space-4);
  padding: var(--space-2) var(--space-3);
  border: var(--border-hairline) solid var(--color-accent-ring);
  border-radius: var(--radius-pill);
  background: var(--color-accent-soft);
  color: var(--color-accent);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  line-height: var(--leading-sm);
}
.firas-ask-loading__dots {
  display: inline-flex; align-items: center; gap: 4px; flex: none;
}
.firas-ask-loading__dots > span {
  inline-size: 6px; block-size: 6px; border-radius: var(--radius-pill);
  background: currentColor;
  animation: firas-ask-dot 1.2s ease-in-out infinite;
}
.firas-ask-loading__dots > span:nth-child(2) { animation-delay: 0.18s; }
.firas-ask-loading__dots > span:nth-child(3) { animation-delay: 0.36s; }
.firas-ask-loading__text { white-space: nowrap; }
@keyframes firas-ask-dot {
  0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }
  40% { opacity: 1; transform: translateY(-3px); }
}
@media (prefers-reduced-motion: reduce) {
  .firas-ask-loading__dots > span { animation: firas-ask-fade 1.4s ease-in-out infinite; }
  @keyframes firas-ask-fade { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }
}

.firas-ask {
  margin-block: var(--space-4);
  padding: var(--space-4);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: var(--shadow-xs);
}
.firas-ask__intro {
  margin: 0 0 var(--space-3);
  color: var(--color-text-secondary);
  font-size: var(--text-sm); line-height: var(--leading-sm);
}
.firas-ask .ask-q { border: none; margin: 0 0 var(--space-4); padding: 0; min-inline-size: 0; }
.firas-ask .ask-q:last-of-type { margin-block-end: var(--space-3); }
.ask-q__title {
  padding: 0; margin: 0 0 var(--space-2);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  color: var(--color-text-primary);
}
.ask-q__opts { display: flex; flex-direction: column; gap: var(--space-2); }

.choice-card {
  position: relative;
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-3);
  border: var(--border-strong) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.choice-card:hover { border-color: var(--color-border-strong); background: var(--color-bg-subtle); }

/* The native input is visually hidden but stays focusable + accessible. */
.choice-card__input {
  position: absolute; inline-size: 1px; block-size: 1px;
  margin: 0; padding: 0; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}
/* The drawn control (circle for radio, rounded square for checkbox). */
.choice-card__mark {
  flex: 0 0 auto; margin-block-start: 1px;
  inline-size: 18px; block-size: 18px;
  border: var(--border-strong) solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
  position: relative;
}
.choice-card__input[type="checkbox"] ~ .choice-card__mark { border-radius: var(--radius-xs); }
.choice-card__mark::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  opacity: 0; transition: opacity var(--dur-fast) var(--ease-out);
}
.choice-card__input[type="radio"] ~ .choice-card__mark::after {
  inline-size: 8px; block-size: 8px; border-radius: var(--radius-pill);
  background: var(--color-on-accent);
}
.choice-card__input[type="checkbox"] ~ .choice-card__mark::after {
  inline-size: 9px; block-size: 5px;
  /* physical borders (not logical) so the tick looks identical in LTR + RTL */
  border-left: 2px solid var(--color-on-accent);
  border-bottom: 2px solid var(--color-on-accent);
  transform: translateY(-1px) rotate(-45deg);
}
.choice-card__input:checked ~ .choice-card__mark { background: var(--color-accent); border-color: var(--color-accent); }
.choice-card__input:checked ~ .choice-card__mark::after { opacity: 1; }

/* Selected card highlight. */
.choice-card:has(.choice-card__input:checked) {
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}
/* Keyboard focus ring on the card. */
.choice-card:has(.choice-card__input:focus-visible) { box-shadow: var(--shadow-focus); }

.choice-card__main { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
.choice-card__label-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.choice-card__label {
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  color: var(--color-text-primary);
}
.choice-card__desc {
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-secondary);
}
.choice-card__badge {
  display: inline-flex; align-items: center;
  padding: 2px 8px; border-radius: var(--radius-pill);
  background: var(--color-accent-soft); color: var(--color-accent);
  font-size: 11px; font-weight: var(--weight-semibold);
  white-space: nowrap;
}

.firas-ask__foot {
  display: flex; align-items: center; gap: var(--space-2);
  margin-block-start: var(--space-3); flex-wrap: wrap;
}
.firas-ask__extra {
  flex: 1 1 180px; min-inline-size: 0;
  block-size: 38px; padding-inline: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  color: var(--color-text-primary);
  font-family: inherit; font-size: var(--text-sm);
}
.firas-ask__extra:focus-visible { outline: none; border-color: var(--color-accent); box-shadow: var(--shadow-focus); }
.firas-ask__continue {
  display: inline-flex; align-items: center; gap: var(--space-2);
  block-size: 38px; padding-inline: var(--space-5);
  border-radius: var(--radius-pill);
  background: var(--color-accent); color: var(--color-on-accent);
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  white-space: nowrap; box-shadow: var(--shadow-xs);
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .firas-ask__continue { font-family: var(--font-arabic); }
.firas-ask__continue svg { inline-size: 16px; block-size: 16px; }
[dir="rtl"] .firas-ask__continue svg { transform: scaleX(-1); }
.firas-ask__continue:hover { background: var(--color-accent-hover); transform: translateY(-1px); }
.firas-ask__continue:active { transform: translateY(0); }
.firas-ask__continue:disabled { opacity: .85; pointer-events: none; transform: none; }
.firas-ask__ok svg { inline-size: 15px; block-size: 15px; }
[dir="rtl"] .firas-ask__ok svg { transform: none; }

/* Wizard navigation (one question at a time): Back + Next/Confirm. */
.firas-ask__nav { display: inline-flex; align-items: center; gap: var(--space-2); margin-inline-start: auto; }
.firas-ask__back {
  display: inline-flex; align-items: center; gap: 6px;
  block-size: 38px; padding-inline: var(--space-4);
  border-radius: var(--radius-pill);
  border: var(--border-hairline) solid var(--color-border-strong);
  background: transparent; color: var(--color-text-secondary);
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: var(--weight-medium);
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .firas-ask__back { font-family: var(--font-arabic); }
.firas-ask__back:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.firas-ask__back[hidden] { display: none; }
.firas-ask__back svg { inline-size: 16px; block-size: 16px; transform: scaleX(-1); }
[dir="rtl"] .firas-ask__back svg { transform: none; }

/* Per-question step counter above the question text. */
.ask-q__step {
  display: block; margin-block-end: 6px;
  font-size: var(--text-xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wide); color: var(--color-accent);
}
.ask-q__q { display: block; }

/* Answered state — locked, de-emphasized. */
.firas-ask.is-answered { opacity: .72; }
.firas-ask.is-answered .choice-card { cursor: default; }
.firas-ask.is-answered .choice-card:hover { border-color: var(--color-border-hairline); background: var(--color-bg); }
.firas-ask.is-answered .choice-card:has(.choice-card__input:checked) {
  border-color: var(--color-accent); background: var(--color-accent-soft);
}

/* ----------------------------------------------------------------------------
   7. Composer
---------------------------------------------------------------------------- */
.composer-wrap {
  position: absolute; inset-inline: 0; inset-block-end: 0;
  display: flex; flex-direction: column; align-items: center;
  padding: var(--space-4) var(--space-6) max(var(--space-6), env(safe-area-inset-bottom));
  pointer-events: none;
  background: linear-gradient(transparent, var(--color-bg) 46%);
}
.composer {
  pointer-events: auto;
  inline-size: 100%; max-inline-size: var(--column-w);
  display: flex; flex-direction: column; align-items: stretch;
  padding: var(--space-3) var(--space-3) var(--space-2);
  border-radius: var(--radius-xl);
  border: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-surface);
  box-shadow: var(--shadow-composer);
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.composer.is-focused {
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-composer), var(--shadow-focus);
}
.composer__textarea {
  flex: 1; resize: none; border: none; background: transparent;
  font-family: var(--font-sans);
  font-size: var(--text-md); line-height: var(--leading-md);
  color: var(--color-text-primary);
  padding: 6px var(--space-2);
  max-block-size: 200px;
  overflow-y: auto;
}
.composer__textarea:focus { outline: none; }
.composer__textarea::placeholder { color: var(--color-text-muted); }
.composer__textarea[dir="rtl"] { font-family: var(--font-arabic); }

/* ── Drop veil — shown while a FILE is dragged over the chat ────────────────────
   Absolute inside .composer-wrap (itself position:absolute), so inset:0 covers the
   composer card and the gradient strip above it, and nothing escapes into the topbar.
   pointer-events stays NONE — the veil must never become the drag target, or the
   element underneath fires dragleave the moment it appears and the overlay flickers.
   Like Brain's .fb-drop, visibility is the `hidden` ATTRIBUTE, so the [hidden] rule
   has to restate display:none — display:flex would otherwise out-rank the UA sheet.
   No product gate needed: .product-code and .product-brain already display:none the
   whole .composer-wrap, and this lives inside it. */
.composer-drop {
  position: absolute; inset: 0; z-index: 4;
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-4) var(--space-6);
  pointer-events: none;
  background: color-mix(in srgb, var(--color-bg) 74%, transparent);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.composer-drop[hidden] { display: none; }
.composer-drop__in {
  display: flex; align-items: center; justify-content: center; gap: var(--space-2);
  inline-size: 100%; max-inline-size: var(--column-w);
  padding: var(--space-6) var(--space-8);
  border: var(--border-strong) dashed var(--color-accent);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  box-shadow: var(--shadow-composer);
  color: var(--color-accent);
  font: var(--weight-semibold) var(--text-sm)/var(--leading-sm) var(--font-sans);
  text-align: center;
}
.composer-drop__ico { flex: 0 0 auto; inline-size: 20px; block-size: 20px; }
/* The shell is fixed LTR, so [dir="rtl"] never matches here — the Arabic face has to be
   selected from the LANGUAGE, the way the rest of the static chrome does it. */
[lang="ar"] .composer-drop__in { font-family: var(--font-arabic); }

.composer__attach { flex: 0 0 auto; inline-size: 36px; block-size: 36px; min-inline-size: 36px; }
.composer__attach svg { inline-size: 20px; block-size: 20px; }

/* (Response-mode selector is now a compact dropdown — see section 8c .mode-select) */

/* Thinking toggle — composer toolbar pill (device pref) */
.think-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  flex: 0 0 auto;
  block-size: 32px;
  padding-inline: var(--space-3);
  border-radius: var(--radius-pill);
  border: var(--border-hairline) solid var(--color-border-hairline);
  background: transparent;
  color: var(--color-text-muted);
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .think-toggle { font-family: var(--font-arabic); }
.think-toggle__ico { inline-size: 16px; block-size: 16px; flex: 0 0 auto; }
.think-toggle:hover { color: var(--color-text-secondary); border-color: var(--color-border-strong); }
.think-toggle.is-on {
  color: var(--color-accent);
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}
.think-toggle.is-on:hover { color: var(--color-accent-hover); }

.composer__send {
  flex: 0 0 auto;
  inline-size: 32px; block-size: 32px; min-inline-size: 32px;
  border-radius: var(--radius-sm);
  display: grid; place-items: center;
  background: var(--color-accent);
  color: var(--color-on-accent);
  transition: transform var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.composer__send svg { inline-size: 18px; block-size: 18px; }

/* ── THE COMPOSER CONTROLS ARE ROUND ──────────────────────────────────────────────
   They were square, and only looked it when touched. `.icon-btn` carries
   `border-radius: var(--radius-sm)` — 3px on a 36px button, which is a square with the corners
   barely taken off. With no background at rest the shape was invisible; the moment a finger
   landed, `:active` painted that background and a hard-edged box appeared under the phone icon.
   Reported three times as "the call button turns into a square when I press it", and it was
   never the tap highlight: it was the button's own shape, revealed by its own press state.

   Fixed for all four together, because a lone round call button beside three squares would be a
   different inconsistency. Scoped to the composer so `.icon-btn` everywhere else is untouched. */
.composer__call, .composer__mic, .composer__attach, .composer__send { border-radius: 50%; }
/* The press now reads as a circle sinking, not a box appearing. */
.composer__call:active, .composer__mic:active, .composer__attach:active { transform: scale(.92); }
.composer__send:active:not(:disabled) { transform: scale(.92); }
.composer__send:hover:not(:disabled) { background: var(--color-accent-hover); }
.composer__send:active:not(:disabled) { transform: scale(0.96); }
.composer__send:disabled { background: var(--color-border-strong); color: var(--color-text-muted); cursor: not-allowed; }
.composer__send.is-stop { background: var(--color-accent); }
.composer__send.is-stop .ico-send { display: none; }
.composer__send .ico-stop { display: none; }
.composer__send.is-stop .ico-stop { display: block; }

.send-pulse { animation: send-pulse var(--dur-slow) var(--ease-out); }
@keyframes send-pulse {
  0% { transform: scale(0.92); }
  100% { transform: scale(1); }
}

.composer__hint {
  pointer-events: none;
  margin-block-start: var(--space-2);
  text-align: center;
  font-size: var(--text-xs); color: var(--color-text-muted);
}

/* Scroll-to-bottom */
.scroll-bottom {
  position: absolute; inset-block-end: calc(100% + 10px); inset-inline-start: 50%;
  transform: translateX(-50%) translateY(6px);
  inline-size: 36px; block-size: 36px; border-radius: var(--radius-pill);
  display: grid; place-items: center;
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  box-shadow: var(--shadow-md);
  color: var(--color-text-secondary);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-base), transform var(--dur-base) var(--ease-out), border-color var(--dur-base);
  z-index: var(--z-scrollbtn);
}
.scroll-bottom:hover { background: var(--color-bg-subtle); border-color: var(--color-border-strong); }
.scroll-bottom.is-visible { opacity: 1; pointer-events: auto; transform: translateX(-50%) translateY(0); }
.scroll-bottom svg { inline-size: 18px; block-size: 18px; }

/* ----------------------------------------------------------------------------
   8. Mobile drawer + scrim
---------------------------------------------------------------------------- */
.scrim {
  position: fixed; inset: 0; z-index: var(--z-overlay);
  background: rgba(0,0,0,0.52);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-base);
}
.scrim.is-open { opacity: 1; pointer-events: auto; }

/* ----------------------------------------------------------------------------
   8b. Landing / hero (logged-out) — polished, animated, fully responsive
---------------------------------------------------------------------------- */
.landing {
  position: fixed; inset: 0; z-index: var(--z-toast);
  display: flex; flex-direction: column; align-items: center;
  justify-content: safe center; gap: var(--space-8);
  min-block-size: 100dvh;
  padding: max(var(--space-7), env(safe-area-inset-top)) var(--space-6)
           max(var(--space-8), env(safe-area-inset-bottom));
  background: var(--color-bg);
  overflow: hidden auto;
}
.landing[hidden] { display: none; }
/* ---- The instrument behind the hero ----
   Replaces three 60px-blurred accent orbs. A blurred coloured blob is atmosphere
   standing in for an idea; these are the astrolabe's actual parts, drawn as real
   graduations. All three are pure CSS — no image, no canvas, no layout cost — and
   they sit behind content at z-index 0. */
.landing__bg { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.astro {
  position: absolute; inset-block-start: 50%; inset-inline-start: 50%;
  border-radius: 50%; aspect-ratio: 1;
  translate: -50% -50%;
  transform-origin: 50% 50%;
}

/* LIMB — the graduated outer ring. A tick every 3°, with a longer tick every 15°,
   drawn by two repeating conic gradients and masked to a narrow band. This is a real
   scale: 120 minor divisions, exactly as the degree ring is cut. */
.astro--limb {
  inline-size: min(132vmin, 1180px);
  background:
    repeating-conic-gradient(from 0deg, var(--color-line) 0deg 0.14deg, transparent 0.14deg 3deg);
  -webkit-mask: radial-gradient(circle, transparent calc(50% - 13px), #000 calc(50% - 13px) calc(50% - 1px), transparent calc(50% - 1px));
          mask: radial-gradient(circle, transparent calc(50% - 13px), #000 calc(50% - 13px) calc(50% - 1px), transparent calc(50% - 1px));
  opacity: 0.34;
}
.astro--limb::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: repeating-conic-gradient(from 0deg, var(--color-line) 0deg 0.3deg, transparent 0.3deg 15deg);
  -webkit-mask: radial-gradient(circle, transparent calc(50% - 26px), #000 calc(50% - 26px) calc(50% - 1px), transparent calc(50% - 1px));
          mask: radial-gradient(circle, transparent calc(50% - 26px), #000 calc(50% - 26px) calc(50% - 1px), transparent calc(50% - 1px));
}

/* PLATE — the fixed disc, engraved with the almucantar arcs you read altitude against.
   Concentric hairlines, tightening toward the rim exactly as the projection does. */
.astro--plate {
  inline-size: min(96vmin, 860px);
  background:
    repeating-radial-gradient(circle, transparent 0 38px, var(--color-line) 38px 38.7px);
  /* Clear the middle. The hero copy sits there, and an engraved arc running behind a
     line of text costs more legibility than the arc adds. */
  -webkit-mask: radial-gradient(circle, transparent 30%, #000 44%, #000 62%, transparent 78%);
          mask: radial-gradient(circle, transparent 30%, #000 44%, #000 62%, transparent 78%);
  opacity: 0.13;
}

/* RETE — the pierced star-map that turns over the plate. The page's ONE authored
   motion: four minutes per revolution, slow enough to read as an instrument settling
   rather than an animation playing. */
.astro--rete {
  inline-size: min(108vmin, 960px);
  background:
    repeating-conic-gradient(from 0deg, var(--color-line) 0deg 0.5deg, transparent 0.5deg 60deg),
    repeating-conic-gradient(from 30deg, var(--color-success) 0deg 0.35deg, transparent 0.35deg 120deg);
  /* The star-pointers stop well clear of the reading column — they belong to the rim. */
  -webkit-mask: radial-gradient(circle, transparent 33%, #000 46%, #000 49%, transparent 50%);
          mask: radial-gradient(circle, transparent 33%, #000 46%, #000 49%, transparent 50%);
  opacity: 0.26;
  animation: astro-turn 240s linear infinite;
}
@keyframes astro-turn { to { rotate: 360deg; } }
.landing__inner {
  position: relative; z-index: 1;
  inline-size: 100%; max-inline-size: 540px;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--space-5);
}
/* The mark sits ON the plate — no drift, no float. A bobbing logo is a mood; an
   instrument's centre pin is fixed, and everything else moves around it. The old
   rgba(35,122,104) drop shadow was hard-coded teal the token layer could never reach. */
.landing__mark {
  animation: landing-rise var(--dur-slow) var(--ease-out) both;
  box-shadow: 0 2px 10px rgba(0,0,0,0.45);
}
.landing__wordmark { margin: 0; animation: landing-rise var(--dur-slow) var(--ease-out) 0.06s both; }
.landing__wordmark .wordmark { font-size: clamp(34px, 9vw, 52px); }
.landing__about {
  margin: 0; max-inline-size: 460px;
  color: var(--color-text-secondary);
  font-size: var(--text-md); line-height: var(--leading-md);
  animation: landing-rise var(--dur-slow) var(--ease-out) 0.12s both;
}
[dir="rtl"] .landing__about { line-height: 1.9; }
/* THE TWO LANDING BUTTONS, and the emphasis between them is now the opposite of what it was.
   The owner's instruction: «لديك حساب؟ تسجيل الدخول» takes the coloured weight and
   «ابدأ الآن — بدون حساب» takes the plain one, and the pair STACKS instead of standing side by
   side. His reason is a real one — a coloured box beside a plain box of a different visual weight
   reads as unbalanced on a desktop screen; stacked, it reads as one clear primary action with a
   quieter alternative directly under it.

   THE CLASSES DID NOT MOVE, THE WEIGHTS DID. .landing__cta is still #landingStart — the guest
   entry, and the only one of the two that ever goes busy, because startGuestSession() adds
   .is-busy to it and .landing__cta.is-busy::after is what draws the spinner (in four places in
   this file, counting the reduced-motion and motion-off overrides). Swapping the classes in the
   markup instead would have put a spinner rule on a button that never spins and taken it off the
   one that does. So read this pair as "the quiet one" and "the loud one", not as their names.

   VISUAL ORDER IS SOURCE ORDER. index.html puts the sign-in button first so the column puts it on
   top; there is no `order` and no `column-reverse` here, because either would leave a keyboard
   user tabbing bottom-to-top through a stack they are reading top-to-bottom. */
.landing__cta {
  min-block-size: 52px;
  padding-inline: var(--space-5);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text-secondary);
  border: var(--border-hairline) solid var(--color-border-strong);
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  /* NO entrance animation here: the .landing__actions wrapper that holds both buttons owns the
     stack's entrance. Running `landing-rise` on both meant the button faded in INSIDE a fading-in
     parent — doubled opacity, and a child left stranded at keyframe 0 whenever the parent's
     animation was cancelled or replaced by the JS reveal. */
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .landing__cta { font-family: var(--font-arabic); }
.landing__cta:hover { color: var(--color-text-primary); border-color: var(--color-accent); background: var(--color-accent-soft); }
.landing__cta:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
.landing__cta.is-busy { opacity: .7; pointer-events: none; }
/* currentColor, so the spinner follows whichever weight this button is carrying. It reads as
   secondary text now instead of on-accent, and it is still the only feedback a visitor gets
   between pressing "start" and the app appearing. */
.landing__cta.is-busy::after {
  content: ""; inline-size: 15px; block-size: 15px; margin-inline-start: var(--space-2);
  border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;
  display: inline-block; vertical-align: -2px; animation: landing-spin .7s linear infinite;
}
@keyframes landing-spin { to { transform: rotate(360deg); } }

/* A COLUMN AT EVERY WIDTH. This used to be a wrapping row that broke into a column under 420px;
   the phone case was the only one that looked right, so it is now the only case. The
   @media (max-width: 420px) block that did it is gone with it — a media query whose declarations
   have become identical to the base rule is a rule nobody can tell is dead.
   `margin-block-start` sits on the wrapper rather than on a button: it separates the stack from
   .landing__about, and on a button it would have become extra space BETWEEN the two. */
.landing__actions {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  inline-size: 100%; gap: var(--space-3);
  margin-block-start: var(--space-2);
  animation: landing-rise var(--dur-slow) var(--ease-out) 0.18s both;
}
/* One width for both, so the stack has one edge down its side instead of two ragged ones. */
.landing__cta, .landing__signin { inline-size: 100%; max-inline-size: 340px; }
/* THE PRIMARY. A struck brass key, not a pill. Cut corners and a hairline bright edge along the
   top read as machined metal catching light; the old 22px teal glow read as a button hovering
   above a page. This is the treatment that was on .landing__cta until the emphasis was swapped —
   the same rule, moved, not a second one written to look like it. */
.landing__signin {
  min-block-size: 52px; padding-inline: var(--space-8);
  border-radius: var(--radius-md);
  background: var(--color-accent); color: var(--color-on-accent);
  font-family: var(--font-sans);
  font-size: var(--text-md); font-weight: var(--weight-semibold);
  letter-spacing: 0.01em;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.30), 0 2px 6px rgba(0,0,0,0.40);
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .landing__signin { font-family: var(--font-arabic); }
.landing__signin:hover { background: var(--color-accent-hover); box-shadow: inset 0 1px 0 rgba(255,255,255,0.38), 0 4px 12px rgba(0,0,0,0.45); }
/* The lift is real-hover only: on touch, :hover sticks after a tap and the button would sit
   shifted -1px until focus moves. Colour/shadow still respond everywhere. The lift follows the
   colour, so it moved here with it — and so did its two motion-off overrides near the end of this
   file, which are the reader's off switch for it. A lift with no off switch is exactly the bug
   those overrides exist to prevent. */
@media (hover: hover) and (pointer: fine) {
  .landing__signin:hover { transform: translateY(-1px); }
}
.landing__signin:active { transform: translateY(0); }
.landing__signin:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 3px; }
.landing__hint {
  margin: 0; color: var(--color-text-muted);
  font-size: var(--text-xs); line-height: var(--leading-sm);
  max-inline-size: 400px;
  animation: landing-rise var(--dur-slow) var(--ease-out) 0.21s both;
}
@keyframes landing-rise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  /* The rete stops turning, but the instrument stays drawn — the graduations ARE the
     composition, so killing them would remove content, not motion. */
  .astro--rete, .landing__mark { animation: none !important; }
  .landing__wordmark, .landing__about, .landing__actions, .landing__hint, .landing__mark,
  .landing__stats { animation: landing-rise 0.001ms both !important; }
  .landing__stat-dot { animation: none !important; }
}

/* ---- Landing: the product scale (under the CTA) ----
   Five marks on one graduated rule. Replaces the fabricated "+N users" counters;
   this states what the product is, and can never go stale or be untrue. */
.landing__stats {
  display: flex; align-items: center; justify-content: center;
  inline-size: 100%;
  margin-block-start: var(--space-4);
  animation: landing-rise var(--dur-slow) var(--ease-out) 0.24s both;
}
/* Three website product marks: Chat, Code (coming soon), and Brain. */
.landing__scale {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  inline-size: 100%; max-inline-size: 560px;
  border-block-start: 1px solid var(--color-border-strong);
}
.landing__mark-item {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding-block-start: var(--space-3); min-inline-size: 0;
}
/* Each product is a division cut into the rule — the tick is the graduation, and it
   sits ON the line, not beside it. */
.landing__mark-tick {
  inline-size: 1px; block-size: 9px; background: var(--color-line);
  margin-block-start: -1px; margin-block-end: 3px;
}
.landing__mark-name {
  font-family: var(--font-display); font-size: var(--text-sm);
  font-weight: var(--weight-semibold); letter-spacing: 0.02em;
  color: var(--color-text-primary);
}
.landing__mark-desc {
  font-size: var(--text-xs); line-height: 1.4; color: var(--color-text-muted);
  text-align: center; overflow-wrap: anywhere;
}
@media (max-width: 400px) {
  .landing__mark-desc { font-size: 11px; }
}
.landing__stat { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.landing__stat-num {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: clamp(20px, 5.5vw, 26px); font-weight: 700; line-height: 1;
  color: var(--color-text-primary);
}
.landing__stat-val { display: inline-block; }
.landing__stat-val.is-bumped { animation: stat-bump 0.45s var(--ease-out); }
@keyframes stat-bump {
  0% { transform: translateY(0); color: var(--color-accent); }
  45% { transform: translateY(-3px); }
  100% { transform: translateY(0); }
}
.landing__stat-label {
  font-family: var(--font-sans); font-size: var(--text-xs);
  color: var(--color-text-secondary); /* AA contrast on the landing bg */
}
[dir="rtl"] .landing__stat-label { font-family: var(--font-arabic); }
.landing__stat-dot {
  inline-size: 9px; block-size: 9px; border-radius: 50%; background: #2ecc71;
  animation: landing-live 1.6s ease-in-out infinite;
}
@keyframes landing-live {
  0%, 100% { box-shadow: 0 0 0 0 rgba(46, 204, 113, 0.55); }
  50% { box-shadow: 0 0 0 6px rgba(46, 204, 113, 0); }
}
.landing__stat-div { inline-size: 1px; align-self: stretch; background: var(--color-border-hairline); }
@media (max-width: 480px) { .landing__stat-div { display: none; } }

/* ---- Landing: features + experimental note ---- */
.landing__details {
  position: relative; z-index: 1;
  inline-size: 100%; max-inline-size: 980px;
  display: flex; flex-direction: column; gap: var(--space-6);
  animation: landing-rise var(--dur-slow) var(--ease-out) 0.3s both;
}
.landing__details-head { text-align: center; display: flex; flex-direction: column; gap: var(--space-2); }
.landing__details-title { margin: 0; font-size: clamp(22px, 5vw, 30px); }
.landing__details-sub {
  margin: 0 auto; max-inline-size: 580px;
  color: var(--color-text-secondary); font-size: var(--text-md); line-height: var(--leading-md);
}
[dir="rtl"] .landing__details-sub { line-height: 1.9; }
.landing__features {
  display: grid; gap: var(--space-3);
  /* min(258px, 100%) lets the track shrink below 258px on ultra-narrow/foldable
     phones instead of overflowing the container. */
  grid-template-columns: repeat(auto-fit, minmax(min(258px, 100%), 1fr));
}
.landing__feature {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-4);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  text-align: start;
  transition: border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.landing__feature:hover { border-color: var(--color-accent); transform: translateY(-2px); box-shadow: var(--shadow-sm); }
.landing__feature-ico {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  inline-size: 42px; block-size: 42px; border-radius: var(--radius-md);
  background: var(--color-accent-soft); color: var(--color-accent);
}
.landing__feature-ico svg { inline-size: 22px; block-size: 22px; }
.landing__feature-tx { display: flex; flex-direction: column; gap: 4px; min-inline-size: 0; }
.landing__feature-title {
  margin: 0; font-family: var(--font-sans);
  font-size: var(--text-md); font-weight: var(--weight-semibold); color: var(--color-text-primary);
}
[dir="rtl"] .landing__feature-title { font-family: var(--font-arabic); }
.landing__feature-desc {
  margin: 0; font-size: var(--text-sm); line-height: var(--leading-sm);
  color: var(--color-text-secondary);
}
[dir="rtl"] .landing__feature-desc { line-height: 1.8; }
.landing__imgnote {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-lg);
  background: linear-gradient(135deg, var(--color-accent-soft), transparent 80%);
  border: var(--border-hairline) solid var(--color-accent);
  text-align: start;
}
.landing__imgnote-ico {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  inline-size: 44px; block-size: 44px; border-radius: var(--radius-md);
  background: var(--color-accent); color: var(--color-on-accent);
}
.landing__imgnote-ico svg { inline-size: 24px; block-size: 24px; }
.landing__imgnote-tx { display: flex; flex-direction: column; gap: 5px; min-inline-size: 0; }
.landing__imgnote-title {
  margin: 0; display: inline-flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  font-family: var(--font-sans); font-size: var(--text-md); font-weight: var(--weight-semibold);
  color: var(--color-text-primary);
}
[dir="rtl"] .landing__imgnote-title { font-family: var(--font-arabic); }
.landing__imgnote-badge {
  font-family: var(--font-sans); font-size: 10px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  padding: 2px 8px; border-radius: var(--radius-pill);
  background: var(--color-accent); color: var(--color-on-accent);
}
.landing__imgnote-body {
  margin: 0; font-size: var(--text-sm); line-height: var(--leading-sm);
  color: var(--color-text-secondary);
}
[dir="rtl"] .landing__imgnote-body { line-height: 1.85; }

/* Auth brand becomes a clickable button (returns to landing) */
.auth__brand-btn { background: none; border: none; cursor: pointer; padding: 0; }
.auth__brand-btn:hover { opacity: 0.85; }

/* ----------------------------------------------------------------------------
   8c. Mode selector dropdown (Auto / Plan) — compact trigger + menu
---------------------------------------------------------------------------- */
.mode-select { position: relative; display: inline-flex; flex: 0 0 auto; }
.mode-select__trigger {
  display: inline-flex; align-items: center; gap: 6px;
  block-size: 34px; min-block-size: 34px;
  padding-inline: var(--space-3);
  border-radius: var(--radius-pill);
  border: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .mode-select__trigger { font-family: var(--font-arabic); }
.mode-select__trigger .mode-ico { display: inline-flex; }
.mode-select__trigger .mode-ico svg { inline-size: 15px; block-size: 15px; }
.mode-select__trigger:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); background: var(--color-surface); }
.mode-select.is-open .mode-select__trigger,
.mode-select__trigger[aria-expanded="true"] {
  color: var(--color-accent); border-color: var(--color-accent); background: var(--color-accent-soft);
}
.mode-select__caret { display: inline-flex; }
.mode-select__caret svg { inline-size: 13px; block-size: 13px; transition: transform var(--dur-fast) var(--ease-out); }
.mode-select.is-open .mode-select__caret svg { transform: rotate(180deg); }
.mode-select__menu {
  position: absolute; inset-block-end: calc(100% + 8px); inset-inline-start: 0;
  z-index: var(--z-dropdown);
  inline-size: 248px; max-inline-size: 78vw;
  padding: var(--space-1);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 2px;
  animation: msg-in var(--dur-fast) var(--ease-out);
}
/* RTL: the base `inset-inline-start: 0` already right-aligns the menu to the
   trigger and lets it extend leftward (on-screen). The old `inset-inline-end: 0`
   override flipped it to the left edge, pushing the 248px menu ~170px off-screen. */
.mode-select__item {
  display: flex; align-items: flex-start; gap: var(--space-3);
  inline-size: 100%;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  text-align: start;
  color: var(--color-text-primary);
  transition: background var(--dur-fast) var(--ease-out);
}
.mode-select__item:hover, .mode-select__item:focus-visible { background: var(--color-bg-subtle); outline: none; }
.mode-select__item.is-active { background: var(--color-accent-soft); }
.mode-select__item .mode-ico { display: inline-flex; flex: 0 0 auto; margin-block-start: 1px; color: var(--color-text-secondary); }
.mode-select__item.is-active .mode-ico { color: var(--color-accent); }
.mode-select__item .mode-ico svg { inline-size: 18px; block-size: 18px; }
.mode-select__item-text { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; flex: 1 1 auto; }
.mode-select__item-label { font-family: var(--font-sans); font-size: var(--text-sm); font-weight: var(--weight-semibold); }
[dir="rtl"] .mode-select__item-label { font-family: var(--font-arabic); }
.mode-select__item.is-active .mode-select__item-label { color: var(--color-accent); }
.mode-select__item-hint { font-size: var(--text-xs); line-height: var(--leading-xs); color: var(--color-text-muted); }
.mode-select__check { display: inline-flex; flex: 0 0 auto; color: var(--color-accent); margin-block-start: 2px; }
.mode-select__check svg { inline-size: 16px; block-size: 16px; }
.mode-select__check:empty { inline-size: 16px; }

/* ----- Tools "+" menu (Web search + Thinking toggles) — compact, any screen ----- */
.tools-menu { position: relative; display: inline-flex; flex: 0 0 auto; }
.tools-menu__trigger {
  position: relative; display: inline-grid; place-items: center;
  inline-size: 34px; block-size: 34px; min-inline-size: 34px;
  border-radius: var(--radius-pill);
  border: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-bg); color: var(--color-text-secondary);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.tools-menu__trigger svg { inline-size: 18px; block-size: 18px; }
.tools-menu__trigger:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); background: var(--color-surface); }
.tools-menu.is-open .tools-menu__trigger { color: var(--color-accent); border-color: var(--color-accent); background: var(--color-accent-soft); }
/* small dot when a tool is active */
.tools-menu.has-active .tools-menu__trigger::after {
  content: ""; position: absolute; top: 1px; inset-inline-end: 1px;
  inline-size: 8px; block-size: 8px; border-radius: 50%;
  background: var(--color-accent); border: 2px solid var(--color-bg);
}
.tools-menu__panel {
  position: absolute; inset-block-end: calc(100% + 8px); inset-inline-start: 0;
  z-index: var(--z-dropdown);
  inline-size: 236px; max-inline-size: 80vw;
  padding: var(--space-1);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 2px;
  animation: msg-in var(--dur-fast) var(--ease-out);
}
.tools-menu__panel[hidden] { display: none; }
.tools-menu__item {
  display: flex; align-items: center; gap: var(--space-2);
  inline-size: 100%; padding: var(--space-2);
  border-radius: var(--radius-md); color: var(--color-text-primary);
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: var(--weight-medium);
  text-align: start; transition: background var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .tools-menu__item { font-family: var(--font-arabic); }
.tools-menu__item:hover, .tools-menu__item:focus-visible { background: var(--color-bg-subtle); outline: none; }
.tools-menu__ico { display: inline-flex; flex: 0 0 auto; color: var(--color-text-secondary); transition: color var(--dur-fast); }
.tools-menu__ico svg { inline-size: 18px; block-size: 18px; }
.tools-menu__item.is-on .tools-menu__ico { color: var(--color-accent); }
.tools-menu__label { flex: 1 1 auto; min-inline-size: 0; }
.tools-menu__switch {
  position: relative; flex: 0 0 auto; inline-size: 34px; block-size: 20px;
  border-radius: var(--radius-pill); background: var(--color-border-strong);
  transition: background var(--dur-fast) var(--ease-out);
}
.tools-menu__switch::after {
  content: ""; position: absolute; top: 2px; inset-inline-start: 2px;
  inline-size: 16px; block-size: 16px; border-radius: 50%;
  background: #fff; box-shadow: var(--shadow-xs);
  transition: transform var(--dur-fast) var(--ease-out);
}
.tools-menu__item.is-on .tools-menu__switch { background: var(--color-accent); }
.tools-menu__item.is-on .tools-menu__switch::after { transform: translateX(14px); }
[dir="rtl"] .tools-menu__item.is-on .tools-menu__switch::after { transform: translateX(-14px); }

/* ----------------------------------------------------------------------------
   8d. Composer trailing controls — tidy [mode][think] group + [attach][send]
---------------------------------------------------------------------------- */
.composer__tools { display: inline-flex; align-items: center; gap: var(--space-2); flex: 0 0 auto; }
.composer__actions {
  display: inline-flex; align-items: center; gap: var(--space-1);
  flex: 0 0 auto;
  padding: 2px; border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
}
.composer__actions .composer__attach {
  inline-size: 36px; block-size: 36px; min-inline-size: 36px;
  border-radius: var(--radius-pill); color: var(--color-text-secondary);
}
.composer__actions .composer__attach:hover { background: var(--color-surface); color: var(--color-text-primary); }
.composer__actions .composer__send { inline-size: 36px; block-size: 36px; min-inline-size: 36px; border-radius: var(--radius-pill); }
.composer__actions .composer__send svg { inline-size: 19px; block-size: 19px; }

/* ----------------------------------------------------------------------------
   8e. Collapsible sidebar (desktop taskbar) — hide/restore the conversation list
---------------------------------------------------------------------------- */
.topbar__desktop-only { display: grid; }
.topbar__sidebar-toggle { flex: 0 0 auto; }
@media (min-width: 768.02px) {
  body.sidebar-collapsed .app { grid-template-columns: 0 1fr; }
  body.sidebar-collapsed .sidebar {
    transform: translateX(-100%);
    inline-size: 0; min-inline-size: 0; padding: 0; border-inline-end: none;
    overflow: hidden; pointer-events: none;
  }
  [dir="rtl"] body.sidebar-collapsed .sidebar { transform: translateX(100%); }
  .sidebar {
    transition: transform var(--dur-base) var(--ease-out);
  }
  /* When collapsed, the topbar shows the single logo lockup (sidebar logo hidden);
     when expanded, the sidebar carries the only logo (avoid a duplicate mark). */
  body:not(.sidebar-collapsed) .topbar__desktop-mark { display: none; }
  body.sidebar-collapsed .topbar__desktop-mark { display: inline-flex; }
}

/* ----------------------------------------------------------------------------
   8f. File disclosure (masked file content under the file card)
---------------------------------------------------------------------------- */
.firas-file-loading { color: var(--color-accent); }
.file-disclosure {
  margin-block: var(--space-2) var(--space-1);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-bg-subtle);
  overflow: hidden;
}
.file-disclosure__summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  color: var(--color-text-secondary);
  user-select: none;
  transition: color var(--dur-fast), background var(--dur-fast);
}
[dir="rtl"] .file-disclosure__summary { font-family: var(--font-arabic); }
.file-disclosure__summary::-webkit-details-marker { display: none; }
.file-disclosure__summary::before {
  content: ""; inline-size: 7px; block-size: 7px; flex: 0 0 auto;
  border-inline-end: 2px solid currentColor; border-block-end: 2px solid currentColor;
  transform: rotate(-45deg); transition: transform var(--dur-fast) var(--ease-out);
  margin-inline-end: 2px;
}
.file-disclosure[open] .file-disclosure__summary::before { transform: rotate(45deg); }
.file-disclosure__summary:hover { color: var(--color-accent); background: var(--color-accent-soft); }
.file-disclosure__body {
  padding: var(--space-3) var(--space-4) var(--space-4);
  border-block-start: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-surface);
  font-family: var(--font-serif);
  font-size: var(--text-base); line-height: var(--leading-base);
}
[dir="rtl"] .file-disclosure__body { font-family: var(--font-arabic); }

/* ----------------------------------------------------------------------------
   9. Responsive
---------------------------------------------------------------------------- */
@media (max-width: 768px) {
  .app { grid-template-columns: 1fr; }

  /* Sidebar collapse is a desktop-only feature; on mobile the drawer rules win. */
  body.sidebar-collapsed .app { grid-template-columns: 1fr; }
  .topbar__desktop-only { display: none; }

  .sidebar {
    position: fixed; inset-block: 0; inset-inline-start: 0; z-index: var(--z-drawer);
    inline-size: min(288px, 88vw);
    transform: translateX(-100%);
    transition: transform var(--dur-base) var(--ease-out);
    box-shadow: var(--shadow-lg);
  }
  /* RTL drawer: base inset-inline-start:0 already pins it to the RIGHT edge in RTL,
     so only flip the hide direction (slide off the right). Overriding the inset here
     was the bug — it re-pinned to the left, leaving ~102px peeking on-screen. */
  [dir="rtl"] .sidebar { transform: translateX(100%); }
  .sidebar.is-open { transform: translateX(0); }
  .sidebar .sidebar__close { display: grid; }

  .topbar { padding-inline: var(--space-3); }
  .topbar .topbar__mobile-only { display: grid; }
  .topbar__desktop-mark { display: none; }
  .topbar__wordmark-center {
    position: absolute; inset-inline: 0; margin-inline: auto;
    inline-size: max-content; pointer-events: none;
  }
  @media (max-width: 600px) {
    .topbar .topbar__wordmark-center { display: none; } /* 0,2,0 to beat the mobile-only show bump */
  }
  .topbar__newchat-label { display: none; }
  .topbar__newchat { padding-inline: var(--space-2); }

  .chat-inner { padding-inline: var(--space-4); }
  .welcome { padding: var(--space-12) var(--space-4) 0; }
  .welcome__title { font-size: var(--text-2xl); line-height: var(--leading-2xl); }

  .msg-user__bubble { max-inline-size: 88%; }

  .composer-wrap { padding-inline: var(--space-4); }
}

/* Composer: on narrow widths put the textarea on its own row, with the tool
   group + action cluster on a second row so nothing overflows or clips. */
@media (max-width: 560px) {
  .composer__row { flex-wrap: wrap; }
  .composer__textarea { flex: 1 1 100%; inline-size: 100%; order: -1; }
  .composer__tools { flex: 1 1 auto; }
  .composer__actions { margin-inline-start: auto; }
}

/* Comfortable touch targets (>=44px) on touch / coarse pointers. */
@media (pointer: coarse), (max-width: 560px) {
  .composer__actions .composer__attach,
  .composer__actions .composer__send { inline-size: 44px; block-size: 44px; min-inline-size: 44px; }
  .composer__actions .composer__send svg { inline-size: 20px; block-size: 20px; }
  .mode-select__trigger { block-size: 40px; min-block-size: 40px; }
  .think-toggle { block-size: 40px; }
  .icon-btn { inline-size: 44px; block-size: 44px; min-inline-size: 44px; }
}

@media (max-width: 420px) {
  .tier-switch__btn .tier-name { font-size: 0; }
  .tier-switch__btn .tier-name::after { content: attr(data-short); font-size: var(--text-sm); }
  .tier-switch__btn[data-tier="ultra"] .tier-name::before { font-size: 10px; }
  /* Keep the composer roomy: thinking toggle becomes icon-only */
  .think-toggle__label { display: none; }
  .think-toggle { padding-inline: var(--space-2); }
  /* Mode selector becomes ICON-ONLY on a tight composer so its label never clips
     (the bolt vs checklist icon distinguishes Auto/Plan; the menu shows full
     labels + hints). */
  .mode-select__trigger .mode-name { display: none; }
  .mode-select__trigger { padding-inline: var(--space-2); gap: 4px; }
  .mode-select__menu { inline-size: min(86vw, 248px); }
}

/* With FOUR tiers + share + the notifications bell, the topbar is crowded on phones.
   The theme toggle moved into Settings; these rules make the remaining row bullet-proof:
   tight gaps, a shrinkable tier switch that can scroll internally instead of bursting out
   of its pill, compact icon buttons — nothing ever pushes past the screen edge. */
@media (max-width: 600px) {
  .topbar { gap: var(--space-2); }
  .tier-switch { min-inline-size: 0; flex: 0 1 auto; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .tier-switch::-webkit-scrollbar { display: none; }
  .tier-switch__btn { flex: 0 0 auto; }
}
@media (max-width: 480px) {
  .topbar { gap: var(--space-1); padding-inline: var(--space-2); }
  .topbar .icon-btn { inline-size: 34px; block-size: 34px; }
  .tier-switch { gap: 1px; }
  .tier-switch__btn { padding-inline: 6px; }
  .tier-switch__btn[data-tier="max"] .tier-beta { display: none; }
  .topbar__newchat { padding-inline: var(--space-1); }
  .topbar__newchat-label { display: none; }          /* + icon says it */
}
/* Very small phones (iPhone SE 320, Galaxy Fold 280) */
@media (max-width: 360px) {
  .topbar { gap: 3px; padding-inline: var(--space-1); }
  .tier-switch { gap: 1px; }
  .tier-switch__btn { padding-inline: 4px; font-size: 12px; gap: 3px; }
  .topbar .icon-btn { inline-size: 31px; block-size: 31px; }
}

/* ----------------------------------------------------------------------------
   Mobile-only drawer trigger uses the spark mark; hide aurora flourish hooks
---------------------------------------------------------------------------- */
.topbar__mobile-only-hide { /* legacy hook kept harmless */ }

/* ----------------------------------------------------------------------------
   10. Auth screen
---------------------------------------------------------------------------- */
.auth {
  position: fixed; inset: 0; z-index: var(--z-toast);
  display: grid; place-items: center;
  padding: var(--space-6);
  background: var(--color-bg);
  overflow-y: auto;
}
.auth[hidden] { display: none; }
.auth__card {
  inline-size: 100%; max-inline-size: 400px;
  display: flex; flex-direction: column;
  padding: var(--space-8);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  animation: msg-in var(--dur-slow) var(--ease-out);
}
.auth__brand {
  display: inline-flex; align-items: center; gap: var(--space-3);
  margin-bottom: var(--space-6);
  align-self: center;
}
.auth__title {
  font-family: var(--font-serif);
  font-size: var(--text-2xl); line-height: var(--leading-2xl);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
  color: var(--color-text-primary);
  text-align: start;
}
[dir="rtl"] .auth__title { font-family: var(--font-arabic); }
.auth__subtitle {
  margin-block-start: var(--space-1) var(--space-5);
  margin-bottom: var(--space-6);
  font-size: var(--text-base); line-height: var(--leading-base);
  color: var(--color-text-secondary);
  text-align: start;
}
/* Continue with Google — Claude/teal-tinted neutral button with brand "G" */
.auth__google {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-3);
  inline-size: 100%;
  block-size: 44px;
  padding-inline: var(--space-4);
  border-radius: var(--radius-sm);
  border: var(--border-hairline) solid var(--color-border-strong);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-base); font-weight: var(--weight-medium);
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .auth__google { font-family: var(--font-arabic); }
.auth__google[hidden] { display: none; }
.auth__google:hover:not(:disabled) {
  background: var(--color-bg-subtle);
  border-color: var(--color-accent);
}
.auth__google:disabled { opacity: 0.65; cursor: progress; }
.auth__google.is-loading { position: relative; color: transparent; }
.auth__google.is-loading .auth__google-icon { opacity: 0; }
.auth__google.is-loading::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  inline-size: 18px; block-size: 18px;
  border: 2px solid var(--color-accent); border-top-color: transparent;
  border-radius: var(--radius-pill);
  animation: auth-spin 0.7s linear infinite;
}
.auth__google-icon { display: inline-grid; place-items: center; flex: 0 0 auto; }
.auth__google-icon svg { display: block; inline-size: 18px; block-size: 18px; }

/* Subtle "or" divider between Google and email/password */
.auth__divider {
  display: flex; align-items: center; gap: var(--space-3);
  margin-block: var(--space-5);
  color: var(--color-text-muted);
  font-size: var(--text-xs); letter-spacing: var(--tracking-wide); text-transform: uppercase;
}
.auth__divider[hidden] { display: none; }
.auth__divider::before, .auth__divider::after {
  content: ""; flex: 1; block-size: var(--border-hairline);
  background: var(--color-border-hairline);
}

.auth__form { display: flex; flex-direction: column; gap: var(--space-4); }
.auth__field { display: flex; flex-direction: column; gap: var(--space-2); }
.auth__field[hidden] { display: none; }
.auth__label {
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  color: var(--color-text-secondary);
  text-align: start;
}
.auth__field input {
  block-size: 42px;
  inline-size: 100%;
  padding-inline: var(--space-3);
  background: var(--color-bg);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-sm);
  color: var(--color-text-primary);
  font-size: var(--text-base);
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.auth__field input::placeholder { color: var(--color-text-muted); }
.auth__field input:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}
.auth__error {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--color-error);
  border-radius: var(--radius-sm);
  color: var(--color-error);
  font-size: var(--text-sm);
  text-align: start;
}
.auth__error[hidden] { display: none; }
.auth__forgot {
  align-self: flex-end;
  background: none; border: none; cursor: pointer;
  color: var(--color-text-secondary);
  font: 500 var(--text-sm)/1.4 var(--font-sans);
  padding: 2px 2px;
}
.auth__forgot:hover { color: var(--color-accent); text-decoration: underline; }
.auth__forgot[hidden] { display: none; }
.auth__forgot:disabled { opacity: .55; cursor: default; }
.auth__back {
  align-self: center; margin-top: 4px;
  background: none; border: none; cursor: pointer;
  color: var(--color-text-secondary);
  font: 500 var(--text-sm)/1.4 var(--font-sans);
  padding: 6px 4px;
}
.auth__back:hover { color: var(--color-accent); }
.auth__back[hidden] { display: none; }
.auth__note {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--color-accent-soft);
  border: var(--border-hairline) solid var(--color-accent);
  border-radius: var(--radius-sm);
  color: var(--color-text-primary);
  font-size: var(--text-sm);
  text-align: start;
}
.auth__note[hidden] { display: none; }

/* Cookie consent (auth screen only, until a choice is saved) */
.cookie-consent {
  position: fixed; inset-inline: 0; bottom: 0; z-index: calc(var(--z-toast) + 1);
  display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap;
  padding: 14px 20px;
  background: var(--color-surface);
  border-top: 1px solid var(--color-border-strong);
  box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.28);
  animation: cookieUp 0.3s var(--ease-out, ease);
}
.cookie-consent[hidden] { display: none; }
@keyframes cookieUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
.cookie-consent__text { margin: 0; flex: 1 1 320px; max-inline-size: 620px; font: 400 13.5px/1.6 var(--font-sans); color: var(--color-text-secondary); }
.cookie-consent__actions { display: flex; gap: 8px; flex: 0 0 auto; }
.cookie-consent__btn { cursor: pointer; border-radius: var(--radius-pill); padding: 9px 22px; font: 600 13px var(--font-sans); transition: background .15s, color .15s, border-color .15s; border: 1px solid transparent; }
.cookie-consent__accept { background: var(--color-accent); color: var(--color-on-accent); border-color: var(--color-accent); }
.cookie-consent__accept:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); }
.cookie-consent__reject { background: transparent; color: var(--color-text-secondary); border-color: var(--color-border-strong); }
.cookie-consent__reject:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
@media (max-width: 560px) {
  .cookie-consent { flex-direction: column; align-items: stretch; gap: 10px; padding: 14px 16px; text-align: center; }
  /* P0 — this banner was covering the whole sign-in form on phones.
     `.cookie-consent__text` carries `flex: 1 1 320px`, written for the default ROW layout
     where 320px is a basis on the INLINE axis. Flipping the container to `column` here turns
     that same 320px into a minimum BLOCK size, so the paragraph became 320px TALL and the
     banner grew to ~403px — half the screen. The banner is position:fixed at the bottom with
     a higher z-index than `.auth`, so it did not merely overlap the email field, the submit
     button and the "already have an account?" switch — it swallowed their taps too
     (elementFromPoint over the submit button returned the banner).
     It is shown only to visitors who have not chosen yet, i.e. every FIRST-TIME visitor:
     the app's front door was blocked for exactly the people arriving at it. */
  .cookie-consent__text { flex: 0 0 auto; }
  .cookie-consent__actions { justify-content: center; }
  .cookie-consent__btn { flex: 1; }
}

.auth__submit {
  block-size: 44px;
  margin-block-start: var(--space-1);
  border-radius: var(--radius-sm);
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-family: var(--font-sans);
  font-size: var(--text-md); font-weight: var(--weight-semibold);
  transition: background var(--dur-fast) var(--ease-out), opacity var(--dur-fast);
}
[dir="rtl"] .auth__submit { font-family: var(--font-arabic); }
.auth__submit:hover:not(:disabled) { background: var(--color-accent-hover); }
.auth__submit:disabled { opacity: 0.65; cursor: progress; }
.auth__submit.is-loading { position: relative; color: transparent; }
.auth__submit.is-loading::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  inline-size: 18px; block-size: 18px;
  border: 2px solid var(--color-on-accent); border-top-color: transparent;
  border-radius: var(--radius-pill);
  animation: auth-spin 0.7s linear infinite;
}
@keyframes auth-spin { to { transform: rotate(360deg); } }
.auth__switch {
  margin-block-start: var(--space-6); margin-bottom: 0;
  text-align: center;
  font-size: var(--text-sm); color: var(--color-text-secondary);
}
.auth__switch-btn {
  margin-inline-start: 4px;
  color: var(--color-accent); font-weight: var(--weight-medium);
  font-size: inherit;
}
.auth__switch-btn:hover { color: var(--color-accent-hover); text-decoration: underline; }

/* ----------------------------------------------------------------------------
   11. Toast + thread loading
---------------------------------------------------------------------------- */
.toast {
  position: fixed; inset-block-end: var(--space-8); inset-inline: 0;
  margin-inline: auto; inline-size: max-content; max-inline-size: 90vw;
  z-index: var(--z-toast);
  padding: var(--space-3) var(--space-5);
  background: var(--color-text-primary); color: var(--color-bg);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  opacity: 0; transform: translateY(8px); pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
.toast.is-visible { opacity: 1; transform: translateY(0); }

/* The undo toast carries a real <button>. `.toast` above is `pointer-events: none` for a
   reason: the pill is centred over the composer, so making the whole surface clickable
   would swallow composer clicks for as long as any toast is up. The modifier therefore
   only lays the row out — clicks are re-enabled on the BUTTON alone (a descendant may opt
   back in under a `pointer-events: none` parent), and only while this variant is present. */
.toast--action { display: flex; align-items: center; gap: var(--space-2); }
.toast--action .toast__msg { min-inline-size: 0; }
.toast--action .toast__undo {
  pointer-events: auto;
  flex: none;
  appearance: none; -webkit-appearance: none;
  border: 0; background: none; cursor: pointer;
  color: inherit; font: inherit;
  font-weight: var(--weight-semibold);
  text-decoration: underline; text-underline-offset: 3px;
  padding: var(--space-1) var(--space-2);
  margin-inline-start: var(--space-1);
  border-radius: var(--radius-sm);
  transition: opacity var(--dur-fast) var(--ease-out);
}
.toast--action .toast__undo:hover { opacity: 0.7; }
.toast--action .toast__undo:focus-visible { outline: 2px solid var(--color-bg); outline-offset: 2px; }

.thread-loading {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: var(--space-16) 0;
}
/* Three graduations catching the light in turn, not three bouncing balls. The dots used to
   hop 4px vertically — a toy-app idiom that reads as impatience, and one that belongs to no
   part of an instrument. Weight and brightness travel along the row instead, which says
   "working" without anything moving out of place. */
.thread-loading__dot {
  inline-size: 8px; block-size: 8px; border-radius: var(--radius-pill);
  background: var(--color-text-muted);
  animation: thread-glow 1.2s var(--ease-inout) infinite;
}
.thread-loading__dot:nth-child(2) { animation-delay: 0.18s; }
.thread-loading__dot:nth-child(3) { animation-delay: 0.36s; }
@keyframes thread-glow {
  0%, 80%, 100% { opacity: 0.28; background: var(--color-text-muted); }
  40%           { opacity: 1;    background: var(--color-accent); }
}

/* ----------------------------------------------------------------------------
   11b. Skeletons — content-shaped loading placeholders.
   No palette change: everything is derived from existing theme tokens, so it
   adapts to light/dark automatically. The global prefers-reduced-motion rule
   (top of file) already freezes the shimmer into a calm static block, so no
   per-rule motion guard is needed here.
---------------------------------------------------------------------------- */
.skeleton {
  display: block;
  position: relative;
  overflow: hidden;
  background: var(--color-bg-subtle);
  border-radius: var(--radius-sm);
}
.skeleton::after {
  content: "";
  position: absolute; inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--color-text-muted) 16%, transparent) 50%,
    transparent 100%
  );
  animation: skeleton-sweep 1.5s ease-in-out infinite;
}
@keyframes skeleton-sweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}
.skeleton--line { block-size: 11px; border-radius: var(--radius-xs); }

/* Sidebar history skeleton — mirrors the 36px chat-item rows */
.hist-skel { display: flex; flex-direction: column; gap: 2px; padding-block: var(--space-1); }
.hist-skel__label { block-size: 9px; inline-size: 44px; margin: var(--space-3) var(--space-3) var(--space-2); border-radius: var(--radius-xs); }
.hist-skel__row { display: flex; align-items: center; block-size: 36px; padding-inline: var(--space-3); }
.hist-skel__bar { block-size: 10px; border-radius: var(--radius-xs); }

/* Message-thread skeleton — mirrors the real .turn / .msg-user / .msg-ai layout */
.thread-skel { padding-block: var(--space-6) var(--space-4); }
.thread-skel__turn { margin-bottom: var(--space-8); }
.thread-skel__turn--user { display: flex; justify-content: flex-end; }
.thread-skel__bubble {
  max-inline-size: 62%; inline-size: 62%; min-block-size: 44px;
  border-radius: var(--radius-lg);
}
.thread-skel__head { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); }
.thread-skel__avatar { inline-size: 24px; block-size: 24px; border-radius: 26%; flex: 0 0 auto; }
.thread-skel__name { inline-size: 68px; block-size: 11px; }
.thread-skel__lines { display: grid; gap: 10px; }

/* Firas Code recent-projects grid skeleton — injected into .cw-home__grid,
   sized to match the real .cw-proj tiles (~58px). */
.cw-skel-card { block-size: 58px; border-radius: var(--radius-lg); border: var(--border-hairline) solid var(--color-border-hairline); }

/* Recoverable in-thread error (failed conversation load) — not a dead blank */
.thread-error {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--space-1); padding: var(--space-16) var(--space-6);
  color: var(--color-text-secondary);
  animation: msg-in var(--dur-slow) var(--ease-out);
}
.thread-error__icon { color: var(--color-error); display: grid; place-items: center; margin-bottom: var(--space-2); }
.thread-error__title { margin: 0; font-family: var(--font-serif); font-size: var(--text-md); font-weight: var(--weight-semibold); color: var(--color-text-primary); }
.thread-error__sub { margin: 0; font-size: var(--text-sm); }
.thread-error__retry {
  margin-top: var(--space-4);
  padding: var(--space-2) var(--space-5);
  background: var(--color-accent); color: var(--color-on-accent);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.thread-error__retry:hover { background: var(--color-accent-hover); }
.thread-error__retry:active { transform: scale(0.97); }

/* ----------------------------------------------------------------------------
   11c. User reading-size + motion preferences (Settings). Same theme; only the
   conversation text scales, so chrome/layout is untouched.
---------------------------------------------------------------------------- */
:root { --read-scale: 1; }
:root[data-fontsize="sm"] { --read-scale: 0.92; }
:root[data-fontsize="lg"] { --read-scale: 1.14; }
.msg-ai__body { font-size: calc(var(--text-lg) * var(--read-scale)); line-height: calc(var(--leading-lg) * var(--read-scale)); }
.msg-user__bubble { font-size: calc(var(--text-base) * var(--read-scale)); line-height: calc(var(--leading-base) * var(--read-scale)); }

/* ══ READING COMFORT ═══════════════════════════════════════════════════════════════════
   Two axes the three-step segment above cannot express: a zoom finer than its steps, and a
   LEADING that moves on its own — which is the half Arabic actually needs.

   Gated on [data-reading], which app.js stamps only once the reader has moved something. At the
   defaults these rules are not in the cascade at all, so a thread nobody has adjusted renders
   byte-for-byte as it did before. Higher specificity than the two rules it extends on purpose:
   an appended copy at equal weight would win on order today and lose it the moment another
   patch appends after this one. */
:root[data-reading] .msg-ai__body {
  font-size: calc(var(--text-lg) * var(--read-scale) * var(--read-zoom, 1));
  line-height: calc(var(--leading-lg) * var(--read-scale) * var(--read-lead, 1));
}
/* RESTATED FOR ARABIC, and this line is the reason the control exists at all.
   `.msg-ai__body[dir="rtl"]` pins Arabic leading at a flat 1.9 and outranks the rule above it,
   so the language whose lines most need opening up was the one language where nothing moved.
   (0,4,0) here beats that (0,2,0); the size axis is inherited from the rule above. */
:root[data-reading] .msg-ai__body[dir="rtl"] { line-height: calc(1.9 * var(--read-lead, 1)); }

.read-panel {
  position: fixed;
  z-index: 1200;
  inline-size: min(88vw, 268px);
  padding: 6px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: 16px;
  background: var(--color-surface);
  box-shadow: 0 4px 10px rgba(0,0,0,.08), 0 18px 48px rgba(0,0,0,.24);
}
.read-panel__h {
  padding: 8px 10px 6px;
  font: 700 10.5px var(--font-sans);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--color-text-muted);
}
[dir="rtl"] .read-panel__h { font-family: var(--font-arabic); letter-spacing: 0; text-transform: none; }
.read-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 6px 10px;
}
.read-row__l { font: 400 12.5px var(--font-sans); color: var(--color-text-secondary); }
[dir="rtl"] .read-row__l { font-family: var(--font-arabic); }
/* Laid out start-to-end, never with a physical left/right: the panel carries dir="rtl" in
   Arabic and the minus has to land on the side the reader reaches for in BOTH directions. */
.read-step { display: flex; align-items: center; gap: 2px; }
.read-step__b {
  inline-size: 26px; block-size: 26px;
  display: grid; place-items: center;
  border: 0; border-radius: 8px;
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
  font: 600 14px var(--font-sans); line-height: 1;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.read-step__b:hover:not(:disabled) { background: var(--color-border-hairline); color: var(--color-text-primary); }
.read-step__b:disabled { opacity: .35; cursor: default; }
.read-step__b:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.read-step__v {
  min-inline-size: 36px; text-align: center;
  font: 600 10.5px var(--font-mono);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
.read-panel__r {
  inline-size: 100%;
  margin-block-start: 2px;
  padding: 7px 10px;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--color-text-muted);
  font: 500 12px var(--font-sans); text-align: center;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.read-panel__r:hover:not(:disabled) { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.read-panel__r:disabled { opacity: .4; cursor: default; }
.read-panel__r:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
[dir="rtl"] .read-panel__r { font-family: var(--font-arabic); }

/* == VOICE NOTE =============================================================
   The topbar control and its panel. Sized, coloured and shadowed off .read-panel
   above, its neighbour in the same bar: two popovers one press apart that did not
   match would read as two different applications. */
.vnote-btn { position: relative; }
/* A dot, not a number. The button says THERE IS ONE; the panel a press away says how long.
   Inline-end, so it lands on the same corner of the button in both directions. */
.vnote-btn.has-note::after {
  content: "";
  position: absolute;
  inset-block-start: 6px; inset-inline-end: 6px;
  inline-size: 6px; block-size: 6px;
  border-radius: 50%;
  background: var(--color-accent);
}
.vnote-panel {
  position: fixed;
  z-index: 1200;
  inline-size: min(88vw, 276px);
  padding: 6px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: 16px;
  background: var(--color-surface);
  box-shadow: 0 4px 10px rgba(0,0,0,.08), 0 18px 48px rgba(0,0,0,.24);
}
.vnote-panel__h {
  padding: 8px 10px 6px;
  font: 700 10.5px var(--font-sans);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--color-text-muted);
}
[dir="rtl"] .vnote-panel__h { font-family: var(--font-arabic); letter-spacing: 0; text-transform: none; }
.vnote-panel__f {
  margin: 0;
  padding: 2px 10px 6px;
  font: 400 11px var(--font-sans); line-height: 1.45;
  color: var(--color-text-muted);
}
[dir="rtl"] .vnote-panel__f { font-family: var(--font-arabic); }
/* Start-to-end, never a physical left/right: the panel carries dir="rtl" in Arabic and every
   control in this row has to land on the side the reader reaches for in BOTH directions. */
.vnote-row { display: flex; align-items: center; gap: 8px; padding: 4px 10px 6px; }
.vnote-sp { flex: 1 1 auto; }
.vnote-b {
  flex: none;
  inline-size: 30px; block-size: 30px;
  display: grid; place-items: center;
  border: 0; border-radius: 9px;
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.vnote-b svg { inline-size: 15px; block-size: 15px; }
.vnote-b:hover { background: var(--color-border-hairline); color: var(--color-text-primary); }
.vnote-b:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.vnote-b--stop { background: var(--color-accent); color: #fff; }
.vnote-b--stop:hover { background: var(--color-accent); color: #fff; }
.vnote-b--del.is-armed { background: var(--color-error); color: #fff; }
.vnote-b--del.is-armed:hover { background: var(--color-error); color: #fff; }
/* The microphone is LIVE, which is the one thing a recorder must never be quiet about. Under
   "reduce motion" the global rule above stops the pulse; the running clock and the two buttons
   beside it still say it, so nothing is carried by the animation alone. */
.vnote-dot {
  flex: none;
  inline-size: 7px; block-size: 7px; border-radius: 50%;
  background: var(--color-accent);
  animation: vnote-live 1.4s ease-in-out infinite;
}
@keyframes vnote-live { 0%, 100% { opacity: 1; } 50% { opacity: .28; } }
.vnote-track {
  flex: 1 1 auto; min-inline-size: 36px;
  block-size: 4px; border-radius: 999px;
  background: var(--color-border-hairline);
  overflow: hidden;
}
.vnote-track__f {
  block-size: 100%; inline-size: 0%;
  border-radius: 999px;
  background: var(--color-accent);
}
.vnote-time {
  flex: none;
  font: 600 10.5px var(--font-mono);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
/* The Arabic-Indic numerals vnClock returns need the Arabic face. The element keeps its own
   dir="ltr" regardless — a clock still reads left to right. */
[dir="rtl"] .vnote-time { font-family: var(--font-arabic); }
.vnote-start {
  inline-size: 100%;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 9px 12px;
  border: 0; border-radius: var(--radius-sm);
  background: var(--color-bg-subtle);
  color: var(--color-text-primary);
  font: 500 12.5px var(--font-sans);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.vnote-start:hover:not(:disabled) { background: var(--color-border-hairline); }
.vnote-start:disabled { opacity: .5; cursor: default; }
.vnote-start:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.vnote-start__i { display: grid; place-items: center; color: var(--color-accent); }
.vnote-start__i svg { inline-size: 16px; block-size: 16px; }
[dir="rtl"] .vnote-start { font-family: var(--font-arabic); }

/* ══ PARALLEL TEXT (the Agent's translate_pair step) ═══════════════════════════════════
   Two columns that stay level. The row is the grid — not the column — so a long Arabic segment
   and its shorter English counterpart share one row height and the pair never drifts apart
   further down the page, which is the entire reason this is not two stacked lists. */
.fpr {
  margin-block: var(--space-5);
  border: var(--border-hairline) solid var(--color-border);
  border-radius: 14px;
  overflow: hidden;
  background: var(--color-surface);
}
.fpr__r { display: grid; grid-template-columns: 1fr 1fr; }
.fpr__r + .fpr__r { border-block-start: var(--border-hairline) solid var(--color-border-hairline); }
/* Sized in em, so the reading control above reaches the columns too — a bilingual page the
   reader cannot enlarge is the page that most needed it. */
.fpr__c {
  min-inline-size: 0;
  padding: 11px 14px;
  font-family: var(--font-sans);
  font-size: .94em; line-height: 1.7;
  color: var(--color-text-primary);
  overflow-wrap: break-word; word-break: normal;
}
.fpr__c[dir="rtl"] { font-family: var(--font-arabic); line-height: 1.95; }
/* Logical, so the divider sits between the columns whichever one leads. */
.fpr__c + .fpr__c { border-inline-start: var(--border-hairline) solid var(--color-border-hairline); }
.fpr__hd { background: var(--color-bg-subtle); }
.fpr__hd .fpr__c {
  padding-block: 7px;
  font-size: 10.5px; font-weight: 700; line-height: 1.4;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--color-text-muted);
}
.fpr__hd .fpr__c[dir="rtl"] { letter-spacing: 0; text-transform: none; }
/* On a phone the answer column IS the screen, and two columns of prose in 360px is two columns
   of single words. Stacked, the pair still reads — original, then its counterpart directly
   under it — which is the one degradation that keeps the alignment meaningful. */
@media (max-width: 640px) {
  .fpr__r { grid-template-columns: 1fr; }
  .fpr__c + .fpr__c {
    border-inline-start: 0;
    border-block-start: var(--border-hairline) dashed var(--color-border-hairline);
  }
}

/* Explicit "reduce motion" toggle — mirrors the prefers-reduced-motion rule */
/* The explicit in-app "Reduce motion" toggle. Mirrors the media-query rule above exactly —
   including keeping busy indicators alive — so choosing it in Settings and setting it at the
   OS level produce the same product, not two different ones. */
:root[data-motion="off"] *,
:root[data-motion="off"] *::before,
:root[data-motion="off"] *::after {
  animation-duration: 0.001ms !important;
  animation-delay: 0ms !important;
  transition-duration: 0.001ms !important;
  scroll-behavior: auto !important;
}
:root[data-motion="off"] body::before { display: none; }
:root[data-motion="off"] .landing__cta.is-busy::after,
:root[data-motion="off"] .auth__submit.is-busy::after,
:root[data-motion="off"] .cw-bar__btn--primary.is-busy .cw-bar__btn-ico,
:root[data-motion="off"] .cw-live__spin,
:root[data-motion="off"] .cw-plan__spin,
:root[data-motion="off"] .cw-status__save.is-saving::before,
:root[data-motion="off"] .thread-loading__dot,
:root[data-motion="off"] .firas-ask-loading__dots > span,
:root[data-motion="off"] .skeleton,
:root[data-motion="off"] .stream-caret::after {
  animation-name: rm-working !important;
  animation-duration: 1.5s !important;
  animation-iteration-count: infinite !important;
  animation-timing-function: var(--ease-inout) !important;
  transform: none !important;
}

/* ----------------------------------------------------------------------------
   12. Image attachments (composer tray + in-message thumbnails)
---------------------------------------------------------------------------- */
/* Composer is a column: optional attachment tray sits above the input row */
.composer__row {
  display: flex; align-items: flex-end; gap: var(--space-2); inline-size: 100%;
}

.attach-tray {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  padding: 2px var(--space-2) var(--space-3);
}
.attach-tray:empty { display: none; }
.attach-thumb {
  position: relative; inline-size: 56px; block-size: 56px;
  border-radius: var(--radius-sm); overflow: hidden;
  border: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-bg-subtle); flex: 0 0 auto;
}
.attach-thumb img { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }
.attach-thumb.is-loading::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  inline-size: 16px; block-size: 16px;
  border: 2px solid var(--color-accent); border-top-color: transparent;
  border-radius: var(--radius-pill); animation: auth-spin 0.7s linear infinite;
}
.attach-thumb__remove {
  position: absolute; inset-block-start: 2px; inset-inline-end: 2px;
  inline-size: 18px; block-size: 18px; border-radius: var(--radius-pill);
  display: grid; place-items: center;
  background: rgba(0,0,0,0.55); color: #fff;
  transition: background var(--dur-fast);
}
.attach-thumb__remove:hover { background: rgba(0,0,0,0.75); }
.attach-thumb__remove svg { inline-size: 11px; block-size: 11px; }

/* Images rendered inside a sent user bubble */
.msg-user__images {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin-block-end: var(--space-2);
}
.msg-user__images img {
  max-inline-size: 180px; max-block-size: 180px;
  /* Follows the bubble's corner instead of cutting across it: the usable inner radius is
     (--soft-radius minus the padding), so a 3px square-ish corner read as a notch in the arc. */
  border-radius: var(--radius-lg);
  border: var(--border-hairline) solid var(--color-border-hairline);
  display: block; object-fit: cover;
}
.msg-user__bubble:has(.msg-user__images:only-child) { /* image-only bubble stays tidy */ }
.msg-user__images img { cursor: zoom-in; transition: filter var(--dur-fast) var(--ease-out); }
.msg-user__images img:hover { filter: brightness(1.06); }

/* Full-screen attached-image viewer */
.img-lightbox {
  position: fixed; inset: 0; z-index: 9999;
  display: flex; align-items: center; justify-content: center;
  padding: 24px; background: rgba(0, 0, 0, 0.82);
  cursor: zoom-out; animation: lbFade 0.15s ease;
}
@supports (backdrop-filter: blur(2px)) { .img-lightbox { backdrop-filter: blur(2px); } }
.img-lightbox__img {
  max-inline-size: 94vw; max-block-size: 92vh;
  border-radius: 8px; background: #fff; cursor: default;
  box-shadow: 0 16px 56px rgba(0, 0, 0, 0.55);
}
.img-lightbox__close {
  position: absolute; top: 16px; inset-inline-end: 20px;
  inline-size: 40px; block-size: 40px; border-radius: 50%; border: 0;
  background: rgba(255, 255, 255, 0.16); color: #fff;
  font-size: 26px; line-height: 1; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.img-lightbox__close:hover { background: rgba(255, 255, 255, 0.3); }
@keyframes lbFade { from { opacity: 0; } to { opacity: 1; } }

/* ----------------------------------------------------------------------------
   13. KaTeX math — keep LTR for formulas even inside RTL bubbles
---------------------------------------------------------------------------- */
.md .katex, .md .katex-display { direction: ltr; unicode-bidi: isolate; }
.md .katex-display { overflow-x: auto; overflow-y: hidden; padding-block: 2px; }
[dir="rtl"] .md .katex-display { text-align: center; }
/* User-message math must ALSO stay LTR inside an RTL (Arabic) bubble, else the equation renders reversed */
.msg-user__bubble .katex, .msg-user__bubble .katex-display { direction: ltr; unicode-bidi: isolate; }
.msg-user__bubble .katex-display { overflow-x: auto; overflow-y: hidden; padding-block: 2px; }
[dir="rtl"] .msg-user__bubble .katex-display { text-align: center; }

/* ----------------------------------------------------------------------------
   14. Export menu (Download / تصدير) — anchored dropdown on assistant actions
---------------------------------------------------------------------------- */
.export-menu { position: relative; display: inline-flex; }
.export-menu__trigger .export-menu__caret {
  display: inline-flex; margin-inline-start: 1px;
}
.export-menu__trigger .export-menu__caret svg { inline-size: 13px; block-size: 13px; }
.export-menu__list {
  position: absolute; inset-block-end: calc(100% + 6px); inset-inline-start: 0;
  z-index: var(--z-dropdown);
  min-inline-size: 196px;
  padding: var(--space-1);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 1px;
  animation: msg-in var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .export-menu__list { inset-inline-start: auto; inset-inline-end: 0; }
.export-menu__item {
  display: flex; align-items: center; gap: var(--space-2);
  inline-size: 100%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-size: var(--text-sm); color: var(--color-text-secondary);
  text-align: start; white-space: nowrap;
  transition: background var(--dur-fast), color var(--dur-fast);
}
[dir="rtl"] .export-menu__item { font-family: var(--font-arabic); }
.export-menu__item:hover, .export-menu__item:focus-visible {
  background: var(--color-bg-subtle); color: var(--color-text-primary); outline: none;
}
.export-menu__item svg { inline-size: 16px; block-size: 16px; flex: 0 0 auto; color: var(--color-text-muted); }
.export-menu__item:hover svg, .export-menu__item:focus-visible svg { color: var(--color-accent); }

/* ----------------------------------------------------------------------------
   SHARE CARD — an answer as a PNG (exportImage() in app.js).

   Not app chrome: an off-screen page that exists for exactly as long as html2canvas needs to
   photograph it. It lives here rather than in a JS style string for two reasons. It uses the
   same tokens as everything else, so it follows the reader's theme in all six of them instead
   of freezing one palette into JavaScript. And the answer inside it is the app's OWN `.md`
   subtree, so every rule that shapes an answer on screen shapes it here too — which is the
   whole premise of rasterising real DOM rather than drawing the text.

   What must be RESTATED are the few rules the thread scopes to `.msg-ai__body`, which this
   card is not inside: per-block alignment, and the isolation that keeps an equation out of the
   bidi run of the sentence around it.
---------------------------------------------------------------------------- */
#firasImgCardRoot {
  /* PHYSICAL left/top on purpose. `inset-inline-start` resolves against this element's OWN
     direction, and this element carries dir="rtl" for an Arabic answer — the parking spot
     would flip to the right edge with it. An off-screen offset is not layout. */
  position: fixed; top: 0; left: -10000px; z-index: -1;
  /* exportImage() writes --imgcard-w from IMG_CARD_W; the literal is the fallback only. */
  inline-size: var(--imgcard-w, 720px);
  padding: var(--space-5);
  background: var(--color-bg);
  font-family: var(--font-sans);
  font-size: var(--text-base); line-height: var(--leading-base);
  color: var(--color-text-primary);
  -webkit-font-smoothing: antialiased;
}
.imgcard {
  padding: var(--space-6) var(--space-6) var(--space-5);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
}
.imgcard__top {
  display: flex; align-items: center; gap: var(--space-2);
  padding-block-end: var(--space-4);
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
}
.imgcard__mark { display: inline-flex; }
.imgcard__mark svg { display: block; }
.imgcard__word {
  font-family: var(--font-display);
  font-weight: var(--weight-semibold);
  font-size: var(--text-lg);
  letter-spacing: normal;              /* never tracking — see the shaping invariant below */
  color: var(--color-text-primary);
  direction: ltr; unicode-bidi: isolate;
  white-space: nowrap;
}
.imgcard__q {
  margin-block: var(--space-5) var(--space-4);
  padding-inline-start: var(--space-3);
  border-inline-start: var(--border-strong) solid var(--color-accent);
}
.imgcard__q-k {
  display: block;
  font-family: var(--font-sans);
  font-size: var(--text-xs); line-height: var(--leading-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-accent);
  margin-block-end: var(--space-1);
}
.imgcard__q-t {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-md); line-height: var(--leading-md);
  font-weight: var(--weight-medium);
  color: var(--color-text-primary);
  text-align: start;
}
/* Restated from `.msg-ai__body .md > *` — the card is not inside the thread, and without this
   every block falls back to the ROOT's alignment instead of its own resolved direction. */
.imgcard__a > p,
.imgcard__a > h1, .imgcard__a > h2, .imgcard__a > h3,
.imgcard__a > h4, .imgcard__a > h5, .imgcard__a > h6,
.imgcard__a > ul > li, .imgcard__a > ol > li,
.imgcard__a > blockquote,
.imgcard__a td, .imgcard__a th { text-align: start; }
/* An equation on its own line is never a participant in a bidi run, so it can never be
   reordered against the sentence around it. Same rule the thread applies, same reason. */
.imgcard__a .katex-display {
  display: block;
  direction: ltr; unicode-bidi: isolate;
  text-align: center;
  margin-block: var(--space-4);
  overflow: hidden; padding-block: 2px;   /* exportImage shrinks a too-wide one first; this is the backstop */
}
.imgcard__a :not(.katex-display) > .katex { direction: ltr; unicode-bidi: isolate; }
/* A PHOTOGRAPH HAS NO SCROLLBARS. Every `overflow-x: auto` in the thread is a promise the user
   can drag — in a PNG it is just content silently cut off at the card's edge. So the two
   scrolling blocks wrap instead. */
.imgcard__a table { display: table; inline-size: 100%; overflow: visible; }
.imgcard__a pre { white-space: pre-wrap; overflow-wrap: anywhere; overflow: visible; }
.imgcard__a .code-block { overflow: hidden; }
.imgcard__a img, .imgcard__a svg { max-inline-size: 100%; }
/* html2canvas mis-paints ::marker digits (the documented "60" printing as "30"), so
   imgCardNumberLists() replaces each marker with a real span. These make it look like one. */
.imgcard__a ol.li-explicit { list-style: none; padding-inline-start: 0; }
.imgcard__a ol.li-explicit > li { display: flex; align-items: flex-start; gap: 2px; }
.imgcard__a ol.li-explicit > li > .li-n {
  flex: 0 0 auto; min-inline-size: 2.2em; text-align: end;
  padding-inline-end: var(--space-2);
  color: var(--color-accent); font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}
.imgcard__a ol.li-explicit > li > .li-body { flex: 1 1 auto; min-inline-size: 0; }
.imgcard__cut {
  margin-block-start: var(--space-4);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-bg-subtle);
  font-family: var(--font-sans);
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-secondary);
  text-align: start;
}
/* WHERE THIS PAGE IS IN THE SET — drawn only when there IS a set, and deliberately NOT a
   footer: no rule above it, no site, no date. It is a page number, the one thing a reader needs
   to put four pictures back into order. LTR and isolated because "2 / 4" is a pair of numbers
   and must never mirror inside an Arabic card. */
.imgcard__pg {
  margin-block-start: var(--space-5);
  font-family: var(--font-sans);
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-muted);
  text-align: center;
  direction: ltr; unicode-bidi: isolate;
  font-variant-numeric: tabular-nums;
}
.imgcard__foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  margin-block-start: var(--space-5);
  padding-block-start: var(--space-3);
  border-block-start: var(--border-hairline) solid var(--color-border-hairline);
  font-family: var(--font-sans);
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-muted);
}
.imgcard__site { direction: ltr; unicode-bidi: isolate; }

/* THE ARABIC FACE, STATED — not left to inheritance luck. The card is photographed once and
   a fallback face that cannot shape Arabic is unrecoverable, so the two card-owned text blocks
   and any block the dir pass marked rtl are pinned to --font-arabic. `pre`/`code` are excluded
   because a monospace block marked rtl is still code and still wants the mono face. */
.imgcard-root--ar .imgcard__a,
.imgcard-root--ar .imgcard__q-t,
.imgcard-root--ar .imgcard__cut,
.imgcard__a [dir="rtl"]:not(pre):not(code),
.imgcard__q[dir="rtl"] .imgcard__q-t { font-family: var(--font-arabic); }

/* THE SHAPING INVARIANT, restated for the card — the same one exportCss pins for the PDF.
   html2canvas switches to per-grapheme drawing the instant computed letter-spacing is
   non-zero, and per-grapheme Arabic paints isolated letterforms over one another. `.md h1-h3`
   carry --tracking-tight, so this is not hypothetical. Two STATIC selectors and no `:dir()`:
   an in-app WebView that does not know the modern pseudo-class would drop the whole rule and
   take the bug back with it. Tracking is a Latin device; the Arabic card loses nothing. */
.imgcard-root--ar *,
#firasImgCardRoot [dir="rtl"],
#firasImgCardRoot [dir="rtl"] * { letter-spacing: normal !important; }

/* ----------------------------------------------------------------------------
   File card — Claude-style downloadable file, shown when the user asked for a
   file (PDF / Word / Excel / PowerPoint). Bordered surface, teal accent.
---------------------------------------------------------------------------- */
.file-card {
  display: flex; align-items: center; gap: var(--space-3);
  inline-size: 100%; max-inline-size: 420px;
  margin-block: var(--space-3) var(--space-1);
  padding: var(--space-3);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.file-card:hover {
  border-color: var(--color-accent);
  box-shadow: var(--shadow-sm);
}
.file-card:focus-visible {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}
.file-card__icon {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 40px; block-size: 40px; flex: 0 0 auto;
  border-radius: var(--radius-md);
  background: var(--color-accent-soft);
  color: var(--color-accent);
}
.file-card__icon svg { inline-size: 22px; block-size: 22px; }
.file-card__info {
  display: flex; flex-direction: column; gap: 2px;
  min-inline-size: 0; flex: 1 1 auto;
}
.file-card__name {
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  color: var(--color-text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  text-align: start;
}
.file-card__label {
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-muted);
}
[dir="rtl"] .file-card__label { font-family: var(--font-arabic); }
.file-card__dl {
  display: inline-flex; align-items: center; gap: var(--space-1);
  flex: 0 0 auto;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .file-card__dl { font-family: var(--font-arabic); }
.file-card__dl:hover { background: var(--color-accent-hover); }
.file-card__dl:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.file-card__dl svg { inline-size: 15px; block-size: 15px; }

/* ---- Generated image card ---- */
/* ══ THE IMAGE CARD ════════════════════════════════════════════════════════════
   The plate is SMALLER than the finished picture while it works. It is a placeholder, not
   the picture, and should not claim the final size; growing into the real one at the end
   turns the arrival into a change of state rather than a swap. Every colour below is a theme
   token, so the whole thing is teal on Dark, navy on Midnight and brass on Amber for free. */
.image-card {
  inline-size: 100%; max-inline-size: 420px;
  margin-block: var(--space-3) var(--space-1);
  transition: max-inline-size 900ms var(--ease-out);
}
.image-card.is-loading { max-inline-size: 300px; }

.image-card__frame {
  position: relative; overflow: hidden;
  inline-size: 100%;
  /* --img-ar is set from the file's OWN naturalWidth/Height the instant it decodes, before the
     reveal begins, so the frame grows straight into the picture's shape instead of snapping to
     a square and then correcting. */
  aspect-ratio: var(--img-ar, 1 / 1);
  border-radius: var(--soft-radius);
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--color-border-hairline);
  transition: aspect-ratio 900ms var(--ease-out);
}
.image-card.is-loading .image-card__frame { aspect-ratio: 1 / 1; }

/* THE SHEEN. One pass, top-right to bottom-left, only during the reveal. Deliberately
   PHYSICAL translate values, not logical: the sweep is a light source crossing a plate, so it
   must fall the same way in Arabic as in English — this is the one place in this file where a
   logical property would be the bug rather than the fix. */
.image-card__frame::after {
  content: ""; position: absolute; inset: -25%;
  z-index: 3; pointer-events: none; opacity: 0;
  background: linear-gradient(205deg,
    transparent 40%,
    color-mix(in srgb, #FFFFFF 24%, transparent) 50%,
    transparent 60%);
  transform: translate3d(65%, -65%, 0);
}
.image-card.is-revealing .image-card__frame::after {
  animation: imgSheen 1100ms cubic-bezier(.4, 0, .2, 1) 360ms both;
}
@keyframes imgSheen {
  0%   { opacity: 0; transform: translate3d(65%, -65%, 0); }
  15%  { opacity: 1; }
  85%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d(-65%, 65%, 0); }
}

/* ── The dot field ─────────────────────────────────────────────────────────── */
.image-card__loader {
  position: absolute; inset: 0; z-index: 2;
  opacity: 1; transition: opacity 620ms var(--ease-out);
}
.image-card.is-revealing .image-card__loader { opacity: 0; }
.image-card.is-done .image-card__loader { display: none; }
.imgload__dots { position: absolute; inset: 0; display: block; inline-size: 100%; block-size: 100%; }
/* The header follows the LANGUAGE: the element carries its own dir, so inset-inline-start puts
   the pulse and the word on the right in Arabic and on the left in English. */
.imgload__head {
  position: absolute; inset-block-start: 0; inset-inline-start: 0;
  display: inline-flex; align-items: center; gap: 10px;
  padding: 18px 20px;
  font-family: var(--font-sans); font-size: 13.5px; font-weight: var(--weight-medium);
  color: var(--color-text-primary);
  user-select: none;
}
.imgload__head[dir="rtl"], [dir="rtl"] .imgload__head { font-family: var(--font-arabic); }
.imgload__pulse { position: relative; flex: 0 0 auto; inline-size: 8px; block-size: 8px; display: inline-flex; align-items: center; justify-content: center; }
.imgload__pulse i {
  inline-size: 6.5px; block-size: 6.5px; border-radius: 50%;
  background: var(--color-accent); box-shadow: 0 0 12px var(--color-accent-ring);
}
.imgload__pulse::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  border: 1px solid var(--color-accent);
  animation: imgPulseRing 2.2s cubic-bezier(.2, .8, .2, 1) infinite;
}
@keyframes imgPulseRing {
  0%        { transform: scale(.8); opacity: .85; }
  80%, 100% { transform: scale(2.8); opacity: 0; }
}
.imgload__word { transition: opacity .24s var(--ease-out), transform .24s var(--ease-out); }
.imgload__word.is-out { opacity: 0; transform: translateY(-4px); }

/* Failure keeps the plate and the word, drops everything that implies progress. */
.image-card.is-error .imgload__dots,
.image-card.is-error .imgload__pulse { display: none; }
.image-card.is-error .imgload__word { color: var(--color-error); }

/* ── The failure plate ─────────────────────────────────────────────────
   A sentence and a way out, in the bottom of the plate that was going to hold the picture —
   below the headline the loader's own word already carries at the top, so the two do not fight
   for the same corner. It exists only in .is-error, so a card that works never pays for it.
   The button is a full 44px tall: this is the one control on a card whose whole job is to be
   pressed on the phone where the picture failed. */
.imgload__fail {
  position: absolute; inset: 0;
  z-index: 4;
  display: none;
  flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-3);
  /* Clear of the headline the loader's own head is still carrying in the top corner. */
  padding: calc(var(--space-4) + 34px) var(--space-4) var(--space-4);
  text-align: center;
}
.image-card.is-error .imgload__fail { display: flex; }
/* A FAILED PLATE IS NOT A PICTURE, so it does not take a picture's room. Left at the finished
   card's 420px square it was a large empty box with two lines adrift in the middle of it — the
   layout of something that arrived, wrapped around something that did not. */
.image-card.is-error { max-inline-size: 340px; }
.image-card.is-error .image-card__frame { aspect-ratio: 4 / 3; }
.imgload__why {
  margin: 0;
  color: var(--color-text-secondary);
  font-family: var(--font-sans); font-size: var(--text-sm); line-height: 1.55;
  overflow-wrap: anywhere;
}
.imgload__fail[dir="rtl"] .imgload__why,
.imgload__fail[dir="rtl"] .imgload__retry { font-family: var(--font-arabic); }
.imgload__retry {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; min-block-size: 44px;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--color-accent); color: var(--color-on-accent);
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: var(--weight-medium);
  cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}
.imgload__retry:hover { background: var(--color-accent-hover); }
.imgload__retry:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.imgload__retry[disabled] { opacity: .6; cursor: default; }

/* The picture is a real control once there is really a picture, so it needs a real ring. */
.image-card__img:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }


@media (prefers-reduced-motion: reduce) {
  .imgload__pulse::after { animation: none; }
  .image-card.is-revealing .image-card__frame::after { animation: none; }
}
:root[data-motion="off"] .imgload__pulse::after,
:root[data-motion="off"] .image-card.is-revealing .image-card__frame::after { animation: none; }

.image-card__img {
  display: block; position: absolute; inset: 0;
  inline-size: 100%; block-size: 100%; object-fit: cover;
  opacity: 0; transform: scale(1.04);
  transition: opacity 700ms var(--ease-out), transform 900ms var(--ease-out);
}
.image-card.is-revealing .image-card__img { opacity: 1; transform: scale(1); transition-delay: 500ms; }
.image-card.is-done .image-card__frame { aspect-ratio: auto; }
.image-card.is-done .image-card__img { position: relative; block-size: auto; opacity: 1; transform: none; cursor: zoom-in; }
.image-card__bar {
  display: flex; align-items: center; gap: var(--space-2);
  margin-block-start: var(--space-2);
}
.image-card__cap {
  flex: 1 1 auto; min-inline-size: 0;
  color: var(--color-text-muted); font-size: var(--text-xs);
  font-family: var(--font-sans);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
[dir="rtl"] .image-card__cap { font-family: var(--font-arabic); }
.image-card__dl {
  display: inline-flex; align-items: center; gap: var(--space-1); flex: 0 0 auto;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-accent); color: var(--color-on-accent);
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: var(--weight-medium);
  cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .image-card__dl { font-family: var(--font-arabic); }
.image-card__dl[hidden] { display: none; }
.image-card__dl:hover { background: var(--color-accent-hover); }
.image-card__dl svg { inline-size: 15px; block-size: 15px; }

/* ── THE READER'S CAPTION ─────────────────────────────────────────────────────
   Quieter than the answer it sits under and quieter than the button beside it — it is a note,
   not a heading. It WRAPS, unlike the single-line .image-card__cap the old prompt line used:
   that one was truncated machine text and losing its tail cost nothing, while losing the tail
   of a sentence someone wrote themselves is the whole point going missing.
   The dir is stamped onto the element by the script, from the caption's own script, so these
   are attribute selectors and nothing here reaches up to :root. */
.image-card__note {
  margin-block-start: var(--space-2);
  color: var(--color-text-secondary);
  font-family: var(--font-sans); font-size: var(--text-sm);
  line-height: 1.55;
  overflow-wrap: anywhere;
}
.image-card__note[dir="rtl"] { font-family: var(--font-arabic); }
.image-card__note[hidden] { display: none; }

.image-card__note-in {
  display: block; inline-size: 100%;
  margin-block-start: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  border: var(--border-hairline) solid var(--color-border);
  background: var(--color-bg-subtle); color: var(--color-text-primary);
  font-family: var(--font-sans); font-size: var(--text-sm); line-height: 1.5;
}
.image-card__note-in[dir="rtl"] { font-family: var(--font-arabic); }
.image-card__note-in:focus { outline: 2px solid var(--color-accent-ring); outline-offset: 1px; }

/* Beside Download, never competing with it: the save is what most people came for, so this
   one is a ghost of the same pill rather than a second solid button. */
.image-card__note-btn {
  display: inline-flex; align-items: center; gap: var(--space-1); flex: 0 0 auto;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle); color: var(--color-text-secondary);
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: var(--weight-medium);
  cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .image-card__note-btn { font-family: var(--font-arabic); }
/* The button carries its own dir now, so the font has to follow that and not only an ancestor. */
.image-card__note-btn[dir="rtl"] { font-family: var(--font-arabic); }
.image-card__note-btn[hidden] { display: none; }
.image-card__note-btn:hover { background: var(--color-border-hairline); color: var(--color-text-primary); }
.image-card__note-btn svg { inline-size: 14px; block-size: 14px; }

/* THE BAR NOW CARRIES TWO PILLS, and both of them are `flex: 0 0 auto` and `nowrap`. It was
   built for one fixed button beside a caption that could shrink to nothing (.image-card__cap,
   `flex: 1 1 auto; min-inline-size: 0`), so with two rigid children and no wrap the second one
   simply pushes out of a narrow card — a phone, the share page's column, a card sitting in a
   split view. Wrapping is the whole fix; row-gap keeps the second line off the first.
   Declared here rather than edited into the rule above so the original stays as it was. */
.image-card__bar { flex-wrap: wrap; row-gap: var(--space-2); }


/* ══ FULL-SIZE VIEWER ══════════════════════════════════════════════════════════
   Sized in dvh/dvw so a phone's collapsing address bar cannot crop the picture, and every
   control is inset from the safe area so nothing lands under a notch or a home indicator. */
body.imgview-open { overflow: hidden; }
.imgview {
  position: fixed; inset: 0; z-index: 1400;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px;
  padding: max(64px, calc(env(safe-area-inset-top) + 56px)) 16px max(16px, calc(env(safe-area-inset-bottom) + 8px));
  background: rgba(0, 0, 0, .86);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  opacity: 0; transition: opacity 200ms var(--ease-out);
}
.imgview.is-open { opacity: 1; }
.imgview__stage {
  flex: 1 1 auto; min-block-size: 0;
  inline-size: 100%;
  display: flex; align-items: center; justify-content: center;
}
.imgview__img {
  max-inline-size: min(100%, 1400px);
  max-block-size: 100%;
  inline-size: auto; block-size: auto;
  object-fit: contain;
  border-radius: var(--soft-radius);
  box-shadow: 0 30px 80px rgba(0, 0, 0, .6);
  transform: scale(.96); transition: transform 260ms var(--ease-out);
}
.imgview.is-open .imgview__img { transform: scale(1); }
.imgview__x {
  position: absolute;
  inset-block-start: max(12px, env(safe-area-inset-top));
  inset-inline-end: max(12px, env(safe-area-inset-right));
  inline-size: 40px; block-size: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: rgba(255, 255, 255, .12); color: #fff;
  transition: background 150ms var(--ease-out);
}
.imgview__x:hover { background: rgba(255, 255, 255, .22); }
.imgview__x svg { inline-size: 20px; block-size: 20px; }
.imgview__note {
  flex: 0 0 auto;
  max-inline-size: min(100%, 1400px);
  color: rgba(255, 255, 255, .82);
  font-family: var(--font-sans); font-size: 13.5px; line-height: 1.55;
  text-align: center;
}
.imgview__note[dir="rtl"] { font-family: var(--font-arabic); }
.imgview__bar { flex: 0 0 auto; }
.imgview__dl {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 11px 20px; border-radius: var(--radius-pill);
  background: var(--color-accent); color: var(--color-on-accent);
  font: var(--weight-semibold) 14px var(--font-sans);
  transition: background 150ms var(--ease-out);
}
[dir="rtl"] .imgview__dl { font-family: var(--font-arabic); }
.imgview__dl:hover { background: var(--color-accent-hover); }
.imgview__dl svg { inline-size: 16px; block-size: 16px; }

/* Phones: the picture takes the room, the chrome shrinks out of its way. */
@media (max-width: 560px) {
  .imgview { gap: 10px; padding-inline: 10px; }
  .imgview__x { inline-size: 36px; block-size: 36px; }
  .imgview__dl { padding: 10px 18px; font-size: 13.5px; }
  .imgload__head { padding: 14px 15px; font-size: 12.5px; }
  .image-card.is-loading { max-inline-size: 260px; }
  .imgload__fail { padding: var(--space-3); gap: var(--space-2); }
  .imgload__why { font-size: var(--text-xs); }
}

/* ══ THE GALLERY — every generated picture in the conversation, on one plate ═════
   Same dvh-safe, inset-aware shape as the viewer above, because it is the same kind of
   surface: a full-bleed layer over the app on a phone as much as on a desktop.

   IT SITS BELOW THE VIEWER — 1390 against .imgview's 1400 — so tapping a tile opens the
   picture ON TOP of the grid rather than replacing it, and closing the picture puts the
   reader back exactly where they were looking. Both are children of <body>, never nested,
   so the backdrop-filter here cannot become a containing block for the one above it. */
body.imggal-open { overflow: hidden; }
.imggal {
  position: fixed; inset: 0; z-index: 1390;
  display: flex; flex-direction: column;
  padding-block: max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--color-bg) 95%, transparent);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  opacity: 0; transition: opacity 200ms var(--ease-out);
}
.imggal.is-open { opacity: 1; }

.imggal__head {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--space-3);
  padding: 10px max(14px, env(safe-area-inset-right)) 10px max(14px, env(safe-area-inset-left));
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
}
.imggal__title { display: flex; align-items: baseline; gap: 8px; min-inline-size: 0; }
.imggal__h {
  font: var(--weight-semibold) 15px var(--font-sans);
  color: var(--color-text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The title element carries its OWN dir (the shell is locked LTR and inherits nothing), so
   this is a descendant selector off that stamp, not off anything on :root. */
[dir="rtl"] > .imggal__h { font-family: var(--font-arabic); }
.imggal__n {
  font: 12px var(--font-sans);
  color: var(--color-text-secondary);
  font-variant-numeric: tabular-nums;
}
.imggal__sp { flex: 1; }
/* padding:0 on every button here, and it is not decoration. The global reset at the top of
   this file drops the border and the background but NOT the user-agent's 1px/6px button
   padding — and with border-box that padding eats into a fixed 30/36px control and puts a
   6px gutter down both sides of a tile whose <img> is sized at 100%. */
.imggal__x {
  inline-size: 36px; block-size: 36px; min-inline-size: 36px;
  padding: 0;
  display: grid; place-items: center;
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.imggal__x:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.imggal__x svg { inline-size: 19px; block-size: 19px; }

.imggal__all {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 14px; border-radius: var(--radius-pill);
  background: var(--color-accent); color: var(--color-on-accent);
  font: var(--weight-semibold) 13px var(--font-sans);
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}
.imggal__all:hover { background: var(--color-accent-hover); }
.imggal__all[disabled] { opacity: .6; cursor: default; }
.imggal__all svg { inline-size: 15px; block-size: 15px; }
/* Tabular figures because this label counts up in place — "3 / 12" must not reflow the
   button on every fetch. The dir on it is stamped by the script, and flips to ltr while it
   is showing digits: an Arabic paragraph direction turns "3 / 12" into "12 / 3". */
.imggal__all-tx { font-variant-numeric: tabular-nums; }
.imggal__all-tx[dir="rtl"] { font-family: var(--font-arabic); }

.imggal__grid {
  flex: 1 1 auto; min-block-size: 0;
  overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  display: grid; align-content: start;
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  gap: 12px;
  padding: 16px max(14px, env(safe-area-inset-right)) 28px max(14px, env(safe-area-inset-left));
}
.imggal__cell {
  position: relative;
  margin: 0;                      /* <figure> ships one, and it breaks the grid gutters */
  border-radius: var(--soft-radius);
  overflow: hidden;
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--color-border-hairline);
}
.imggal__open { display: block; inline-size: 100%; padding: 0; aspect-ratio: 1 / 1; cursor: zoom-in; }
/* `cover` on a fixed square: the grid is for FINDING a picture, not judging it, and a wall of
   letterboxed thumbnails in four different aspect ratios is harder to scan than a wall of
   squares. The real shape is one click away in the viewer. */
.imggal__img {
  inline-size: 100%; block-size: 100%;
  display: block; object-fit: cover;
  transition: transform 320ms var(--ease-out);
}
.imggal__cell:hover .imggal__img { transform: scale(1.04); }

.imggal__i {
  position: absolute;
  inset-block-start: 6px; inset-inline-start: 6px;
  min-inline-size: 20px; padding-inline: 5px;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, .55); color: #fff;
  font: 11px/20px var(--font-sans);
  text-align: center; font-variant-numeric: tabular-nums;
  pointer-events: none;
}
.imggal__acts {
  position: absolute;
  inset-block-end: 6px; inset-inline-end: 6px;
  display: flex; gap: 5px;
  opacity: 0; transition: opacity 160ms var(--ease-out);
}
.imggal__cell:hover .imggal__acts,
.imggal__cell:focus-within .imggal__acts { opacity: 1; }
/* Permanent where there is no hover to reveal them with. A tile whose only two actions need a
   mouse is a tile with no actions at all on the device most of these are looked at on. */
@media (hover: none) { .imggal__acts { opacity: 1; } }
.imggal__act {
  inline-size: 30px; block-size: 30px;
  padding: 0;
  display: grid; place-items: center;
  border-radius: 50%;
  background: rgba(0, 0, 0, .58); color: #fff;
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  transition: background 150ms var(--ease-out);
}
.imggal__act:hover { background: rgba(0, 0, 0, .8); }
.imggal__act svg { inline-size: 15px; block-size: 15px; }

@media (max-width: 560px) {
  .imggal__grid { grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: 8px; padding-block: 12px 22px; }
  .imggal__all { padding: 8px 12px; font-size: 12.5px; }
  .imggal__h { font-size: 14px; }
}
@media (prefers-reduced-motion: reduce) {
  .imggal, .imggal__img, .imggal__acts { transition: none; }
  .imggal__cell:hover .imggal__img { transform: none; }
}

/* THE ONLY REPORT THE ARCHIVE HAS IS A TOAST, and `--z-toast` is 500 — under this overlay's
   1390, behind a 95%-opaque ground with a 10px blur on it. The grid stays open while the ZIP
   is being built and stays open after, so "saved", "3 couldn't be fetched" and "that failed
   entirely" all looked identical from in here: nothing happened. Lifted only while the grid is
   up, and still below .imgview's 1400 so it cannot cover an open picture. */
body.imggal-open #toast { z-index: 1395; }

/* Seven controls on a row budgeted for six. The topbar comment above .tier-select spends the
   360/320/280px widths down to the pixel and lands on "279px of 280" — the gallery button is
   a 44px seventh, so on a Fold the row is 25px over. `.tier-select` is `flex: 0 1 auto` and
   DOES give way, but its trigger carries a fixed `inline-size: clamp(80px,…)`, so it kept its
   width and simply printed on top of the new-chat button instead. This is the missing half of
   the shrink that comment already assumes: the name inside is `text-overflow: ellipsis`
   already, so it degrades to a narrower pill instead of an overlap. Bounded to small phones —
   nothing at tablet width or above can see it. */
@media (max-width: 400px) {
  .tier-select__trigger { max-inline-size: 100%; }
}
/* Tablets and up: the loader plate can afford to be a little larger. */
@media (min-width: 768px) {
  .image-card.is-loading { max-inline-size: 320px; }
}

/* ---- Code deliverable card (live editor window) — always dark, editor-style ---- */
.code-card {
  margin-block: var(--space-3) var(--space-1);
  inline-size: 100%;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: var(--border-hairline) solid rgba(255, 255, 255, 0.08);
  background: #0d1117;
  box-shadow: var(--shadow-sm);
  font-family: var(--font-sans);
}
.code-card__head {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 8px 12px;
  background: #161b22;
  border-block-end: 1px solid rgba(255, 255, 255, 0.06);
}
.code-card__dots { display: inline-flex; gap: 6px; flex: 0 0 auto; }
.code-card__dots i { inline-size: 11px; block-size: 11px; border-radius: 50%; display: block; }
.code-card__dots i:nth-child(1) { background: #ff5f56; }
.code-card__dots i:nth-child(2) { background: #ffbd2e; }
.code-card__dots i:nth-child(3) { background: #27c93f; }
.code-card__name {
  color: #e6edf3; font-size: var(--text-xs); font-weight: var(--weight-semibold);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-inline-size: 38%;
}
.code-card__lang {
  flex: 0 0 auto; font-size: 10px; font-weight: var(--weight-semibold);
  color: #9da7b3; background: rgba(255, 255, 255, 0.07);
  padding: 2px 7px; border-radius: var(--radius-pill); letter-spacing: 0.04em;
}
.code-card__count { flex: 0 0 auto; color: #6e7681; font-size: 11px; }
.code-card__spacer { flex: 1 1 auto; }
.code-card__actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.code-card__writing { display: inline-flex; align-items: center; gap: 6px; color: #7d8590; font-size: 11px; }
.code-card__writing::before {
  content: ""; inline-size: 7px; block-size: 7px; border-radius: 50%;
  background: #27c93f; animation: codePulse 1.1s ease-in-out infinite;
}
@keyframes codePulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(39, 201, 63, 0.5); }
  50% { box-shadow: 0 0 0 5px rgba(39, 201, 63, 0); }
}
.code-card__btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 9px; border-radius: var(--radius-md);
  background: rgba(255, 255, 255, 0.06); color: #c9d1d9;
  font-size: 11px; font-weight: var(--weight-medium); cursor: pointer;
  border: 1px solid transparent;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.code-card__btn:hover { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.12); }
.code-card__btn svg { inline-size: 13px; block-size: 13px; }
.code-card__btn span { white-space: nowrap; }
/* Continue bar under the code — discoverable where the eye lands when code cuts off. */
.code-card__foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  padding: 7px 12px; background: #161b22;
  border-block-start: 1px solid rgba(255, 255, 255, 0.06);
}
.code-card__foot[hidden] { display: none; }
.code-card__foot-hint { color: #7d8590; font-size: 11.5px; }
.code-card__btn.code-card__contbtn {
  background: rgba(63, 185, 80, 0.16); color: #5dd77a;
  border-color: rgba(63, 185, 80, 0.35); font-weight: var(--weight-semibold);
}
.code-card__btn.code-card__contbtn:hover { background: rgba(63, 185, 80, 0.26); border-color: rgba(63, 185, 80, 0.55); }
.code-card__body {
  max-block-size: 460px; overflow: auto; background: #0d1117;
  scrollbar-width: thin; scrollbar-color: #30363d transparent;
}
.code-card__body::-webkit-scrollbar { inline-size: 10px; block-size: 10px; }
.code-card__body::-webkit-scrollbar-thumb { background: #30363d; border-radius: 6px; }
.code-card__body pre { margin: 0; padding: 14px 16px; }
.code-card__code {
  font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace);
  font-size: 12.5px; line-height: 1.65; color: #e6edf3;
  white-space: pre; tab-size: 2; display: block;
}
.code-card.is-streaming .code-card__code::after {
  content: "▋"; color: #58a6ff; animation: codeCaret 1s steps(1) infinite;
}
@keyframes codeCaret { 50% { opacity: 0; } }

@media (max-width: 560px) {
  .file-card { max-inline-size: 100%; gap: var(--space-2); padding: var(--space-2) var(--space-3); }
  .file-card__icon { inline-size: 36px; block-size: 36px; }
  .file-card__dl span { display: none; }
  .file-card__dl { padding: var(--space-2); border-radius: var(--radius-pill); }
}

/* Code-block head can now host Copy + Preview side by side */
.code-block__head { gap: var(--space-2); }
.code-block__head .code-block__lang { margin-inline-end: auto; }
.code-preview-btn { color: var(--color-accent); }
.code-preview-btn:hover { color: var(--color-accent-hover); background: var(--color-accent-soft); }
.code-preview-btn svg { inline-size: 14px; block-size: 14px; }

/* ----------------------------------------------------------------------------
   15. Live HTML preview modal (sandboxed iframe)
---------------------------------------------------------------------------- */
.preview-overlay {
  position: fixed; inset: 0; z-index: var(--z-toast);
  display: grid; place-items: center;
  /* No padding: the panel below fills the viewport edge to edge. The backdrop colour stays for
     the fade-in, and for the brief moment before the panel paints. */
  padding: 0;
  background: rgba(0,0,0,0.46);
  opacity: 0; transition: opacity var(--dur-base) var(--ease-out);
}
.preview-overlay.is-open { opacity: 1; }
.preview-panel {
  /* GENUINELY FULL SCREEN. This was capped at 1040px wide and 86dvh tall, so "preview" showed a
     1040-pixel letterbox of a page that was designed for a real viewport — a responsive layout
     would even resolve to its TABLET breakpoint inside it and misrepresent the work. The header
     bar keeps close / refresh / open-in-tab / download reachable, and Escape still closes. */
  inline-size: 100%; max-inline-size: none;
  block-size: 100dvh;
  display: flex; flex-direction: column;
  background: var(--color-surface);
  border: 0;
  border-radius: 0;
  overflow: hidden;
  transform: translateY(8px) scale(0.99);
  transition: transform var(--dur-base) var(--ease-out);
}
.preview-overlay.is-open .preview-panel { transform: none; }
.preview-panel__head {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-sidebar-bg);
}
.preview-panel__title {
  display: inline-flex; align-items: center; gap: var(--space-2);
  margin-inline-end: auto;
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  color: var(--color-text-primary);
}
[dir="rtl"] .preview-panel__title { font-family: var(--font-arabic); }
.preview-panel__title svg { inline-size: 17px; block-size: 17px; color: var(--color-accent); }
.preview-panel__tools { display: inline-flex; align-items: center; gap: 2px; }
.preview-tool {
  display: grid; place-items: center;
  inline-size: 34px; block-size: 34px;
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
  transition: background var(--dur-fast), color var(--dur-fast);
}
.preview-tool:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.preview-tool svg { inline-size: 17px; block-size: 17px; }
.preview-tool--close:hover { background: var(--color-accent-soft); color: var(--color-accent); }
.preview-frame {
  flex: 1; inline-size: 100%; border: none;
  background: #fff;
  display: block;
}

@media (max-width: 768px) {
  .preview-overlay { padding: var(--space-2); }
  .preview-panel { block-size: 92dvh; max-inline-size: 100%; border-radius: var(--radius-lg); }
  .export-menu__list { min-inline-size: 176px; }
}

/* Utility */
.sr-only {
  position: absolute; inline-size: 1px; block-size: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.hidden { display: none !important; }
[hidden] { display: none !important; }

/* ===== Persistent memory viewer ===== */
.mem-overlay{position:fixed;inset:0;z-index:1200;display:flex;align-items:center;justify-content:center;padding:20px;background:rgba(0,0,0,.55);opacity:0;transition:opacity var(--dur-fast,.18s) var(--ease-out,ease)}
.mem-overlay.is-open{opacity:1}
.mem-card{width:min(540px,100%);max-height:82vh;display:flex;flex-direction:column;background:var(--color-surface);color:var(--color-text-primary);border:1px solid var(--color-border-strong);border-radius:var(--radius-lg);box-shadow:0 24px 64px rgba(0,0,0,.45);transform:translateY(10px) scale(.98);transition:transform var(--dur-fast,.18s) var(--ease-out,ease)}
.mem-overlay.is-open .mem-card{transform:none}
.mem-head{display:flex;align-items:flex-start;gap:10px;padding:18px 20px;border-bottom:1px solid var(--color-border-hairline)}
.mem-head h3{margin:0;font:600 17px/1.3 var(--font-sans)}
.mem-head p{margin:3px 0 0;font:400 13px/1.45 var(--font-sans);color:var(--color-text-secondary)}
.mem-x{background:none;border:none;color:var(--color-text-muted);cursor:pointer;padding:6px;border-radius:8px;display:flex;flex:0 0 auto}
.mem-x:hover{background:var(--color-bg-subtle);color:var(--color-text-primary)}
.mem-list{list-style:none;margin:0;padding:10px 12px;overflow:auto;flex:1}
.mem-item{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:var(--radius-md)}
.mem-item:hover{background:var(--color-bg-subtle)}
.mem-item span{flex:1;font:400 14px/1.45 var(--font-sans)}
.mem-del{background:none;border:none;color:var(--color-text-muted);cursor:pointer;font-size:20px;line-height:1;padding:0 6px;border-radius:6px;flex:0 0 auto}
.mem-del:hover{color:var(--color-error);background:var(--color-bg-subtle)}
.mem-empty{padding:36px 22px;text-align:center;color:var(--color-text-secondary);font:400 14px/1.55 var(--font-sans)}

/* ═══ محفوظاتي — the saved-snippets shelf ══════════════════════════════════════
   The panel reuses the .mem-overlay / .mem-card family above wholesale; only what the updates
   list does not already have lives here. Everything is a token, so all six themes follow with
   no per-theme block. */
.sidebar__saved{
  display:flex; align-items:center; gap:var(--space-2);
  inline-size:100%; block-size:32px; margin-block-end:var(--space-1);
  padding-inline:var(--space-3);
  border:var(--border-hairline) solid transparent; border-radius:var(--radius-sm);
  background:transparent; color:var(--color-text-secondary);
  font-size:var(--text-sm); font-weight:var(--weight-medium); cursor:pointer;
  transition:background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out);
}
.sidebar__saved svg{inline-size:15px; block-size:15px; flex:0 0 auto}
.sidebar__saved:hover{background:var(--color-surface); color:var(--color-text-primary); border-color:var(--color-border-hairline)}
/* The label hugs its icon and is NOT flexed to fill the row. Its own dir is rtl in Arabic, so a
   flexed box would push the words to the far end and leave a gap where the icon should be. */
.sidebar__saved-lb{flex:0 1 auto; min-inline-size:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.sidebar__saved-n{
  flex:0 0 auto; margin-inline-start:auto;
  min-inline-size:18px; padding-inline:5px; block-size:17px;
  border-radius:var(--radius-pill); background:var(--color-bg-subtle); color:var(--color-text-muted);
  font:var(--weight-semibold) 11px/17px var(--font-sans); text-align:center;
}

/* == UZ - the usage strip ======================================================
   Sits between the history and the account pill, which is exactly where the sidebar stops being
   about your conversations and starts being about you. It borrows .sidebar__saved's row metrics
   wholesale so the two rows above the pill read as one family; everything is a token, so all six
   themes follow with no per-theme block.

   HIDDEN ON PHONES IN CSS, not in JS: the sidebar is a drawer there and the one thing anybody
   opens it for is a conversation. Done here, a rotation needs no listener and no resize handler. */
.uz{ margin-block-start:var(--space-2) }
@media (max-width:900px){ .uz{ display:none } }
/* UI 2.0 has its own sidebar and its own inspector; a second summary panel inside it is the same
   surface twice. Written on :root because that is where data-ui is stamped. */
:root[data-ui="2"] .uz{ display:none }
.uz__row{
  display:flex; align-items:center; gap:var(--space-2);
  inline-size:100%; block-size:32px;
  padding-inline:var(--space-3);
  border:var(--border-hairline) solid transparent; border-radius:var(--radius-sm);
  background:transparent; color:var(--color-text-secondary);
  font-size:var(--text-sm); font-weight:var(--weight-medium); cursor:pointer;
  transition:background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out);
}
.uz__row:hover{ background:var(--color-surface); color:var(--color-text-primary); border-color:var(--color-border-hairline) }
.uz__ic{ display:flex; flex:0 0 auto }
.uz__ic svg{ inline-size:15px; block-size:15px }
/* The label hugs its icon and is NOT flexed to fill the row: its own dir is rtl in Arabic, and a
   flexed box would push the words to the far end and leave a gap where the icon should be. Same
   trap .sidebar__saved-lb documents directly above. */
.uz__lb{ flex:0 1 auto; min-inline-size:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.uz__n{
  flex:0 0 auto; margin-inline-start:auto;
  min-inline-size:18px; padding-inline:5px; block-size:17px;
  border-radius:var(--radius-pill); background:var(--color-bg-subtle); color:var(--color-text-muted);
  font:var(--weight-semibold) 11px/17px var(--font-sans); text-align:center;
}
.uz__chev{ display:flex; flex:0 0 auto; color:var(--color-text-muted); transition:transform var(--dur-fast) var(--ease-out) }
.uz__chev svg{ inline-size:13px; block-size:13px }
.uz.is-open .uz__chev{ transform:rotate(180deg) }
@media (prefers-reduced-motion: reduce){ .uz__chev{ transition:none } }
.uz[dir="rtl"]{ font-family:var(--font-arabic) }
.uz__body{ padding:var(--space-2) var(--space-3) 0 }
.uz__sec{ margin-block-end:11px }
.uz__h{
  display:flex; align-items:baseline; gap:var(--space-2); margin-block-end:6px;
  font-size:11px; font-weight:var(--weight-semibold); color:var(--color-text-muted);
}
.uz__hn{ margin-inline-start:auto; font-family:var(--font-mono); font-size:11px; color:var(--color-text-secondary) }
/* A flex row, so the seven days follow the block's own dir for free: in Arabic the oldest day
   lands on the right and today on the left, which is the direction the reader is already going. */
.uz__bars{ display:flex; align-items:flex-end; gap:4px }
.uz__day{ flex:1 1 0; min-inline-size:0; display:flex; flex-direction:column; align-items:center; gap:3px }
/* The track carries the full height so all seven columns share one baseline; the fill inside it
   is what varies. Sizing the bar itself instead would let a short day float. */
.uz__bar{
  display:flex; align-items:flex-end; inline-size:100%; block-size:26px;
  border-radius:3px; background:var(--color-bg-subtle);
}
.uz__bar > i{ display:block; inline-size:100%; border-radius:3px; background:var(--color-border-hairline) }
.uz__bar.is-on > i{ background:var(--color-accent) }
.uz__dl{ font-size:9.5px; line-height:1; color:var(--color-text-muted) }
.uz__p{ display:flex; align-items:center; gap:var(--space-2); margin-block-start:5px }
.uz__pn{
  flex:0 0 52px; font-size:11px; color:var(--color-text-secondary);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.uz__pbar{ flex:1 1 auto; min-inline-size:0; block-size:5px; border-radius:var(--radius-pill); background:var(--color-bg-subtle) }
.uz__pbar > i{ display:block; block-size:100%; border-radius:var(--radius-pill); background:var(--color-accent) }
.uz__pv{ flex:0 0 auto; font-family:var(--font-mono); font-size:10.5px; color:var(--color-text-muted) }
.uz__note{ margin:0 0 var(--space-1); font-size:10.5px; line-height:1.6; color:var(--color-text-muted) }

/* == CSTAT - this conversation, in numbers =====================================
   The twin of .uz directly above it, and deliberately so: same row metrics, same disclosure,
   same tokens. One row is the account, the next is the thread on screen, and two rows above the
   pill that behaved differently would read as two features instead of one pair. Everything is a
   token, so all six themes follow with no per-theme block.

   HIDDEN ON PHONES IN CSS, exactly as .uz is: the sidebar is a drawer there and the one thing
   anybody opens it for is a conversation. Done here, a rotation needs no listener. */
.cstat{ margin-block-start:var(--space-1) }
@media (max-width:900px){ .cstat{ display:none } }
/* UI 2.0 keeps per-conversation detail in its own inspector rail. Written on :root because that
   is where data-ui is stamped - :root IS <html>, so a `body :root[...]` could never match. */
:root[data-ui="2"] .cstat{ display:none }
.cstat__row{
  display:flex; align-items:center; gap:var(--space-2);
  inline-size:100%; block-size:32px;
  padding-inline:var(--space-3);
  border:var(--border-hairline) solid transparent; border-radius:var(--radius-sm);
  background:transparent; color:var(--color-text-secondary);
  font-size:var(--text-sm); font-weight:var(--weight-medium); cursor:pointer;
  transition:background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out);
}
.cstat__row:hover{ background:var(--color-surface); color:var(--color-text-primary); border-color:var(--color-border-hairline) }
.cstat__ic{ display:flex; flex:0 0 auto }
.cstat__ic svg{ inline-size:15px; block-size:15px }
/* Hugs its icon and is NOT flexed to fill the row: its own dir is rtl in Arabic, and a flexed box
   would push the words to the far end and leave a gap where the icon should be - the same trap
   .uz__lb and .sidebar__saved-lb both document. */
.cstat__lb{ flex:0 1 auto; min-inline-size:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.cstat__n{
  flex:0 0 auto; margin-inline-start:auto;
  min-inline-size:18px; padding-inline:5px; block-size:17px;
  border-radius:var(--radius-pill); background:var(--color-bg-subtle); color:var(--color-text-muted);
  font:var(--weight-semibold) 11px/17px var(--font-sans); text-align:center;
}
.cstat__chev{ display:flex; flex:0 0 auto; color:var(--color-text-muted); transition:transform var(--dur-fast) var(--ease-out) }
.cstat__chev svg{ inline-size:13px; block-size:13px }
.cstat.is-open .cstat__chev{ transform:rotate(180deg) }
@media (prefers-reduced-motion: reduce){ .cstat__chev{ transition:none } }
.cstat[dir="rtl"]{ font-family:var(--font-arabic) }
.cstat__body{ padding:var(--space-2) var(--space-3) 0 }
.cstat__sec{ margin-block-end:11px }
.cstat__h{
  display:flex; align-items:baseline; gap:var(--space-2); margin-block-end:6px;
  font-size:11px; font-weight:var(--weight-semibold); color:var(--color-text-muted);
}
.cstat__hn{ margin-inline-start:auto; font-family:var(--font-mono); font-size:11px; color:var(--color-text-secondary) }
/* Three equal columns rather than a flex row: the figures under them differ by an order of
   magnitude (nine questions, four thousand words) and a flexed row would size each tile to its
   own content, which reads as three unrelated chips instead of one set of numbers. */
.cstat__facts{ display:grid; grid-template-columns:repeat(3, 1fr); gap:4px }
.cstat__f{
  display:flex; flex-direction:column; align-items:center; gap:3px;
  min-inline-size:0; padding:7px 3px;
  border-radius:var(--radius-sm); background:var(--color-bg-subtle);
}
.cstat__fv{ font-family:var(--font-mono); font-size:13px; font-weight:var(--weight-semibold); line-height:1; color:var(--color-text-primary) }
.cstat__fl{ font-size:9.5px; line-height:1.25; text-align:center; color:var(--color-text-muted) }
.cstat__kv{ display:flex; align-items:baseline; gap:var(--space-2); margin-block-start:7px; font-size:11px; color:var(--color-text-secondary) }
.cstat__kvv{ margin-inline-start:auto; font-family:var(--font-mono); color:var(--color-text-primary) }
.cstat__p{ display:flex; align-items:center; gap:var(--space-2); margin-block-start:5px }
.cstat__pn{
  flex:0 0 46px; font-size:11px; color:var(--color-text-secondary);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.cstat__pbar{ flex:1 1 auto; min-inline-size:0; block-size:5px; border-radius:var(--radius-pill); background:var(--color-bg-subtle) }
.cstat__pbar > i{ display:block; block-size:100%; border-radius:var(--radius-pill); background:var(--color-accent) }
.cstat__pv{ flex:0 0 auto; font-family:var(--font-mono); font-size:10.5px; color:var(--color-text-muted) }
/* text-align:start, so the snippet follows the row's own dir in both languages. */
.cstat__l{
  display:flex; align-items:center; gap:var(--space-2);
  inline-size:100%; margin-block-start:4px; padding:4px 6px;
  border:var(--border-hairline) solid transparent; border-radius:var(--radius-sm);
  background:transparent; color:var(--color-text-secondary);
  font-size:11px; text-align:start; cursor:pointer;
  transition:background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out);
}
.cstat__l:hover{ background:var(--color-surface); color:var(--color-text-primary); border-color:var(--color-border-hairline) }
.cstat__ln{ flex:0 0 auto; font-family:var(--font-mono); font-size:10px; color:var(--color-text-muted) }
.cstat__lt{ flex:1 1 auto; min-inline-size:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.cstat__lw{ flex:0 0 auto; font-family:var(--font-mono); font-size:10px; color:var(--color-text-muted) }
.cstat__note{ margin:0 0 var(--space-1); font-size:10.5px; line-height:1.6; color:var(--color-text-muted) }

/* == WIK - a week in review ====================================================
   The third of the three rows above the account pill and the middle one down the sidebar:
   .uz is the whole account, this is the last seven days, .cstat is the thread on screen.
   Same row metrics, same disclosure, same tokens as both, because three rows in a stack that
   behaved differently would read as three features instead of one family. Everything is a
   token, so all six themes follow with no per-theme block.

   HIDDEN ON PHONES IN CSS, exactly as its two siblings are: the sidebar is a drawer there and
   the one thing anybody opens it for is a conversation. Done here, a rotation needs no
   listener and no resize handler. */
.wik{ margin-block-start:var(--space-1) }
@media (max-width:900px){ .wik{ display:none } }
/* UI 2.0 draws its own sidebar and keeps its summaries in its own inspector rail. Written on
   :root because that is where data-ui is stamped - :root IS <html>, so a `body :root[...]`
   could never match. */
:root[data-ui="2"] .wik{ display:none }
.wik__row{
  display:flex; align-items:center; gap:var(--space-2);
  inline-size:100%; block-size:32px;
  padding-inline:var(--space-3);
  border:var(--border-hairline) solid transparent; border-radius:var(--radius-sm);
  background:transparent; color:var(--color-text-secondary);
  font-size:var(--text-sm); font-weight:var(--weight-medium); cursor:pointer;
  transition:background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out);
}
.wik__row:hover{ background:var(--color-surface); color:var(--color-text-primary); border-color:var(--color-border-hairline) }
.wik__ic{ display:flex; flex:0 0 auto }
.wik__ic svg{ inline-size:15px; block-size:15px }
/* Hugs its icon and is NOT flexed to fill the row: its own dir is rtl in Arabic, and a flexed
   box would push the words to the far end and leave a gap where the icon should be - the same
   trap .uz__lb, .cstat__lb and .sidebar__saved-lb all document. */
.wik__lb{ flex:0 1 auto; min-inline-size:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.wik__n{
  flex:0 0 auto; margin-inline-start:auto;
  min-inline-size:18px; padding-inline:5px; block-size:17px;
  border-radius:var(--radius-pill); background:var(--color-bg-subtle); color:var(--color-text-muted);
  font:var(--weight-semibold) 11px/17px var(--font-sans); text-align:center;
}
.wik__chev{ display:flex; flex:0 0 auto; color:var(--color-text-muted); transition:transform var(--dur-fast) var(--ease-out) }
.wik__chev svg{ inline-size:13px; block-size:13px }
.wik.is-open .wik__chev{ transform:rotate(180deg) }
@media (prefers-reduced-motion: reduce){ .wik__chev{ transition:none } }
.wik[dir="rtl"]{ font-family:var(--font-arabic) }
.wik__body{ padding:var(--space-2) var(--space-3) 0 }
.wik__sec{ margin-block-end:11px }
.wik__h{
  display:flex; align-items:baseline; gap:var(--space-2); margin-block-end:6px;
  font-size:11px; font-weight:var(--weight-semibold); color:var(--color-text-muted);
}
.wik__hn{ margin-inline-start:auto; font-family:var(--font-mono); font-size:11px; color:var(--color-text-secondary) }
/* Three equal columns rather than a flex row, for the reason .cstat__facts gives: the figures
   differ by an order of magnitude and a flexed row would size each tile to its own content,
   which reads as three unrelated chips instead of one set of numbers. */
.wik__facts{ display:grid; grid-template-columns:repeat(3, 1fr); gap:4px }
.wik__f{
  display:flex; flex-direction:column; align-items:center; gap:3px;
  min-inline-size:0; padding:7px 3px;
  border-radius:var(--radius-sm); background:var(--color-bg-subtle);
}
.wik__fv{ font-family:var(--font-mono); font-size:13px; font-weight:var(--weight-semibold); line-height:1; color:var(--color-text-primary) }
.wik__fl{ font-size:9.5px; line-height:1.25; text-align:center; color:var(--color-text-muted) }
/* A word out of a title and a document name are both longer than a product label, so the name
   takes the room here and the bar gives it up - .uz__pn can afford a fixed 52px because the
   four things it ever names are four known words. */
.wik__p{ display:flex; align-items:center; gap:var(--space-2); margin-block-start:5px }
.wik__pn{
  flex:1 1 auto; min-inline-size:0; font-size:11px; color:var(--color-text-secondary);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.wik__pbar{ flex:0 0 40px; block-size:5px; border-radius:var(--radius-pill); background:var(--color-bg-subtle) }
.wik__pbar > i{ display:block; block-size:100%; border-radius:var(--radius-pill); background:var(--color-accent) }
.wik__pv{ flex:0 0 auto; font-family:var(--font-mono); font-size:10.5px; color:var(--color-text-muted) }
/* text-align:start, so the title follows the row's own dir in both languages. */
.wik__l{
  display:flex; align-items:center; gap:var(--space-2);
  inline-size:100%; margin-block-start:4px; padding:4px 6px;
  border:var(--border-hairline) solid transparent; border-radius:var(--radius-sm);
  background:transparent; color:var(--color-text-secondary);
  font-size:11px; text-align:start; cursor:pointer;
  transition:background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out);
}
.wik__l:hover{ background:var(--color-surface); color:var(--color-text-primary); border-color:var(--color-border-hairline) }
.wik__ln{ flex:0 0 auto; font-family:var(--font-mono); font-size:10px; color:var(--color-text-muted) }
.wik__lt{ flex:1 1 auto; min-inline-size:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.wik__lw{ flex:0 0 auto; font-family:var(--font-mono); font-size:10px; color:var(--color-text-muted) }
.wik__note{ margin:0 0 var(--space-1); font-size:10.5px; line-height:1.6; color:var(--color-text-muted) }

.shelf-card{inline-size:min(620px,100%)}
.shelf-tools{display:flex; align-items:center; gap:var(--space-3); padding:12px 20px 0}
.shelf-q{
  flex:1; min-inline-size:0; block-size:34px; padding-inline:var(--space-3);
  background:var(--color-bg-subtle); color:var(--color-text-primary);
  border:var(--border-hairline) solid var(--color-border-hairline); border-radius:var(--radius-sm);
  font:400 13px/1.4 var(--font-sans);
}
.shelf-q::placeholder{color:var(--color-text-muted)}
.shelf-q:focus{outline:none; border-color:var(--color-border-strong)}
.shelf-count{flex:0 0 auto; font:600 12px/1 var(--font-mono); color:var(--color-text-muted)}

.shelf-list{display:flex; flex-direction:column; gap:8px}
.shelf-item{
  padding:10px 12px; border:1px solid var(--color-border-hairline);
  border-radius:var(--radius-lg); background:var(--color-bg-subtle);
}
/* Scrolls rather than clamps: a saved passage is there to be re-read, and a "show more" toggle
   on a block whose whole purpose is text you then select would fight the selection. */
.shelf-item__text{
  max-block-size:11em; overflow:auto;
  white-space:pre-wrap; overflow-wrap:anywhere;
  font:400 13.5px/1.7 var(--font-sans); color:var(--color-text-primary);
}
.shelf-item__foot{
  display:flex; align-items:center; gap:var(--space-2);
  margin-block-start:8px; padding-block-start:8px;
  border-block-start:1px solid var(--color-border-hairline);
}
.shelf-item__src{
  min-inline-size:0; max-inline-size:50%;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  font:400 12px/1.5 var(--font-sans); color:var(--color-text-secondary);
}
.shelf-item__ts{flex:0 0 auto; font:400 11px/1.5 var(--font-sans); color:var(--color-text-muted)}
.shelf-item__acts{margin-inline-start:auto; flex:0 0 auto; display:flex; align-items:center; gap:4px}
/* .shelf-item__acts sets display:flex, which outranks the UA sheet's [hidden]{display:none} —
   without this the confirm step would appear NEXT TO the buttons it replaces. */
.shelf-item__acts[hidden]{display:none}
.shelf-act{
  inline-size:26px; block-size:26px; display:grid; place-items:center;
  border:none; background:transparent; border-radius:6px; cursor:pointer;
  color:var(--color-text-muted);
  transition:background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.shelf-act svg{inline-size:15px; block-size:15px}
.shelf-act:hover{background:var(--color-surface); color:var(--color-text-primary)}
.shelf-act.is-done{color:var(--color-success)}
.shelf-act--del:hover{color:var(--color-error)}

.shelf-ask{margin-inline-start:auto; flex:0 0 auto; display:flex; align-items:center; gap:var(--space-2)}
.shelf-ask__q{font:500 12px/1.5 var(--font-sans); color:var(--color-text-secondary)}
.shelf-ask__go,.shelf-ask__no{
  border:none; background:none; cursor:pointer;
  padding:3px 7px; border-radius:6px; font:600 12px/1.5 var(--font-sans);
}
.shelf-ask__go{color:var(--color-error)}
.shelf-ask__no{color:var(--color-text-secondary)}
.shelf-ask__go:hover,.shelf-ask__no:hover{background:var(--color-surface)}

/* ═══ قصاصاتي — the saved-code shelf ═══════════════════════════════
   Deliberately thin. The panel, its search row, the sidebar door and every control inside a row
   are the passage shelf's own furniture (.sidebar__saved / .shelf-tools / .shelf-q /
   .shelf-item__foot / .shelf-act / .shelf-ask) — reused rather than restated, so the two lists
   stay one family through all six themes and whatever the next one does to them. Only what CODE
   makes different lives here, and everything is a token. */
.snip-card{inline-size:min(680px,100%)}
.snip-list{display:flex; flex-direction:column; gap:8px}
.snip-item{
  padding:10px 12px; border:1px solid var(--color-border-hairline);
  border-radius:var(--radius-lg); background:var(--color-bg-subtle);
}
/* SCROLLS IN BOTH DIRECTIONS, where the passage shelf wraps. Wrapping moves where a line of code
   breaks, and a snippet about to be pasted has to be read exactly as it will be pasted — the
   indentation is half of what it means. The direction is pinned for the reason snipRowEl gives:
   one Arabic string literal is enough to reorder the brackets around it. */
.snip-item__code{
  margin:0; max-block-size:14em; overflow:auto;
  white-space:pre; direction:ltr; unicode-bidi:isolate; text-align:start;
  font:400 12.5px/1.65 var(--font-mono); color:var(--color-text-primary);
}
/* THE COLOUR HAS TO BE TAKEN BACK OFF THE <code>, not just set on the <pre>. highlight.js is
   loaded with the GITHUB LIGHT theme, whose stylesheet is appended to <head> at runtime and
   whose base rule is `.hljs{color:#24292e;background:#fff}` — so the moment a row is
   highlighted it paints a white slab with near-black text, and five of the six themes here are
   dark. `.code-block code` is the same one line, for the same reason, on the block this was
   saved from; the passage shelf never needed it because prose has no <code> to highlight. The
   TOKEN colours sit on the inner spans and are deliberately left alone. */
.snip-item__code code{color:var(--color-text-primary); background:none; padding:0}
/* THE FOOT WRAPS HERE AND ONLY HERE. The passage shelf's foot carries a source, a time and the
   buttons, and its source is the one thing that can ellipsis away when the row is narrow. This
   one adds a language tag and, when the snippet was cut, a word saying so — neither of which
   can shrink. On a 360px phone that pushed Copy and Delete off the side of the panel. Wrapping
   is scoped to .snip-item so the shelf's own rows are untouched, and `acts` keeps its
   margin-inline-start:auto, so it still sits at the end of whichever line it lands on. */
.snip-item .shelf-item__foot{flex-wrap:wrap; row-gap:6px}
.snip-item__lang{
  flex:0 0 auto; padding-inline:6px; border-radius:var(--radius-xs);
  background:var(--color-surface); color:var(--color-text-muted);
  font:600 10.5px/17px var(--font-mono); text-transform:uppercase; letter-spacing:var(--tracking-wide);
}
/* The one thing a code row must never be quiet about. Muted rather than an error colour: nothing
   went wrong, the snippet simply is not all of it. */
.snip-item__cut{flex:0 0 auto; font:500 11px/1.5 var(--font-sans); color:var(--color-text-muted)}

/* Save borrows .code-block__copy's metrics wholesale; only the saved state is its own, and it
   is the same green .shelf-act.is-done flashes. */
.code-save-btn.is-done{color:var(--color-success)}
/* THREE CONTROLS IN ONE HEAD is one more than a narrow phone has room for, and .code-block__head
   neither wraps nor scrolls — it sits inside .code-block's overflow:hidden, so the last button
   in the row is simply cut off. Below 560px the NEWEST control gives way to its mark alone;
   Copy and Preview keep the words they have always had, and the aria-label relabel() stamps is
   what carries the name once the span is gone. */
@media (max-width:560px){
  .code-save-btn span{display:none}
  .code-save-btn{padding-inline:6px}
}
.mem-foot{padding:13px 20px;border-top:1px solid var(--color-border-hairline);display:flex;justify-content:space-between;align-items:center;gap:10px}
.mem-foot small{color:var(--color-text-muted);font:400 12px var(--font-sans)}
.mem-clear{background:none;border:1px solid var(--color-border-strong);color:var(--color-text-secondary);padding:7px 14px;border-radius:var(--radius-pill);cursor:pointer;font:500 13px var(--font-sans)}
.mem-clear:hover{border-color:var(--color-error);color:var(--color-error)}

/* ===== Notifications bell + site-updates panel ===== */
.topbar__notify{position:relative}
/* Unread updates → a single GREEN dot (site accent) on the bell, not a count. */
.notify-badge{position:absolute;top:-2px;inset-inline-end:-2px;width:9px;height:9px;padding:0;border-radius:50%;background:var(--color-accent);box-shadow:0 0 0 2px var(--color-surface),0 0 8px var(--color-accent);animation:annBadgePop .3s var(--ease-out,ease)}
@keyframes annBadgePop{from{transform:scale(.3);opacity:0}to{transform:scale(1);opacity:1}}

.ann-card{width:min(580px,100%);max-height:86vh}
.ann-card .mem-head{background:linear-gradient(180deg,var(--color-accent-soft),transparent)}
.ann-list{padding:14px 16px;display:flex;flex-direction:column;gap:10px;overscroll-behavior:contain}
.ann-item{display:flex;align-items:center;gap:12px;cursor:pointer;padding:12px 14px;border-radius:var(--radius-lg);border:1px solid var(--color-border-hairline);background:var(--color-surface);box-shadow:var(--shadow-sm,0 1px 2px rgba(0,0,0,.05));transition:box-shadow .18s var(--ease-out,ease),transform .18s var(--ease-out,ease),border-color .18s}
.ann-item:hover{box-shadow:0 8px 24px rgba(0,0,0,.12);transform:translateY(-1px);border-color:var(--color-border-strong)}
.ann-item:focus-visible{outline:none;box-shadow:var(--shadow-focus)}
.ann-item-thumb{inline-size:54px;block-size:54px;flex:0 0 auto;object-fit:cover;border-radius:var(--radius-md);background:var(--color-bg-subtle)}
.ann-item-main{flex:1;min-inline-size:0;display:flex;flex-direction:column;gap:3px}
.ann-item-title{margin:0;font:700 15px/1.35 var(--font-sans);color:var(--color-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ann-item-body{margin:0;font:400 13px/1.55 var(--font-sans);color:var(--color-text-secondary);display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}
.ann-item-body:empty{display:none}
.ann-item-date{font:500 11px var(--font-sans);color:var(--color-text-muted);letter-spacing:.02em}
.ann-item-go{flex:0 0 auto;color:var(--color-text-muted);display:inline-flex}
[dir=rtl] .ann-item-go svg{transform:scaleX(-1)}

/* Full-screen update reader */
.ann-reader-overlay{z-index:1300;align-items:center;justify-content:center;padding:22px}
.ann-reader{display:flex;flex-direction:column;width:min(880px,94vw);max-height:92vh;background:var(--color-surface);border:1px solid var(--color-border-strong);border-radius:var(--radius-lg);box-shadow:0 24px 64px rgba(0,0,0,.5);overflow:hidden;transform:translateY(10px) scale(.985);transition:transform var(--dur-fast,.18s) var(--ease-out,ease)}
.ann-reader-overlay.is-open .ann-reader{transform:none}
.ann-reader-bar{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 12px;border-bottom:1px solid var(--color-border-hairline);background:var(--color-bg-subtle)}
.ann-langs{display:inline-flex;gap:3px;background:var(--color-surface);border:1px solid var(--color-border-hairline);border-radius:var(--radius-pill);padding:3px}
.ann-lang{background:none;border:none;cursor:pointer;border-radius:var(--radius-pill);padding:0 13px;min-block-size:32px;font:600 12.5px var(--font-sans);color:var(--color-text-secondary);transition:.15s}
.ann-lang:hover{color:var(--color-text-primary)}
.ann-lang.is-on{background:var(--color-accent);color:var(--color-on-accent)}
.ann-lang:disabled{opacity:.6;cursor:default}
.ann-reader-actions{display:inline-flex;gap:4px;align-items:center}
.ann-icon-btn{display:inline-grid;place-items:center;inline-size:38px;block-size:38px;border:none;background:none;color:var(--color-text-muted);border-radius:var(--radius-sm);cursor:pointer;transition:.15s}
.ann-icon-btn:hover{background:var(--color-surface);color:var(--color-text-primary)}
.ann-reader-del:hover{color:var(--color-error)}
.ann-reader-body{padding:20px 26px 28px;overflow:auto;overscroll-behavior:contain}
.ann-reader-img{display:block;max-width:100%;border-radius:var(--radius-md);margin:0 0 16px;cursor:zoom-in;box-shadow:0 4px 18px rgba(0,0,0,.18)}
.ann-reader-title{margin:0 0 6px;font:700 22px/1.4 var(--font-sans);color:var(--color-text-primary);overflow-wrap:anywhere}
.ann-reader-title[hidden]{display:none}
.ann-reader-date{display:block;margin:0 0 18px;font:500 12px var(--font-sans);color:var(--color-text-muted)}
.ann-reader-text{font:400 15.5px/1.85 var(--font-sans);color:var(--color-text-primary);white-space:pre-wrap;overflow-wrap:anywhere}

/* Admin edit modal */
.ann-edit-overlay{z-index:1350}
.ann-edit-card{width:min(560px,100%);max-height:88vh}
.ann-edit-form{margin:14px 16px 16px}
.ann-img-remove{background:none;border:1px solid var(--color-border-strong);color:var(--color-text-secondary);border-radius:var(--radius-pill);padding:7px 14px;font:600 12.5px var(--font-sans);cursor:pointer;transition:.15s}
.ann-img-remove:hover{border-color:var(--color-error);color:var(--color-error)}
.ann-img-remove[hidden]{display:none}

.ann-form{display:flex;flex-direction:column;gap:9px;padding:16px;margin:12px 16px 2px;background:var(--color-bg-subtle);border:1px solid var(--color-border-hairline);border-radius:var(--radius-lg)}
.ann-in{width:100%;background:var(--color-surface);color:var(--color-text-primary);border:1px solid var(--color-border-strong);border-radius:var(--radius-md);padding:9px 12px;font:400 14px/1.5 var(--font-sans);resize:vertical;transition:border-color .15s,box-shadow .15s}
.ann-in:focus{outline:none;border-color:var(--color-accent);box-shadow:var(--shadow-focus)}
.ann-form-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.ann-img-btn{cursor:pointer;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--color-border-strong);border-radius:var(--radius-pill);padding:7px 14px;font:600 12.5px var(--font-sans);color:var(--color-text-secondary);flex:0 0 auto;transition:.15s}
.ann-img-btn:hover{border-color:var(--color-accent);color:var(--color-accent)}
.ann-img-name{flex:1;min-width:0;font:400 12px var(--font-sans);color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ann-post{margin-inline-start:auto;background:var(--color-accent);color:#fff;border:none;border-radius:var(--radius-pill);padding:8px 20px;font:700 13px var(--font-sans);cursor:pointer;flex:0 0 auto;transition:.15s}
.ann-post:hover{background:var(--color-accent-hover)}
.ann-post:disabled{opacity:.55;cursor:default}
.ann-img-preview{max-width:100%;border-radius:var(--radius-md);margin-top:4px}

/* Mobile: the updates panel becomes a bottom sheet; the reader goes truly full-screen */
@media (max-width:560px){
  .ann-overlay{align-items:flex-end;padding:0}
  .ann-card{width:100%;max-height:90vh;border-radius:var(--radius-lg) var(--radius-lg) 0 0;transform:translateY(18px)}
  .ann-overlay.is-open .ann-card{transform:none}
  .ann-form{margin:12px 12px 2px}
  .ann-list{padding:12px}
  .ann-reader-overlay{padding:0;align-items:stretch}
  .ann-reader{width:100%;max-height:100vh;min-height:100vh;border-radius:0;border:none}
  .ann-reader-body{padding:18px 18px 30px}
  .ann-reader-title{font-size:19px}
  .ann-edit-overlay{align-items:flex-end;padding:0}
  .ann-edit-card{width:100%;max-height:92vh;border-radius:var(--radius-lg) var(--radius-lg) 0 0}
}

/* Account settings panel */
/* A FIXED height, not a floor. The dialog is centred, so while its height tracked the
   active tab the whole panel slid up and down the screen on every tab change — measured
   at 189px between Account and Voice, which moves the tab strip out from under the finger
   that just tapped it. A min-height only shrinks that jump; pinning the height removes it.
   The body already scrolls, so a tall tab is unaffected. */
.settings-card{width:min(470px,100%);height:min(620px,88vh);max-height:88vh}
/* min-height: the dialog is vertically centred, so without a floor its height follows the
   active tab's content and the whole panel JUMPS up the screen when you move from Account
   (five cards) to Voice (two). The tab strip should stay under the same finger. */
.set-body{padding:16px 18px 20px;overflow:auto;display:flex;flex-direction:column;gap:14px}
/* account hero */
.set-hero{display:flex;align-items:center;gap:14px;padding:16px 16px;border-radius:var(--radius-lg);background:linear-gradient(150deg,var(--color-accent-soft),transparent 70%),var(--color-bg-subtle);border:1px solid var(--color-border-hairline)}
.set-avatar{inline-size:48px;block-size:48px;border-radius:var(--radius-pill);display:grid;place-items:center;flex:0 0 auto;background:var(--color-accent);color:var(--color-on-accent);font:700 20px var(--font-sans);box-shadow:0 4px 14px var(--color-accent-ring)}
.set-hero-info{display:flex;flex-direction:column;min-width:0;gap:1px}
.set-hero-eyebrow{font:600 10.5px/1 var(--font-sans);letter-spacing:.08em;text-transform:uppercase;color:var(--color-text-muted)}
.set-acct-name{font:600 16px var(--font-sans);color:var(--color-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-top:2px}
.set-acct-email{font:400 13px var(--font-sans);color:var(--color-text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* action cards */
.set-card{display:flex;flex-direction:column;gap:10px;padding:16px;border:1px solid var(--color-border-hairline);border-radius:var(--radius-lg);background:var(--color-surface)}
.set-card-h{display:flex;align-items:center;gap:8px;font:600 14px/1.3 var(--font-sans);color:var(--color-text-primary)}
.set-ico{display:inline-grid;place-items:center;inline-size:26px;block-size:26px;border-radius:8px;background:var(--color-accent-soft);color:var(--color-accent);flex:0 0 auto}
/* appearance: light/dark segmented control */
.set-theme-seg{display:flex;gap:6px;padding:4px;background:var(--color-bg-subtle);border:1px solid var(--color-border-hairline);border-radius:var(--radius-pill)}
.set-theme-opt{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:9px 14px;border-radius:var(--radius-pill);background:transparent;color:var(--color-text-secondary);font:500 13.5px var(--font-sans);transition:background .15s,color .15s,box-shadow .15s}
[dir="rtl"] .set-theme-opt{font-family:var(--font-arabic)}
.set-theme-opt:hover{color:var(--color-text-primary)}
.set-theme-opt.is-active{background:var(--color-surface);color:var(--color-text-primary);box-shadow:var(--shadow-xs);font-weight:600}
.set-theme-opt.is-active svg{color:var(--color-accent)}
.set-field{display:flex;flex-direction:column;gap:5px}
.set-lbl{font:500 12px/1.3 var(--font-sans);color:var(--color-text-secondary)}
.set-lbl-hint{color:var(--color-text-muted);font-weight:400}
.set-in{width:100%;background:var(--color-bg-subtle);color:var(--color-text-primary);border:1px solid var(--color-border-strong);border-radius:var(--radius-md);padding:10px 12px;font:400 14px/1.4 var(--font-sans);transition:border-color .15s,box-shadow .15s}
.set-in:focus{outline:none;border-color:var(--color-accent);box-shadow:var(--shadow-focus);background:var(--color-surface)}

/* ── Settings: tabs ──────────────────────────────────────────────────────────
   The panel used to be one flat scroll of thirteen cards, so reaching "about" meant
   passing the delete-account button, and changing a password meant scrolling past the
   theme. Five tabs; the strip scrolls sideways rather than wrapping on a narrow phone. */
.set-tabs{display:flex;gap:2px;padding:0 14px;flex:0 0 auto;overflow-x:auto;scrollbar-width:none;border-bottom:1px solid var(--color-border-hairline)}
.set-tabs::-webkit-scrollbar{display:none}
.set-tab{flex:0 0 auto;padding:11px 12px;background:transparent;color:var(--color-text-secondary);font:500 13.5px var(--font-sans);white-space:nowrap;box-shadow:inset 0 -2px 0 transparent;transition:color .15s,box-shadow .15s}
.set-tab:hover{color:var(--color-text-primary)}
.set-tab.is-active{color:var(--color-text-primary);font-weight:600;box-shadow:inset 0 -2px 0 var(--color-accent)}
/* An inactive pane is hidden, never detached — every querySelector in openSettingsPanel
   must keep resolving whether or not its tab has been opened. */
.set-pane{display:none;flex-direction:column;gap:14px}
.set-pane.is-active{display:flex}

/* ── Settings: the keyboard map ─────────────────────────────────────────
   Rows the shape of the shortcut sheet's, because they are the same sentence said twice: the
   caps ARE .cw-kbd__cap, so a chord looks like a key in both places and there is one file
   location that decides what a key looks like. What is new is the right-hand column — a row you
   can change carries buttons, a row you cannot carries the word for it.
   The card is stamped dir in JS; these only have to give Arabic its face. */
.set-km{gap:6px}
.km-grp{margin-block-start:10px;font:600 11px/1 var(--font-sans);letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted)}
.km-grp:first-of-type{margin-block-start:2px}
[dir="rtl"] .km-grp{font-family:var(--font-arabic);letter-spacing:0;text-transform:none}
.km-row{display:flex;align-items:center;gap:10px;padding:6px 0;border-block-end:1px solid var(--color-border-hairline)}
.km-lbl{flex:1 1 auto;min-inline-size:0;font:400 13px/1.35 var(--font-sans);color:var(--color-text-primary)}
[dir="rtl"] .km-lbl{font-family:var(--font-arabic)}
/* The chord block is stamped dir="ltr" in JS; this only has to stop it shrinking. */
.km-keys{flex:0 0 auto;display:flex;align-items:center;gap:5px}
.km-acts{flex:0 0 auto;display:flex;align-items:center;gap:6px}
.km-b{padding:5px 9px;border-radius:var(--radius-pill);border:1px solid var(--color-border-hairline);background:var(--color-bg-subtle);color:var(--color-text-secondary);font:500 11.5px var(--font-sans);white-space:nowrap;transition:color .15s,border-color .15s,background-color .15s}
[dir="rtl"] .km-b{font-family:var(--font-arabic)}
.km-b:hover{color:var(--color-text-primary);border-color:var(--color-border-strong)}
/* Listening: the same accent the held cap takes, so the two halves of a rebind read as one act. */
.km-b.is-rec{background:var(--color-accent-soft);border-color:var(--color-accent);color:var(--color-accent);font-weight:600}
.km-fix{font:400 11px var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .km-fix{font-family:var(--font-arabic)}
.km-warn{margin-block:-2px 4px;font:400 11.5px/1.4 var(--font-sans);color:var(--color-error,#c0392b)}
[dir="rtl"] .km-warn{font-family:var(--font-arabic)}
/* Never opens below 640px — kmAvailable() is false on a phone — so this is only the band
   between a phone and a laptop, where the label wants the row to itself. */
@media (max-width:720px){
  .km-row{flex-wrap:wrap}
  .km-lbl{flex:1 1 100%}
}
@media (prefers-reduced-motion:reduce){.km-b{transition:none}}
:root[data-motion="off"] .km-b{transition:none}

/* ── Settings: theme tiles ───────────────────────────────────────────────────
   Each tile paints its own ground, surface and accent, so a theme is chosen by looking
   rather than by reading six names none of which mean anything until you try them. */
.set-theme-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.set-theme-tile{display:flex;flex-direction:column;align-items:center;gap:7px;padding:9px 6px;border-radius:var(--radius-md);border:1px solid var(--color-border-hairline);background:var(--color-bg-subtle);transition:border-color .15s,box-shadow .15s}
.set-theme-tile:hover{border-color:var(--color-border-strong)}
.set-theme-tile.is-active{border-color:var(--color-accent);box-shadow:0 0 0 1px var(--color-accent)}
/* A hairline in a neutral grey, not a token: the chip shows OTHER themes' colours, and a
   themed border would misreport the tile it frames. */
.set-tile-chip{position:relative;display:block;width:100%;height:34px;border-radius:var(--radius-sm);border:1px solid rgba(128,128,128,.3);overflow:hidden}
.set-tile-surf{position:absolute;inset:auto 0 0 0;height:13px;display:block}
.set-tile-acc{position:absolute;top:6px;left:6px;width:15px;height:6px;border-radius:3px;display:block}
.set-tile-name{font:500 12px var(--font-sans);color:var(--color-text-secondary);line-height:1.2}
.set-theme-tile.is-active .set-tile-name{color:var(--color-text-primary);font-weight:600}

/* ── Settings: select (call voice, dictation dialect) ────────────────────── */
.set-select{width:100%;background:var(--color-bg-subtle);color:var(--color-text-primary);border:1px solid var(--color-border-strong);border-radius:var(--radius-md);padding:10px 12px;font:400 14px/1.4 var(--font-sans);cursor:pointer;transition:border-color .15s,box-shadow .15s}
.set-select:focus{outline:none;border-color:var(--color-accent);box-shadow:var(--shadow-focus);background:var(--color-surface)}
/* Native dropdown lists ignore the control's colours in several browsers; setting them on
   the options is what keeps the list readable in the dark themes. */
.set-select option{background:var(--color-surface);color:var(--color-text-primary)}

/* ── Settings: labelled switch rows ──────────────────────────────────────── */
.set-row{display:flex;align-items:center;justify-content:space-between;gap:12px}
.set-row + .set-row{padding-top:12px;border-top:1px solid var(--color-border-hairline)}
.set-row-t{display:flex;flex-direction:column;gap:2px;min-width:0}
.set-row-t strong{font:600 13.5px var(--font-sans);color:var(--color-text-primary)}
.set-row-t em{font:400 12px/1.35 var(--font-sans);color:var(--color-text-muted);font-style:normal}
.set-switch{flex:0 0 auto;position:relative;width:42px;height:24px;padding:0;border-radius:var(--radius-pill);background:var(--color-bg-subtle);border:1px solid var(--color-border-strong);transition:background .18s,border-color .18s}
.set-switch>span{position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:var(--color-text-muted);transition:transform .18s,background .18s}
.set-switch.is-on{background:var(--color-accent-soft);border-color:var(--color-accent)}
.set-switch.is-on>span{background:var(--color-accent);transform:translateX(18px)}
/* The shell is fixed LTR (see html.dir in app.js), so this never fires today. It is here
   because inset + translateX is the pair that silently breaks if any ancestor ever turns
   RTL: the anchor flips to the right edge while the transform still travels right, and the
   knob leaves the switch entirely. */
[dir="rtl"] .set-switch>span{left:auto;right:2px}
[dir="rtl"] .set-switch.is-on>span{transform:translateX(-18px)}
.set-save{align-self:flex-start;margin-top:2px;background:var(--color-accent);color:var(--color-on-accent);border:none;border-radius:var(--radius-pill);padding:10px 22px;font:600 13px var(--font-sans);cursor:pointer;transition:.15s}
.set-save:hover{background:var(--color-accent-hover)}
.set-save:disabled{opacity:.55;cursor:default}
.set-err{font:500 12.5px/1.5 var(--font-sans);color:var(--color-error);background:color-mix(in srgb,var(--color-error) 13%,transparent);border-radius:var(--radius-sm);padding:8px 11px}
.set-danger{border-color:color-mix(in srgb,var(--color-error) 35%,var(--color-border-hairline));background:color-mix(in srgb,var(--color-error) 6%,var(--color-surface))}
.set-danger-h{color:var(--color-error)}
.set-danger-h .set-ico{background:color-mix(in srgb,var(--color-error) 14%,transparent);color:var(--color-error)}
.set-danger-note{margin:0;font:400 13px/1.65 var(--font-sans);color:var(--color-text-secondary)}
.set-del-btn,.set-del-final{align-self:flex-start;background:var(--color-error);color:#fff;border:none;border-radius:var(--radius-pill);padding:10px 20px;font:600 13px var(--font-sans);cursor:pointer;transition:.15s}
.set-del-btn:hover,.set-del-final:hover{filter:brightness(1.08)}
.set-del-confirm{display:flex;flex-direction:column;gap:10px;margin-top:4px;padding-top:12px;border-top:1px solid color-mix(in srgb,var(--color-error) 22%,transparent)}
.set-del-row{display:flex;gap:9px;flex-wrap:wrap}
.set-del-cancel{background:var(--color-surface);color:var(--color-text-primary);border:1px solid var(--color-border-strong);border-radius:var(--radius-pill);padding:10px 20px;font:600 13px var(--font-sans);cursor:pointer}
.set-del-cancel:hover{background:var(--color-bg-subtle)}
/* Expanded settings: side-by-side actions, secondary (outline) buttons, about row */
.set-btn-row{display:flex;gap:8px;margin-top:2px;flex-wrap:wrap}
.set-btn-row .set-save{flex:1;min-width:130px;align-self:stretch;text-align:center}
.set-save.set-secondary{background:transparent;color:var(--color-text-primary);border:1px solid var(--color-border-strong)}
.set-save.set-secondary:hover{background:var(--color-bg-subtle);border-color:var(--color-accent);color:var(--color-accent)}
.set-about-row{display:flex;align-items:center;justify-content:space-between;gap:8px;font:400 13px var(--font-sans);color:var(--color-text-secondary)}
.set-ver{color:var(--color-text-primary);font-weight:600;font-variant-numeric:tabular-nums;direction:ltr}
.set-ver.skeleton{display:inline-block;block-size:12px}

/* ─── Subscriptions / redeem / plans / admin codes (theme-token based) ─── */
:root{ --plan-gold:#B8862A; --plan-diamond:#3E7CB1; }
[data-theme="dark"]{ --plan-gold:#D8B45A; --plan-diamond:#8FB4E0; }
.sub-head{display:flex;flex-wrap:wrap;align-items:center;gap:8px;justify-content:space-between}
.sub-chip{display:inline-flex;align-items:center;gap:6px;padding:4px 12px;border-radius:var(--radius-pill);font:600 13px var(--font-sans);border:1px solid var(--color-border-strong);color:var(--color-text-primary);background:var(--color-bg-subtle)}
.sub-chip--gold{color:var(--plan-gold);border-color:color-mix(in srgb,var(--plan-gold) 55%,transparent);background:color-mix(in srgb,var(--plan-gold) 12%,transparent)}
.sub-chip--diamond,.sub-chip--unlimited{color:var(--plan-diamond);border-color:color-mix(in srgb,var(--plan-diamond) 55%,transparent);background:color-mix(in srgb,var(--plan-diamond) 12%,transparent)}
.sub-exp{margin:0;font-size:var(--text-xs);color:var(--color-text-muted)}
.sub-meters{display:grid;gap:10px;margin-top:2px}
.sub-meter__top{display:flex;justify-content:space-between;font-size:var(--text-xs);color:var(--color-text-secondary);margin-bottom:4px}
.sub-meter__val{font-variant-numeric:tabular-nums;color:var(--color-text-primary);font-weight:600}
.sub-meter__val.is-inf{color:var(--color-accent);font-size:15px}
.sub-meter__bar{block-size:6px;border-radius:var(--radius-pill);background:var(--color-bg-subtle);overflow:hidden}
.sub-meter__bar span{display:block;block-size:100%;background:var(--color-accent);border-radius:var(--radius-pill);transition:width var(--dur-slow) var(--ease-out)}
.sub-meter__bar span.is-full{background:var(--color-error)}

.redeem-body{display:flex;flex-direction:column;gap:12px;padding:4px 2px}
.redeem-in{width:100%;text-align:center;letter-spacing:.12em;text-transform:uppercase;font:600 16px var(--font-mono);padding:14px;border:1.5px solid var(--color-border-strong);border-radius:var(--radius-md);background:var(--color-bg-subtle);color:var(--color-text-primary)}
.redeem-in:focus{outline:none;border-color:var(--color-accent);box-shadow:var(--shadow-focus);background:var(--color-surface)}
.redeem-in.hidden,.redeem-go.hidden{display:none}
.redeem-msg{font:500 13px var(--font-sans);color:var(--color-error);background:color-mix(in srgb,var(--color-error) 12%,transparent);border-radius:var(--radius-sm);padding:9px 12px;text-align:center}
.redeem-go{align-self:stretch;text-align:center}
.redeem-ok{display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center;padding:12px;border-radius:var(--radius-md);background:color-mix(in srgb,var(--color-success) 10%,transparent);animation:msg-in var(--dur-slow) var(--ease-out)}
.redeem-ok__badge{font-size:34px}
.redeem-ok strong{font-family:var(--font-serif);font-size:var(--text-md);color:var(--color-text-primary)}
.redeem-ok span{font-size:var(--text-sm);color:var(--color-text-secondary)}

.plans-card{max-inline-size:900px;width:min(92vw,900px)}
/* P0 — the subscription funnel was dead on every phone.
   This grid is a DIRECT child of .mem-card, which is a flex column capped at 82dvh, inside a
   position:fixed overlay with no overflow — and openPlansModal() calls lockBodyScroll(), so
   the page cannot scroll either. Three stacked plan cards measure ~1250px inside a ~692px
   box, so ~560px spilled out of the bottom with no gesture able to reach it: the Gold and
   Diamond "I have a code" buttons — the only way to redeem a subscription — rendered
   off-screen and untappable.

   Every sibling modal body in this file is already a scroller (.mem-list, .set-body,
   .ac-list); this grid was the one that was not. `min-block-size: 0` is required, not
   decorative: as a flex item its default `min-height: auto` keeps it at content size and
   would defeat the scroller entirely. */
.plans-grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;padding:4px;
  overflow:auto;
  flex:1 1 auto;
  min-block-size:0;
  overscroll-behavior:contain;
}
.plan-card{position:relative;display:flex;flex-direction:column;gap:10px;padding:20px 18px;border:1.5px solid var(--color-border-hairline);border-radius:var(--radius-lg);background:var(--color-surface);text-align:center}
.plan-card--gold{border-color:color-mix(in srgb,var(--plan-gold) 40%,var(--color-border-hairline))}
.plan-card--diamond{border-color:color-mix(in srgb,var(--plan-diamond) 40%,var(--color-border-hairline))}
.plan-card.is-current{box-shadow:0 0 0 2px var(--color-accent) inset}
.plan-current{position:absolute;inset-block-start:-11px;inset-inline-start:50%;transform:translateX(-50%);background:var(--color-accent);color:var(--color-on-accent);font:600 11px var(--font-sans);padding:3px 12px;border-radius:var(--radius-pill);white-space:nowrap}
.plan-ic{font-size:30px}
.plan-card h4{font-family:var(--font-serif);font-size:var(--text-lg)}
.plan-price{font-size:var(--text-sm);color:var(--color-text-muted)}
.plan-feats{list-style:none;padding:0;margin:6px 0;display:grid;gap:8px;text-align:start}
.plan-feats li{display:flex;align-items:flex-start;gap:8px;font-size:var(--text-sm);color:var(--color-text-secondary)}
.plan-feats li svg{color:var(--color-accent);flex:0 0 auto;margin-top:3px}
[dir="rtl"] .plan-feats li{font-family:var(--font-arabic)}
.plan-cta{margin-top:auto;padding:10px;border-radius:var(--radius-pill);font:600 13px var(--font-sans);background:var(--color-accent);color:var(--color-on-accent);transition:background var(--dur-fast) var(--ease-out)}
.plan-cta:hover{background:var(--color-accent-hover)}
.plan-cta--muted{background:var(--color-bg-subtle);color:var(--color-text-muted);cursor:default}

.admincodes-card{max-inline-size:820px;width:min(94vw,820px)}
.ac-body{display:flex;flex-direction:column;gap:14px}
.ac-create{display:flex;flex-direction:column;gap:10px;padding:14px;border:1px solid var(--color-border-hairline);border-radius:var(--radius-lg);background:var(--color-bg-subtle)}
.ac-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}
.ac-fld{display:flex;flex-direction:column;gap:4px;font:500 12px var(--font-sans);color:var(--color-text-secondary)}
.ac-in{width:100%;padding:9px 11px;border:1px solid var(--color-border-strong);border-radius:var(--radius-md);background:var(--color-surface);color:var(--color-text-primary);font:400 13.5px var(--font-sans)}
.ac-in:focus{outline:none;border-color:var(--color-accent);box-shadow:var(--shadow-focus)}
.ac-create-btn{align-self:flex-start}
.ac-search-row{display:flex}
.ac-list{display:flex;flex-direction:column;gap:8px;max-block-size:46vh;overflow:auto}
.ac-row{display:flex;flex-direction:column;gap:6px;padding:11px 13px;border:1px solid var(--color-border-hairline);border-radius:var(--radius-md);background:var(--color-surface)}
.ac-row--disabled,.ac-row--expired,.ac-row--used-up{opacity:.62}
.ac-row-main{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.ac-code{font:600 14px var(--font-mono);letter-spacing:.04em;background:var(--color-bg-subtle);padding:3px 9px;border-radius:var(--radius-sm);cursor:pointer;color:var(--color-text-primary)}
.ac-badge{font:600 11px var(--font-sans);text-transform:uppercase;letter-spacing:.04em;padding:2px 9px;border-radius:var(--radius-pill);background:var(--color-bg-subtle);color:var(--color-text-secondary)}
.ac-badge--gold{color:var(--plan-gold);background:color-mix(in srgb,var(--plan-gold) 13%,transparent)}
.ac-badge--diamond,.ac-badge--unlimited{color:var(--plan-diamond);background:color-mix(in srgb,var(--plan-diamond) 13%,transparent)}
.ac-status{margin-inline-start:auto;font:600 11px var(--font-sans);padding:2px 9px;border-radius:var(--radius-pill)}
.ac-status--active{color:var(--color-success);background:color-mix(in srgb,var(--color-success) 13%,transparent)}
.ac-status--disabled,.ac-status--expired,.ac-status--used-up{color:var(--color-text-muted);background:var(--color-bg-subtle)}
.ac-row-meta{display:flex;flex-wrap:wrap;gap:5px 14px;font-size:var(--text-xs);color:var(--color-text-muted)}
.ac-note-tag{color:var(--color-text-secondary)}
.ac-row-acts{display:flex;gap:7px;flex-wrap:wrap}
.ac-act{font:600 12px var(--font-sans);padding:5px 12px;border-radius:var(--radius-pill);border:1px solid var(--color-border-strong);background:transparent;color:var(--color-text-primary);transition:border-color var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out)}
.ac-act:hover{border-color:var(--color-accent);color:var(--color-accent)}
.ac-del:hover{border-color:var(--color-error);color:var(--color-error)}
@media (max-width:560px){
  .settings-overlay{align-items:flex-end;padding:0}
  .settings-card{width:100%;max-height:92vh;border-radius:var(--radius-lg) var(--radius-lg) 0 0;transform:translateY(18px)}
  .settings-overlay.is-open .settings-card{transform:none}
  .set-body{padding:14px}
}

/* Pinned conversations */
.chat-item__pin{opacity:0;transition:opacity .15s,color .15s}
.chat-item.is-pinned .chat-item__pin{opacity:1}
/* Opacity alone does not move anything, so this one never broke a tap — but a control that
   only appears on a hover the device cannot produce is simply unreachable. */
@media (hover: hover) and (pointer: fine) { .chat-item:hover .chat-item__pin{opacity:1} }
@media (hover: none) { .chat-item__pin{opacity:.75} }

/* The announcements panel opens INSTANTLY now and fills in when the data lands, so it needs a
   waiting face. Sized like the real card so the swap is not a jump. */
.ann-card--wait { min-block-size: 190px; display: flex; align-items: center; justify-content: center; }
.ann-spin {
  inline-size: 26px; block-size: 26px; border-radius: 50%;
  border: 2px solid var(--color-border-strong);
  border-top-color: var(--color-accent);
  animation: annSpin .8s linear infinite;
}
@keyframes annSpin { to { transform: rotate(360deg); } }

/* ══ THE MENTRONX SIGNATURE (entry interstitial) ══════════════════════════════
   Silent monochrome: both strokes take the ACTIVE THEME'S text tokens, so the card
   is native in all six themes and needs no palette of its own. The M-line's dash
   length is the measured path length (~198), not a round guess — a short dash makes
   the pen visibly "jump" at the end of the write. */
.mx-intro {
  position: fixed; inset: 0; z-index: 4000;
  display: flex; align-items: center; justify-content: center;
  background: var(--color-bg);
  opacity: 1; transition: opacity 340ms var(--ease-out);
}
.mx-intro.is-out { opacity: 0; pointer-events: none; }
.mx-intro__stage { display: flex; align-items: center; gap: 0; }
.mx-intro__mark { width: 200px; height: auto; transition: width 560ms var(--ease-inout); }
.mx-intro__m, .mx-intro__x { stroke-width: 8; stroke-linecap: round; stroke-linejoin: round; fill: none; }
.mx-intro__m { stroke: var(--color-text-primary); stroke-dasharray: 200; stroke-dashoffset: 200; }
.mx-intro__x { stroke: var(--color-text-muted);   stroke-dasharray: 49;  stroke-dashoffset: 49; }
/* The write: pen pace — an ease that starts deliberate and lands soft, then the
   crossing stroke closes the figure in one shorter gesture. */
.mx-intro.is-draw .mx-intro__m { animation: mxWrite 1150ms cubic-bezier(.5, .05, .3, 1) forwards; }
.mx-intro.is-draw .mx-intro__x { animation: mxWrite 420ms cubic-bezier(.4, 0, .3, 1) 1120ms forwards; }
@keyframes mxWrite { to { stroke-dashoffset: 0; } }
/* The bow: the mark shrinks aside and its name arrives. Reduced motion enters here
   directly — finished figure, no theatre — so the strokes must be complete WITHOUT
   the draw animation ever having run. */
.mx-intro.is-lockup .mx-intro__m, .mx-intro.is-lockup .mx-intro__x { stroke-dashoffset: 0; animation: none; }
.mx-intro.is-lockup .mx-intro__mark { width: 64px; }
.mx-intro__word {
  font: 600 22px var(--font-sans); letter-spacing: -0.01em;
  color: var(--color-text-primary);
  max-width: 0; overflow: hidden; white-space: nowrap;
  opacity: 0; transform: translateX(-6px);
  transition: opacity 480ms var(--ease-out) 180ms, transform 480ms var(--ease-out) 180ms,
              max-width 560ms var(--ease-inout), margin 560ms var(--ease-inout);
}
.mx-intro.is-lockup .mx-intro__word { max-width: 240px; margin-inline-start: 16px; opacity: 1; transform: none; }
/* `top: -3px`: the BY is small caps beside a large word, and sharing the baseline makes it
   read as SUNK — optical alignment wants small type lifted toward the big type's midline. */
.mx-intro__word i { font-style: normal; font-weight: 500; font-size: 13px; letter-spacing: .14em; color: var(--color-text-muted); margin-inline-end: 4px; position: relative; top: -3px; }
:root[data-motion="off"] .mx-intro__mark,
:root[data-motion="off"] .mx-intro__word { transition: none; }
:root[data-motion="off"] .ann-spin { animation-duration: 2.4s; }
.chat-item__pin.is-on{color:var(--color-accent)}
.history__group-label--pinned{display:flex;align-items:center;gap:5px;color:var(--color-accent)}
.history__group-label--pinned .pin-dot{display:inline-flex}
.history__group-label--pinned .pin-dot svg{width:12px;height:12px}

/* ===== Document attachments (PDF / code / text) ===== */
.attach-file{position:relative;display:flex;align-items:center;gap:8px;max-width:210px;padding-block:8px;padding-inline-start:9px;padding-inline-end:30px;background:var(--color-bg-subtle);border:1px solid var(--color-border-hairline);border-radius:var(--radius-md)}
.attach-file.is-loading{opacity:.6}
/* Only part of this file fitted in the request budget. The chip carries the full explanation
   in its title; this is the mark that makes the user look. --color-error is defined in both
   the light and dark palettes, so one rule covers both themes. */
.attach-file.is-cut .attach-file__icon{background:var(--color-error)}
.attach-file.is-cut::after{content:"…";position:absolute;inset-block-end:2px;inset-inline-end:22px;font:700 11px var(--font-mono);color:var(--color-error);line-height:1}
.attach-file__icon{font:700 9px/1 var(--font-mono);letter-spacing:.5px;color:var(--color-on-accent);background:var(--color-accent);padding:4px 5px;border-radius:5px;flex:0 0 auto}
.attach-file__name{font:500 12.5px var(--font-sans);color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.msg-user__files{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
.msg-file-chip{display:inline-flex;align-items:center;gap:6px;max-width:230px;padding:5px 11px;background:rgba(127,127,127,.18);border-radius:var(--radius-pill)}
.msg-file-chip__ic{font:700 9px/1 var(--font-mono);letter-spacing:.5px;opacity:.65}
.msg-file-chip__nm{font:500 12px var(--font-sans);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Armed only where a question can still be scoped to the document behind it — see askFileArm.
   The unarmed chip keeps the flat, unclickable look it has always had, so the affordance
   appears exactly where it is real. */
.msg-file-chip--ask{cursor:pointer;transition:background var(--dur-fast) var(--ease-out)}
.msg-file-chip--ask:hover{background:rgba(127,127,127,.30)}
.msg-file-chip--ask:focus-visible{outline:2px solid var(--color-accent-deep, #14544A);outline-offset:2px}
/* askFileArm reads the width ONCE, when the turn is built, and the turn memo then holds that
   node until something unrelated rebuilds it — so a window dragged narrow kept a tap target
   sitting under the reader's own words, which is the one place the phone was excluded from.
   The width is a question only CSS can keep answering, so it answers it here. */
@media (max-width: 640px){.msg-file-chip--ask{cursor:default;pointer-events:none;transition:none}}
@media (prefers-reduced-motion: reduce){.msg-file-chip--ask{transition:none}}

/* ═══ ⑥ In-browser deck presenter (animated slideshow) ═══ */
.file-card__present{display:inline-flex;align-items:center;gap:var(--space-1);flex:0 0 auto;margin-inline-end:var(--space-2);padding:var(--space-2) var(--space-3);border-radius:var(--radius-pill);background:transparent;border:1.5px solid var(--color-accent);color:var(--color-accent);font-family:var(--font-sans);font-size:var(--text-sm);font-weight:var(--weight-medium);white-space:nowrap;transition:background var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out)}
[dir="rtl"] .file-card__present{font-family:var(--font-arabic)}
.file-card__present:hover{background:var(--color-accent);color:var(--color-on-accent)}
.deck-pres{position:fixed;inset:0;z-index:1200;background:#101014;display:flex;flex-direction:column;color:#fff}
.deck-pres__bar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;background:rgba(255,255,255,.04);border-bottom:1px solid rgba(255,255,255,.08)}
.deck-pres__name{font:600 14px var(--font-sans);opacity:.85;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
[dir="rtl"] .deck-pres__name{font-family:var(--font-arabic)}
.deck-pres__ctrls{display:flex;align-items:center;gap:8px;flex:0 0 auto}
.deck-pres__btn{padding:6px 12px;border-radius:var(--radius-pill);background:rgba(255,255,255,.1);color:#fff;font:500 12.5px var(--font-sans);white-space:nowrap;transition:background .15s}
[dir="rtl"] .deck-pres__btn{font-family:var(--font-arabic)}
.deck-pres__btn:hover{background:rgba(255,255,255,.22)}
.deck-pres__btn--x{font-size:15px;padding:6px 11px}
.deck-pres__anim{padding:6px 10px;border-radius:var(--radius-pill);background:rgba(255,255,255,.1);color:#fff;border:none;font:500 12.5px var(--font-sans);cursor:pointer}
[dir="rtl"] .deck-pres__anim{font-family:var(--font-arabic)}
.deck-pres__anim option{color:#111}
.deck-pres__count{font:600 12.5px var(--font-mono);opacity:.75;letter-spacing:.06em}
.deck-pres__stage{flex:1;display:flex;align-items:center;justify-content:center;padding:22px;min-height:0;cursor:pointer}
.deck-pres__slide{width:min(96vw,1200px);aspect-ratio:16/9;max-height:100%;border-radius:14px;overflow:hidden;background:#fff;box-shadow:0 24px 80px rgba(0,0,0,.55)}
/* slide TRANSITIONS (switchable) */
.deck-pres__slide.dp-anim-slide.is-in{animation:dpSlideIn .5s var(--ease-out) both}
.deck-pres__slide.dp-anim-fade.is-in{animation:dpFadeIn .55s ease both}
.deck-pres__slide.dp-anim-zoom.is-in{animation:dpZoomIn .5s var(--ease-out) both}
@keyframes dpSlideIn{from{opacity:0;transform:translateX(46px)}to{opacity:1;transform:none}}
[dir="rtl"] .deck-pres__slide.dp-anim-slide.is-in{animation-name:dpSlideInR}
@keyframes dpSlideInR{from{opacity:0;transform:translateX(-46px)}to{opacity:1;transform:none}}
@keyframes dpFadeIn{from{opacity:0}to{opacity:1}}
@keyframes dpZoomIn{from{opacity:0;transform:scale(.92)}to{opacity:1;transform:none}}
/* CONTENT entrance animations (staggered) */
.dp-cover,.dp-section{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:6%;position:relative;color:#fff}
.dp-cover__ring{position:absolute;inset:auto;width:44%;aspect-ratio:1;border:1.5px solid;border-radius:50%;top:-18%;inset-inline-end:-12%;opacity:.5}
.dp-cover__kicker{font:700 13px var(--font-sans);letter-spacing:.35em;animation:dpUp .6s .1s var(--ease-out) both}
.dp-cover__title{font:700 clamp(26px,4.6vw,52px)/1.25 var(--font-serif);margin:18px 0 6px;animation:dpUp .6s .22s var(--ease-out) both}
[dir="rtl"] .dp-cover__title{font-family:var(--font-arabic);font-weight:800}
.dp-cover__sub{font:500 clamp(14px,1.8vw,19px) var(--font-sans);opacity:.95;animation:dpUp .6s .34s var(--ease-out) both}
[dir="rtl"] .dp-cover__sub{font-family:var(--font-arabic)}
.dp-cover__rule{width:84px;height:4px;border-radius:2px;margin-top:22px;animation:dpGrow .5s .45s var(--ease-out) both}
.dp-cover__hint{position:absolute;bottom:5%;font:500 12.5px var(--font-sans);opacity:.5;animation:dpFadeIn 1s .9s both}
[dir="rtl"] .dp-cover__hint{font-family:var(--font-arabic)}
.dp-section__no{font:700 16px var(--font-mono);letter-spacing:.3em;animation:dpUp .5s .08s var(--ease-out) both}
.dp-section__title{font:700 clamp(24px,4vw,46px)/1.3 var(--font-serif);margin:14px 0 8px;animation:dpUp .55s .2s var(--ease-out) both}
[dir="rtl"] .dp-section__title{font-family:var(--font-arabic);font-weight:800}
.dp-slide{height:100%;display:flex;gap:4%;padding:4.5% 5%;color:#1a1a18;background:#fff;position:relative}
.dp-slide__bar{position:absolute;top:0;left:0;right:0;height:6px}
.dp-slide__txt{flex:1;min-width:0;display:flex;flex-direction:column}
.dp-slide__title{font:700 clamp(20px,3vw,34px)/1.3 var(--font-serif);animation:dpUp .5s .08s var(--ease-out) both}
[dir="rtl"] .dp-slide__title{font-family:var(--font-arabic);font-weight:800}
.dp-slide__rule{width:64px;height:4px;border-radius:2px;margin:10px 0 4%;animation:dpGrow .45s .2s var(--ease-out) both}
.dp-slide__txt ul{list-style:none;display:flex;flex-direction:column;gap:3.5%;flex:1;margin:0;padding:0}
.dp-slide__txt li{font:500 clamp(14px,1.9vw,21px)/1.55 var(--font-sans);padding-inline-start:1.15em;position:relative;animation:dpUp .5s calc(.3s + var(--i,0)*.14s) var(--ease-out) both}
[dir="rtl"] .dp-slide__txt li{font-family:var(--font-arabic)}
.dp-slide__txt li::before{content:"";position:absolute;inset-inline-start:0;top:.62em;width:.45em;height:.45em;border-radius:50%;background:currentColor;opacity:.45}
.dp-slide__fig{flex:0 0 38%;align-self:center;margin:0;animation:dpZoomIn .6s .35s var(--ease-out) both}
.dp-slide__fig img{width:100%;aspect-ratio:1/1;object-fit:cover;border-radius:12px;box-shadow:0 10px 34px rgba(0,0,0,.22)}
.dp-slide__fig figcaption{font:400 italic 12px var(--font-sans);opacity:.6;text-align:center;margin-top:6px}

/* ═══ Rich presenter layouts — stats / comparison / steps / cards / quote / hero / full-image ═══ */
.dp-rich{height:100%;display:flex;flex-direction:column;padding:4.5% 5.5%;background:#fff;color:#1a1a18;position:relative}
.dp-head{margin-bottom:3%}
.dp-rich .dp-slide__title{font:800 clamp(20px,3vw,34px)/1.25 var(--font-serif);animation:dpUp .5s .06s var(--ease-out) both}
[dir="rtl"] .dp-rich .dp-slide__title{font-family:var(--font-arabic)}
.dp-rich .dp-slide__rule{width:64px;height:4px;border-radius:2px;margin-top:12px;animation:dpGrow .45s .2s var(--ease-out) both}
/* Stats */
.dp-stats__grid{flex:1;display:flex;gap:3%;align-items:stretch;justify-content:center}
.dp-stat{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:.35em;padding:5% 3%;background:linear-gradient(160deg,#fff,#f5f8f6);border:1px solid #ecebe3;border-radius:20px;box-shadow:0 14px 38px rgba(0,0,0,.07);animation:dpUp .55s calc(.2s + var(--i)*.1s) var(--ease-out) both}
.dp-stat__v{font:800 clamp(34px,6.6vw,74px)/1 var(--font-sans);letter-spacing:-.025em}
.dp-stat__l{font:600 clamp(12px,1.5vw,17px)/1.4 var(--font-sans);color:#6b6a63}
[dir="rtl"] .dp-stat__l{font-family:var(--font-arabic)}
/* Comparison */
.dp-cmp__grid{flex:1;display:flex;gap:3%;align-items:stretch}
.dp-col{flex:1;display:flex;flex-direction:column;background:#fff;border:1px solid #ecebe3;border-radius:18px;overflow:hidden;box-shadow:0 14px 38px rgba(0,0,0,.07);animation:dpUp .55s calc(.2s + var(--i)*.12s) var(--ease-out) both}
.dp-col__h{padding:.7em 1em;color:#fff;font:800 clamp(15px,1.9vw,22px) var(--font-sans);text-align:center}
[dir="rtl"] .dp-col__h{font-family:var(--font-arabic)}
.dp-col ul{list-style:none;margin:0;padding:1.1em 1.2em;display:flex;flex-direction:column;gap:.65em}
.dp-col li{font:500 clamp(13px,1.6vw,18px)/1.5 var(--font-sans);padding-inline-start:1.1em;position:relative;color:#33322e}
[dir="rtl"] .dp-col li{font-family:var(--font-arabic)}
.dp-col li::before{content:"";position:absolute;inset-inline-start:0;top:.6em;width:.42em;height:.42em;border-radius:50%;background:currentColor;opacity:.4}
/* Steps / timeline */
.dp-steps__row{flex:1;display:flex;gap:2.5%;align-items:flex-start;justify-content:center;padding-top:2%}
.dp-step{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;gap:.5em;position:relative;animation:dpUp .55s calc(.2s + var(--i)*.12s) var(--ease-out) both}
.dp-step:not(:last-child)::after{content:"";position:absolute;top:1.05em;inset-inline-start:62%;width:76%;height:3px;background:#e4e2d9;z-index:0}
.dp-step__n{width:2.1em;height:2.1em;border-radius:50%;display:grid;place-items:center;color:#fff;font:800 clamp(15px,1.9vw,22px) var(--font-sans);z-index:1;box-shadow:0 8px 18px rgba(0,0,0,.16)}
.dp-step__t{font:800 clamp(14px,1.7vw,20px)/1.3 var(--font-sans)}
[dir="rtl"] .dp-step__t{font-family:var(--font-arabic)}
.dp-step__d{font:500 clamp(12px,1.35vw,15.5px)/1.5 var(--font-sans);color:#6b6a63;max-width:15em}
[dir="rtl"] .dp-step__d{font-family:var(--font-arabic)}
/* Cards */
.dp-cards__grid{flex:1;display:grid;gap:3%;grid-template-columns:repeat(2,1fr)}
.dp-cards__grid--3{grid-template-columns:repeat(3,1fr)}
.dp-cards__grid--1{grid-template-columns:1fr}
.dp-card{display:flex;flex-direction:column;gap:.35em;padding:4% 4.5%;background:linear-gradient(160deg,#fff,#f5f8f6);border:1px solid #ecebe3;border-inline-start:4px solid;border-radius:16px;box-shadow:0 12px 32px rgba(0,0,0,.06);animation:dpUp .5s calc(.2s + var(--i)*.09s) var(--ease-out) both}
.dp-card__ic{font-size:clamp(26px,3.4vw,40px);line-height:1;margin-bottom:.1em}
.dp-card__t{font:800 clamp(15px,1.8vw,21px)/1.25 var(--font-sans)}
[dir="rtl"] .dp-card__t{font-family:var(--font-arabic)}
.dp-card__d{font:500 clamp(12.5px,1.4vw,16px)/1.5 var(--font-sans);color:#6b6a63}
[dir="rtl"] .dp-card__d{font-family:var(--font-arabic)}
/* Quote */
.dp-quote{height:100%;display:flex;flex-direction:column;justify-content:center;padding:7% 9%;color:#fff;position:relative}
.dp-quote__mark{font:800 clamp(80px,14vw,150px)/.6 var(--font-serif);animation:dpUp .5s .1s var(--ease-out) both}
.dp-quote blockquote{margin:0;font:600 italic clamp(22px,3.6vw,44px)/1.35 var(--font-serif);animation:dpUp .6s .25s var(--ease-out) both}
[dir="rtl"] .dp-quote blockquote{font-family:var(--font-arabic);font-style:normal;font-weight:700}
.dp-quote cite{margin-top:1em;font:700 clamp(14px,1.8vw,20px) var(--font-sans);font-style:normal;animation:dpUp .6s .4s var(--ease-out) both}
[dir="rtl"] .dp-quote cite{font-family:var(--font-arabic)}
/* Hero */
.dp-hero{height:100%;display:flex;flex-direction:column;justify-content:center;padding:8% 9%;background:linear-gradient(160deg,#faf9f5,#eeece3);position:relative}
.dp-hero__bar{position:absolute;top:0;bottom:0;inset-inline-start:0;width:10px}
.dp-hero h1{margin:0;font:800 clamp(30px,5.5vw,64px)/1.15 var(--font-serif);letter-spacing:-.01em;animation:dpUp .6s .12s var(--ease-out) both}
[dir="rtl"] .dp-hero h1{font-family:var(--font-arabic)}
.dp-hero p{margin:.8em 0 0;font:500 clamp(16px,2.2vw,26px)/1.5 var(--font-sans);color:#4a4842;animation:dpUp .6s .3s var(--ease-out) both}
[dir="rtl"] .dp-hero p{font-family:var(--font-arabic)}
/* Full image */
.dp-imgfull{height:100%;position:relative;background:#111;overflow:hidden}
.dp-imgfull img{width:100%;height:100%;object-fit:cover;animation:dpZoomIn .8s var(--ease-out) both}
.dp-imgfull__grad{position:absolute;inset:0;background:linear-gradient(to top,rgba(0,0,0,.78),rgba(0,0,0,.05) 55%)}
.dp-imgfull h2{position:absolute;inset-inline:6% 6%;bottom:7%;margin:0;color:#fff;font:800 clamp(24px,4vw,46px)/1.2 var(--font-serif);text-shadow:0 2px 22px rgba(0,0,0,.55);animation:dpUp .6s .2s var(--ease-out) both;max-width:82%}
[dir="rtl"] .dp-imgfull h2{font-family:var(--font-arabic)}
@keyframes dpUp{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:none}}
@keyframes dpGrow{from{transform:scaleX(0)}to{transform:scaleX(1)}}
.deck-pres__nav{position:absolute;top:50%;transform:translateY(-50%);z-index:2;width:44px;height:64px;border-radius:12px;background:rgba(255,255,255,.07);color:#fff;font-size:20px;opacity:.6;transition:opacity .15s,background .15s}
.deck-pres__nav:hover{opacity:1;background:rgba(255,255,255,.18)}
.deck-pres__nav--prev{inset-inline-start:10px}
.deck-pres__nav--next{inset-inline-end:10px}
.deck-pres__notes{max-height:22vh;overflow:auto;padding:12px 20px;background:rgba(255,255,255,.06);border-top:1px solid rgba(255,255,255,.1);font:400 14px/1.7 var(--font-sans);color:#e8e6df}
[dir="rtl"] .deck-pres__notes{font-family:var(--font-arabic)}
.deck-pres__progress{height:4px;background:rgba(255,255,255,.08)}
.deck-pres__progress span{display:block;height:100%;background:linear-gradient(90deg,#c96442,#e0a458);transition:width .35s var(--ease-out)}
@media (max-width:640px){
  .dp-slide{flex-direction:column;overflow-y:auto}
  .dp-slide__fig{flex:0 0 auto;width:56%}
  .dp-slide__fig--chart{width:88%;align-self:center}
  .deck-pres__nav{display:none}
  .deck-pres__name{display:none}
  /* bar must WRAP — otherwise the ✕ close button lands off-screen on ≤360px phones (no Esc there!) */
  .deck-pres__bar{flex-wrap:wrap;padding:8px 10px;justify-content:flex-end}
  .deck-pres__ctrls{flex-wrap:wrap;justify-content:flex-end;min-width:0;row-gap:6px}
  /* portrait phones: a forced 16:9 slide is ~195px tall and CLIPS bullets — let it use the stage */
  .deck-pres__slide{aspect-ratio:auto;height:100%;width:96vw}
}
/* touch devices reach :fullscreen too (Android) — hover-reveal means INVISIBLE yet tappable
   controls that swallow taps; keep them visible where hover doesn't exist */
@media (hover:none){
  .deck-pres:fullscreen .deck-pres__bar,.deck-pres:fullscreen .deck-pres__nav{opacity:1}
}

/* ═══ Deck card (live-building + editable slides) ═══ */
.deck-card{border:1px solid var(--color-border);border-radius:var(--radius-lg);background:var(--color-surface);overflow:hidden;margin:2px 0}
.deck-card__head{display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--color-border)}
.deck-card__ic{flex:0 0 auto;width:42px;height:42px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:20px}
.deck-card__meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.deck-card__meta strong{font:600 15px var(--font-sans);color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
[dir="rtl"] .deck-card__meta strong{font-family:var(--font-arabic)}
.deck-card__sub{font:400 12.5px var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .deck-card__sub{font-family:var(--font-arabic)}
.deck-card__live{color:var(--color-accent);animation:deckPulse 1.4s ease-in-out infinite}
@keyframes deckPulse{0%,100%{opacity:1}50%{opacity:.45}}
.deck-card__acts{display:flex;gap:8px;flex:0 0 auto;flex-wrap:wrap}
.deck-card__btn{padding:7px 14px;border-radius:var(--radius-pill);border:1.5px solid var(--color-border);background:transparent;color:var(--color-text-primary);font:500 13px var(--font-sans);white-space:nowrap;transition:all .15s}
[dir="rtl"] .deck-card__btn{font-family:var(--font-arabic)}
.deck-card__btn:hover{border-color:var(--color-accent);color:var(--color-accent)}
.deck-card__btn--go{background:var(--color-accent);border-color:var(--color-accent);color:var(--color-on-accent)}
.deck-card__btn--go:hover{background:var(--color-accent-hover);color:var(--color-on-accent)}
.deck-card__btn--danger:hover{border-color:#c0392b;color:#c0392b}
.deck-card__btn:disabled{opacity:.55;pointer-events:none}
.deck-card__list{display:flex;flex-direction:column}
.deck-card__row,.deck-card__slide summary{display:flex;align-items:center;gap:10px;padding:10px 16px;font:500 13.5px var(--font-sans);color:var(--color-text-primary);border-bottom:1px solid var(--color-border)}
[dir="rtl"] .deck-card__row,[dir="rtl"] .deck-card__slide summary{font-family:var(--font-arabic)}
.deck-card__row:last-child{border-bottom:none}
.is-building .deck-card__row{animation:deckIn .4s var(--ease-out) both}
@keyframes deckIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.deck-card__n{flex:0 0 auto;width:24px;height:24px;border-radius:8px;background:var(--color-surface-sunken);display:flex;align-items:center;justify-content:center;font:600 11.5px var(--font-mono);color:var(--color-text-muted)}
.deck-card__row--sec{background:var(--color-surface-sunken);font-weight:600}
.deck-card__sec{font:600 10.5px var(--font-sans);color:var(--color-on-accent);background:var(--color-accent);border-radius:var(--radius-pill);padding:2px 9px}
[dir="rtl"] .deck-card__sec{font-family:var(--font-arabic)}
.deck-card__slide summary{cursor:pointer;list-style:none;user-select:none}
.deck-card__slide summary::-webkit-details-marker{display:none}
.deck-card__slide summary:hover{background:var(--color-surface-sunken)}
.deck-card__st{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.deck-card__editTag{flex:0 0 auto;font:500 11.5px var(--font-sans);color:var(--color-text-muted);opacity:0;transition:opacity .15s}
[dir="rtl"] .deck-card__editTag{font-family:var(--font-arabic)}
.deck-card__slide summary:hover .deck-card__editTag,.deck-card__slide[open] .deck-card__editTag{opacity:1;color:var(--color-accent)}
.deck-card__panel{padding:12px 16px 16px;background:var(--color-surface-sunken);border-bottom:1px solid var(--color-border);display:flex;flex-direction:column;gap:8px}
.deck-card__lbl{font:600 11.5px var(--font-sans);color:var(--color-text-muted);margin-top:4px}
[dir="rtl"] .deck-card__lbl{font-family:var(--font-arabic)}
.deck-card__inp{width:100%;padding:8px 11px;border:1px solid var(--color-border);border-radius:var(--radius-md);background:var(--color-surface);color:var(--color-text-primary);font:400 13.5px var(--font-sans);resize:vertical}
[dir="rtl"] .deck-card__inp{font-family:var(--font-arabic)}
.deck-card__inp:focus{outline:none;border-color:var(--color-accent)}
.dk-brow{display:flex;gap:6px;align-items:center}
.dk-brow .deck-card__inp{flex:1}
.dk-x{flex:0 0 auto;width:26px;height:26px;border-radius:8px;background:transparent;color:var(--color-text-muted);font-size:13px;transition:all .15s}
.dk-x:hover{background:#c0392b1a;color:#c0392b}
.dk-bullets{display:flex;flex-direction:column;gap:6px}
.deck-card__mini{align-self:flex-start;padding:4px 12px;border-radius:var(--radius-pill);border:1px dashed var(--color-border);background:transparent;color:var(--color-text-muted);font:500 12px var(--font-sans);transition:all .15s}
[dir="rtl"] .deck-card__mini{font-family:var(--font-arabic)}
.deck-card__mini:hover{border-color:var(--color-accent);color:var(--color-accent)}
.dk-asset{display:flex;align-items:center;gap:8px;padding:7px 11px;border:1px solid var(--color-border);border-radius:var(--radius-md);background:var(--color-surface);font:500 12.5px var(--font-sans);color:var(--color-text-primary)}
[dir="rtl"] .dk-asset{font-family:var(--font-arabic)}
.dk-asset__u{flex:1;min-width:0;font:400 11px var(--font-mono);color:var(--color-text-muted);direction:ltr;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.deck-card__panelacts{display:flex;gap:8px;flex-wrap:wrap;margin-top:4px}
.deck-card__lock{padding:10px 16px;font:500 12.5px var(--font-sans);color:var(--color-text-muted);background:var(--color-surface-sunken);text-align:center}
[dir="rtl"] .deck-card__lock{font-family:var(--font-arabic)}
/* chart figure slot in the presenter */
.dp-slide__fig--chart{background:#fff;border-radius:12px;padding:8px;box-shadow:0 8px 30px rgba(0,0,0,.12);flex:0 0 44%}
.dp-slide__fig--chart svg{width:100%;height:auto}
/* ═══ TRUE fullscreen — the slide fills the screen edge-to-edge like real PowerPoint ═══ */
.deck-pres:fullscreen{background:#000}
.deck-pres:fullscreen .deck-pres__stage{padding:0}
.deck-pres:fullscreen .deck-pres__slide{width:100vw;height:100vh;max-height:none;aspect-ratio:auto;border-radius:0;box-shadow:none}
.deck-pres:fullscreen .deck-pres__bar{position:absolute;top:0;left:0;right:0;z-index:4;background:linear-gradient(rgba(0,0,0,.55),transparent);border-bottom:none;opacity:0;transition:opacity .25s}
.deck-pres:fullscreen .deck-pres__bar:hover,.deck-pres:fullscreen .deck-pres__bar:focus-within{opacity:1}
.deck-pres:fullscreen .deck-pres__progress{position:absolute;bottom:0;left:0;right:0;z-index:4}
.deck-pres:fullscreen .deck-pres__notes{position:absolute;bottom:4px;left:0;right:0;z-index:4;background:rgba(0,0,0,.78)}
.deck-pres:fullscreen .deck-pres__nav{opacity:0}
.deck-pres:fullscreen .deck-pres__nav:hover{opacity:1}
@media (max-width:640px){.deck-card__head{flex-wrap:wrap}.deck-card__acts{width:100%;justify-content:stretch}.deck-card__acts .deck-card__btn{flex:1;text-align:center}.deck-card__panelacts .deck-card__btn{flex:1}}

/* ═══ Firas Code — the browser IDE workspace ═══ */
.product-code .chat-scroll,.product-code .composer-wrap{display:none}
.product-code #tierSwitch{display:none}
#codeWorkspace{display:none}
.product-code #codeWorkspace{display:flex;flex:1;min-height:0;flex-direction:column;background:var(--color-bg);position:relative}
/* home */
.cw-home{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:22px;padding:24px;overflow-y:auto}
.cw-home__hero{text-align:center;display:flex;flex-direction:column;align-items:center;gap:8px}
.cw-home__mark{width:64px;height:64px;border-radius:18px;display:grid;place-items:center;font-size:30px;color:#fff;background:linear-gradient(140deg,var(--color-accent),#5b8def);box-shadow:0 10px 30px color-mix(in srgb,var(--color-accent) 35%,transparent)}
.cw-home__hero h1{font:700 34px var(--font-serif);color:var(--color-text-primary)}
.cw-home__hero p{font:400 15px/1.6 var(--font-sans);color:var(--color-text-secondary);max-width:440px}
[dir="rtl"] .cw-home__hero p{font-family:var(--font-arabic)}
.cw-home__card{width:min(94vw,430px);margin-inline:auto;align-self:center;display:flex;flex-direction:column;gap:8px;padding:20px;border:1px solid var(--color-border);border-radius:var(--radius-lg);background:var(--color-surface)}
.cw-home__lbl{font:600 12px var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .cw-home__lbl{font-family:var(--font-arabic)}
.cw-home__name{padding:11px 13px;border:1px solid var(--color-border-strong);border-radius:var(--radius-md);background:var(--color-bg-subtle);color:var(--color-text-primary);font:400 14px var(--font-sans)}
.cw-home__name:focus{outline:none;border-color:var(--color-accent)}
.cw-home__tpls{display:flex;gap:8px;flex-wrap:wrap}
.cw-home__tpl{flex:1;min-width:120px;padding:10px;border:1.5px solid var(--color-border);border-radius:var(--radius-md);background:transparent;color:var(--color-text-secondary);font:500 13px var(--font-sans);transition:.15s}
[dir="rtl"] .cw-home__tpl{font-family:var(--font-arabic)}
.cw-home__tpl.is-active{border-color:var(--color-accent);color:var(--color-accent);background:var(--color-accent-soft)}
.cw-home__create{margin-top:6px;padding:12px;border:none;border-radius:var(--radius-pill);background:var(--color-accent);color:var(--color-on-accent);font:600 14px var(--font-sans);cursor:pointer}
[dir="rtl"] .cw-home__create{font-family:var(--font-arabic)}
.cw-home__create:hover{background:var(--color-accent-hover)}
.cw-home__hint{font:400 12.5px var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .cw-home__hint{font-family:var(--font-arabic)}
/* IDE frame */
.cw{flex:1;display:flex;flex-direction:column;min-height:0}
.cw-bar{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--color-border-hairline);background:var(--color-surface)}
.cw-bar__home{width:32px;height:32px;border-radius:9px;background:var(--color-bg-subtle);color:var(--color-text-primary);font-size:16px}
.cw-bar__home:hover{background:var(--color-accent-soft);color:var(--color-accent)}
.cw-bar__name{font:600 14px var(--font-sans);color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
[dir="rtl"] .cw-bar__name{font-family:var(--font-arabic)}
.cw-bar__sp{flex:1}
.cw-bar__btn{padding:7px 13px;border-radius:var(--radius-pill);border:1px solid var(--color-border);background:transparent;color:var(--color-text-primary);font:500 12.5px var(--font-sans);white-space:nowrap;transition:.15s}
[dir="rtl"] .cw-bar__btn{font-family:var(--font-arabic)}
.cw-bar__btn:hover{border-color:var(--color-accent);color:var(--color-accent)}
.cw-main{flex:1;display:grid;grid-template-columns:200px minmax(0,1.1fr) minmax(0,1fr);min-height:0}
/* tree */
.cw-tree{border-inline-end:1px solid var(--color-border-hairline);background:var(--color-surface);overflow-y:auto}
.cw-file{display:flex;align-items:center;gap:7px;padding:8px 10px;cursor:pointer;border-bottom:1px solid var(--color-border-hairline);font:500 12.5px var(--font-mono);color:var(--color-text-secondary)}
.cw-file:hover{background:var(--color-bg-subtle)}
.cw-file.is-active{background:var(--color-accent-soft);color:var(--color-accent)}
.cw-file__p{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}
.cw-file__size{font-size:10.5px;color:var(--color-text-muted)}
.cw-file__x{width:20px;height:20px;border-radius:6px;color:var(--color-text-muted);font-size:11px;opacity:0;transition:.15s}
.cw-file:hover .cw-file__x{opacity:1}
.cw-file__x:hover{background:#c0392b1a;color:#c0392b}
/* editor */
/* A COLUMN now, because the tab strip sits above the code. The editor keeps every pixel
   the strip does not take: flex:1 + min-height:0, never a percentage height - CodeMirror
   measures its own element, and height:100% inside a column that also holds a 34px strip
   overflows the panel by exactly the strip, hiding the last line behind the status bar. */
.cw-ed{min-width:0;display:flex;flex-direction:column;border-inline-end:1px solid var(--color-border-hairline);background:#fff}
[data-theme="dark"] .cw-ed{background:#1e1e1c}
.cw-ed .CodeMirror{flex:1;height:auto;min-height:0;font:13px/1.6 var(--font-mono);direction:ltr}
[data-theme="dark"] .cw-ed .CodeMirror{background:#1e1e1c;color:#e8e6df}
[data-theme="dark"] .cw-ed .CodeMirror-gutters{background:#262624;border-color:#3a3a37}
[data-theme="dark"] .cw-ed .CodeMirror-linenumber{color:#6d6b64}
[data-theme="dark"] .cw-ed .CodeMirror-cursor{border-color:#e8e6df}
.cw-ed__ta{flex:1;border:none;resize:none;padding:12px;font:13px/1.6 var(--font-mono);background:transparent;color:var(--color-text-primary);direction:ltr;outline:none}
/* right pane */
.cw-right{min-width:0;display:flex;flex-direction:column;background:var(--color-surface)}
.cw-tabs{display:flex;align-items:center;gap:4px;padding:6px 8px;border-bottom:1px solid var(--color-border-hairline)}
.cw-tab{padding:6px 14px;border-radius:var(--radius-pill);background:transparent;color:var(--color-text-secondary);font:500 12.5px var(--font-sans);position:relative}
[dir="rtl"] .cw-tab{font-family:var(--font-arabic)}
.cw-tab.is-active{background:var(--color-accent-soft);color:var(--color-accent);font-weight:600}
.cw-tab.has-errors::after{content:"";position:absolute;top:4px;inset-inline-end:4px;width:7px;height:7px;border-radius:50%;background:#c0392b}
.cw-conclear{margin-inline-start:auto;width:26px;height:26px;border-radius:8px;color:var(--color-text-muted);font-size:12px}
.cw-conclear:hover{background:var(--color-bg-subtle)}
.cw-preview{flex:1;border:none;background:#fff;width:100%}
/* A COLUMN, NOT A SCROLLER. This was `flex:1;overflow-y:auto` — the whole pane scrolled — so the
   prompt added underneath would have scrolled away with the output, which is the one thing a
   prompt must never do. The LIST scrolls now; the prompt is pinned to the bottom of the pane. */
.cw-console{flex:1;min-block-size:0;display:flex;flex-direction:column;background:#141412}
.cw-console__list{flex:1;min-block-size:0;overflow-y:auto;padding:8px 10px;display:flex;flex-direction:column;gap:2px}
/* ── the prompt ─────────────────────────────────────────────────────────────────────────────
   Hard-coded dark like every other rule in this pane: a console is a console in all six themes,
   and half-tokenising one surface reads worse than either choice made consistently. */
.cw-term{flex:0 0 auto;display:flex;align-items:center;gap:6px;padding:6px 10px;
  border-block-start:1px solid #ffffff14;background:#0f0f0e;direction:ltr}
.cw-term__caret{font:12px/1 var(--font-mono);color:#8fd694;flex:0 0 auto;user-select:none}
.cw-term__in{flex:1;min-inline-size:0;border:0;background:transparent;outline:none;
  font:12px/1.6 var(--font-mono);color:#e8e6df;direction:ltr;text-align:left}
.cw-term__in::placeholder{color:#6f6d66}
.cw-term:focus-within{background:#131312}
.cw-term:focus-within .cw-term__caret{color:#b7e6b9}
/* What was typed, and what came back. The echo is dimmer than the answer: the reader wrote it,
   so it is there to locate the reply, not to be read again. */
.cw-conrow--in{color:#8a8880}
.cw-conrow--ret{color:#e8e6df}
.cw-conrow{font:12px/1.5 var(--font-mono);color:#c9c7bf;direction:ltr;text-align:left;white-space:pre-wrap;word-break:break-word;border-bottom:1px solid #ffffff0a;padding:2px 0}
.cw-conrow--error{color:#ff8a80}
.cw-conrow--warn{color:#ffd54f}
.cw-conrow--info{color:#81d4fa}
.cw-conrow--ok{color:#8fd694}
.cw-conrow--run{color:#81d4fa}
/* The app's OWN messages, as opposed to program output. .cw-conrow forces direction:ltr —
   correct for stdout and tracebacks, wrong for an Arabic sentence, whose trailing punctuation
   and Latin runs get reordered inside an LTR paragraph. plaintext lets the row take its
   direction from its own first strong character, which is what dir=auto on the element means. */
.cw-conrow.is-msg{unicode-bidi:plaintext;text-align:start}
/* AI bar */
.cw-ai{display:flex;gap:8px;padding:10px 12px;border-top:1px solid var(--color-border-hairline);background:var(--color-surface)}
.cw-ai__in{flex:1;padding:11px 14px;border:1.5px solid var(--color-border-strong);border-radius:var(--radius-pill);background:var(--color-bg-subtle);color:var(--color-text-primary);font:400 13.5px var(--font-sans)}
[dir="rtl"] .cw-ai__in{font-family:var(--font-arabic)}
.cw-ai__in:focus{outline:none;border-color:var(--color-accent)}
.cw-ai__go{padding:0 20px;border-radius:var(--radius-pill);border:none;background:var(--color-accent);color:var(--color-on-accent);font:600 13px var(--font-sans);white-space:nowrap}
[dir="rtl"] .cw-ai__go{font-family:var(--font-arabic)}
.cw-ai__go:hover{background:var(--color-accent-hover)}
.cw-ai__go:disabled{opacity:.6}
/* diff review overlay */
.cw-diff{position:absolute;inset:0;z-index:20;background:color-mix(in srgb,var(--color-bg) 55%,transparent);backdrop-filter:blur(3px);display:flex;align-items:center;justify-content:center;padding:18px}
.cw-diff__card{width:min(96vw,760px);max-height:86%;display:flex;flex-direction:column;border:1px solid var(--color-border);border-radius:var(--radius-lg);background:var(--color-surface);box-shadow:0 18px 60px rgba(0,0,0,.25);overflow:hidden}
.cw-diff__head{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--color-border-hairline);font:600 14px var(--font-sans);color:var(--color-text-primary)}
[dir="rtl"] .cw-diff__head{font-family:var(--font-arabic)}
.cw-diff__sum{font:400 12.5px var(--font-sans);color:var(--color-text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cw-diff__list{flex:1;overflow-y:auto;padding:8px 12px;display:flex;flex-direction:column;gap:8px}
.cw-diff__file{border:1px solid var(--color-border-hairline);border-radius:var(--radius-md);overflow:hidden}
.cw-diff__file summary{display:flex;align-items:center;gap:8px;padding:9px 12px;cursor:pointer;list-style:none;background:var(--color-bg-subtle);font:500 12.5px var(--font-mono)}
.cw-diff__file summary::-webkit-details-marker{display:none}
.cw-diff__badge{font:600 10px var(--font-sans);padding:2px 8px;border-radius:var(--radius-pill)}
[dir="rtl"] .cw-diff__badge{font-family:var(--font-arabic)}
.cw-diff__badge--new{background:#1e824c22;color:#1e824c}
.cw-diff__badge--edit{background:#b9770e22;color:#b9770e}
.cw-diff__badge--del{background:#c0392b22;color:#c0392b}
.cw-diff__lines{max-height:300px;overflow:auto;direction:ltr;text-align:left}
.cw-dl{font:11.5px/1.55 var(--font-mono);white-space:pre-wrap;word-break:break-all;padding:0 10px}
.cw-dl--add{background:#1e824c18;color:#1e824c}
.cw-dl--del{background:#c0392b14;color:#c0392b;text-decoration:line-through}
.cw-dl--same{color:var(--color-text-secondary)}
.cw-dl--fold{text-align:center;color:var(--color-text-muted);background:var(--color-bg-subtle)}
.cw-diff__all{padding:10px 12px;font:500 12px var(--font-sans);color:var(--color-text-secondary)}
.cw-diff__pre{max-height:260px;overflow:auto;font:11.5px/1.55 var(--font-mono);direction:ltr;text-align:left;background:var(--color-bg-subtle);border-radius:8px;padding:8px;margin-top:6px}
.cw-diff__all--del{color:#c0392b;font-weight:600}
.cw-diff__acts{display:flex;gap:10px;padding:12px 16px;border-top:1px solid var(--color-border-hairline)}
.cw-diff__apply{flex:1;padding:11px;border:none;border-radius:var(--radius-pill);background:var(--color-accent);color:var(--color-on-accent);font:600 13.5px var(--font-sans)}
[dir="rtl"] .cw-diff__apply{font-family:var(--font-arabic)}
.cw-diff__apply:hover{background:var(--color-accent-hover)}
.cw-diff__cancel{padding:11px 22px;border:1px solid var(--color-border);border-radius:var(--radius-pill);background:transparent;color:var(--color-text-primary);font:500 13px var(--font-sans)}
[dir="rtl"] .cw-diff__cancel{font-family:var(--font-arabic)}
/* project-card bridge button */
.proj-card__opencode{background:linear-gradient(120deg,var(--color-accent),#5b8def) !important;color:#fff !important;border:none !important}
/* mobile: stacked panes */
@media (max-width:860px){
  .cw-main{grid-template-columns:1fr;grid-template-rows:auto minmax(0,1.05fr) minmax(0,1fr)}
  .cw-tree{max-height:120px;border-inline-end:none;border-bottom:1px solid var(--color-border-hairline)}
  .cw-ed{border-inline-end:none;border-bottom:1px solid var(--color-border-hairline)}
  .cw-bar{flex-wrap:wrap}
}

/* ═══ Firas Code — design polish v2 (premium warm-IDE look) ═══ */
/* entrance */
.cw,.cw-home{animation:cwIn .45s var(--ease-out) both}
@keyframes cwIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
/* home: dotted paper backdrop + glowing mark + template CARDS */
.cw-home{background:radial-gradient(circle at 50% -10%,color-mix(in srgb,var(--color-accent) 7%,transparent),transparent 55%),radial-gradient(color-mix(in srgb,var(--color-text-muted) 14%,transparent) 1px,transparent 1px);background-size:auto,22px 22px}
.cw-home__mark{position:relative;width:72px;height:72px;border-radius:20px;font-size:0;background:linear-gradient(140deg,var(--color-accent),#5b8def);box-shadow:0 14px 40px color-mix(in srgb,var(--color-accent) 40%,transparent),inset 0 1px 0 rgba(255,255,255,.35);animation:cwFloat 5s ease-in-out infinite}
.cw-home__mark-glyph{font:800 24px/1 var(--font-mono);color:#fff;letter-spacing:-1px}
@keyframes cwFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}
/* (Removed) A clipped-gradient headline lived here. Every one of its four properties was
   already overridden by the repair layer at "Firas Code home: remove the splash-screen
   vocabulary" further down, so it painted nothing — it only survived as a false positive in
   design scans and as one more era for the next reader to untangle. Deleted, not restyled. */
.cw-home__card{box-shadow:0 20px 60px color-mix(in srgb,var(--color-text-primary) 7%,transparent);border-radius:20px;backdrop-filter:blur(6px)}
.cw-home__tpls{flex-direction:column}
.cw-home__tpl{display:flex;align-items:center;gap:12px;text-align:start;padding:12px 14px;min-width:0;position:relative}
.cw-home__tpl-ic{flex:0 0 auto;width:38px;height:38px;border-radius:11px;display:grid;place-items:center;font-size:18px;background:var(--color-bg-subtle)}
.cw-home__tpl.is-active .cw-home__tpl-ic{background:color-mix(in srgb,var(--color-accent) 16%,transparent)}
.cw-home__tpl-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.cw-home__tpl-txt strong{font:600 13.5px var(--font-sans);color:var(--color-text-primary)}
.cw-home__tpl-txt small{font:400 11.5px var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .cw-home__tpl-txt strong,[dir="rtl"] .cw-home__tpl-txt small{font-family:var(--font-arabic)}
.cw-home__tpl-check{flex:0 0 auto;width:20px;height:20px;border-radius:50%;display:grid;place-items:center;font-size:10px;color:#fff;background:var(--color-accent);opacity:0;transform:scale(.5);transition:.18s}
.cw-home__tpl.is-active .cw-home__tpl-check{opacity:1;transform:scale(1)}
.cw-home__tpl:hover{transform:translateY(-1px);box-shadow:0 6px 18px color-mix(in srgb,var(--color-text-primary) 8%,transparent)}
.cw-home__create{box-shadow:0 8px 24px color-mix(in srgb,var(--color-accent) 35%,transparent);transition:.2s}
.cw-home__create:hover{transform:translateY(-1px);box-shadow:0 12px 30px color-mix(in srgb,var(--color-accent) 45%,transparent)}
/* bar: glassy chrome with a brand chip */
.cw-bar{gap:10px;padding:9px 14px;background:color-mix(in srgb,var(--color-surface) 88%,transparent);backdrop-filter:blur(8px)}
.cw-bar__logo{width:28px;height:28px;border-radius:9px;display:grid;place-items:center;font:800 11px var(--font-mono);color:#fff;background:linear-gradient(140deg,var(--color-accent),#5b8def);box-shadow:0 4px 12px color-mix(in srgb,var(--color-accent) 35%,transparent)}
.cw-bar__count{font:500 11px var(--font-sans);color:var(--color-text-muted);background:var(--color-bg-subtle);border-radius:var(--radius-pill);padding:3px 10px}
[dir="rtl"] .cw-bar__count{font-family:var(--font-arabic)}
.cw-bar__btn{transition:.18s}
.cw-bar__btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px color-mix(in srgb,var(--color-accent) 20%,transparent)}
.cw-run{border-color:var(--color-accent);color:var(--color-accent);background:color-mix(in srgb,var(--color-accent) 8%,transparent)}
/* tree */
.cw-tree__head{display:flex;align-items:center;gap:8px;padding:10px 12px 8px;font:700 10.5px var(--font-sans);letter-spacing:.14em;text-transform:uppercase;color:var(--color-text-muted)}
[dir="rtl"] .cw-tree__head{font-family:var(--font-arabic);letter-spacing:.04em}
.cw-tree__n{margin-inline-start:auto;font:600 10px var(--font-mono);background:var(--color-bg-subtle);border-radius:var(--radius-pill);padding:2px 8px}
.cw-file{border-bottom:none;border-radius:9px;margin:1px 6px;padding:7px 9px;position:relative;transition:background .15s,color .15s}
.cw-file.is-active{background:color-mix(in srgb,var(--color-accent) 11%,transparent);color:var(--color-accent);font-weight:600}
.cw-file.is-active::before{content:"";position:absolute;inset-inline-start:-6px;top:20%;bottom:20%;width:3px;border-radius:2px;background:var(--color-accent)}
.cw-file__dot{flex:0 0 auto;width:9px;height:9px;border-radius:3px}
.cw-file__dot[data-ext="html"]{background:#e0764f}
.cw-file__dot[data-ext="css"]{background:#5b8def}
.cw-file__dot[data-ext="js"]{background:#e0b35e}
.cw-file__dot[data-ext="py"]{background:#57ae9c}
.cw-file__dot[data-ext="json"]{background:#a78bfa}
.cw-file__dot[data-ext="md"],.cw-file__dot[data-ext="txt"]{background:var(--color-text-muted)}
/* editor: warm CodeMirror theme (light) */
.cw-ed{background:#FDFCF8}
.cw-ed .CodeMirror{background:#FDFCF8;color:#3f3e38;font-size:13.5px}
.cw-ed .CodeMirror-gutters{background:#F6F4EC;border-color:#ECE9DE}
.cw-ed .CodeMirror-linenumber{color:#B5B1A2}
.cw-ed .CodeMirror-activeline-background{background:#F3F0E5}
.cw-ed .CodeMirror-selected{background:#E8E2CF}
.cw-ed .cm-tag{color:#B3512F}
.cw-ed .cm-bracket{color:#8f8a7a}
.cw-ed .cm-attribute{color:#23786A}
.cw-ed .cm-string,.cw-ed .cm-string-2{color:#2E7D5B}
.cw-ed .cm-keyword{color:#A8562E;font-weight:600}
.cw-ed .cm-def{color:#5B6ABF}
.cw-ed .cm-variable{color:#3f3e38}
.cw-ed .cm-variable-2{color:#23786A}
.cw-ed .cm-property{color:#5B6ABF}
.cw-ed .cm-number,.cw-ed .cm-atom{color:#9C6B1F}
.cw-ed .cm-comment{color:#A5A294;font-style:italic}
.cw-ed .cm-operator{color:#8a5a3a}
.cw-ed .cm-qualifier{color:#23786A}
.cw-ed .cm-builtin{color:#7C3AED}
.cw-ed .cm-meta{color:#8f8a7a}
.cw-ed .cm-error{background:transparent;color:#c0392b}
.cw-ed .CodeMirror-cursor{border-color:#A8562E;border-width:1.6px}
/* editor: charcoal theme (dark) */
[data-theme="dark"] .cw-ed,[data-theme="dark"] .cw-ed .CodeMirror{background:#1E1E1C;color:#E4E1D6}
[data-theme="dark"] .cw-ed .CodeMirror-gutters{background:#242422;border-color:#34342f}
[data-theme="dark"] .cw-ed .CodeMirror-linenumber{color:#6d6b60}
[data-theme="dark"] .cw-ed .CodeMirror-activeline-background{background:#282824}
[data-theme="dark"] .cw-ed .CodeMirror-selected{background:#3a382e}
[data-theme="dark"] .cw-ed .cm-tag{color:#E08B66}
[data-theme="dark"] .cw-ed .cm-attribute{color:#6FB8AB}
[data-theme="dark"] .cw-ed .cm-string,[data-theme="dark"] .cw-ed .cm-string-2{color:#8FC9A8}
[data-theme="dark"] .cw-ed .cm-keyword{color:#E39A72}
[data-theme="dark"] .cw-ed .cm-def{color:#9BA8E8}
[data-theme="dark"] .cw-ed .cm-variable{color:#E4E1D6}
[data-theme="dark"] .cw-ed .cm-variable-2{color:#6FB8AB}
[data-theme="dark"] .cw-ed .cm-property{color:#9BA8E8}
[data-theme="dark"] .cw-ed .cm-number,[data-theme="dark"] .cw-ed .cm-atom{color:#E0B35E}
[data-theme="dark"] .cw-ed .cm-comment{color:#7a776a}
[data-theme="dark"] .cw-ed .cm-operator{color:#d9a583}
[data-theme="dark"] .cw-ed .cm-builtin{color:#C4A7F5}
[data-theme="dark"] .cw-ed .CodeMirror-cursor{border-color:#E39A72}
/* right pane: browser-style preview frame */
.cw-tabs{padding:8px 10px;gap:6px}
.cw-tab{border:1px solid transparent}
.cw-tab.is-active{border-color:color-mix(in srgb,var(--color-accent) 30%,transparent);box-shadow:0 2px 8px color-mix(in srgb,var(--color-accent) 14%,transparent)}
.cw-prevwrap{flex:1;display:flex;flex-direction:column;min-height:0;margin:0 10px 10px;border:1px solid var(--color-border-hairline);border-radius:12px;overflow:hidden;box-shadow:0 8px 26px color-mix(in srgb,var(--color-text-primary) 7%,transparent);background:#fff}
.cw-prevchrome{display:flex;align-items:center;gap:10px;padding:7px 12px;background:var(--color-bg-subtle);border-bottom:1px solid var(--color-border-hairline)}
.cw-prevdots{display:inline-flex;gap:5px;direction:ltr}
.cw-prevdots i{width:9px;height:9px;border-radius:50%;background:#e0764f;opacity:.75}
.cw-prevdots i:nth-child(2){background:#e0b35e}
.cw-prevdots i:nth-child(3){background:#57ae9c}
.cw-prevaddr{flex:1;text-align:center;font:500 10.5px var(--font-mono);color:var(--color-text-muted);background:var(--color-surface);border:1px solid var(--color-border-hairline);border-radius:var(--radius-pill);padding:3px 12px;direction:ltr}
.cw-preview{flex:1}
/* console: terminal feel */
.cw-console{margin:0 10px 10px;border-radius:12px;border:1px solid #ffffff14}
.cw-conrow{border-bottom:none;padding:2px 0 2px 16px;position:relative}
.cw-conrow::before{content:"›";position:absolute;left:2px;color:#57ae9c;opacity:.7}
.cw-conrow--error::before{content:"✕";color:#ff8a80}
.cw-conrow--warn::before{content:"⚠";font-size:10px;color:#ffd54f}
.cw-conrow--ok::before{content:"✓";color:#8fd694}
.cw-conrow--run::before{content:"▶";font-size:9px;color:#81d4fa}
/* On a message row the marker FLOWS instead of being pinned with `left`. An absolute marker
   cannot follow the text: dir="auto" does not match a [dir="rtl"] selector, and
   unicode-bidi:plaintext changes only how inline content is ordered, not the `direction`
   property that inset-inline-start resolves against — so every logical-position trick still
   lands it on the left while Arabic text sits on the right. As a static inline box it is a
   NEUTRAL character at the logical start, which the bidi algorithm places correctly in both
   directions with no branching at all. */
/* The layout half of `is-msg` lives further down, scoped to .cw--v3 — a later rule there
   re-applies the gutter this needs removed, and at equal specificity the later rule wins. */
/* AI bar: glowing focus + gradient action */
.cw-ai{gap:10px;padding:12px 14px}
.cw-ai__in{transition:border-color .2s,box-shadow .2s}
.cw-ai__in:focus{box-shadow:0 0 0 4px color-mix(in srgb,var(--color-accent) 14%,transparent)}
.cw-ai__go{background:linear-gradient(120deg,var(--color-accent),#5b8def);box-shadow:0 6px 18px color-mix(in srgb,var(--color-accent) 30%,transparent);transition:.2s}
.cw-ai__go:hover{transform:translateY(-1px);box-shadow:0 9px 24px color-mix(in srgb,var(--color-accent) 42%,transparent)}
/* diff overlay: scale-in + gutter signs */
.cw-diff__card{animation:cwDiffIn .3s var(--ease-out) both;border-radius:18px}
@keyframes cwDiffIn{from{opacity:0;transform:scale(.96) translateY(8px)}to{opacity:1;transform:none}}
.cw-dl{padding:0 10px 0 26px;position:relative}
.cw-dl--add::before{content:"+";position:absolute;left:8px;font-weight:700}
.cw-dl--del::before{content:"−";position:absolute;left:8px;font-weight:700}
.cw-dl--del{text-decoration:none;opacity:.8}
.cw-diff__head{background:linear-gradient(100deg,color-mix(in srgb,var(--color-accent) 9%,transparent),transparent)}
/* scrollbars inside the workspace */
#codeWorkspace ::-webkit-scrollbar{width:9px;height:9px}
#codeWorkspace ::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--color-text-muted) 32%,transparent);border-radius:8px;border:2px solid transparent;background-clip:content-box}
#codeWorkspace ::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--color-text-muted) 50%,transparent);background-clip:content-box;border:2px solid transparent}
#codeWorkspace ::-webkit-scrollbar-track{background:transparent}

/* ═══ Share sheet (link + guaranteed copy + OS share) ═══ */
.share-sheet{position:fixed;inset:0;z-index:1300;background:rgba(0,0,0,.35);backdrop-filter:blur(2px);display:flex;align-items:flex-end;justify-content:center;opacity:0;transition:opacity .18s}
.share-sheet.is-open{opacity:1}
.share-sheet__card{width:min(96vw,480px);margin-bottom:max(14px,env(safe-area-inset-bottom));background:var(--color-surface);border:1px solid var(--color-border);border-radius:18px;padding:16px;display:flex;flex-direction:column;gap:10px;box-shadow:0 18px 60px rgba(0,0,0,.3);transform:translateY(14px);transition:transform .2s var(--ease-out)}
.share-sheet.is-open .share-sheet__card{transform:none}
.share-sheet__head{display:flex;align-items:center;justify-content:space-between;font:600 15px var(--font-sans);color:var(--color-text-primary)}
[dir="rtl"] .share-sheet__head{font-family:var(--font-arabic)}
.share-sheet__x{width:40px;height:40px;border-radius:10px;color:var(--color-text-muted);font-size:15px;flex:0 0 auto}
.share-sheet__x:hover{background:var(--color-bg-subtle);color:var(--color-text-primary)}
.share-sheet__link{width:100%;padding:11px 12px;border:1px solid var(--color-border-strong);border-radius:var(--radius-md);background:var(--color-bg-subtle);color:var(--color-text-primary);font:500 12.5px var(--font-mono);text-overflow:ellipsis}
.share-sheet__link:focus{outline:none;border-color:var(--color-accent)}
.share-sheet__acts{display:flex;gap:8px;flex-wrap:wrap}
.share-sheet__copy{flex:1;padding:12px;border:none;border-radius:var(--radius-pill);background:var(--color-accent);color:var(--color-on-accent);font:600 13.5px var(--font-sans)}
[dir="rtl"] .share-sheet__copy{font-family:var(--font-arabic)}
.share-sheet__copy:hover{background:var(--color-accent-hover)}
.share-sheet__os{flex:1;padding:12px;border:1.5px solid var(--color-border);border-radius:var(--radius-pill);background:transparent;color:var(--color-text-primary);font:500 13px var(--font-sans)}
[dir="rtl"] .share-sheet__os{font-family:var(--font-arabic)}
.share-sheet__os:hover{border-color:var(--color-accent);color:var(--color-accent)}
.share-sheet__note{font:400 11.5px var(--font-sans);color:var(--color-text-muted);text-align:center;margin:0}
[dir="rtl"] .share-sheet__note{font-family:var(--font-arabic)}

/* ═══════════ FIRAS CODE — Batch 1 enhancements (50-agent dev) ═══════════ */
/* ═══ bottom STATUS BAR ═══ */
.cw-status{display:flex;align-items:center;gap:14px;padding:5px 14px;border-top:1px solid var(--color-border-hairline);background:var(--color-bg-subtle);font:500 11.5px var(--font-mono);color:var(--color-text-secondary);white-space:nowrap;overflow-x:auto;scrollbar-width:none}
.cw-status::-webkit-scrollbar{display:none}
.cw-status__seg{display:inline-flex;align-items:center;gap:5px;direction:ltr;unicode-bidi:isolate}
.cw-status__sp{flex:1}
.cw-status__save{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;border-radius:var(--radius-pill);font:600 11px var(--font-sans);direction:ltr}
[dir="rtl"] .cw-status__save{font-family:var(--font-arabic);direction:rtl}
.cw-status__save::before{content:"";width:7px;height:7px;border-radius:50%;background:currentColor;flex:none}
.cw-status__save.is-saved{background:color-mix(in srgb,var(--color-accent) 14%,transparent);color:var(--color-accent)}
.cw-status__save.is-editing{background:#b9770e1f;color:#b9770e}
.cw-status__save.is-saving{background:#5b8def22;color:#5b8def}
.cw-status__save.is-saving::before{animation:cwStatusPulse .9s ease-in-out infinite}
@keyframes cwStatusPulse{0%,100%{opacity:1}50%{opacity:.3}}
@media (max-width:860px){.cw-status{gap:10px;font-size:11px}.cw-status__lang,.cw-status__tab{display:none}}
/* ═══ LIVE PREVIEW UPGRADES (device presets · auto-reload · reload · open-in-tab) ═══ */
.cw-prevchrome{flex-wrap:wrap;row-gap:6px}
.cw-prevaddr{min-width:80px}
.cw-prevseg{display:inline-flex;gap:2px;padding:2px;border-radius:var(--radius-pill);background:var(--color-surface);border:1px solid var(--color-border-hairline);direction:ltr}
.cw-prevdev{display:inline-flex;align-items:center;gap:5px;padding:4px 9px;border-radius:var(--radius-pill);background:transparent;color:var(--color-text-secondary);font:600 10.5px var(--font-sans);line-height:1;cursor:pointer;border:none;transition:.15s;white-space:nowrap}
[dir="rtl"] .cw-prevdev{font-family:var(--font-arabic)}
.cw-prevdev__t{font-size:10.5px}
.cw-prevdev:hover{color:var(--color-text-primary)}
.cw-prevdev.is-active{background:var(--color-accent-soft);color:var(--color-accent)}
@media(max-width:900px){.cw-prevdev__t{display:none}.cw-prevdev{padding:5px 8px}}
.cw-prevctl{display:inline-flex;align-items:center;gap:5px;direction:ltr}
.cw-prevbtn{display:inline-flex;align-items:center;justify-content:center;gap:5px;height:26px;min-width:26px;padding:0 8px;border-radius:8px;border:1px solid var(--color-border-hairline);background:var(--color-surface);color:var(--color-text-secondary);font:600 10.5px var(--font-sans);line-height:1;cursor:pointer;transition:.15s}
[dir="rtl"] .cw-prevbtn{font-family:var(--font-arabic)}
.cw-prevbtn:hover{border-color:var(--color-accent);color:var(--color-accent);transform:translateY(-1px)}
.cw-prevreload,.cw-prevtab{font-size:14px;padding:0}
.cw-prevauto .cw-prevauto__dot{width:7px;height:7px;border-radius:50%;background:var(--color-text-muted);transition:.2s}
.cw-prevauto.is-on{border-color:color-mix(in srgb,var(--color-accent) 45%,transparent);color:var(--color-accent);background:var(--color-accent-soft)}
.cw-prevauto.is-on .cw-prevauto__dot{background:#42b883;box-shadow:0 0 0 3px color-mix(in srgb,#42b883 22%,transparent);animation:cwPulse 1.8s ease-in-out infinite}
@keyframes cwPulse{0%,100%{opacity:1}50%{opacity:.55}}
.cw-prevstage{flex:1;min-height:0;display:flex;align-items:stretch;justify-content:center;overflow:auto;background:#fff}
.cw-prevstage.is-framed{align-items:center;padding:12px;background:repeating-linear-gradient(45deg,var(--color-bg-subtle),var(--color-bg-subtle) 10px,var(--color-bg) 10px,var(--color-bg) 20px)}
.cw-prevstage.is-framed .cw-preview{flex:0 0 auto;height:100%;max-height:100%;border-radius:14px;box-shadow:0 10px 40px color-mix(in srgb,var(--color-text-primary) 22%,transparent);border:1px solid var(--color-border-hairline);background:#fff}
[data-theme="dark"] .cw-prevstage.is-framed .cw-preview{box-shadow:0 10px 40px rgba(0,0,0,.5)}
.cw-prevstage .cw-preview{border:none;background:#fff;width:100%;height:100%}
/* ═══ FILE OPS (context menu + inline rename) ═══ */
.cw-tree__head{display:flex;align-items:center;gap:6px}
.cw-tree__newfolder{margin-inline-start:auto;width:24px;height:22px;border-radius:7px;color:var(--color-text-muted);font-size:12px;line-height:1;transition:.15s}
.cw-tree__newfolder:hover{background:var(--color-accent-soft);color:var(--color-accent)}
.cw-file__rename{width:100%;min-width:0;border:1.5px solid var(--color-accent);border-radius:6px;padding:2px 6px;background:var(--color-surface);color:var(--color-text-primary);font:500 12.5px var(--font-mono);direction:ltr;text-align:left;outline:none}
.cw-fmenu{position:fixed;z-index:60;min-width:196px;padding:6px;border-radius:12px;background:var(--color-surface);border:1px solid var(--color-border-strong);box-shadow:0 18px 50px color-mix(in srgb,var(--color-text-primary) 22%,transparent),0 2px 8px rgba(0,0,0,.08);animation:cwMenuIn .13s var(--ease-out) both}
@keyframes cwMenuIn{from{opacity:0;transform:scale(.96) translateY(-4px)}to{opacity:1;transform:none}}
.cw-fmenu__item{display:block;width:100%;text-align:start;padding:8px 12px;border-radius:8px;background:transparent;color:var(--color-text-primary);font:500 13px var(--font-sans);white-space:nowrap;cursor:pointer;transition:.12s}
[dir="rtl"] .cw-fmenu__item{font-family:var(--font-arabic)}
.cw-fmenu__item:hover{background:var(--color-accent-soft);color:var(--color-accent)}
.cw-fmenu__item.is-danger{color:#c0392b}
.cw-fmenu__item.is-danger:hover{background:#c0392b14;color:#c0392b}
.cw-fmenu__sep{height:1px;margin:5px 8px;background:var(--color-border-hairline)}
[data-theme="dark"] .cw-fmenu{box-shadow:0 18px 50px rgba(0,0,0,.55),0 2px 8px rgba(0,0,0,.4)}
/* Context menu, part two — what the ported model needs on top of the box that was already here:
   a header naming the target, an icon column the label can truncate beside, a keyboard-active
   row that reads the same as hover, and a body that scrolls instead of running off the viewport.
   See the note above cwOpenMenu in app.js for what came from the reference and what did not. */
.cw-fmenu{max-width:min(320px,calc(100vw - 16px));overflow-y:auto;overscroll-behavior:contain;outline:none}
.cw-fmenu__head{padding:4px 10px 7px;margin-bottom:4px;border-bottom:1px solid var(--color-border-hairline);color:var(--color-text-muted);font:500 11px/1.35 var(--font-mono);direction:ltr;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cw-fmenu__item{display:flex;align-items:center;gap:9px;text-align:start}
.cw-fmenu__ico{flex:0 0 auto;width:16px;text-align:center;font-size:12.5px;line-height:1;opacity:.85}
.cw-fmenu__lbl{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis}
/* .is-on is the arrow-key highlight and must read as the hover it replaces; the danger pair sits
   at the same weight as .is-danger:hover so a keyboard walk onto Delete still looks like Delete. */
.cw-fmenu__item.is-on{background:var(--color-accent-soft);color:var(--color-accent)}
.cw-fmenu__item.is-danger.is-on{background:#c0392b14;color:#c0392b}
.cw-fmenu__item:focus-visible{box-shadow:var(--shadow-focus)}
@media (prefers-reduced-motion:reduce){.cw-fmenu{animation:none}}
/* ═══ PROJECT SNAPSHOTS / VERSION HISTORY ═══ */
.cw-snapbtn{gap:5px}
.cw-snap{position:absolute;inset:0;z-index:22;background:color-mix(in srgb,var(--color-bg) 55%,transparent);backdrop-filter:blur(3px);display:flex;align-items:center;justify-content:center;padding:18px}
.cw-snap__card{width:min(96vw,560px);max-height:86%;display:flex;flex-direction:column;border:1px solid var(--color-border-strong);border-radius:14px;background:var(--color-surface);box-shadow:0 18px 60px rgba(0,0,0,.25);overflow:hidden;animation:cwDiffIn .28s var(--ease-out) both}
.cw-snap__head{display:flex;align-items:center;gap:8px;padding:12px 14px;border-bottom:1px solid var(--color-border-hairline);font:600 14px var(--font-sans);color:var(--color-text-primary)}
[dir="rtl"] .cw-snap__head{font-family:var(--font-arabic)}
.cw-snap__head strong{margin-inline-end:auto}
.cw-snap__now{padding:6px 12px;border:none;border-radius:var(--radius-pill);background:var(--color-accent);color:#fff;font:600 11.5px var(--font-sans);cursor:pointer;transition:.15s}
.cw-snap__now:hover{filter:brightness(1.08)}
[dir="rtl"] .cw-snap__now{font-family:var(--font-arabic)}
.cw-snap__clear{padding:6px 10px;border:1px solid var(--color-border-strong);border-radius:var(--radius-pill);background:transparent;color:var(--color-text-muted);font:500 11px var(--font-sans);cursor:pointer;transition:.15s}
.cw-snap__clear:hover{border-color:#c0392b;color:#c0392b}
[dir="rtl"] .cw-snap__clear{font-family:var(--font-arabic)}
.cw-snap__x{width:26px;height:26px;border-radius:8px;background:var(--color-bg-subtle);color:var(--color-text-secondary);font-size:12px;cursor:pointer;transition:.15s}
.cw-snap__x:hover{background:var(--color-accent-soft);color:var(--color-accent)}
.cw-snap__list{flex:1;overflow-y:auto;padding:8px 10px;display:flex;flex-direction:column;gap:6px}
.cw-snap__row{display:flex;align-items:center;gap:10px;padding:9px 11px;border:1px solid var(--color-border-hairline);border-radius:10px;background:var(--color-bg-subtle);transition:.15s}
.cw-snap__row:hover{border-color:var(--color-border-strong)}
.cw-snap__row.is-fresh{animation:cwSnapFlash 1.1s var(--ease-out) both}
@keyframes cwSnapFlash{0%{background:var(--color-accent-soft);border-color:var(--color-accent)}100%{background:var(--color-bg-subtle)}}
.cw-snap__kind{flex:0 0 auto;font:600 9.5px var(--font-sans);letter-spacing:.04em;text-transform:uppercase;padding:3px 8px;border-radius:var(--radius-pill)}
[dir="rtl"] .cw-snap__kind{font-family:var(--font-arabic);letter-spacing:0}
.cw-snap__kind--manual{background:color-mix(in srgb,var(--color-accent) 16%,transparent);color:var(--color-accent)}
.cw-snap__kind--auto{background:var(--color-bg);color:var(--color-text-muted);border:1px solid var(--color-border-hairline)}
.cw-snap__meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.cw-snap__name{font:600 12.5px var(--font-mono);color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:start}
.cw-snap__sub{font:400 11px var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .cw-snap__sub{font-family:var(--font-arabic)}
.cw-snap__restore{flex:0 0 auto;padding:6px 14px;border:1px solid var(--color-accent);border-radius:var(--radius-pill);background:transparent;color:var(--color-accent);font:600 11.5px var(--font-sans);cursor:pointer;transition:.15s}
.cw-snap__restore:hover{background:var(--color-accent);color:#fff}
[dir="rtl"] .cw-snap__restore{font-family:var(--font-arabic)}
.cw-snap__del{flex:0 0 auto;width:24px;height:24px;border-radius:7px;background:transparent;color:var(--color-text-muted);font-size:11px;cursor:pointer;transition:.15s}
.cw-snap__del:hover{background:#c0392b1a;color:#c0392b}
.cw-snap__empty{padding:34px 18px;text-align:center;font:400 13px/1.6 var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .cw-snap__empty{font-family:var(--font-arabic)}
/* ═══ SNAPSHOT COMPARE — arming chrome in the history list, card inside .cw-diff ═══ */
.cw-snap__cmp{flex:0 0 auto;width:26px;height:24px;border:1px solid var(--color-border-hairline);border-radius:7px;background:transparent;color:var(--color-text-muted);font-size:12px;line-height:1;cursor:pointer;transition:.15s}
.cw-snap__cmp:hover{border-color:var(--color-accent);color:var(--color-accent)}
.cw-snap__cmp.is-on{background:var(--color-accent);border-color:var(--color-accent);color:var(--color-on-accent)}
.cw-snap__cmpbar{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--color-border-hairline);background:var(--color-accent-soft);font:500 11.5px var(--font-sans);color:var(--color-text-secondary)}
[dir="rtl"] .cw-snap__cmpbar{font-family:var(--font-arabic)}
.cw-snap__cmpbar>span{flex:1;min-width:0}
.cw-snap__cmpcur{flex:0 0 auto;padding:5px 11px;border:1px solid var(--color-accent);border-radius:var(--radius-pill);background:transparent;color:var(--color-accent);font:600 11px var(--font-sans);cursor:pointer;transition:.15s}
[dir="rtl"] .cw-snap__cmpcur{font-family:var(--font-arabic)}
.cw-snap__cmpcur:hover{background:var(--color-accent);color:var(--color-on-accent)}
.cw-snap__cmpx{flex:0 0 auto;width:22px;height:22px;border:0;border-radius:6px;background:transparent;color:var(--color-text-muted);font-size:11px;cursor:pointer;transition:.15s}
.cw-snap__cmpx:hover{color:#c0392b}
/* The width has to out-rank `.cw--v3 .cw-diff__card{max-width:760px}` further down this file, and
   760px is far too narrow for two columns of code — hence the id. */
#codeWorkspace .cw-diff__card.cw-cmp{width:min(96vw,1120px);max-width:min(96vw,1120px)}
.cw-cmp .cw-diff__totals{margin-inline-start:auto;font:500 11.5px var(--font-sans);color:var(--color-text-secondary);white-space:nowrap}
[dir="rtl"] .cw-cmp .cw-diff__totals{font-family:var(--font-arabic)}
.cw-cmp .cw-diff__totals .add{color:#1e824c}
.cw-cmp .cw-diff__totals .del{color:#c0392b}
.cw-cmp__body{flex:1;min-height:0;display:flex}
.cw-cmp__rail{flex:0 0 208px;min-width:0;overflow-y:auto;border-inline-end:1px solid var(--color-border-hairline);padding:8px;display:flex;flex-direction:column;gap:4px;background:var(--color-bg-subtle)}
.cw-cmp__f{display:flex;align-items:center;gap:7px;width:100%;padding:7px 8px;border:1px solid transparent;border-radius:8px;background:transparent;cursor:pointer;text-align:start;transition:.15s}
.cw-cmp__f:hover{background:var(--color-surface);border-color:var(--color-border-hairline)}
.cw-cmp__f.is-on{background:var(--color-surface);border-color:var(--color-accent)}
.cw-cmp__f.is-quiet{opacity:.5}
.cw-cmp__fb{flex:0 0 auto;font:600 9px var(--font-sans);letter-spacing:.03em;padding:2px 6px;border-radius:var(--radius-pill)}
[dir="rtl"] .cw-cmp__fb{font-family:var(--font-arabic)}
.cw-cmp__fb--new{background:#1e824c22;color:#1e824c}
.cw-cmp__fb--del{background:#c0392b22;color:#c0392b}
.cw-cmp__fb--edit{background:#b9770e22;color:#b9770e}
.cw-cmp__fb--same{background:var(--color-bg);color:var(--color-text-muted)}
.cw-cmp__fp{flex:1;min-width:0;font:500 11.5px var(--font-mono);color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cw-cmp__fc{flex:0 0 auto;font:600 10px var(--font-mono)}
.cw-cmp__fc .add{color:#1e824c}
.cw-cmp__fc .del{color:#c0392b}
.cw-cmp__pane{flex:1;min-width:0;overflow:auto}
.cw-cmp__note{padding:16px 18px;font:400 12.5px/1.7 var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .cw-cmp__note{font-family:var(--font-arabic)}
/* ONE grid holds both columns on purpose: two independently scrolling panes desynchronise the
   moment a line wraps, and here a wrapped cell just makes its whole grid ROW taller, so the two
   sides stay level with no JS scroll-syncing at all. */
.cw-cmp__grid{display:grid;grid-template-columns:1fr 1fr;min-width:560px;align-items:stretch}
.cw-cmp__h{position:sticky;top:0;z-index:1;padding:7px 10px;background:var(--color-bg-subtle);border-bottom:1px solid var(--color-border-hairline);font:600 11px var(--font-sans);color:var(--color-text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
[dir="rtl"] .cw-cmp__h{font-family:var(--font-arabic)}
.cw-cmp__c{display:flex;gap:8px;padding:0 8px;font:11.5px/1.6 var(--font-mono);color:var(--color-text-secondary)}
/* border-LEFT, not border-inline-start: the caption cells carry dir="rtl" in Arabic, and a logical
   property resolves against the element's OWN direction — it would flip the divider to the far
   side of a grid that is permanently LTR. */
.cw-cmp__c--b{border-left:1px solid var(--color-border-hairline)}
.cw-cmp__n{flex:0 0 34px;text-align:end;color:var(--color-text-muted);opacity:.6;user-select:none}
.cw-cmp__s{flex:1;min-width:0;white-space:pre-wrap;word-break:break-all}
.cw-cmp__c--add{background:#1e824c18;color:#1e824c}
.cw-cmp__c--del{background:#c0392b14;color:#c0392b}
.cw-cmp__c--void{background:repeating-linear-gradient(45deg,transparent,transparent 5px,var(--color-bg-subtle) 5px,var(--color-bg-subtle) 10px)}
.cw-cmp__fold{grid-column:1/-1;text-align:center;padding:1px 0;font:10.5px var(--font-mono);color:var(--color-text-muted);background:var(--color-bg-subtle);border-block:1px solid var(--color-border-hairline)}
.cw-cmp__back{padding:11px 22px;border:1px solid var(--color-border);border-radius:var(--radius-pill);background:transparent;color:var(--color-text-primary);font:500 13px var(--font-sans);cursor:pointer;transition:.15s}
[dir="rtl"] .cw-cmp__back{font-family:var(--font-arabic)}
.cw-cmp__back:hover{border-color:var(--color-accent);color:var(--color-accent)}
.cw-cmp .cw-diff__cancel{margin-inline-start:auto}
@media (max-width:860px){
  .cw-cmp__body{flex-direction:column}
  .cw-cmp__rail{flex:0 0 auto;flex-direction:row;flex-wrap:nowrap;max-height:56px;overflow-x:auto;overflow-y:hidden;border-inline-end:0;border-bottom:1px solid var(--color-border-hairline)}
  .cw-cmp__f{width:auto;flex:0 0 auto}
  .cw-cmp__fp{max-width:120px}
}
/* ═══ CHANGE LOG — applied AI edits, as dated events ═══
   The same overlay geometry as .cw-snap above: the two panels hand off to each other by name, and
   a different card size would read as a different product rather than the other half of one. */
/* The door INTO here from the history panel. It lives with the change log rather than with the
   snapshot rules it sits among, because this is what it opens and this is what gets removed with
   it. Sized like .cw-snap__clear next to it so the header stays one row of equals. */
.cw-snap__tolog{padding:6px 10px;border:1px solid var(--color-border-strong);border-radius:var(--radius-pill);background:transparent;color:var(--color-text-secondary);font:600 11px var(--font-sans);cursor:pointer;transition:.15s}
[dir="rtl"] .cw-snap__tolog{font-family:var(--font-arabic)}
.cw-snap__tolog:hover{border-color:var(--color-accent);color:var(--color-accent)}
.cw-clog{position:absolute;inset:0;z-index:22;background:color-mix(in srgb,var(--color-bg) 55%,transparent);backdrop-filter:blur(3px);display:flex;align-items:center;justify-content:center;padding:18px}
.cw-clog__card{width:min(96vw,600px);max-height:86%;display:flex;flex-direction:column;border:1px solid var(--color-border-strong);border-radius:14px;background:var(--color-surface);box-shadow:0 18px 60px rgba(0,0,0,.25);overflow:hidden;animation:cwDiffIn .28s var(--ease-out) both}
.cw-clog__head{display:flex;align-items:center;gap:8px;padding:12px 14px;border-bottom:1px solid var(--color-border-hairline);font:600 14px var(--font-sans);color:var(--color-text-primary)}
[dir="rtl"] .cw-clog__head{font-family:var(--font-arabic)}
.cw-clog__head strong{margin-inline-end:auto}
.cw-clog__tosnap{padding:6px 11px;border:1px solid var(--color-border-strong);border-radius:var(--radius-pill);background:transparent;color:var(--color-text-secondary);font:600 11px var(--font-sans);cursor:pointer;transition:.15s}
[dir="rtl"] .cw-clog__tosnap{font-family:var(--font-arabic)}
.cw-clog__tosnap:hover{border-color:var(--color-accent);color:var(--color-accent)}
.cw-clog__clear{padding:6px 10px;border:1px solid var(--color-border-strong);border-radius:var(--radius-pill);background:transparent;color:var(--color-text-muted);font:500 11px var(--font-sans);cursor:pointer;transition:.15s}
[dir="rtl"] .cw-clog__clear{font-family:var(--font-arabic)}
.cw-clog__clear:hover{border-color:#c0392b;color:#c0392b}
.cw-clog__x{width:26px;height:26px;border-radius:8px;background:var(--color-bg-subtle);color:var(--color-text-secondary);font-size:12px;cursor:pointer;transition:.15s}
.cw-clog__x:hover{background:var(--color-accent-soft);color:var(--color-accent)}
.cw-clog__list{flex:1;overflow-y:auto;padding:8px 10px;display:flex;flex-direction:column;gap:6px}
.cw-clog__row{display:flex;align-items:center;gap:10px;padding:9px 11px;border:1px solid var(--color-border-hairline);border-radius:10px;background:var(--color-bg-subtle);transition:.15s}
.cw-clog__row:hover{border-color:var(--color-border-strong)}
/* An undone edit still happened: dimmed, never removed — and never struck through, because a line
   drawn through Arabic cuts the joins the letters are made of. The word "undone" says it instead. */
.cw-clog__row.is-undone{opacity:.55}
.cw-clog__src{flex:0 0 auto;font:600 9.5px var(--font-sans);letter-spacing:.04em;text-transform:uppercase;padding:3px 8px;border-radius:var(--radius-pill)}
[dir="rtl"] .cw-clog__src{font-family:var(--font-arabic);letter-spacing:0}
.cw-clog__src--ai{background:color-mix(in srgb,var(--color-accent) 16%,transparent);color:var(--color-accent)}
.cw-clog__src--improve{background:#b9770e22;color:#b9770e}
.cw-clog__meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.cw-clog__sum{font:600 12.5px var(--font-sans);color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:start}
[dir="rtl"] .cw-clog__sum{font-family:var(--font-arabic)}
.cw-clog__sub{font:400 11px var(--font-sans);color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:start}
[dir="rtl"] .cw-clog__sub{font-family:var(--font-arabic)}
.cw-clog__when{font-variant-numeric:tabular-nums}
.cw-clog__stat{flex:0 0 auto;font:600 10.5px var(--font-mono);white-space:nowrap}
.cw-clog__stat .add{color:#1e824c}
.cw-clog__stat .del{color:#c0392b}
.cw-clog__view{flex:0 0 auto;width:26px;height:24px;border:1px solid var(--color-border-hairline);border-radius:7px;background:transparent;color:var(--color-text-muted);font-size:12px;line-height:1;cursor:pointer;transition:.15s}
.cw-clog__view:hover:not(:disabled){border-color:var(--color-accent);color:var(--color-accent)}
.cw-clog__view:disabled{opacity:.35;cursor:default}
.cw-clog__del{flex:0 0 auto;width:24px;height:24px;border-radius:7px;background:transparent;color:var(--color-text-muted);font-size:11px;cursor:pointer;transition:.15s}
.cw-clog__del:hover{background:#c0392b1a;color:#c0392b}
.cw-clog__empty{padding:34px 18px;text-align:center;font:400 13px/1.6 var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .cw-clog__empty{font-family:var(--font-arabic)}
@media (max-width:640px){
  .cw-clog{padding:10px}
  .cw-clog__card{max-height:92%}
  .cw-clog__head{gap:6px;padding:10px 11px;font-size:13px}
  /* .cw-snap__tolog is shrunk here too: it is the FOURTH control this patch puts into the history
     panel's header, which has no narrow rule of its own, and the button that made that row tight
     is the one that should give the width back. */
  .cw-clog__tosnap,.cw-clog__clear,.cw-snap__tolog{padding:5px 8px;font-size:10.5px}
  .cw-clog__sum{font-size:12px}
}
@media (prefers-reduced-motion:reduce){.cw-clog__card{animation:none}}
/* ═══ COMMAND PALETTE (Ctrl+K / Ctrl+Shift+P) ═══ */
.cw-pal{position:absolute;inset:0;z-index:40;background:color-mix(in srgb,var(--color-bg) 45%,transparent);backdrop-filter:blur(4px);display:flex;align-items:flex-start;justify-content:center;padding:12vh 18px 18px;opacity:0;transition:opacity .16s var(--ease-out)}
.cw-pal.is-open{opacity:1}
.cw-pal__card{width:min(94vw,620px);max-height:66vh;display:flex;flex-direction:column;background:var(--color-surface);border:1px solid var(--color-border-strong);border-radius:16px;box-shadow:0 24px 70px rgba(0,0,0,.34);overflow:hidden;transform:translateY(-8px) scale(.98);transition:transform .18s var(--ease-out)}
.cw-pal.is-open .cw-pal__card{transform:none}
.cw-pal__in{border:none;border-bottom:1px solid var(--color-border-hairline);padding:16px 18px;background:transparent;color:var(--color-text-primary);font:500 15px var(--font-sans)}
[dir="rtl"] .cw-pal__in{font-family:var(--font-arabic)}
.cw-pal__in:focus{outline:none}
.cw-pal__in::placeholder{color:var(--color-text-muted)}
.cw-pal__list{flex:1;overflow-y:auto;padding:6px}
.cw-pal__row{display:flex;align-items:center;gap:12px;padding:9px 12px;border-radius:10px;cursor:pointer;color:var(--color-text-primary)}
.cw-pal__row.is-sel{background:var(--color-accent-soft)}
.cw-pal__ic{flex:0 0 auto;width:22px;text-align:center;font-size:14px;color:var(--color-accent);opacity:.9}
.cw-pal__lbl{flex:1;min-width:0;font:500 13.5px var(--font-sans);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
[dir="rtl"] .cw-pal__lbl{font-family:var(--font-arabic)}
.cw-pal__hit{background:transparent;color:var(--color-accent);font-weight:700;padding:0}
.cw-pal__hint{flex:0 0 auto;font:400 11.5px var(--font-sans);color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:44%}
[dir="rtl"] .cw-pal__hint{font-family:var(--font-arabic)}
.cw-pal__empty{padding:24px 14px;text-align:center;color:var(--color-text-muted);font:400 13px var(--font-sans)}
[dir="rtl"] .cw-pal__empty{font-family:var(--font-arabic)}
.cw-pal__foot{display:flex;gap:16px;padding:9px 16px;border-top:1px solid var(--color-border-hairline);background:var(--color-bg-subtle);font:400 11px var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .cw-pal__foot{font-family:var(--font-arabic)}
.cw-pal__foot b{color:var(--color-text-secondary);font-weight:700}
/* ═══ FIRAS CODE — COMMAND PALETTE, 21st.dev "OMNI" MODEL ═══
   Heading blocks, a two-line row, key chips. The .cw--v3 palette rules further down this file
   cannot match anything: .cw-pal is appended to #codeWorkspace, a SIBLING of .cw.cw--v3, so the
   hover and selected-bar polish they were written for is re-stated here, where it lands. */
.cw-pal__head{display:flex;align-items:center;gap:10px;padding:0 16px;border-bottom:1px solid var(--color-border-hairline)}
.cw-pal__mag{flex:0 0 auto;display:flex;color:var(--color-text-muted)}
.cw-pal__mag svg{width:15px;height:15px}
.cw-pal__head .cw-pal__in{flex:1;min-width:0;border:none;padding:16px 0}
/* The field has no box of its own in here any more, so the inset ring styles.css puts on
   .cw-pal__in:focus-visible drew a bare rectangle across the header. It belongs to the row now. */
.cw-pal__head .cw-pal__in:focus-visible{outline:none}
.cw-pal__head:focus-within{box-shadow:inset 0 0 0 2px var(--color-accent-ring)}
.cw-pal__kbd{flex:0 0 auto;font:600 10.5px var(--font-mono);color:var(--color-text-muted);background:var(--color-bg-subtle);border:1px solid var(--color-border-hairline);border-radius:6px;padding:3px 6px}
.cw-pal__grp{padding:9px 12px 3px;font:600 10px var(--font-sans);letter-spacing:.09em;text-transform:uppercase;color:var(--color-text-muted)}
/* Arabic has no case to upper, and letter-spacing cuts the joins between letters. */
[dir="rtl"] .cw-pal__grp{font-family:var(--font-arabic);letter-spacing:0;text-transform:none}
.cw-pal__row:hover{background:var(--color-bg-subtle)}
.cw-pal__row.is-sel,.cw-pal__row.is-sel:hover{background:var(--color-accent-soft);box-shadow:inset 2px 0 0 var(--color-accent)}
[dir="rtl"] .cw-pal__row.is-sel,[dir="rtl"] .cw-pal__row.is-sel:hover{box-shadow:inset -2px 0 0 var(--color-accent)}
.cw-pal__txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.cw-pal__txt .cw-pal__lbl{flex:0 0 auto}
.cw-pal__sub{font:400 11.5px var(--font-sans);color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
[dir="rtl"] .cw-pal__sub{font-family:var(--font-arabic)}
.cw-pal__keys{flex:0 0 auto;display:flex;align-items:center;gap:3px}
.cw-pal__keys kbd{font:600 10px var(--font-mono);color:var(--color-text-muted);background:var(--color-bg-subtle);border:1px solid var(--color-border-hairline);border-radius:5px;padding:2px 5px;line-height:1.45}
.cw-pal__hit{border-radius:2px}
.cw-pal__zt{font:500 13.5px var(--font-sans);color:var(--color-text-primary)}
.cw-pal__zs{margin-top:5px;font:400 12px var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .cw-pal__zt,[dir="rtl"] .cw-pal__zs{font-family:var(--font-arabic)}
/* On a phone the chip column is the first thing to cost a file name its last characters, and the
   chords it names are not reachable from that keyboard anyway. */
@media (max-width:860px){.cw-pal__keys{display:none}.cw-pal__kbd{display:none}}
/* ═══ TEMPLATES GALLERY — rich card badge + scroll cap ═══ */
.cw-home__card{max-height:min(72vh,620px);overflow-y:auto}
.cw-home__tpls{gap:7px}
.cw-home__tpl-badge{flex:0 0 auto;margin-inline-start:6px;padding:2px 8px;border-radius:var(--radius-pill);font:700 9.5px/1.4 var(--font-mono);letter-spacing:.02em;color:#fff;background:var(--_bc,var(--color-accent));white-space:nowrap;box-shadow:0 2px 6px color-mix(in srgb,var(--_bc,var(--color-accent)) 40%,transparent)}
[dir="rtl"] .cw-home__tpl-badge{font-family:var(--font-mono);letter-spacing:0}
.cw-home__tpl-check{margin-inline-start:2px}
[data-theme="dark"] .cw-home__tpl-badge{box-shadow:none;filter:saturate(.92)}
.cw-home__card::-webkit-scrollbar{width:8px}
.cw-home__card::-webkit-scrollbar-thumb{background:var(--color-border-strong);border-radius:8px}
.cw-home__card::-webkit-scrollbar-track{background:transparent}
/* ═══ FIRAS CODE — natural-language "Create with AI" on the home ═══ */
.cw-home__desc{width:100%;min-height:74px;resize:vertical;padding:11px 13px;border:1.5px solid var(--color-border);border-radius:12px;background:var(--color-surface);color:var(--color-text-primary);font:400 13.5px/1.55 var(--font-sans);margin-bottom:6px;transition:.15s}
[dir="rtl"] .cw-home__desc{font-family:var(--font-arabic)}
.cw-home__desc:focus{outline:none;border-color:var(--color-accent);box-shadow:0 0 0 3px var(--color-accent-soft)}
.cw-home__desc::placeholder{color:var(--color-text-muted)}
.cw-home__or{display:flex;align-items:center;gap:10px;margin:12px 0 4px;color:var(--color-text-muted);font:500 11px var(--font-sans)}
[dir="rtl"] .cw-home__or{font-family:var(--font-arabic)}
.cw-home__or::before,.cw-home__or::after{content:"";flex:1;height:1px;background:var(--color-border-hairline)}
.cw-home__create.is-building{opacity:.9;cursor:progress}
.cw-home__spin{display:inline-block;width:14px;height:14px;margin-inline-end:8px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;vertical-align:-2px;animation:cwSpin .7s linear infinite}
@keyframes cwSpin{to{transform:rotate(360deg)}}


/* ============================================================================
   FirasAI · CENTERED PROFESSIONAL CONVERSATION  —  FINAL
   Append at the END of styles.css (wins on source order at equal specificity).
   Additive · CSS-only · uses ONLY existing custom properties.
   Light + [data-theme="dark"] + [dir="rtl"] + mobile (<=640px).

   Model:
     · --column-w (744px, existing shared lever) keeps composer + welcome +
       chat frame perfectly edge-aligned by construction.
     · --reading-w (704px) clamps ONLY assistant/user TEXT RUNS to a calm
       ~66ch serif measure. .md and every embedded card keep full column width.
     · Assistant = bubble-less serif prose, whisper hairline between turns.
     · User = warm clay panel pinned inline-end (a hue no other chrome owns).

   SAFETY (never break — verified against app.js aiTurnEl L3877 / userTurnEl L3813):
     · .md and .msg-ai__body are NEVER clamped (cards mount + stream there).
     · Clamp is an explicit allowlist of flowing text runs; NO card class,
       NO .code-block / .plot-figure / .firas-ask / .katex-display / table.
     · .file-card / .plan-start / .msg-actions stay direct children of .turn,
       never reparented, so card.closest('.msg-ai') keeps resolving.
     · Typography rhythm is scoped to `.msg-ai__body > .md > *` DIRECT children,
       so it cannot cascade into .code-card / .file-disclosure internal markdown.
============================================================================ */

/* ---- 1. Tokens ----------------------------------------------------------- */
:root {
  /* --column-w stays 744px (defined L108) — composer/welcome/frame lever.   */
  --reading-w: 704px;                 /* prose measure (~66ch @ 17px serif)  */
  --turn-gap:  var(--space-10);       /* 40px vertical rhythm between turns  */
  /* THE USER BUBBLE NOW READS AS A BUBBLE. It was --color-accent at 12% over the surface:
     a tint so faint that on a dark ground it was indistinguishable from any other card, so
     the reader's own words never separated from the assistant's. Same idea at real strength.

     Built from --color-accent-deep, NOT --color-accent: the deep token is the saturated,
     low-luminance member of each palette, which is what gives a solid fill weight instead of
     glare — full-strength --color-accent would be a bright mint slab.

     DERIVED, never hard-coded. Every theme already defines --color-accent-deep, so the bubble
     comes out teal on Dark and Black, navy on Midnight, brass on Amber, and any theme added
     later inherits a harmonised version with no extra rule. That is the whole point of the
     replace-values-not-names architecture this file is built on. */
  --user-fill: color-mix(in srgb, var(--color-accent-deep) 90%, var(--color-surface));
  /* White clears AA on every one of the six deep tokens (measured: 5.1:1 on Amber's #8A6234,
     the tightest of them; 8.4:1 on Light's #14544A). */
  --user-ink:  #FFFFFF;
  --user-edge: color-mix(in srgb, var(--color-accent) 40%, transparent);
  /* One hairline of light along the top edge — the machined-plate treatment the rest of this
     world uses. Not a gradient wash: a coloured bleed is the exact tell the design refuses. */
  --user-sheen: inset 0 1px 0 color-mix(in srgb, #FFFFFF 16%, transparent);
  /* A DELIBERATE BREAK FROM THE RADIUS SCALE, which tops out at --radius-xl: 9px because the
     rest of this world is machined plate, not soft UI. The user bubble is the one element that
     should read as speech, so it gets its own named token rather than a magic number or a
     redefined --radius-lg (which would round every card in the app).
     20px against the ~58px single-line bubble stays clearly UNDER half its height, so a short
     message reads as a softly-rounded card. The first cut used 24px, which on a short "هلو"
     closed into a full capsule — and a small capsule beside a composer full of pill buttons
     reads as ANOTHER BUTTON, which is exactly what Firas flagged (الحجم أو الشكل). */
  /* Renamed from --user-radius: the picture frame and the viewer now share this exact curve,
     so the name can no longer belong to the bubble alone. One value, one family — a bubble at
     24px beside a picture at 7px read as two different design languages in the same column. */
  --soft-radius: 20px;
}
/* Raised, not removed. Prose past roughly 90 characters a line is measurably harder to read —
   the eye loses its place on the return sweep — so "wide" buys room without abandoning the
   measure entirely. The phone override below still wins: 100% is already wider than either.
   A SEPARATE rule on purpose. The first version of this override was spliced into the MIDDLE
   of the tokens block above, which silently moved every token after --reading-w (the bubble's
   fill, ink, edge and radius) INSIDE the wide-only rule — braces stayed balanced, node had
   nothing to say, and the bubbles quietly lost their entire design except when width=wide. */
:root[data-width="wide"] { --reading-w: 920px; }
[data-theme="dark"] {
  /* Dark surfaces sit close in value, so the fill leans further off the surface to separate. */
  --user-fill: color-mix(in srgb, var(--color-accent-deep) 94%, var(--color-surface));
}

/* ---- 2. Column frame — retune top rhythm only (centering already exists) - */
.chat-inner { padding-block-start: var(--space-10); }   /* a touch more air up top */

/* ---- 3. Turn rhythm ------------------------------------------------------
   SPACE SEPARATES TURNS, NOT A LINE. The hairline drew a rule across the reading
   column every few lines, which reads as a form to fill in rather than a
   conversation to follow; neither ChatGPT nor Claude draws one. The gap stays
   doubled (margin + padding) precisely because the line is no longer there to
   help the eye. The app does the same, deliberately, so the two match.

   Taken out, put back on the owner's word, and taken out again once he saw it:
   he wants the column clean and only the closing rule under the last turn. If
   that closing rule is ever traced to this selector, it is NOT this one - this
   one draws above every turn but the first, and he asked for none of those. */
.chat-inner .turn { margin-bottom: var(--turn-gap); }
.chat-inner .turn + .turn {
  margin-top: var(--turn-gap);
  padding-top: var(--turn-gap);
}
.chat-inner .turn:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

/* ---- 4. User turn — warm clay panel, inline-end -------------------------- */
.msg-user__bubble {
  max-inline-size: min(80%, 544px);
  background: var(--user-fill);
  border-color: var(--user-edge);
  border-radius: var(--soft-radius); /* uniform — no asymmetric corner (RTL-safe) */
  box-shadow: var(--user-sheen);
  color: var(--user-ink);
}
/* Was a second copy of the neutral hairline, which on a saturated fill drew a grey ring around
   a coloured bubble. The edge token already carries the theme. */
[data-theme="dark"] .msg-user__bubble {
  border-color: var(--user-edge);
}
/* Everything INSIDE the bubble now sits on a saturated ground, so anything that painted itself
   from a text or accent token would drop out of contrast. --color-accent links in particular
   were near-invisible on their own deep tone. */
.msg-user__bubble a { color: var(--user-ink); text-decoration: underline; text-underline-offset: 2px; }
.msg-user__bubble code,
.msg-user__bubble kbd { background: color-mix(in srgb, #000000 22%, transparent); color: var(--user-ink); }
.msg-user__bubble .katex { color: var(--user-ink); }

/* ---- 5. Assistant prose — measured TEXT RUNS, cards untouched ------------
   Clamp the flowing text elements to the reading measure; cards + code + math
   + tables keep the full column (some intentionally exceed prose width).
   Scoped to DIRECT children of .md so it never reaches card-internal markdown. */
.msg-ai__body > .md > p,
.msg-ai__body > .md > h1,
.msg-ai__body > .md > h2,
.msg-ai__body > .md > h3,
.msg-ai__body > .md > h4,
.msg-ai__body > .md > ul,
.msg-ai__body > .md > ol,
.msg-ai__body > .md > blockquote,
.msg-ai__body > .md > hr {
  max-inline-size: var(--reading-w);
}
/* A little more presence + separation for the assistant head. */
.msg-ai__head { margin-bottom: var(--space-4); }

/* Comfort rhythm (D5) — scoped to DIRECT prose children so it can't leak into
   a card's internal .md. Base already has serif 17/29; we relax block spacing. */
.msg-ai__body > .md > p { margin-block-end: var(--space-5); }          /* 20px */
.msg-ai__body > .md > ul,
.msg-ai__body > .md > ol { margin-block-end: var(--space-5); }
.msg-ai__body > .md > blockquote {
  margin-block: var(--space-5);
  color: var(--color-text-secondary);
}
.msg-ai__body > .md > h1,
.msg-ai__body > .md > h2 { margin-block-start: var(--space-8); }
.msg-ai__body > .md > h3,
.msg-ai__body > .md > h4 { margin-block-start: var(--space-6); }
.msg-ai__body > .md > *:first-child { margin-block-start: 0; }

/* Arabic: keep the base-tuned 1.9 leading (do NOT regress). RTL uses logical
   props above, so the measure clamp mirrors automatically. */

/* Dense elements sit calmly in serif prose — plain-markdown ONLY (direct child,
   so .code-card's internal .code-block is never touched). */
.msg-ai__body > .md > .code-block { margin-block: var(--space-5); }
.msg-ai__body .md :not(pre) > code {
  font-size: 0.86em;
  padding: 1px 5px;
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-xs);
  overflow-wrap: anywhere;              /* long tokens never overflow on mobile */
}
.msg-ai__body > .md > table { font-size: var(--text-sm); line-height: var(--leading-sm); }

/* ---- 6. Composer — floats over the same column, lifted (edges aligned) --- */
.composer {
  box-shadow:
    0 1px 2px rgba(0,0,0,0.04),
    0 12px 32px rgba(0,0,0,0.08);
}
.composer.is-focused {
  box-shadow:
    0 1px 2px rgba(0,0,0,0.04),
    0 12px 32px rgba(0,0,0,0.10),
    var(--shadow-focus);
}
[data-theme="dark"] .composer {
  box-shadow:
    0 1px 2px rgba(0,0,0,0.30),
    0 12px 32px rgba(0,0,0,0.42);
}
[data-theme="dark"] .composer.is-focused {
  box-shadow:
    0 1px 2px rgba(0,0,0,0.30),
    0 12px 32px rgba(0,0,0,0.48),
    var(--shadow-focus);
}

/* ---- 7. Mobile — deterministic full width, 640px (matches the prompt axis)
   Column -> 100%, clamps off, 16px gutters on all three wrappers, bubble 88%.
   Placed at top level (not nested) so it can't be missed. Note: the existing
   768px block already sets 16px gutters; these reinforce + reset the clamps. */
@media (max-width: 640px) {
  :root { --reading-w: 100%; --turn-gap: var(--space-8); }

  .chat-inner   { padding-inline: var(--space-4); }
  .welcome      { padding-inline: var(--space-4); }
  .composer-wrap { padding-inline: var(--space-4); }

  .msg-ai__body > .md > p,
  .msg-ai__body > .md > h1,
  .msg-ai__body > .md > h2,
  .msg-ai__body > .md > h3,
  .msg-ai__body > .md > h4,
  .msg-ai__body > .md > ul,
  .msg-ai__body > .md > ol,
  .msg-ai__body > .md > blockquote,
  .msg-ai__body > .md > hr { max-inline-size: none; }

  .msg-user__bubble { max-inline-size: 88%; }

  .chat-inner .turn + .turn { padding-top: var(--space-6); margin-top: var(--space-6); }
}

/* ---- 8. High-contrast courtesy ------------------------------------------- */
@media (prefers-contrast: more) {
  .msg-user__bubble { border-color: var(--color-border-strong); }
}
/* ═══ FIRAS CODE — PLAN-THEN-BUILD step list (Claude-Code engine) ═══ */
.cw-plan__list{display:flex;flex-direction:column;gap:5px;padding:10px 4px;max-height:46vh;overflow-y:auto}
.cw-plan__row{display:flex;align-items:center;gap:9px;padding:7px 10px;border-radius:9px;background:var(--color-bg-subtle);font:500 12.5px var(--font-sans);color:var(--color-text-secondary);transition:.15s}
[dir="rtl"] .cw-plan__row{font-family:var(--font-arabic)}
.cw-plan__row--run{background:var(--color-accent-soft);color:var(--color-text-primary)}
.cw-plan__row--done{opacity:.72}
.cw-plan__row code{font-family:var(--font-mono,Consolas,monospace);font-size:11.5px;color:var(--color-text-primary);direction:ltr}
.cw-plan__does{flex:1;min-width:0;font-size:11px;color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cw-plan__ic{flex:0 0 auto;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;font-size:11px}
.cw-plan__ic--ok{color:var(--color-accent);font-weight:700}
.cw-plan__ic--wait{width:8px;height:8px;border-radius:50%;border:1.5px solid var(--color-border-strong)}
.cw-plan__spin{flex:0 0 auto;width:14px;height:14px;border:2px solid var(--color-accent);border-top-color:transparent;border-radius:50%;animation:cwSpin .7s linear infinite}
.cw-plan__spin--lg{width:16px;height:16px}

/* ═══════════ FIRAS CODE v2 — direct create + recent projects + mobile ═══════════ */
.cw-home__inner{width:100%;max-width:640px;margin:0 auto;display:flex;flex-direction:column;gap:22px}
.cw-home__acts{display:flex;gap:10px;margin-top:12px}
.cw-home__create{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:13px 18px;border:none;border-radius:12px;background:var(--color-accent);color:#fff;font:700 14.5px var(--font-sans);cursor:pointer;transition:.15s}
[dir="rtl"] .cw-home__create{font-family:var(--font-arabic)}
.cw-home__create:hover{filter:brightness(1.07)}
.cw-home__blank{flex:0 0 auto;padding:13px 18px;border:1.5px solid var(--color-border);border-radius:12px;background:transparent;color:var(--color-text-secondary);font:600 13.5px var(--font-sans);cursor:pointer;transition:.15s}
[dir="rtl"] .cw-home__blank{font-family:var(--font-arabic)}
.cw-home__blank:hover{border-color:var(--color-accent);color:var(--color-accent)}
/* recent projects grid */
.cw-home__recent-h{font:700 13px var(--font-sans);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px}
[dir="rtl"] .cw-home__recent-h{font-family:var(--font-arabic);letter-spacing:0}
.cw-home__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}
.cw-proj{display:flex;align-items:center;gap:10px;padding:12px 13px;border:1px solid var(--color-border-hairline);border-radius:12px;background:var(--color-surface);cursor:pointer;text-align:start;transition:.15s;position:relative}
.cw-proj:hover{border-color:var(--color-accent);transform:translateY(-1px);box-shadow:0 6px 18px color-mix(in srgb,var(--color-text-primary) 8%,transparent)}
.cw-proj__ic{flex:0 0 auto;width:34px;height:34px;display:grid;place-items:center;border-radius:9px;background:var(--color-accent-soft);color:var(--color-accent);font:700 13px var(--font-mono)}
.cw-proj__meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.cw-proj__meta strong{font:600 13.5px var(--font-sans);color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
[dir="rtl"] .cw-proj__meta strong{font-family:var(--font-arabic)}
.cw-proj__meta small{font:400 11px var(--font-sans);color:var(--color-text-muted)}
.cw-proj__del{position:absolute;top:6px;inset-inline-end:6px;width:20px;height:20px;display:grid;place-items:center;border-radius:6px;color:var(--color-text-muted);font-size:10px;opacity:0;transition:.15s}
.cw-proj:hover .cw-proj__del{opacity:1}
.cw-proj__del:hover{background:#c0392b1a;color:#c0392b}
.cw-home__empty{grid-column:1/-1;display:flex;flex-direction:column;align-items:center;gap:8px;padding:26px;text-align:center;color:var(--color-text-muted);font:400 13px var(--font-sans);border:1px dashed var(--color-border-strong);border-radius:12px}
.cw-home__empty-ic{display:grid;place-items:center;opacity:.7}
[dir="rtl"] .cw-home__empty{font-family:var(--font-arabic)}
/* The launcher shelf — a framed window onto a drifting list of your files — was removed with
   the home screen's form; see the note in app.js where cwHeroStage used to be. Its rules, its
   cwShelf keyframe and the four comments explaining its masking went with it. */
/* The shelf is decoration; "Build with AI" is the reason the screen exists. At full height the
   strip plus its caption cost ~130px above that button on a phone, which is enough to push it
   under the fold on any account that already has projects. Two rows and no caption there. */
@media (max-width: 640px){
  /* Two rows, so 22px of fade at each end would leave the crisp band sitting on the seam
     between them and nothing legible. 12px keeps both rows readable. */
}
/* A list creeping in the corner of the eye is precisely what this setting asks us to stop.
   The fade goes with it: frozen, it hides most of the three rows that are left instead of
   promising more below, and there is no motion for it to soften any more. */
@media (prefers-reduced-motion: reduce){
}
/* starter-templates gallery on the Code home */
.cw-home__tplrow{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}
.cw-tpl{display:flex;align-items:center;gap:10px;padding:12px 13px;border:1px solid var(--color-border-hairline);border-radius:12px;background:var(--color-surface);cursor:pointer;text-align:start;transition:.15s;position:relative}
.cw-tpl:hover{border-color:var(--color-accent);transform:translateY(-1px);box-shadow:0 6px 18px color-mix(in srgb,var(--color-text-primary) 8%,transparent)}
.cw-tpl__ic{flex:0 0 auto;width:34px;height:34px;display:grid;place-items:center;border-radius:9px;background:var(--color-accent-soft);font-size:17px}
.cw-tpl__meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.cw-tpl__meta strong{font:600 13.5px var(--font-sans);color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
[dir="rtl"] .cw-tpl__meta strong{font-family:var(--font-arabic)}
.cw-tpl__meta small{font:400 11px var(--font-sans);color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
[dir="rtl"] .cw-tpl__meta small{font-family:var(--font-arabic)}
.cw-tpl__badge{flex:0 0 auto;padding:2px 8px;border-radius:999px;font:700 10px var(--font-mono);color:var(--tplc,#888);background:color-mix(in srgb,var(--tplc,#888) 14%,transparent);border:1px solid color-mix(in srgb,var(--tplc,#888) 35%,transparent)}
/* AI command bar: keep the INNER input+button centered to a comfortable measure, but the bar
   itself stays FULL-WIDTH at the bottom of the IDE (a narrow centered bar floated over the editor). */
.cw-ai{max-width:none;margin-inline:0}
.cw-ai__in{max-width:760px;margin-inline:auto}
.cw-diff__card{max-width:760px;margin-inline:auto}

/* ── MOBILE BOTTOM NAV + single-pane IDE (≤860px) ── */
.cw-mobnav{display:none}
@media (max-width:860px){
  .cw-mobnav{display:flex;flex:0 0 auto;border-top:1px solid var(--color-border-hairline);background:var(--color-surface)}
  .cw-mobnav__b{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;padding:8px 4px 7px;background:transparent;border:none;color:var(--color-text-muted);font:600 10px var(--font-sans);cursor:pointer;transition:.12s}
  [dir="rtl"] .cw-mobnav__b{font-family:var(--font-arabic)}
  .cw-mobnav__b span{font-size:10px}
  .cw-mobnav__b.is-active{color:var(--color-accent)}
  .cw-mobnav__b.is-active::before{content:"";position:absolute;margin-top:-8px;width:22px;height:3px;border-radius:3px;background:var(--color-accent)}
  .cw-main{flex-direction:column;min-height:0}
  .cw[data-mob] .cw-tree,.cw[data-mob] .cw-ed,.cw[data-mob] .cw-right{display:none!important}
  .cw[data-mob="files"] .cw-tree{display:flex!important;width:100%!important;max-width:none;flex:1}
  .cw[data-mob="code"] .cw-ed{display:flex!important;width:100%!important;flex:1;min-height:0}
  .cw[data-mob="preview"] .cw-right{display:flex!important;width:100%!important;flex:1;min-height:0}
  .cw[data-mob="ai"] .cw-main{display:none}
  .cw-ai{display:none;order:9}
  .cw[data-mob="ai"] .cw-ai{display:flex;flex-direction:column;gap:10px;flex:1;padding:16px;max-width:none}
  .cw[data-mob="ai"] .cw-ai__in{width:100%;min-height:120px}
  .cw[data-mob="ai"] .cw-main{display:none}
  .cw-status{display:none}
  .cw-tree{max-width:none!important;border-inline-end:none;border-bottom:1px solid var(--color-border-hairline)}
  .cw-bar{flex-wrap:wrap;row-gap:6px}
  .cw-bar__name{max-width:40vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .cw-prevchrome{flex-wrap:wrap}
}

/* ═══════════ FIRAS CODE — BULLETPROOF IDE LAYOUT (SHIP1 #1, appended: source-order wins) ═══════════
   .cw is a full-height flex column. .cw-bar / .cw-ai / .cw-status / .cw-mobnav are FIXED-HEIGHT fences
   (flex:0 0 auto); .cw-main is the ONLY growing child. The AI command bar is FULL-WIDTH at the very
   bottom and can NEVER overlap / float over / squeeze the editor or preview — any viewport height,
   any project size, RTL+LTR, light+dark, desktop+mobile. */
.cw{position:relative;box-sizing:border-box}
.cw *,.cw *::before,.cw *::after{box-sizing:border-box}
.cw-bar{flex:0 0 auto}
.cw-ai{flex:0 0 auto;width:100%;max-width:none;margin-inline:0}
.cw-status{flex:0 0 auto}
.cw-mobnav{flex:0 0 auto}
.cw-main{flex:1 1 auto;min-height:0}
.cw-ed,.cw-prevwrap,.cw-console{min-height:180px}
.cw-ai{flex-wrap:nowrap}
.cw-ai__in{flex:1 1 auto;min-width:0;max-width:760px;margin-inline:auto}
.cw-ai__go{flex:0 0 auto}
@media (max-width:860px){
  .cw-main{grid-template-columns:1fr;grid-template-rows:1fr}
  .cw[data-mob="files"] .cw-tree{max-height:none!important}
  .cw[data-mob="ai"] .cw-ai{flex:1 1 auto}
  .cw-mobnav{padding-bottom:env(safe-area-inset-bottom)}
}
.cw-diff{inset:0}

/* ═══════════ FIRAS CODE — PHONE PERFECTION (SHIP1 #5, appended: source-order wins) ═══════════ */
/* Touch targets: any coarse pointer (phone/tablet), width-independent */
@media (pointer: coarse) {
  .cw-mobnav__b { min-height: 52px; }
  .cw-bar__btn  { padding: 9px 14px; min-height: 40px; }
  .cw-bar__home { width: 38px; height: 38px; }
  .cw-tab       { padding: 9px 16px; min-height: 38px; }
  .cw-conclear  { width: 34px; height: 34px; }
  .cw-prevbtn   { height: 34px; min-width: 34px; }
  .cw-prevdev   { padding: 8px 11px; }
  .cw-file      { padding: 11px 12px; }
  .cw-file__x   { width: 32px; height: 32px; font-size: 14px; opacity: .85; }
  .cw-tree__newfolder { width: 32px; height: 30px; font-size: 15px; }
}
/* PHONE (≤640px) */
@media (max-width: 640px) {
  .product-code #codeWorkspace { height: 100vh; height: 100dvh; }
  .cw-bar {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; gap: 8px;
    padding: 8px 12px calc(8px + env(safe-area-inset-top, 0px));
    padding-inline-start: max(12px, env(safe-area-inset-left));
    padding-inline-end: max(12px, env(safe-area-inset-right));
  }
  .cw-bar::-webkit-scrollbar { display: none; }
  .cw-bar__name { max-width: 34vw; }
  .cw-bar__count { display: none; }
  .cw-bar__btn { flex: 0 0 auto; }
  .cw-main {
    display: flex !important; flex-direction: column;
    grid-template-columns: none !important; grid-template-rows: none !important; min-height: 0;
  }
  .cw[data-mob] .cw-tree, .cw[data-mob] .cw-ed, .cw[data-mob] .cw-right { display: none !important; }
  .cw[data-mob="files"]   .cw-tree  { display: flex !important; flex-direction: column; width: 100% !important; max-width: none !important; max-height: none !important; flex: 1; overflow-y: auto; }
  .cw[data-mob="code"]    .cw-ed    { display: flex !important; width: 100% !important; flex: 1; min-height: 0; border-inline-end: none; border-bottom: none; }
  .cw[data-mob="preview"] .cw-right { display: flex !important; width: 100% !important; flex: 1; min-height: 0; }
  .cw-ed .CodeMirror { font-size: 14.5px; line-height: 1.7; }
  .cw-ed .CodeMirror-lines { padding: 6px 0; }
  .cw-ed__ta, .cw-ai__in, .cw-home__name, .cw-home__desc { font-size: 16px !important; }
  .cw-ai { display: none; order: 9; }
  .cw[data-mob="ai"] .cw-main { display: none !important; }
  .cw[data-mob="ai"] .cw-ai {
    display: flex; flex-direction: column; gap: 12px; flex: 1;
    padding: 16px; padding-bottom: max(16px, env(safe-area-inset-bottom));
    max-width: none; margin-inline: 0;
  }
  .cw[data-mob="ai"] .cw-ai__in {
    width: 100%; max-width: none; margin-inline: 0;
    min-height: 120px; resize: none; border-radius: 16px; line-height: 1.6; padding: 14px 16px;
  }
  .cw[data-mob="ai"] .cw-ai__go { width: 100%; padding: 15px; border-radius: 16px; font-size: 15px; min-height: 50px; }
  .cw-prevseg { display: none; }
  .cw-prevchrome { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding: 6px 10px; }
  .cw-prevchrome::-webkit-scrollbar { display: none; }
  .cw-prevstage.is-framed { padding: 0; background: #fff; }
  .cw-prevstage.is-framed .cw-preview { width: 100% !important; max-width: 100% !important; border-radius: 0; box-shadow: none; border: none; }
  .cw-status { display: flex !important; justify-content: flex-end; gap: 0; padding: 4px 12px; border-top: 1px solid var(--color-border-hairline); }
  .cw-status__seg, .cw-status__sp { display: none !important; }
  .cw-status__save { font-size: 11px; }
  .cw-mobnav {
    display: flex; flex: 0 0 auto;
    padding-bottom: env(safe-area-inset-bottom);
    padding-inline: max(0px, env(safe-area-inset-left)) max(0px, env(safe-area-inset-right));
    box-shadow: 0 -1px 0 var(--color-border-hairline), 0 -6px 24px color-mix(in srgb, var(--color-text-primary) 6%, transparent);
  }
  .cw-mobnav__b { position: relative; padding: 9px 4px 8px; font-size: 11px; }
  .cw-mobnav__b > :first-child { font-size: 17px; line-height: 1; }
  .cw-mobnav__b span { font-size: 10px; }
  .cw-mobnav__b.is-active::before { margin-top: -9px; }
  .cw-diff { padding: 12px; align-items: flex-end; }
  .cw-diff__card { width: 100%; max-height: 92%; }
  .cw-diff__acts { padding-bottom: max(12px, env(safe-area-inset-bottom)); }
  .cw-dl { font-size: 12px; word-break: break-word; }
  .cw-home { padding: 20px 16px calc(20px + env(safe-area-inset-bottom)); gap: 16px; }
  .cw-home__card { width: 100%; padding: 18px; }
  .cw-home__hero h1 { font-size: 27px; }
}
@media (max-width: 380px) {
  .cw-bar__name { max-width: 26vw; }
  .cw-mobnav__b span { display: none; }
  .cw-mobnav__b { padding: 12px 4px; }
  .cw-mobnav__b > :first-child { font-size: 19px; }
}
/* Desktop AI bar: keep the <textarea> a clean single-line pill (only phone lets it grow). */
.cw-ai__in{resize:none;line-height:1.4;height:auto;min-height:0;overflow:hidden}
@media(min-width:641px){.cw:not([data-mob="ai"]) .cw-ai__in{white-space:nowrap;overflow-x:auto}}

/* ═══ FIRAS CODE — game verify chip (reuses .cw-diff overlay host + .cw-plan spinner) ═══ */
.cw-verify{display:flex;align-items:center;gap:12px;padding:16px 20px;border:1px solid var(--color-border);border-radius:var(--radius-lg);background:var(--color-surface);box-shadow:0 18px 60px rgba(0,0,0,.22);max-width:min(92vw,420px);animation:cwIn .28s var(--ease-out) both}
.cw-verify__t{font:600 13.5px var(--font-sans);color:var(--color-text-primary)}
[dir="rtl"] .cw-verify__t{font-family:var(--font-arabic)}
[data-theme="dark"] .cw-verify{box-shadow:0 18px 60px rgba(0,0,0,.5)}
@media (max-width:860px){.cw-verify{max-width:88vw;padding:14px 16px}.cw-verify__t{font-size:12.5px}}

/* ═══════════ FIRAS CODE — UNIFIED PREMIUM DESIGN SYSTEM PASS (cw--v3, SHIP1 #15, appended LAST) ═══════════
   Cohesive restyle of every .cw-* surface. Scoped to .cw--v3 (marker on the IDE root) → additive,
   source-order-last, instantly revertible. Light+dark, RTL+LTR, mobile. Logical props only. */
.cw--v3{
  --cwv-radius-chip:10px; --cwv-radius-panel:14px; --cwv-radius-card:18px;
  --cwv-pad-bar:10px 16px; --cwv-gap:10px;
  --cwv-motion:.18s var(--ease-out); --cwv-motion-fast:.13s var(--ease-out);
  --cwv-shadow-panel:0 10px 30px color-mix(in srgb,var(--color-text-primary) 8%,transparent);
  --cwv-shadow-float:0 20px 60px color-mix(in srgb,var(--color-text-primary) 22%,transparent), 0 2px 8px rgba(0,0,0,.08);
  --cwv-hair:var(--color-border-hairline);
  position:relative;
}
[data-theme="dark"] .cw--v3{
  --cwv-shadow-panel:0 10px 30px rgba(0,0,0,.45);
  --cwv-shadow-float:0 20px 60px rgba(0,0,0,.55), 0 2px 8px rgba(0,0,0,.4);
}
.cw--v3 .cw-bar,.cw--v3 .cw-ai,.cw--v3 .cw-status,.cw--v3 .cw-mobnav{flex:0 0 auto}
.cw--v3 .cw-main{min-height:180px}
.cw--v3 .cw-bar{gap:var(--cwv-gap);padding:var(--cwv-pad-bar);background:color-mix(in srgb,var(--color-surface) 86%,transparent);backdrop-filter:saturate(1.4) blur(10px);-webkit-backdrop-filter:saturate(1.4) blur(10px);border-bottom:1px solid var(--cwv-hair)}
.cw--v3 .cw-bar__home{width:34px;height:34px;border-radius:var(--cwv-radius-chip);background:var(--color-bg-subtle);color:var(--color-text-secondary);transition:background var(--cwv-motion),color var(--cwv-motion),transform var(--cwv-motion)}
.cw--v3 .cw-bar__home:hover{background:var(--color-accent-soft);color:var(--color-accent);transform:translateY(-1px)}
.cw--v3 .cw-bar__home:active{transform:translateY(0) scale(.94)}
.cw--v3 .cw-bar__logo{width:30px;height:30px;border-radius:var(--cwv-radius-chip);display:grid;place-items:center;font:800 11px var(--font-mono);color:#fff;background:linear-gradient(140deg,var(--color-accent),#5b8def);box-shadow:0 4px 14px color-mix(in srgb,var(--color-accent) 34%,transparent)}
.cw--v3 .cw-bar__name{font:650 14px var(--font-sans);letter-spacing:-.01em}
.cw--v3 .cw-bar__count{font:600 11px var(--font-mono);color:var(--color-text-muted);background:var(--color-bg-subtle);border:1px solid var(--cwv-hair);border-radius:var(--radius-pill);padding:3px 10px;direction:ltr;unicode-bidi:isolate}
.cw--v3 .cw-bar__btn{padding:8px 15px;border-radius:var(--radius-pill);border:1px solid var(--color-border);background:var(--color-surface);color:var(--color-text-primary);font:550 12.5px var(--font-sans);white-space:nowrap;transition:border-color var(--cwv-motion),color var(--cwv-motion),background var(--cwv-motion),transform var(--cwv-motion),box-shadow var(--cwv-motion)}
.cw--v3 .cw-bar__btn:hover{border-color:var(--color-accent);color:var(--color-accent);transform:translateY(-1px);box-shadow:0 5px 14px color-mix(in srgb,var(--color-accent) 18%,transparent)}
.cw--v3 .cw-bar__btn:active{transform:translateY(0) scale(.97)}
.cw--v3 .cw-bar__btn.cw-run{border-color:transparent;color:#fff;background:linear-gradient(120deg,var(--color-accent),#3fa08c);box-shadow:0 5px 16px color-mix(in srgb,var(--color-accent) 28%,transparent)}
.cw--v3 .cw-bar__btn.cw-run:hover{color:#fff;box-shadow:0 8px 22px color-mix(in srgb,var(--color-accent) 40%,transparent)}
.cw--v3 .cw-tree{background:var(--color-surface);border-inline-end:1px solid var(--cwv-hair)}
.cw--v3 .cw-tree__head{padding:12px 14px 9px;font:700 10.5px var(--font-sans);letter-spacing:.14em;text-transform:uppercase;color:var(--color-text-muted)}
[dir="rtl"] .cw--v3 .cw-tree__head{font-family:var(--font-arabic);letter-spacing:.02em}
.cw--v3 .cw-tree__n{margin-inline-start:auto;font:600 10px var(--font-mono);background:var(--color-bg-subtle);border:1px solid var(--cwv-hair);border-radius:var(--radius-pill);padding:2px 8px}
.cw--v3 .cw-tree__newfolder{width:26px;height:24px;border-radius:8px;color:var(--color-text-muted);transition:background var(--cwv-motion),color var(--cwv-motion),transform var(--cwv-motion)}
.cw--v3 .cw-tree__newfolder:hover{background:var(--color-accent-soft);color:var(--color-accent);transform:scale(1.06)}
.cw--v3 .cw-file{gap:8px;padding:8px 12px;border-bottom:1px solid var(--cwv-hair);border-inline-start:2px solid transparent;transition:background var(--cwv-motion),color var(--cwv-motion),border-color var(--cwv-motion)}
.cw--v3 .cw-file:hover{background:var(--color-bg-subtle)}
.cw--v3 .cw-file.is-active{background:var(--color-accent-soft);color:var(--color-accent);border-inline-start-color:var(--color-accent);font-weight:600}
.cw--v3 .cw-file__dot{box-shadow:0 0 0 3px color-mix(in srgb,var(--color-bg-subtle) 70%,transparent)}
.cw--v3 .cw-file__x{width:22px;height:22px;border-radius:7px;color:var(--color-text-muted);transition:opacity var(--cwv-motion),background var(--cwv-motion),color var(--cwv-motion)}
.cw--v3 .cw-file__x:hover{background:#c0392b1a;color:#c0392b}
.cw--v3 .cw-ed{border-inline-end:1px solid var(--cwv-hair)}
.cw--v3 .cw-ed .CodeMirror{padding-block:4px}
.cw--v3 .cw-ed .CodeMirror-scrollbar-filler,.cw--v3 .cw-ed .CodeMirror-gutter-filler{background:transparent}
.cw--v3 .cw-ed .CodeMirror-selected{border-radius:2px}
.cw--v3 .cw-right{background:var(--color-surface)}
.cw--v3 .cw-tabs{gap:6px;padding:8px 10px;border-bottom:1px solid var(--cwv-hair)}
.cw--v3 .cw-tab{padding:6px 15px;border-radius:var(--radius-pill);color:var(--color-text-secondary);font:550 12.5px var(--font-sans);transition:background var(--cwv-motion),color var(--cwv-motion)}
.cw--v3 .cw-tab:hover{background:var(--color-bg-subtle);color:var(--color-text-primary)}
.cw--v3 .cw-tab.is-active{background:var(--color-accent-soft);color:var(--color-accent);font-weight:650;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--color-accent) 22%,transparent)}
.cw--v3 .cw-tab.has-errors::after{box-shadow:0 0 0 3px color-mix(in srgb,#c0392b 24%,transparent);animation:cwvErrPulse 1.6s ease-in-out infinite}
@keyframes cwvErrPulse{0%,100%{opacity:1}50%{opacity:.45}}
.cw--v3 .cw-conclear{width:28px;height:28px;border-radius:8px;color:var(--color-text-muted);transition:background var(--cwv-motion),color var(--cwv-motion),transform var(--cwv-motion)}
.cw--v3 .cw-conclear:hover{background:var(--color-bg-subtle);color:var(--color-text-primary);transform:rotate(-8deg)}
.cw--v3 .cw-prevwrap{margin:0 12px 12px;border:1px solid var(--cwv-hair);border-radius:var(--cwv-radius-panel);box-shadow:var(--cwv-shadow-panel);transition:box-shadow var(--dur-slow) var(--ease-out)}
.cw--v3 .cw-prevchrome{padding:8px 12px;gap:10px;background:var(--color-bg-subtle);border-bottom:1px solid var(--cwv-hair)}
.cw--v3 .cw-prevdots i{transition:transform var(--cwv-motion)}
.cw--v3 .cw-prevwrap:hover .cw-prevdots i:nth-child(1){transform:scale(1.15)}
.cw--v3 .cw-prevaddr{font:550 10.5px var(--font-mono);color:var(--color-text-muted);background:var(--color-surface);border:1px solid var(--cwv-hair);border-radius:var(--radius-pill);padding:4px 14px}
.cw--v3 .cw-prevseg{gap:2px;padding:3px;border-radius:var(--radius-pill);background:var(--color-surface);border:1px solid var(--cwv-hair)}
.cw--v3 .cw-prevdev{padding:5px 11px;border-radius:var(--radius-pill);font:600 10.5px var(--font-sans);transition:background var(--cwv-motion),color var(--cwv-motion)}
.cw--v3 .cw-prevdev:hover{color:var(--color-text-primary)}
.cw--v3 .cw-prevdev.is-active{background:var(--color-accent-soft);color:var(--color-accent);box-shadow:0 1px 4px color-mix(in srgb,var(--color-accent) 18%,transparent)}
.cw--v3 .cw-prevbtn{height:28px;min-width:28px;border-radius:9px;border:1px solid var(--cwv-hair);background:var(--color-surface);color:var(--color-text-secondary);transition:border-color var(--cwv-motion),color var(--cwv-motion),transform var(--cwv-motion)}
.cw--v3 .cw-prevbtn:hover{border-color:var(--color-accent);color:var(--color-accent);transform:translateY(-1px)}
.cw--v3 .cw-prevbtn:active{transform:translateY(0) scale(.94)}
.cw--v3 .cw-prevreload:hover{transform:rotate(90deg)}
.cw--v3 .cw-console{margin:0 12px 12px;border-radius:var(--cwv-radius-panel);border:1px solid color-mix(in srgb,#ffffff 8%,transparent);background:linear-gradient(180deg,#171715,#131311);box-shadow:var(--cwv-shadow-panel)}
.cw--v3 .cw-console__list{padding:10px 12px;gap:3px}
.cw--v3 .cw-conrow{font:12px/1.55 var(--font-mono);padding:3px 8px;border-radius:6px;border-bottom:none;position:relative}
.cw--v3 .cw-conrow + .cw-conrow{border-top:1px solid #ffffff08}
.cw--v3 .cw-conrow--error{color:#ff8a80;background:#c0392b12}
.cw--v3 .cw-conrow--warn{color:#ffd54f;background:#b9770e10}
.cw--v3 .cw-conrow--info{color:#81d4fa}
.cw--v3 .cw-ai{gap:var(--cwv-gap);padding:12px 16px;border-top:1px solid var(--cwv-hair);background:color-mix(in srgb,var(--color-surface) 92%,transparent);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);max-width:none;margin-inline:0}
.cw--v3 .cw-ai__in{max-width:760px;margin-inline:auto;padding:12px 16px;border:1.5px solid var(--color-border-strong);border-radius:var(--radius-pill);background:var(--color-bg-subtle);font:450 13.5px var(--font-sans);transition:border-color var(--cwv-motion),box-shadow var(--cwv-motion),background var(--cwv-motion)}
.cw--v3 .cw-ai__in:hover{border-color:var(--color-accent-ring)}
.cw--v3 .cw-ai__in:focus{outline:none;border-color:var(--color-accent);background:var(--color-surface);box-shadow:0 0 0 4px color-mix(in srgb,var(--color-accent) 14%,transparent)}
.cw--v3 .cw-ai__go{padding:0 22px;border:none;border-radius:var(--radius-pill);color:var(--color-on-accent);font:650 13px var(--font-sans);white-space:nowrap;background:linear-gradient(120deg,var(--color-accent),#5b8def);box-shadow:0 6px 18px color-mix(in srgb,var(--color-accent) 30%,transparent);transition:transform var(--cwv-motion),box-shadow var(--cwv-motion),filter var(--cwv-motion)}
.cw--v3 .cw-ai__go:hover{transform:translateY(-1px);box-shadow:0 9px 26px color-mix(in srgb,var(--color-accent) 44%,transparent)}
.cw--v3 .cw-ai__go:active{transform:translateY(0) scale(.97)}
.cw--v3 .cw-ai__go:disabled{opacity:.55;transform:none;box-shadow:none;filter:grayscale(.3);cursor:default}
.cw--v3 .cw-status{gap:16px;padding:6px 16px;border-top:1px solid var(--cwv-hair);background:var(--color-bg-subtle);font:550 11.5px var(--font-mono);color:var(--color-text-secondary)}
.cw--v3 .cw-status__save{padding:3px 11px;border-radius:var(--radius-pill);font:600 11px var(--font-sans);transition:background var(--cwv-motion),color var(--cwv-motion)}
.cw--v3 .cw-status__save.is-saved{background:color-mix(in srgb,var(--color-accent) 15%,transparent);color:var(--color-accent)}
.cw--v3 .cw-status__save.is-editing{background:#b9770e20;color:#b9770e}
.cw--v3 .cw-status__save.is-saving{background:#5b8def22;color:#5b8def}

/* ═══════════ FIRAS CODE STATUS BAR — 21st.dev "Status" PORT (@diceui, demo 25395) ═══════════
   The model is one pill: a tinted variant background, a coloured label, and an indicator whose
   halo pings out of a solid core. Ported onto our tokens and outward onto the whole strip —
   every reading that has a job is a button now, with an icon, a hit area and a hover; EOL has no
   job it can honour and stays a span. (--color-border is dead in this project; the hairline and
   accent-soft carry every edge below.) */

/* The row gap shrinks because the segments grew padding of their own: a target you can press,
   at the density the strip had as bare spans. Both skins, since v3 restates the shorthand. */
.cw-status{gap:6px;padding-block:2px}
.cw--v3 .cw-status{gap:8px;padding-block:3px}
.cw-status__seg{appearance:none;-webkit-appearance:none;margin:0;padding:3px 7px;border:0;border-radius:7px;background:transparent;color:inherit;font:inherit;line-height:1.35;cursor:pointer;transition:background var(--dur-fast,.14s) ease,color var(--dur-fast,.14s) ease}
/* `hidden` is a UA rule and the display above is an author one, so the error segment would sit
   there reading 0 forever without this. */
.cw-status__seg[hidden]{display:none}
.cw-status__seg:hover{background:var(--color-accent-soft);color:var(--color-text-primary)}
.cw-status__seg:active{background:color-mix(in srgb,var(--color-accent) 20%,transparent)}
.cw-status__seg:focus-visible{outline:2px solid var(--color-accent);outline-offset:1px}
.cw-status__ico{display:inline-flex;flex:none;width:13px;height:13px;opacity:.6;transition:opacity var(--dur-fast,.14s) ease,color var(--dur-fast,.14s) ease}
.cw-status__ico svg{display:block;width:100%;height:100%}
.cw-status__seg:hover .cw-status__ico{opacity:1;color:var(--color-accent)}
/* The EOL reading wears .cw-status__seg for the row's metrics and for the 860px hide rule, but it
   is a <span> with no job: it must not take the pointer, light up, or flash on mousedown, or it
   advertises a click that does not exist. Every one of these ties the rule it is undoing on
   specificity, so the whole group has to sit AFTER the last of them — written above the icon
   hover it lost the tie and the icon still went accent-coloured under the cursor. */
.cw-status__seg--ro,.cw-status__seg--ro:hover,.cw-status__seg--ro:active{cursor:default;background:transparent;color:inherit}
.cw-status__seg--ro:hover .cw-status__ico,.cw-status__seg--ro:active .cw-status__ico{opacity:.6;color:inherit}

/* A reading declares its own direction while the row around it stays LTR. Written as a SELF
   selector every time — `[dir="rtl"] .x` cannot match anywhere in this workspace, because the
   shell is fixed LTR and nothing above the bar is ever stamped rtl. */
.cw-status__v[dir="ltr"]{direction:ltr;unicode-bidi:isolate}
.cw-status__v[dir="rtl"]{direction:rtl;unicode-bidi:isolate;font-family:var(--font-arabic)}

/* The error count borrows the red the console tab's own dot already uses, so one failure reads
   as one colour wherever it surfaces. */
.cw-status__probs{color:#c0392b}
.cw-status__probs .cw-status__ico{opacity:.95}
.cw-status__probs:hover{background:color-mix(in srgb,#c0392b 14%,transparent);color:#c0392b}
.cw-status__probs:hover .cw-status__ico{color:#c0392b}

/* The segment that just changed flashes once. Colour, not movement — nothing here travels, so
   there is no motion for a reduced-motion reader to be spared. */
.cw-status__seg.is-bump{animation:cwStatusBump .6s ease-out}
@keyframes cwStatusBump{0%{background:color-mix(in srgb,var(--color-accent) 32%,transparent)}100%{background:transparent}}

/* The save pill, now a button — and carrying its own dir, which is what finally gives the
   Arabic label the Arabic face and seats its dot on the leading edge. The rule that wanted
   both, `[dir="rtl"] .cw-status__save`, has never once matched. */
.cw-status__save{appearance:none;-webkit-appearance:none;margin:0;border:0;cursor:pointer}
.cw-status__save[dir="rtl"]{direction:rtl;font-family:var(--font-arabic)}
.cw-status__save:focus-visible{outline:2px solid var(--color-accent);outline-offset:1px}
/* THE PING — the one piece of the model that had to be re-engineered. It animates a second
   layer out of the indicator; we have exactly one pseudo-element to spend (cwSetSaveState
   writes textContent, and a child <span> would not survive the next keystroke), so the halo is
   a box-shadow ring growing out of the same dot. It stays on ::before deliberately: the
   reduced-motion media block and the data-motion="off" block both name that selector by hand,
   and moving the animation elsewhere would put it beyond the reach of either. */
.cw-status__save.is-saving::before{animation:cwStatusPing 1.4s cubic-bezier(.4,0,.6,1) infinite}
@keyframes cwStatusPing{0%{box-shadow:0 0 0 0 color-mix(in srgb,currentColor 60%,transparent)}70%{box-shadow:0 0 0 6px transparent}100%{box-shadow:0 0 0 0 transparent}}

@media (max-width:640px){
  /* PHONE. `.cw-status__seg` is already display:none !important down here — the strip is the
     save pill alone and the port adds nothing to it — so the only thing to hand back is the
     block padding the rule above shortened for the desktop bar. */
  .cw-status{padding-block:4px}
}
.cw--v3 .cw-diff{background:color-mix(in srgb,var(--color-bg) 58%,transparent);backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px)}
.cw--v3 .cw-diff__card{max-width:760px;margin-inline:auto;border-radius:var(--cwv-radius-card);border:1px solid var(--color-border);box-shadow:var(--cwv-shadow-float)}
.cw--v3 .cw-diff__head{padding:14px 18px;font:650 14px var(--font-sans);background:linear-gradient(100deg,color-mix(in srgb,var(--color-accent) 10%,transparent),transparent)}
.cw--v3 .cw-diff__file{border:1px solid var(--cwv-hair);border-radius:var(--radius-md);transition:border-color var(--cwv-motion)}
.cw--v3 .cw-diff__file:hover{border-color:var(--color-border-strong)}
.cw--v3 .cw-diff__file summary{padding:10px 13px;background:var(--color-bg-subtle);font:550 12.5px var(--font-mono)}
.cw--v3 .cw-diff__badge{font-weight:650;padding:2px 9px;border-radius:var(--radius-pill)}
.cw--v3 .cw-diff__apply{padding:12px;border:none;border-radius:var(--radius-pill);color:var(--color-on-accent);font:650 13.5px var(--font-sans);background:linear-gradient(120deg,var(--color-accent),#3fa08c);box-shadow:0 6px 18px color-mix(in srgb,var(--color-accent) 28%,transparent);transition:transform var(--cwv-motion),box-shadow var(--cwv-motion)}
.cw--v3 .cw-diff__apply:hover{transform:translateY(-1px);box-shadow:0 9px 24px color-mix(in srgb,var(--color-accent) 40%,transparent)}
.cw--v3 .cw-diff__apply:active{transform:translateY(0) scale(.98)}
.cw--v3 .cw-diff__cancel{padding:12px 24px;border:1px solid var(--color-border);border-radius:var(--radius-pill);transition:border-color var(--cwv-motion),color var(--cwv-motion),background var(--cwv-motion)}
.cw--v3 .cw-diff__cancel:hover{border-color:var(--color-border-strong);background:var(--color-bg-subtle)}
.cw--v3 .cw-pal__card{border-radius:var(--cwv-radius-card);box-shadow:var(--cwv-shadow-float)}
.cw--v3 .cw-pal__in{padding:17px 20px;font:550 15px var(--font-sans)}
.cw--v3 .cw-pal__row{padding:10px 13px;border-radius:var(--radius-md);transition:background var(--cwv-motion-fast),transform var(--cwv-motion-fast)}
.cw--v3 .cw-pal__row:hover{background:var(--color-bg-subtle)}
.cw--v3 .cw-pal__row.is-sel{background:var(--color-accent-soft);box-shadow:inset 2px 0 0 var(--color-accent)}
[dir="rtl"] .cw--v3 .cw-pal__row.is-sel{box-shadow:inset -2px 0 0 var(--color-accent)}
.cw--v3 .cw-snap__card{border-radius:var(--cwv-radius-card);box-shadow:var(--cwv-shadow-float)}
.cw--v3 .cw-snap__row{padding:10px 12px;border:1px solid var(--cwv-hair);border-radius:var(--radius-md);transition:border-color var(--cwv-motion),transform var(--cwv-motion),box-shadow var(--cwv-motion)}
.cw--v3 .cw-snap__row:hover{border-color:var(--color-border-strong);transform:translateY(-1px);box-shadow:0 4px 12px color-mix(in srgb,var(--color-text-primary) 6%,transparent)}
.cw--v3 .cw-snap__restore{transition:background var(--cwv-motion),color var(--cwv-motion),transform var(--cwv-motion)}
.cw--v3 .cw-snap__restore:hover{transform:translateY(-1px)}
.cw--v3 .cw-fmenu{border-radius:var(--cwv-radius-panel);box-shadow:var(--cwv-shadow-float)}
.cw--v3 .cw-fmenu__item{padding:9px 13px;border-radius:9px;transition:background var(--cwv-motion-fast),color var(--cwv-motion-fast)}
@media (max-width:860px){
  .cw--v3 .cw-bar{padding:9px 12px;row-gap:7px}
  .cw--v3 .cw-mobnav{border-top:1px solid var(--cwv-hair);background:color-mix(in srgb,var(--color-surface) 94%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);padding-bottom:env(safe-area-inset-bottom)}
  .cw--v3 .cw-mobnav__b{min-height:46px;padding:9px 4px 8px;transition:color var(--cwv-motion-fast)}
  .cw--v3 .cw-mobnav__b:active{transform:scale(.92)}
  .cw--v3 .cw-mobnav__b.is-active::before{box-shadow:0 0 8px color-mix(in srgb,var(--color-accent) 55%,transparent)}
  .cw--v3 .cw-prevwrap,.cw--v3 .cw-console{margin:0 8px 8px}
  .cw--v3 .cw-bar__btn{min-height:38px}
}
@media (pointer:coarse){
  .cw--v3 .cw-tab,.cw--v3 .cw-prevbtn,.cw--v3 .cw-prevdev,.cw--v3 .cw-file__x,.cw--v3 .cw-conclear{min-height:38px}
  .cw--v3 .cw-file__x{opacity:1}
}
@media (prefers-reduced-motion:reduce){
  .cw--v3 *,.cw--v3 *::before,.cw--v3 *::after{animation-duration:.01ms!important;transition-duration:.06s!important}
  .cw--v3 .cw-bar__btn:hover,.cw--v3 .cw-bar__home:hover,.cw--v3 .cw-ai__go:hover,.cw--v3 .cw-prevbtn:hover,.cw--v3 .cw-snap__row:hover,.cw--v3 .cw-diff__apply:hover,.cw--v3 .cw-prevreload:hover{transform:none}
}

/* ═══════════ FIRAS CODE — CHAT THREAD (real persisted conversation with the AI) ═══════════ */
.cw-thread{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}
.cw-thread__list{flex:1;min-height:0;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:12px}
.cw-thread__empty{margin:auto;max-width:340px;text-align:center;color:var(--color-text-muted);font:400 13px/1.6 var(--font-sans)}
[dir="rtl"] .cw-thread__empty{font-family:var(--font-arabic)}
.cw-thread__turn{display:flex;flex-direction:column;gap:4px;max-width:88%}
.cw-thread__turn--me{align-self:flex-end;align-items:flex-end}
.cw-input-files{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px;list-style:none;margin:8px 0 0;padding:0;max-inline-size:100%;min-inline-size:0}
.cw-input-files li{max-inline-size:100%;min-inline-size:0}
.cw-input-file{display:flex;align-items:center;gap:8px;min-block-size:44px;max-inline-size:100%;padding:9px 12px;box-sizing:border-box;border:1px solid var(--color-border);border-radius:10px;background:var(--color-surface);color:var(--color-text-primary);text-decoration:none;font:500 12px/1.5 var(--font-sans)}
.cw-input-file bdi{overflow-wrap:anywhere;min-inline-size:0}
.cw-input-file>span{color:var(--color-accent);font-size:18px;flex-shrink:0}
.cw-input-file:hover{border-color:var(--color-accent);background:var(--color-bg)}
.cw-input-file:focus-visible{outline:2px solid var(--color-accent);outline-offset:3px}

.cw-thread__turn--ai{align-self:flex-start;align-items:flex-start}
.cw-thread__who{display:flex;align-items:center;gap:7px;font:600 10.5px var(--font-sans);letter-spacing:.02em;color:var(--color-text-muted);text-transform:uppercase}
.cw-thread__chip{text-transform:none;letter-spacing:0;font:600 10px var(--font-mono);color:var(--color-accent);background:var(--color-accent-soft);border-radius:var(--radius-pill);padding:1px 8px}
.cw-thread__bubble{font:400 13.5px/1.6 var(--font-sans);padding:9px 13px;border-radius:14px;white-space:pre-wrap;word-break:break-word}
[dir="rtl"] .cw-thread__bubble{font-family:var(--font-arabic)}
.cw-thread__turn--me .cw-thread__bubble{background:color-mix(in srgb,var(--color-accent) 14%,var(--color-surface));color:var(--color-text-primary);border-bottom-right-radius:5px}
.cw-thread__turn--ai .cw-thread__bubble{background:var(--color-bg-subtle);border:1px solid var(--color-border-hairline);color:var(--color-text-primary);border-bottom-left-radius:5px}
/* design-system pass */
.cw--v3 .cw-thread{background:var(--color-surface)}
.cw--v3 .cw-thread__bubble{box-shadow:0 1px 2px color-mix(in srgb,var(--color-text-primary) 5%,transparent)}

/* ═══════════ iOS / NATIVE-APP POLISH — safe areas, touch, no zoom-jank ═══════════
   The app shell previously only handled safe areas inside Firas Code, so on a notched
   iPhone the topbar sat under the status bar. Appended last so source-order wins. */
/* 1) topbar clears the notch/status bar; sidebar clears notch + home indicator */
.topbar{
  block-size:calc(var(--topbar-h) + var(--safe-top));
  min-block-size:calc(var(--topbar-h) + var(--safe-top));
  padding-block-start:var(--safe-top);
}
.sidebar{
  padding-block-start:var(--safe-top);
  padding-block-end:var(--safe-bottom);
}
/* 2) no blue tap flash; no long-press callout on chrome */
*{-webkit-tap-highlight-color:transparent}
button,a,[role="button"],.btn,.chip,.tab{-webkit-touch-callout:none}
/* 3) iOS auto-zooms on focus when an input's font-size < 16px — pin it on phones */
@media (max-width:640px){
  input,textarea,select{font-size:16px}
}
/* 4) dynamic viewport instead of 100vh (Safari bar / app chrome) */
.share-page{min-height:100vh;min-height:100dvh}
@media (max-width:860px){
  .ann-reader{max-height:100dvh;min-height:100dvh}
}
/* 5) kill the rubber-band bounce that exposes the system background */
html,body{overscroll-behavior:none}
/* 6) momentum scrolling on the scroll containers */
.chat-scroll,.sidebar,.cw-tree,.cw-thread__list,.cw-console__list{-webkit-overflow-scrolling:touch}

/* ════════════════════════════════════════════════════════════════
   NATIVE SHELL — keyboard lockstep · iOS-26 liquid glass · de-webbify
   Every rule gates on html.native-shell / .native-ios / .native-android,
   added by app.js ONLY when Capacitor.isNativePlatform() === true, so a
   plain browser is completely unaffected. Appended last → source wins.
   ════════════════════════════════════════════════════════════════ */

/* 1 — iOS: the composer is glued to the keyboard and rides it 1:1 -------- */
html.native-ios .composer-wrap{
  transform: translateY(calc(-1 * var(--kb, 0px)));
  transition: transform var(--kb-dur) var(--ease-ios),
              padding-block-end var(--kb-dur) var(--ease-ios);
}
html.native-ios.kb-open .composer-wrap{
  padding-block-end: var(--space-4); /* sit flush on the keyboard; drop the safe-area gap */
  will-change: transform;            /* promote a layer only while the keyboard is up */
}
html.native-ios .chat-scroll{ padding-bottom: calc(150px + var(--kb, 0px)); }
html.native-ios.kb-instant .composer-wrap{ transition: none; } /* didShow drift-fix: snap, no slide */

/* 2 — Android: lift by the real IME inset (edge-to-edge, window never shrinks) */
html.native-android .composer-wrap{
  transform: translateY(calc(-1 * var(--kb-inset, 0px)));
  transition: transform var(--dur-base) var(--ease-out);
  padding-block-end: max(var(--space-4), var(--safe-bottom));
}
html.native-android .chat-scroll{ padding-bottom: calc(150px + var(--kb-inset, 0px)); }

@media (prefers-reduced-motion: reduce){
  html.native-shell .composer-wrap{ transition: none; }
}

/* 3 — iOS-26 Liquid Glass chrome + buttons (theme-adaptive tint) --------- */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){
  html.native-shell .topbar,
  html.native-shell .composer{
    -webkit-backdrop-filter: blur(22px) saturate(180%);
    backdrop-filter: blur(22px) saturate(180%);
    background-color: color-mix(in srgb, var(--color-surface) 62%, transparent);
    border: var(--border-hairline) solid color-mix(in srgb, var(--color-text-primary) 9%, transparent);
    box-shadow: inset 0 1px 0 color-mix(in srgb, #ffffff 14%, transparent),
                0 8px 30px rgba(0,0,0,.30);
  }
  html.native-shell .composer__send,
  html.native-shell .composer__attach,
  html.native-shell .icon-btn{
    -webkit-backdrop-filter: blur(14px) saturate(160%);
    backdrop-filter: blur(14px) saturate(160%);
    background-image: linear-gradient(180deg,
      color-mix(in srgb, #ffffff 16%, transparent),
      color-mix(in srgb, #ffffff 4%, transparent));
    border: var(--border-hairline) solid color-mix(in srgb, #ffffff 16%, transparent);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.20), 0 2px 10px rgba(0,0,0,.24);
    transition: transform var(--dur-fast) var(--spring), filter var(--dur-fast) var(--ease-out);
  }
  html.native-shell .composer__send:active,
  html.native-shell .composer__attach:active,
  html.native-shell .icon-btn:active{ transform: scale(.94); filter: brightness(1.16); }
}

/* 4 — De-webbify: kill the browser tells (native only) ------------------- */
html.native-shell,
html.native-shell *{
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
}
html.native-shell{ -webkit-text-size-adjust: 100%; }
html.native-shell html, html.native-shell body{ overscroll-behavior: none; }
html.native-shell .chat-scroll,
html.native-shell .sidebar,
html.native-shell .cw-tree,
html.native-shell .cw-thread__list{ overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
/* …but keep selection where users legitimately need it: inputs, message text, code editor */
html.native-shell textarea,
html.native-shell input,
html.native-shell [contenteditable="true"],
html.native-shell .md, html.native-shell .md *,
html.native-shell .msg-ai__body, html.native-shell .msg-ai__body *,
html.native-shell pre, html.native-shell code,
html.native-shell .CodeMirror, html.native-shell .CodeMirror *{
  -webkit-user-select: text;
  user-select: text;
}

/* ============================================================================
   VOICE DICTATION — mic button, recording bar, dialect picker
   ========================================================================== */
.composer { position: relative; }
/* The mic inherits .icon-btn's 3px radius, which is invisible until something FILLS it — and
   `is-on` fills it with solid accent. The result was a hard 36px square sitting between the
   pill-shaped send button and the rounded composer, the one square edge in the whole cluster.
   Rounding it here (not on .icon-btn) keeps every other icon button exactly as it was, and it
   is set for ALL states so the hover shape and the recording shape are the same shape. */
.composer__mic { border-radius: var(--radius-pill); }
.composer__mic.is-on { color: #fff; background: var(--color-accent); border-radius: var(--radius-pill); }
/* Recording bar overlays the composer row while recording / transcribing */
.mic-rec {
  position: absolute; inset: 0; z-index: 6;
  display: flex; align-items: center; gap: 10px;
  padding: 0 12px; border-radius: inherit;
  background: var(--color-surface);
}
.mic-rec[hidden] { display: none; }
.mic-rec__dot { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%; background: #e5484d; animation: micPulse 1.15s ease-in-out infinite; }
@keyframes micPulse { 50% { opacity: .3; transform: scale(.8); } }
.mic-rec__viz { flex: 1 1 auto; min-width: 0; height: 34px; }
.mic-rec__viz canvas { width: 100%; height: 100%; display: block; }
.mic-rec__time { flex: 0 0 auto; font: 600 12.5px var(--font-mono); color: var(--color-text-muted); min-width: 34px; text-align: center; font-variant-numeric: tabular-nums; }
.mic-rec__status { flex: 1 1 auto; font: 500 13px var(--font-sans); color: var(--color-text-muted); text-align: center; }
[dir="rtl"] .mic-rec__status { font-family: var(--font-arabic); }
.mic-rec__cancel, .mic-rec__done {
  flex: 0 0 auto; width: 34px; height: 34px; border: 0; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer; transition: .15s;
  background: transparent; color: var(--color-text-muted);
}
.mic-rec__cancel svg, .mic-rec__done svg { width: 17px; height: 17px; }
.mic-rec__cancel:hover { color: #e5484d; background: color-mix(in srgb, #e5484d 12%, transparent); }
.mic-rec__done { color: #fff; background: var(--color-accent); }
.mic-rec__done:hover { transform: scale(1.06); }
.mic-rec__lang {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 11px; border: 1px solid var(--color-border-hairline); border-radius: var(--radius-pill);
  background: var(--color-bg-subtle); color: var(--color-text-secondary);
  font: 600 12px var(--font-sans); cursor: pointer; transition: .15s; white-space: nowrap;
}
[dir="rtl"] .mic-rec__lang { font-family: var(--font-arabic); }
.mic-rec__lang:hover { border-color: var(--color-accent); color: var(--color-accent); }
/* Dialect / language picker */
.mic-menu {
  position: absolute; bottom: calc(100% + 10px); inset-inline-end: 6px; z-index: 40;
  min-width: 240px; max-height: 330px; overflow: auto; overscroll-behavior: contain;
  padding: 6px; border: 1px solid var(--color-border-hairline); border-radius: 14px;
  background: var(--color-surface);
  box-shadow: 0 4px 10px rgba(0,0,0,.08), 0 16px 40px rgba(0,0,0,.16);
  animation: micMenuIn .16s ease;
}
@keyframes micMenuIn { from { opacity: 0; transform: translateY(6px) scale(.98); } }
.mic-menu[hidden] { display: none; }
.mic-menu__title {
  padding: 8px 12px 6px; font: 700 11px var(--font-sans); letter-spacing: .06em; text-transform: uppercase;
  color: var(--color-text-muted);
}
[dir="rtl"] .mic-menu__title { font-family: var(--font-arabic); letter-spacing: 0; }
.mic-menu__item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 11px; border: 0; border-radius: 9px; cursor: pointer;
  background: transparent; color: var(--color-text-secondary);
  font: 500 13.5px var(--font-sans); text-align: start; transition: .12s;
}
[dir="rtl"] .mic-menu__item { font-family: var(--font-arabic); }
.mic-menu__item:hover { background: var(--color-bg-subtle); }
.mic-menu__item.is-active { color: var(--color-accent); background: var(--color-accent-soft); font-weight: 600; }
.mic-menu__flag { flex: 0 0 auto; width: 24px; text-align: center; font-size: 15px; }
.mic-menu__label { flex: 1 1 auto; min-width: 0; }
.mic-menu__check { flex: 0 0 auto; width: 16px; height: 16px; opacity: 0; transform: scale(.6); transition: .15s; }
.mic-menu__check svg { width: 16px; height: 16px; display: block; }
.mic-menu__item.is-active .mic-menu__check { opacity: 1; transform: scale(1); }
/* Current-dialect value in the tools (+) menu row */
.tools-menu__value { flex: 0 0 auto; font: 600 11.5px var(--font-sans); color: var(--color-text-muted); white-space: nowrap; }
[dir="rtl"] .tools-menu__value { font-family: var(--font-arabic); }
@media (prefers-reduced-motion: reduce) {
  .mic-rec__dot { animation: none; }
  .mic-menu { animation: none; }
  .mic-rec__done:hover { transform: none; }
}
/* Live-dictation variant of the bar: status text instead of the waveform */
.mic-rec.is-live .mic-rec__viz { display: none; }
.mic-rec.is-live .mic-rec__status { text-align: start; }

/* ============================================================================
   VOICE CALL — composer call button + full-screen call overlay
   ========================================================================== */
/* The call belongs to Firas AI, but hiding the button in the other products taught people the
   feature had been removed — `state.product` is remembered across visits, so one visit to Firas
   Agent could cost someone the call for ever. The button stays everywhere now; pressing it in
   another product moves the reader to Firas AI and opens the call (see `callOpen`). */
.composer__call.is-unavailable { opacity: .45; }
.composer__call svg { width: 20px; height: 20px; }

.call {
  position: fixed; inset: 0; z-index: 1200;
  display: grid; place-items: center;
  opacity: 0; visibility: hidden; transition: opacity .26s ease, visibility .26s ease;
}
.call[hidden] { display: none; }
.call.is-open { opacity: 1; visibility: visible; }
.call__bg {
  position: absolute; inset: 0;
  background:
    radial-gradient(120% 80% at 50% 12%, color-mix(in srgb, var(--color-accent) 26%, transparent), transparent 60%),
    var(--color-bg);
  backdrop-filter: blur(6px);
}
:root[data-theme="dark"] .call__bg { background: radial-gradient(120% 80% at 50% 12%, color-mix(in srgb, var(--color-accent) 30%, transparent), transparent 60%), #14201d; }
.call__inner {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; align-items: center;
  gap: 14px; padding: 32px; text-align: center; max-width: 460px; width: 92%;
}
/* ── THE STAGE ── just the orb now.
   A reactive halo lived here and was REMOVED on the owner's call, for a reason worth recording:
   `filter: blur()` renders into a RECTANGULAR region, so as the glow swelled with his voice the
   straight edge of that box became visible above the circle — a square outline appearing exactly
   when he spoke. Softening or shrinking it only moves the edge; the shape is inherent to how the
   filter is rasterised. The orb's own shader already carries the light, and it carries it in a
   circle, so the honest fix was to take the halo out rather than chase its corners. */
.call__stage { position: relative; display: grid; place-items: center; }

/* ── THE TIMER ── every serious call surface shows one: it is the difference between "a screen
   that is open" and "a call that is running". Tabular figures so the digits do not jitter as
   they change. */
.call__timer {
  font: 500 13px/1 var(--font-mono, var(--font-sans)); font-variant-numeric: tabular-nums;
  color: var(--color-text-secondary); opacity: .78; margin: -4px 0 0; min-height: 13px;
  letter-spacing: .04em;
}

/* ── THE CONTROL BAR ── the buttons used to float loose on the background. Gathering them into
   one frosted bar gives the eye a single object to aim at and reads as a device rather than two
   stray circles. */
.call__controls.call__controls {
  gap: 18px; margin-top: 22px; padding: 10px 16px; border-radius: 999px;
  background: color-mix(in srgb, var(--color-surface) 62%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
  backdrop-filter: blur(18px) saturate(1.3); -webkit-backdrop-filter: blur(18px) saturate(1.3);
  box-shadow: 0 10px 34px rgba(0,0,0,.22);
}

/* ── ENTRANCE ── the screen assembles instead of appearing all at once. Two short, staggered
   moves; anything longer makes answering a call feel slow. */
@keyframes callRise { from { opacity: 0; transform: translateY(14px) scale(.96); } to { opacity: 1; transform: none; } }
.call.is-open .call__stage { animation: callRise .42s cubic-bezier(.22,1,.36,1) both; }
.call.is-open .call__name { animation: callRise .42s cubic-bezier(.22,1,.36,1) .06s both; }
.call.is-open .call__status,
.call.is-open .call__timer { animation: callRise .42s cubic-bezier(.22,1,.36,1) .10s both; }
.call.is-open .call__controls { animation: callRise .42s cubic-bezier(.22,1,.36,1) .16s both; }
@media (prefers-reduced-motion: reduce) {
  .call.is-open .call__stage, .call.is-open .call__name, .call.is-open .call__status,
  .call.is-open .call__timer, .call.is-open .call__controls { animation: none; }
}

/* Orb */
.call__orb { position: relative; width: 176px; height: 176px; display: grid; place-items: center; cursor: pointer; margin-bottom: 6px; }
/* THE SHADER ORB. Added only when WebGL actually started (JS sets .has-shader), so a browser
   that cannot run it keeps the original core and sonar rings untouched — the fallback is the
   old orb, not an empty circle. */
.call__shader { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: 50%; display: block; z-index: 1; pointer-events: none; }
.call__orb.has-shader .call__core,
.call__orb.has-shader .call__ring { display: none; }
/* The core is hidden GLYPH AND ALL, on purpose. The letter lives inside .call__core, so keeping
   it would mean keeping the disc it is painted on, and a solid gradient disc sitting in the
   hollow centre of a glowing ring reads as a badge stuck on top rather than one object. The
   identity is not lost: the name فِراس sits directly under the orb on the same screen. */
.call__orb.has-shader { place-items: center; }
@media (prefers-reduced-motion: reduce) { .call__shader { opacity: .85; } }
.call__core {
  position: relative; z-index: 3; width: 108px; height: 108px; border-radius: 50%;
  display: grid; place-items: center;
  background: radial-gradient(circle at 32% 28%, color-mix(in srgb, var(--color-accent) 70%, #fff), var(--color-accent) 70%);
  box-shadow: 0 10px 40px color-mix(in srgb, var(--color-accent) 45%, transparent), inset 0 2px 10px rgba(255,255,255,.25);
  transition: transform .25s ease;
}
.call__core .nib { transform: scale(1.25); }
.call__ring {
  position: absolute; inset: 0; margin: auto; width: 108px; height: 108px; border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--color-accent) 55%, transparent);
  opacity: 0; pointer-events: none;
}
/* Idle: a gentle breathing core */
.call__orb .call__core { animation: callBreathe 3.4s ease-in-out infinite; }
@keyframes callBreathe { 50% { transform: scale(1.05); } }
/* Listening: expanding sonar rings */
.call__orb.is-listening .call__ring { animation: callSonar 1.8s ease-out infinite; }
.call__orb.is-listening .call__ring:nth-child(2) { animation-delay: .6s; }
.call__orb.is-listening .call__ring:nth-child(3) { animation-delay: 1.2s; }
@keyframes callSonar { 0% { opacity: .5; transform: scale(1); } 100% { opacity: 0; transform: scale(1.7); } }
/* Thinking: slow shimmer */
.call__orb.is-thinking .call__core { animation: callThink 1.1s ease-in-out infinite; }
@keyframes callThink { 50% { transform: scale(.94); filter: brightness(.9); } }
/* Speaking: quick pulse */
.call__orb.is-speaking .call__core { animation: callTalk .5s ease-in-out infinite; }
@keyframes callTalk { 50% { transform: scale(1.12); } }
.call__name { font: 700 26px var(--font-serif, var(--font-sans)); color: var(--color-text-primary); margin: 0; }
.call__status { font: 600 15px var(--font-sans); color: var(--color-accent); margin: 0; min-height: 20px; }
[dir="rtl"] .call__status { font-family: var(--font-arabic); }
.call__caption {
  font: 400 15px/1.6 var(--font-sans); color: var(--color-text-secondary);
  margin: 2px 0 0; min-height: 44px; max-height: 30vh; overflow: auto;
  max-width: 44ch; overscroll-behavior: contain;
}
[dir="rtl"] .call__caption { font-family: var(--font-arabic); }
.call__controls { display: flex; align-items: center; gap: 22px; margin-top: 16px; }
.call__btn {
  width: 60px; height: 60px; border-radius: 50%; border: 0; cursor: pointer;
  display: grid; place-items: center; transition: transform .15s ease, background .15s ease, color .15s ease;
  /* THE SQUARE ON TAP. The button was always round — what was not round was the browser's own
     tap highlight, which mobile Safari and Chrome paint as a translucent RECTANGLE the size of the
     element box the instant a finger lands. Nothing in this stylesheet drew it, which is why it
     could not be found by looking for a border-radius that goes missing. Suppressed here, and
     replaced below with feedback that respects the shape. */
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none; user-select: none;
  outline: none;
}
/* Press feedback that stays round: the button sinks slightly instead of being boxed. */
.call__btn:active { transform: scale(.93); }
/* Keyboard focus still has to be visible — as a RING that follows the circle, not an outline
   rectangle. Only for keyboard users, so a tap never shows it. */
.call__btn:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 55%, transparent); }
/* Same treatment for the orb: it is tappable, and it was flashing the same rectangle. */
.call__orb { -webkit-tap-highlight-color: transparent; -webkit-user-select: none; user-select: none; }
.call__btn svg { width: 24px; height: 24px; }
.call__btn:hover { transform: translateY(-2px); }
.call__btn--mute { background: var(--color-surface); color: var(--color-text-secondary); border: 1px solid var(--color-border); }
.call__btn--mute.is-muted { background: color-mix(in srgb, #e5484d 15%, transparent); color: #e5484d; border-color: color-mix(in srgb, #e5484d 40%, transparent); }
.call__btn--end { background: #e5484d; color: #fff; box-shadow: 0 8px 24px color-mix(in srgb, #e5484d 45%, transparent); }
.call__btn--end:hover { background: #d13b40; }
@media (prefers-reduced-motion: reduce) {
  .call__orb .call__core, .call__orb.is-listening .call__ring,
  .call__orb.is-thinking .call__core, .call__orb.is-speaking .call__core { animation: none; }
  .call { transition: opacity .1s; }
}
/* ---- Call overlay: fit every device (small phones, notches, landscape) ---- */
.call__inner {
  max-height: 100dvh; overflow-y: auto; overscroll-behavior: contain;
  padding-top: calc(24px + env(safe-area-inset-top, 0px));
  padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
}
@media (max-width: 480px) {
  .call__inner { padding-inline: 16px; gap: 12px; }
  .call__orb { width: 148px; height: 148px; }
  .call__core, .call__ring { width: 92px; height: 92px; }
  .call__core .nib { transform: scale(1.1); }
  .call__name { font-size: 22px; }
  .call__status { font-size: 14px; }
  .call__caption { font-size: 14px; max-height: 26vh; max-width: 92vw; }
  .call__controls { gap: 20px; margin-top: 12px; }
  .call__btn { width: 58px; height: 58px; }
}
/* Short screens (landscape phones): compress vertically so controls never clip */
@media (max-height: 560px) {
  .call__inner { gap: 8px; padding-block: 12px; }
  .call__orb { width: 104px; height: 104px; margin-bottom: 0; }
  .call__core, .call__ring { width: 72px; height: 72px; }
  .call__core .nib { transform: scale(0.95); }
  .call__name { font-size: 19px; }
  .call__caption { max-height: 18vh; min-height: 0; }
  .call__controls { margin-top: 6px; }
  .call__btn { width: 52px; height: 52px; }
}

/* Call: microphone consent gate (a real tap grants mic + unlocks audio for Safari/Chrome) */
.call__consent { display: flex; flex-direction: column; align-items: center; gap: 14px; margin: 4px 0 2px; }
.call__consent[hidden] { display: none; }
.call__consent-text { font: 500 15px/1.6 var(--font-sans); color: var(--color-text-secondary); text-align: center; max-width: 30ch; margin: 0; }
[dir="rtl"] .call__consent-text { font-family: var(--font-arabic); }
.call__consent-btn {
  display: inline-flex; align-items: center; gap: 10px; padding: 13px 24px; border: 0; border-radius: var(--radius-pill);
  background: var(--color-accent); color: #fff; font: 600 15px var(--font-sans); cursor: pointer;
  box-shadow: 0 8px 24px color-mix(in srgb, var(--color-accent) 40%, transparent); transition: transform .15s ease, background .15s ease;
}
[dir="rtl"] .call__consent-btn { font-family: var(--font-arabic); }
.call__consent-btn:hover { transform: translateY(-2px); }
.call__consent-btn:active { transform: translateY(0); }
.call__consent-btn svg { width: 20px; height: 20px; }

/* ═══════════════════════════════════════════════════════════════════════════
   GUEST MODE — the sidebar sign-up pill + the members-only upsell dialog.
   The pill is deliberately BILINGUAL (active language large, the other language
   underneath) so "Sign up now / سجّل الآن" reads for every visitor.
   ═══════════════════════════════════════════════════════════════════════════ */
.guest-slot {
  margin: var(--space-2) var(--space-3) 0;
  padding-block-start: var(--space-3);
  border-block-start: var(--border-hairline) solid var(--color-border-hairline);
}
.guest-slot__note {
  margin: 0 0 var(--space-2);
  color: var(--color-text-muted);
  font-size: 11.5px; line-height: 1.5; text-align: center;
}
[dir="rtl"] .guest-slot__note { font-family: var(--font-arabic); line-height: 1.7; }

.guest-cta {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  min-block-size: 46px;                    /* comfortable touch target on phones */
  padding-inline: var(--space-4);
  border: 0; border-radius: var(--radius-pill);
  background: var(--color-accent); color: var(--color-on-accent);
  cursor: pointer; text-align: center;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--color-accent) 32%, transparent);
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.guest-cta--block { display: flex; inline-size: 100%; }
.guest-cta:hover { background: var(--color-accent-hover); box-shadow: 0 10px 24px color-mix(in srgb, var(--color-accent) 38%, transparent); }
/* Real-hover only — a tapped pill must not stay lifted (sticky :hover on touch). */
@media (hover: hover) and (pointer: fine) {
  .guest-cta:hover { transform: translateY(-1px); }
}
.guest-cta:active { transform: translateY(0); }
.guest-cta:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
.guest-cta__ico { display: inline-flex; flex: 0 0 auto; }
.guest-cta__ico svg { inline-size: 17px; block-size: 17px; }
.guest-cta__tx { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.15; }
[dir="rtl"] .guest-cta__tx { align-items: flex-end; }
.guest-cta__main { font: var(--weight-semibold) 14px/1.2 var(--font-sans); }
[dir="rtl"] .guest-cta__main { font-family: var(--font-arabic); }
.guest-cta__alt  { font: var(--weight-medium) 10.5px/1.3 var(--font-sans); opacity: .78; }
[dir="rtl"] .guest-cta__alt { font-family: var(--font-sans); }

/* ---- Upsell dialog (image generation & other members-only features) ---- */
.guest-ov {
  position: fixed; inset: 0; z-index: 1400;
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-5);
  background: rgba(0,0,0,.55);
  opacity: 0; transition: opacity var(--dur-fast) var(--ease-out);
}
.guest-ov.is-open { opacity: 1; }
.guest-card {
  inline-size: min(420px, 100%);
  display: flex; flex-direction: column; align-items: center; gap: var(--space-3);
  padding: var(--space-8) var(--space-6) var(--space-6);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  box-shadow: var(--shadow-lg, 0 24px 60px rgba(0,0,0,.28));
  text-align: center;
  transform: translateY(14px) scale(.97);
  transition: transform var(--dur-base, .22s) var(--ease-out);
}
.guest-ov.is-open .guest-card { transform: none; }
.guest-card__mark { margin-block-end: var(--space-1); }
.guest-card__title {
  margin: 0; color: var(--color-text-primary);
  font: var(--weight-semibold) 19px/1.35 var(--font-sans);
}
[dir="rtl"] .guest-card__title { font-family: var(--font-arabic); line-height: 1.6; }
.guest-card__body {
  margin: 0; color: var(--color-text-secondary);
  font-size: var(--text-sm); line-height: var(--leading-md);
}
[dir="rtl"] .guest-card__body { font-family: var(--font-arabic); line-height: 1.9; }
.guest-card__actions { display: flex; flex-direction: column; gap: var(--space-2); inline-size: 100%; margin-block-start: var(--space-2); }
.guest-card__cta {
  min-block-size: 48px; inline-size: 100%;
  border: 0; border-radius: var(--radius-pill);
  background: var(--color-accent); color: var(--color-on-accent);
  font: var(--weight-semibold) 15px var(--font-sans); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .guest-card__cta { font-family: var(--font-arabic); }
.guest-card__cta:hover { background: var(--color-accent-hover); }
.guest-card__cta:focus-visible,
.guest-card__later:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
.guest-card__later {
  min-block-size: 44px; inline-size: 100%;
  border: 0; border-radius: var(--radius-pill); background: transparent;
  color: var(--color-text-muted); font: var(--weight-medium) 14px var(--font-sans); cursor: pointer;
}
[dir="rtl"] .guest-card__later { font-family: var(--font-arabic); }
.guest-card__later:hover { color: var(--color-text-primary); background: var(--color-bg-subtle); }

@media (max-width: 480px) {
  .guest-ov { align-items: flex-end; padding: 0; }
  .guest-card { inline-size: 100%; border-end-start-radius: 0; border-end-end-radius: 0; padding-block-end: calc(var(--space-6) + env(safe-area-inset-bottom, 0px)); }
}
@media (prefers-reduced-motion: reduce) {
  .guest-ov, .guest-card { transition: none !important; }
  .landing__cta.is-busy::after { animation: none !important; }
}

/* ============================================================================
   MOBILE HARDENING — touch targets · tier dropdown · safe areas · focus · RTL
   Appended last on purpose. Every rule below overrides an EARLIER one at the
   same specificity, so plain source order does the work and no !important is
   needed. Rewriting the originals in place would have meant editing rules the
   desktop layout, Firas Code and the native shell all sit on top of.
   Transitions added here inherit the global `prefers-reduced-motion` and
   `:root[data-motion="off"]` kill-switches (both zero out transition-duration
   for `*`), so nothing here animates when motion is off.
   ========================================================================== */

/* ----------------------------------------------------------------------------
   A. TOUCH TARGETS — 44x44 minimum wherever a finger is the pointer

   A1. The one existing bump ("Comfortable touch targets", section 9) reached
   five selectors, and the topbar compaction rules then UNDID it:
   `.topbar .icon-btn{inline-size:34px;block-size:34px}` at <=480px and
   `{31px}` at <=360px. Those never reclaimed a single pixel of width — they
   left `min-inline-size:44px` standing, so the button stayed 44px wide and only
   lost 10-13px of HEIGHT. The space they were chasing comes from the GLYPH
   instead (A2), so the hit area can stay 44px on the smallest phones.
---------------------------------------------------------------------------- */
@media (pointer: coarse), (max-width: 560px) {
  /* Topbar chrome — hamburger, share, bell */
  .topbar .icon-btn {
    inline-size: 44px; block-size: 44px;
    min-inline-size: 44px; min-block-size: 44px;
  }
  /* New-chat is not an .icon-btn, so no bump ever reached it: with the label
     hidden below 480px it was a 24x36 target — the smallest in the topbar.
     Height only here; the square icon-only form is below 768px, where the label
     is hidden. A coarse-pointer TABLET still shows the label and must stay
     auto-width or the text clips. */
  .topbar__newchat { block-size: 44px; min-block-size: 44px; }

  /* Tier strip (tablets / coarse pointers ABOVE 600px — below that the strip is
     replaced by the dropdown in section B). 26px tall segments in a scroller
     were the hardest thing in the app to hit accurately. */
  .tier-switch { block-size: auto; }
  .tier-switch__btn { block-size: 44px; min-block-size: 44px; }

  /* Composer toolbar — the "+" tools trigger was omitted from the original bump
     even though its neighbour .mode-select__trigger was included. */
  .tools-menu__trigger { inline-size: 44px; block-size: 44px; min-inline-size: 44px; }
  .mode-select__trigger { block-size: 44px; min-block-size: 44px; }
  .think-toggle { block-size: 44px; min-block-size: 44px; }

  /* Per-message actions (copy / regenerate / export) — the most-tapped controls
     in a thread after send. */
  .msg-action { block-size: 44px; min-block-size: 44px; padding-inline: var(--space-3); }

  /* Drawer account row: two 30px buttons side by side in a 288px panel. */
  .account-pill { gap: var(--space-2); padding-inline: var(--space-2); }
  .account-pill__logout,
  .account-pill__settings { inline-size: 44px; block-size: 44px; }

  /* Scroll-to-latest floats free above the composer — nothing competes for the
     space, so 36px was a pure loss. */
  .scroll-bottom { inline-size: 44px; block-size: 44px; }

  /* Voice dictation bar: a touch-ONLY surface that shipped 34px buttons. The bar
     is `inset:0` over a composer row that is already >=44px tall, so the buttons
     fit; the gap/padding come down to pay for the extra 20px of width. */
  .mic-rec { gap: 6px; padding-inline: 6px; }
  .mic-rec__cancel, .mic-rec__done { width: 44px; height: 44px; }
  .mic-rec__lang {
    min-block-size: 44px; min-inline-size: 44px;
    flex: 0 1 auto; overflow: hidden;   /* may give width back before it clips the bar */
  }

  /* The three escape hatches from full-screen overlays, all just under the line. */
  .img-lightbox__close,
  .share-sheet__x,
  .ann-icon-btn { inline-size: 44px; block-size: 44px; width: 44px; height: 44px; }

  /* Sign-in screen: text-only buttons at 18-30px tall, no padding to speak of. */
  .auth__forgot,
  .auth__back,
  .auth__switch-btn {
    display: inline-flex; align-items: center; justify-content: center;
    min-block-size: 44px; padding-inline: var(--space-2);
  }
  .auth__switch-btn { vertical-align: middle; }

  /* First control a new mobile visitor ever touches. */
  .cookie-consent__btn {
    display: inline-flex; align-items: center; justify-content: center;
    min-block-size: 44px;
  }
}

/* A2. Reclaim the topbar width the compaction rules were after — from the icon,
   not from the button. 20px -> 19px -> 18px glyphs inside a hit area that never
   drops below 44px. */
@media (max-width: 768px) {
  /* Label is hidden at this width, so the button is a square icon target. */
  .topbar__newchat {
    inline-size: 44px; min-inline-size: 44px;
    justify-content: center; padding-inline: 0;
  }
}
@media (max-width: 480px) {
  .topbar .icon-btn svg { inline-size: 19px; block-size: 19px; }
}
@media (max-width: 360px) {
  .topbar .icon-btn svg { inline-size: 18px; block-size: 18px; }
  .topbar__newchat svg { inline-size: 15px; block-size: 15px; }
  /* Elapsed time is the one droppable item in the recording bar on a 280-360px
     phone; the pulsing dot and the waveform already say "recording". */
  .mic-rec__time { display: none; }
}

/* A3. Controls that genuinely cannot be 44px visually get a transparent hit slug
   instead. Only safe where nothing interactive sits inside the expanded area. */
@media (pointer: coarse), (max-width: 560px) {
  /* Remove-attachment: 18px with an 11px glyph, in the corner of a 56px thumb —
     the worst target in the app, and the only way to undo a wrong attachment.
     The frame clipped its own children, so the slug needs the clip lifted; the
     rounding moves onto the image, which is all the clip was really doing. */
  .attach-tray { gap: var(--space-3); }
  .attach-thumb { overflow: visible; }
  .attach-thumb img { border-radius: inherit; }
  .attach-thumb__remove::after {
    content: ""; position: absolute; inset: -13px;   /* 18 + 13 + 13 = 44 */
    border-radius: var(--radius-pill);
  }

  /* Conversation row actions: NOT slugged. Three 26px buttons 2px apart would
     need overlapping -9px slugs, and in an overlap the later sibling takes the
     tap — rename would end up with a 12px live strip, worse than the 26px it
     started with. Real 44px buttons, gap removed, row grown to fit. */
  .chat-item__actions { gap: 0; }
  .chat-item__actions button { inline-size: 44px; block-size: 44px; }

  /* TITLE GETS A LINE OF ITS OWN, because on a phone it was getting no width at all. Measured on a
     375px viewport: the row is 264px, the timestamp takes 56 and the four 44px buttons take 176 and
     never shrink, so the title — the only flexible item — computed to ZERO. The text was in the DOM
     the whole time with nowhere to render.

     THE BUTTONS CANNOT SIMPLY BE HIDDEN, and both reasons are already fixed bugs living in this
     file: they are always-present on touch so that pin/rename/delete are reachable on every row and
     not only the open one, AND so the row's geometry is identical before and after a tap — a
     synthesised :hover that reflowed the row mid-gesture is what made conversations need two taps
     to open. Revealing them on tap or only on the active row re-breaks one of those.

     So they stop SHARING the line instead: title and timestamp on the first, the four buttons at
     full width on the second. Always present, geometry constant, title 178px instead of 0.

     THE :root PREFIX IS LOAD-BEARING, and its absence is why the existing `block-size: auto` above
     never worked: `:root[data-ui="2"] .chat-item` sets block-size: 40px at specificity (0,2,1), so
     a bare `.chat-item` (0,1,0) loses and only min-block-size survived — pinning every row at 48px
     regardless of content, which is what pushed the second line into the next row. Same trap, and
     same remedy, as the note on `.chat-item.is-tagging` elsewhere in this file. */
  :root[data-ui="2"] .chat-item,
  :root:not([data-ui="2"]) .chat-item {
    block-size: auto; min-block-size: 48px;
    padding-inline: var(--space-2); padding-block: 4px;
    flex-wrap: wrap; column-gap: 6px; row-gap: 0; align-items: center;
  }
  :root .chat-item__title   { order: 0; flex: 1 1 auto; min-inline-size: 0; }
  :root .chat-item__stamp   { order: 1; flex: 0 0 auto; }
  :root .chat-item__actions { order: 2; flex: 0 0 100%; justify-content: flex-end; }
}

/* ----------------------------------------------------------------------------
   B. TIER SWITCHER BECOMES A DROPDOWN ON PHONES

   The segmented strip holds four tiers and, below ~365px, silently turns into a
   scroller with no scrollbar, no fade and no arrow — "Max", the newest tier,
   simply is not there. A single trigger + panel makes every tier reachable and
   frees ~90px of topbar.

   360px budget (the <=360px block: 4px padding each side, 3px gaps):
     8 padding + 44 drawer + 94 trigger + 44 new-chat + 44 share + 44 bell
     + 5 gaps x 3 = 293px of 360 -> 67px of slack in .topbar__spacer.
   320px: trigger clamps to 83px -> 282px used.  280px: 80px -> 279px of 280.
   The trigger is `flex: 0 1 auto` with an ellipsised name, so anything that
   grows later (a longer tier name, a bigger safe-area inset) shrinks the
   trigger instead of bursting the row.
---------------------------------------------------------------------------- */
.tier-select { display: none; }

@media (max-width: 600px) {
  .tier-switch { display: none; }
  .tier-select {
    display: block; position: relative;
    flex: 0 1 auto; min-inline-size: 0;
  }
}
/* Firas Agent runs on one permanent engine — no tier choice anywhere, matching
   the existing `.product-agent .tier-switch{display:none}` rule. */
.product-agent .tier-select { display: none !important; }

.tier-select__trigger {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  inline-size: clamp(80px, 26vw, 96px); min-inline-size: 0;
  block-size: 44px; min-block-size: 44px;
  padding-inline: 10px;
  border-radius: var(--radius-pill);
  border: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .tier-select__trigger { font-family: var(--font-arabic); }
.tier-select__trigger:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }
.tier-select.is-open .tier-select__trigger,
.tier-select__trigger[aria-expanded="true"] {
  color: var(--color-accent);
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}
/* A real ring, not a box-shadow — see section D for why. */
.tier-select__trigger:focus-visible {
  outline: 2px solid var(--color-accent-ring);
  outline-offset: 2px;
}
.tier-select__ico { display: inline-flex; flex: 0 0 auto; }
.tier-select__ico, .tier-select__ico svg { inline-size: 16px; block-size: 16px; }
.tier-select__name {
  flex: 0 1 auto; min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tier-select__chev {
  display: inline-flex; flex: 0 0 auto; opacity: .7;
  transition: transform var(--dur-fast) var(--ease-out);
}
.tier-select__chev, .tier-select__chev svg { inline-size: 14px; block-size: 14px; }
.tier-select.is-open .tier-select__chev { transform: rotate(180deg); }

/* The panel is FIXED, not absolute-to-the-trigger. The trigger sits three
   buttons in from the inline-end edge, so a 260px panel anchored to it would
   hang ~45px off the opposite edge of a 360px screen — where `body{overflow
   :hidden}` clips it dead. Fixed + inline-end gutter cannot overflow either
   edge in either direction. (Under the native shell the topbar carries a
   backdrop-filter and becomes the containing block; it spans the full width, so
   the same offsets still land the panel just under the bar.) */
/* A caption over the second group in the model menu. The four primary models need none - product
   names under one trigger explain themselves - but a second run of the SAME four names has to say
   why it is there. */
.tier-select__sec {
  font: 600 12.5px/1.3 var(--font-sans);
  color: var(--color-text-secondary);
  padding: 10px 12px 4px;
}
.tier-select__menu {
  position: fixed;
  inset-block-start: calc(var(--topbar-h) + var(--safe-top) + 6px);
  inset-inline-end: 12px;
  z-index: var(--z-dropdown);
  inline-size: min(260px, calc(100vw - 24px));
  max-inline-size: calc(100vw - 24px);
  max-block-size: calc(100dvh - var(--topbar-h) - var(--safe-top) - 24px);
  overflow-y: auto; overscroll-behavior: contain;
  padding: var(--space-1);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 2px;
  /* `seat`, like its sibling menus (tools/mode/product): the panel opens BELOW its
     trigger, so it settles DOWN from it — `msg-in` rose upward, away from the
     trigger. The keyframes live later in the file; @keyframes are order-free. */
  animation: seat var(--dur-base) var(--ease-out) both;
  transform-origin: top center;
}
.tier-select__menu[hidden] { display: none; }

/* One row per tier. Laid out so it works whether the markup keeps name + hint
   flat inside the row or nests them in a `.tier-select__opt-text` column. */
.tier-select__opt {
  display: flex; flex-wrap: wrap; align-items: center;
  column-gap: var(--space-3); row-gap: 2px;
  inline-size: 100%; min-block-size: 48px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  text-align: start;
  color: var(--color-text-primary);
  transition: background var(--dur-fast) var(--ease-out);
}
.tier-select__opt:hover { background: var(--color-bg-subtle); }
.tier-select__opt:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: -2px; }
.tier-select__opt.is-active { background: var(--color-accent-soft); }
.tier-select__opt-ico { display: inline-flex; flex: 0 0 auto; color: var(--color-text-secondary); }
.tier-select__opt-ico, .tier-select__opt-ico svg { inline-size: 18px; block-size: 18px; }
.tier-select__opt.is-active .tier-select__opt-ico { color: var(--color-accent); }
/* The model name ran straight into its description on phones — "Firas MiniFast for everyday
   questions" — because this rule was written for `.tier-select__opt-text` while the markup
   has always emitted `.tier-select__opt-tx` (app.js, renderTierSelect). The selector matched
   nothing, so the wrapper stayed an ordinary inline span, and `flex` / `order` /
   `inline-size:100%` on its children are inert outside a flex container: both spans simply
   laid out as adjacent inline text with no gap and no line break.
   Both names are matched now so neither side can silently drift again. */
.tier-select__opt-tx,
.tier-select__opt-text {
  display: flex; flex-direction: column;
  gap: 3px;
  flex: 1 1 auto; min-inline-size: 0;
}
.tier-select__opt-name {
  flex: 1 1 auto; min-inline-size: 0;
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
[dir="rtl"] .tier-select__opt-name { font-family: var(--font-arabic); }
.tier-select__opt.is-active .tier-select__opt-name { color: var(--color-accent); }
.tier-select__opt-hint {
  flex: 0 0 auto; inline-size: 100%;   /* forces its own row when the markup is flat */
  order: 2;                            /* ...and stays BELOW the badge whatever order the markup emits */
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-muted);
}
[dir="rtl"] .tier-select__opt-hint { font-family: var(--font-arabic); }
/* Only indent the tagline when it is a direct child (flat markup); inside a
   text column the column itself is already aligned past the icon. */
.tier-select__opt > .tier-select__opt-hint { padding-inline-start: calc(18px + var(--space-3)); }
.tier-select__opt-tx .tier-select__opt-hint,
.tier-select__opt-text .tier-select__opt-hint { inline-size: auto; padding-inline-start: 0; }
/* The name row holds the label plus an optional badge, so it is its own flex row inside the
   column — otherwise the badge would be pushed onto a line of its own. */
.tier-select__opt-tx .tier-select__opt-name,
.tier-select__opt-text .tier-select__opt-name {
  display: flex; align-items: center; gap: var(--space-2);
  white-space: normal;              /* let a long Arabic label wrap instead of ellipsing */
  overflow: visible; text-overflow: clip;
}

.tier-select__badge {
  flex: 0 0 auto; align-self: center; order: 1;
  padding: 2px 7px;
  border-radius: var(--radius-pill);
  border: var(--border-hairline) solid var(--color-accent-ring);
  background: var(--color-accent-soft);
  color: var(--color-accent);
  font-family: var(--font-sans);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.6;
  letter-spacing: 0.04em; text-transform: uppercase; white-space: nowrap;
}

/* ----- The model pinned to ONE conversation ----------------------------------
   Sits immediately after the tier strip and reads as an annotation ON it, not as
   a fifth segment of it: hairline and muted while nothing is pinned, accent once
   something is — "this conversation has its own model" is a state the reader has
   to be able to see without opening anything.

   The panel is absolute inside .topbar, which clips nothing and is already a
   stacking context at --z-sticky. .tier-select__menu next door has to be fixed
   because it opens three buttons in from the edge of a 360px phone; this control
   does not exist below 640px, so it can hang off its own trigger.
------------------------------------------------------------------------------ */
.tpin { position: relative; display: inline-flex; flex: 0 0 auto; }
.tpin__btn {
  display: inline-flex; align-items: center; gap: 5px;
  block-size: 32px; padding-inline: 8px;
  border-radius: var(--radius-sm);
  border: var(--border-hairline) solid transparent;
  color: var(--color-text-muted);
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.tpin__btn:hover { color: var(--color-text-primary); background: var(--color-bg-subtle); }
/* A real ring, matching .tier-select__trigger two blocks up. */
.tpin__btn:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
.tpin__ico { display: inline-flex; flex: 0 0 auto; }
.tpin__ico svg { inline-size: 16px; block-size: 16px; }
.tpin__name { white-space: nowrap; }
/* The dir is stamped on this span ITSELF — the shell around it is pinned LTR — so the
   attribute selector has to be compounded onto it. `[dir="rtl"] .tpin__name` wants an
   ANCESTOR carrying the direction and would never match here. */
.tpin__name[dir="rtl"] { font-family: var(--font-arabic); }
.tpin.is-pinned .tpin__btn {
  color: var(--color-accent);
  border-color: var(--color-accent-ring);
  background: var(--color-accent-soft);
}
.tpin.is-open .tpin__btn {
  color: var(--color-accent);
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}
/* Firas Agent runs on one permanent engine, exactly as `.product-agent
   .tier-switch{display:none}` says. The JS removes the node there too; this is
   the belt for the frame between a product switch and the next render. */
.product-agent .tpin { display: none !important; }
/* And the same belt for a resize: syncTierPin removes it on the next render, but
   the topbar must not overflow in the frames before that one. */
@media (max-width: 640px) { .tpin { display: none; } }

.tpin__menu {
  position: absolute;
  inset-block-start: calc(100% + 8px);
  /* PHYSICAL right, for the reason .scr spells out further up: the inset properties resolve
     against the ELEMENT'S OWN direction, and this panel stamps dir="rtl" the moment the
     interface is Arabic. Measured, not assumed — with dir="rtl" on it, `inset-inline-end: 0`
     computes to `left: 0`, so the Arabic build hung the panel off the trigger's LEFT edge and
     ran it 276px to the RIGHT, through the New-chat button and the icon row and out past the
     window. The shell is pinned LTR and this control lives in the topbar's right cluster, so
     it has exactly one home edge and it is named outright. */
  right: 0;
  z-index: var(--z-dropdown);
  inline-size: min(276px, calc(100vw - 24px));
  max-block-size: calc(100dvh - var(--topbar-h) - var(--safe-top) - 24px);
  overflow-y: auto; overscroll-behavior: contain;
  padding: var(--space-1);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 2px;
  /* `seat`, like every sibling menu: the panel opens BELOW its trigger, so it
     settles down from it rather than rising away. */
  animation: seat var(--dur-base) var(--ease-out) both;
  transform-origin: top center;
}
.tpin__head {
  padding: var(--space-2) var(--space-3) var(--space-1);
  font-family: var(--font-sans);
  font-size: var(--text-xs); line-height: var(--leading-xs);
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--color-text-muted);
}
/* Arabic has no letter case and its letters join, so uppercasing does nothing and
   tracking pulls the joins apart. Both are dropped rather than inherited. */
[dir="rtl"] .tpin__head {
  font-family: var(--font-arabic);
  text-transform: none; letter-spacing: normal;
}
.tpin__opt {
  display: flex; align-items: center; gap: var(--space-3);
  inline-size: 100%; min-block-size: 46px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  text-align: start;
  color: var(--color-text-primary);
  transition: background var(--dur-fast) var(--ease-out);
}
.tpin__opt:hover { background: var(--color-bg-subtle); }
.tpin__opt:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: -2px; }
.tpin__opt.is-on { background: var(--color-accent-soft); }
.tpin__opt-ico { display: inline-flex; flex: 0 0 auto; color: var(--color-text-secondary); }
.tpin__opt-ico svg { inline-size: 18px; block-size: 18px; }
.tpin__opt.is-on .tpin__opt-ico { color: var(--color-accent); }
.tpin__opt-tx {
  display: flex; flex-direction: column; gap: 3px;
  flex: 1 1 auto; min-inline-size: 0;
}
.tpin__opt-name {
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
}
.tpin__opt.is-on .tpin__opt-name { color: var(--color-accent); }
.tpin__opt-hint {
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-muted);
}
[dir="rtl"] .tpin__opt-name,
[dir="rtl"] .tpin__opt-hint { font-family: var(--font-arabic); }
/* How many answers this model wrote here. Mono + isolated, the .msg-ai__badge
   recipe: it is a bare number at the end of an Arabic row, and left as plain text
   the bidi algorithm moves it. */
.tpin__opt-n {
  flex: 0 0 auto;
  padding: 1px 7px;
  border-radius: var(--radius-pill);
  border: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-bg-subtle); color: var(--color-text-muted);
  font-family: var(--font-mono); font-size: 11px; line-height: 1.6;
  direction: ltr; unicode-bidi: isolate;
}
/* "No pin" carries no icon, so it is indented to the text column the four tiers
   share — row padding + the 18px glyph + the row gap. */
.tpin__opt--off {
  min-block-size: 42px;
  margin-block-start: 2px;
  padding-inline-start: calc(var(--space-3) + 18px + var(--space-3));
  border-block-start: var(--border-hairline) solid var(--color-border-hairline);
  border-start-start-radius: 0; border-start-end-radius: 0;
}

/* ----------------------------------------------------------------------------
   C. OVERFLOW + SAFE AREAS

   C1. index.html ships viewport-fit=cover, so on a notched phone in landscape
   the insets are real — and `--safe-left`/`--safe-right` were declared in the
   token block and then consumed nowhere outside Firas Code. The hamburger, the
   bell and the send button sat under the cutout. Physical padding-left/right on
   purpose: safe-area insets are physical, so mapping them through logical
   properties would put the notch inset on the wrong side in Arabic. Each
   breakpoint is restated because the originals compact the padding as the
   viewport narrows and a single blanket rule would undo that.
---------------------------------------------------------------------------- */
.topbar {
  padding-left:  max(var(--space-4), var(--safe-left));
  padding-right: max(var(--space-4), var(--safe-right));
}
.composer-wrap {
  padding-left:  max(var(--space-6), var(--safe-left));
  padding-right: max(var(--space-6), var(--safe-right));
}
.sidebar {
  padding-left:  max(var(--space-3), var(--safe-left));
  padding-right: max(var(--space-3), var(--safe-right));
}
@media (max-width: 768px) {
  .topbar {
    padding-left:  max(var(--space-3), var(--safe-left));
    padding-right: max(var(--space-3), var(--safe-right));
  }
  .composer-wrap {
    padding-left:  max(var(--space-4), var(--safe-left));
    padding-right: max(var(--space-4), var(--safe-right));
  }
}
@media (max-width: 480px) {
  .topbar {
    padding-left:  max(var(--space-2), var(--safe-left));
    padding-right: max(var(--space-2), var(--safe-right));
  }
}
@media (max-width: 360px) {
  .topbar {
    padding-left:  max(var(--space-1), var(--safe-left));
    padding-right: max(var(--space-1), var(--safe-right));
  }
}

/* C2. Two popovers with a physical min-width and no ceiling. The dialect picker
   floors at 240px inside a composer that is 248px wide at 280px viewport, and
   the product menu floors at 230px with a tagline that can push its end edge
   off-screen — both then clipped by `body{overflow:hidden}` rather than
   scrolled. Clamp to the viewport and let long labels wrap. */
.mic-menu {
  min-inline-size: min(240px, calc(100vw - 24px));   /* keeps the 240px floor until the screen is smaller than it */
  max-inline-size: calc(100vw - 24px);
}
.mic-menu__label { overflow-wrap: anywhere; }
.product-menu {
  min-inline-size: min(230px, calc(100vw - 16px));
  max-inline-size: min(320px, calc(100vw - 16px));
}
.product-menu__tag { overflow-wrap: anywhere; }

/* C3. `vh` on iOS Safari is the URL-bar-HIDDEN viewport, so these sheets
   overshoot the visible area by 8-12% whenever the bar is showing. The codebase
   already fixed .share-page / .ann-reader / #codeWorkspace this way; these five
   were missed. The vh line stays first as the fallback for engines without dvh. */
.mem-card      { max-height: 82vh; max-height: 82dvh; }
.ann-card      { max-height: 86vh; max-height: 86dvh; }
.ann-edit-card { max-height: 88vh; max-height: 88dvh; }
.settings-card { max-height: 88vh; max-height: 88dvh; }
.cw-pal        { padding-block-start: 12vh; padding-block-start: 12dvh; }
.cw-pal__card  { max-height: 66vh; max-height: 66dvh; }
@media (max-width: 560px) {
  /* Same widths as the bottom-sheet block that sets these — 560px, not 860px. */
  .ann-card      { max-height: 90vh; max-height: 90dvh; }
  .ann-edit-card { max-height: 92vh; max-height: 92dvh; }
  .settings-card { max-height: 92vh; max-height: 92dvh; }
}

/* C4. Firas Code console: the v3 pass reset `.cw-conrow` padding to `3px 8px`
   and left the `::before` prefix glyph parked at left:2px, so the marker now
   overlaps the first characters of every log line. Restore the 24px gutter the
   glyph is positioned inside. */
.cw--v3 .cw-conrow { padding: 3px 8px 3px 24px; }
/* …EXCEPT the app's own localized messages. Their marker flows inline instead of being parked
   in that gutter (see .cw-conrow.is-msg above), so the 24px would be blank space pushing the
   text out of line with the log rows beneath it. Scoped to .cw--v3 and placed AFTER the rule
   above on purpose: at equal specificity the later declaration wins, and an is-msg rule in the
   earlier block silently loses this exact argument.
   The marker's gap is a SYMMETRIC physical margin, not margin-inline-end. `direction` stays
   ltr here — is-msg only sets unicode-bidi:plaintext — so every inline-logical property still
   resolves to the left/right it would in English, which is the trap this file keeps setting.
   5px on both sides is correct in either direction with nothing to get wrong. */
.cw--v3 .cw-conrow.is-msg { padding: 3px 8px; }
.cw--v3 .cw-conrow.is-msg::before { position: static; margin: 0 5px; }

/* ----------------------------------------------------------------------------
   D. FOCUS VISIBILITY

   The app's only focus indicator was `box-shadow: var(--shadow-focus)` at
   specificity 0,1,0 — so every component that declares its own box-shadow at
   equal-or-higher specificity silently ate the keyboard ring. The active tier
   button, the plan CTA, the scroll-to-latest button, the landing CTA, the
   end-call button and half a dozen others had literally no focus state.
   `outline` is a separate property no component overrides, it paints over any
   box-shadow and it follows border-radius in every current engine.
---------------------------------------------------------------------------- */
:focus-visible {
  outline: 2px solid var(--color-accent-ring);
  outline-offset: 2px;
}
/* Menu rows that killed the outline at 0,2,0 and replaced it with a hover
   background — which is not a focus indicator, it is the hover state. */
.mode-select__item:focus-visible,
.tools-menu__item:focus-visible,
.export-menu__item:focus-visible,
.file-card:focus-visible,
.file-card__dl:focus-visible,
.ann-item:focus-visible,
.firas-ask__extra:focus-visible {
  outline: 2px solid var(--color-accent-ring);
  outline-offset: 2px;
}
/* Two inputs that set `outline:none` with no replacement at all. Inset offset so
   the ring hugs the field inside its rounded container instead of straddling it. */
.cw-pal__in:focus-visible,
.cw-ed__ta:focus-visible {
  outline: 2px solid var(--color-accent-ring);
  outline-offset: -2px;
}

/* Conversation actions were hover-gated with no touch fallback: pin, rename and
   delete only existed on the row you were already hovering, so on a phone they
   were reachable ONLY on the currently-open conversation. There is no
   long-press handler in app.js either. Same guard the deck presenter already
   uses for this exact class of bug. */
@media (hover: none) {
  .chat-item__actions { display: flex; }
}

/* The closed mobile drawer was hidden with `transform: translateX(-100%)` alone —
   nothing set display, visibility, inert or aria-hidden — so tabbing forward from
   the topbar walked through the logo, the product switch, new-chat, the search
   field, every history row and the logout button, all of them off-screen.
   `visibility` removes it from the tab order and the a11y tree; the 0s-duration /
   --dur-base-delay pairing means it only flips to hidden AFTER the slide-out
   finishes, so the closing animation is untouched. (If app.js later sets `inert`
   on the drawer instead, this rule is harmless duplication and can go.) */
@media (max-width: 768px) {
  .sidebar {
    visibility: hidden;
    transition: transform var(--dur-base) var(--ease-out),
                visibility 0s linear var(--dur-base);
  }
  .sidebar.is-open {
    visibility: visible;
    transition: transform var(--dur-base) var(--ease-out), visibility 0s;
  }
}

/* ----------------------------------------------------------------------------
   E. RTL — Arabic is the default language, so these are the DEFAULT rendering

   E1. Two elements mixed a LOGICAL inset with a PHYSICAL transform. In RTL
   `inset-inline-start:50%` resolves to `right:50%`, putting the element's right
   edge on the midpoint, and `translateX(-50%)` then pushes it a further half-
   width the wrong way: the scroll-to-latest button lands 54px left of centre
   and the "current plan" badge hangs off the card. The `.toast` rule already
   uses the correct idiom (`inset-inline:0; margin-inline:auto`).
---------------------------------------------------------------------------- */
.scroll-bottom {
  inset-inline: 0;
  margin-inline: auto;
  transform: translateY(6px);
}
.scroll-bottom.is-visible { transform: translateY(0); }
.plan-current {
  inset-inline: 0; margin-inline: auto;
  inline-size: max-content;
  transform: none;
}

/* E2. Firas Code bubbles: alignment is direction-aware (`align-self:flex-end`)
   but the tail corner was physical, so in Arabic the notch pointed away from
   the speaker. The main thread deliberately avoided this ("uniform — no
   asymmetric corner (RTL-safe)"); the newer Code surface regressed it. Reset the
   physical corner, then set the logical one so it follows the bubble. */
.cw-thread__turn--me .cw-thread__bubble {
  border-bottom-right-radius: 14px;
  border-end-end-radius: 5px;
}
.cw-thread__turn--ai .cw-thread__bubble {
  border-bottom-left-radius: 14px;
  border-end-start-radius: 5px;
}

/* E3. `.cw-file__p` flushes file paths left but never sets `direction:ltr`, so a
   path containing any Arabic character reorders bidirectionally while staying
   left-aligned — a jumbled path. Every other code surface pairs the two
   (.cw-conrow, .cw-diff__lines, .cw-status__seg); this one lost its partner. */
.cw-file__p { direction: ltr; unicode-bidi: isolate; }

/* E4. The tools-menu toggle knob rests on a LOGICAL inset and travels on a
   PHYSICAL transform, negated again for RTL — three places to keep in sync, and
   the knob leaves the track in one direction the moment the 34px track or the
   16px knob changes. Animate the logical inset instead: 34 - 16 - 2 - 2 = 14px
   of travel from a 2px rest, so the on position is 16px, in both directions. */
.tools-menu__switch::after { transition: inset-inline-start var(--dur-fast) var(--ease-out); }
.tools-menu__item.is-on .tools-menu__switch::after,
[dir="rtl"] .tools-menu__item.is-on .tools-menu__switch::after {
  transform: none;
  inset-inline-start: 16px;
}

/* Two sidebar controls the touch-target sweep missed because they live in the
   mobile DRAWER (off-screen at audit time, but fully tappable once it opens):
   the product switcher (34px) and New chat (40px). Both are primary actions —
   switching product and starting a chat — so they get the same 44px floor. */
@media (pointer: coarse), (max-width: 560px) {
  .product-switch { min-block-size: 44px; }
  .btn-newchat    { min-block-size: 44px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   WELCOME — the cinematic home screen.
   The mark sits inside a soft aurora halo that breathes; the greeting reveals
   word by word (spans injected by splitWords()). JS drives the entrance via the
   Motion library; these rules are the STATIC fallback, so if the CDN is blocked
   everything is simply visible with no half-faded text left on screen.
   ═══════════════════════════════════════════════════════════════════════════ */
.welcome { position: relative; }

/* A CSS radial-gradient interpolates alpha LINEARLY between stops. That is fine in the
   middle and wrong at the end: the falloff stops dead at the final stop, and the break in
   its slope reads as a visible circular edge — on a cream ground it looked like a green disc
   floating on white, which is exactly what was reported.

   Fixed by feathering: seven stops approximating a gaussian tail instead of two stops
   approximating a cone. The alpha never lands on a hard endpoint, so there is no edge to see.

   The `filter: blur(38px)` that used to hide this is GONE. It was papering over the gradient
   while costing a full-size offscreen buffer on every frame of an infinite breathing
   animation — the most expensive way possible to soften an edge that should not exist.
   `border-radius` went too: it clipped the paint area to a circle, adding a second hard edge
   underneath the first. */
.welcome__halo {
  position: absolute;
  inset-block-start: -26%;
  inset-inline-start: 50%;
  translate: -50% 0;
  /* Wider than before: the same falloff spread over more pixels is a gentler gradient. */
  inline-size: min(720px, 116vw);
  aspect-ratio: 1;
  pointer-events: none;
  z-index: 0;
  /* `closest-side`, and CENTRED. This is the whole fix.
     A radial-gradient defaults to `farthest-corner`: the ramp is scaled to the far corner,
     so at the NEAR edges it is only ~45% through and still carrying colour. The element then
     just stops — and that abrupt end is a straight, perfectly visible line on all four sides.
     Measured on the previous version at a 375px viewport: 7.66% alpha still painting at the
     top edge, 1.52% at the bottom. That is the rectangle with a horizontal band through it.

     With `closest-side` the ramp completes at the nearest edge, and every pixel beyond the
     inscribed circle takes the final stop — transparent. There is no boundary left to see,
     because the gradient is already zero everywhere it meets the box.
     The last colour stop lands at 88% too, so it is fully faded even before that. */
  background:
    radial-gradient(closest-side circle at 50% 50%,
      color-mix(in srgb, var(--color-accent) 18%, transparent) 0%,
      color-mix(in srgb, var(--color-accent) 13%, transparent) 20%,
      color-mix(in srgb, var(--color-accent) 8%,  transparent) 38%,
      color-mix(in srgb, var(--color-accent) 4%,  transparent) 54%,
      color-mix(in srgb, var(--color-accent) 1.6%,transparent) 68%,
      color-mix(in srgb, var(--color-accent) 0.4%,transparent) 80%,
      transparent 88%),
    radial-gradient(closest-side circle at 62% 62%,
      color-mix(in srgb, var(--color-accent) 9%,  transparent) 0%,
      color-mix(in srgb, var(--color-accent) 5%,  transparent) 28%,
      color-mix(in srgb, var(--color-accent) 2%,  transparent) 50%,
      color-mix(in srgb, var(--color-accent) 0.5%,transparent) 70%,
      transparent 84%);
  opacity: 1;
  animation: welcome-breathe 9s var(--ease-out, ease-in-out) infinite;
}
/* Light needs LESS, not more. The old values had it backwards — .85 on cream and .7 on
   near-black — so the theme where a green tint is most conspicuous was also the strongest.
   These feed the keyframes above, which is the only place opacity is actually decided
   while the halo is breathing. */
[data-theme="light"] .welcome__halo { --halo-lo: .30; --halo-hi: .46; }
[data-theme="dark"]  .welcome__halo { --halo-lo: .62; --halo-hi: .88; }
/* The opacity here is a VARIABLE, not a literal. Keyframe values beat the element's own
   `opacity` declaration for as long as the animation runs, so hard-coding .62/.9 made the
   per-theme opacity below unreachable — the light theme silently kept the dark theme's
   strength, which is half of why the glow read as a disc on cream.
   Scale eased off too: 1.09 on an element that is now 720px wide is a lot of travel. */
@keyframes welcome-breathe {
  0%, 100% { transform: scale(1);    opacity: var(--halo-lo, .62); }
  50%      { transform: scale(1.05); opacity: var(--halo-hi, .9);  }
}

/* Keep the mark and greeting above the halo. */
.welcome__mark, .welcome__title { position: relative; z-index: 1; }

/* `will-change: transform, opacity` was here. It is a hint for an animation that is ABOUT to
   run, not a property — left on at rest it holds a compositor layer for the whole session in
   exchange for nothing, because the entrance has already finished. A running CSS animation
   is promoted automatically, so the hint bought nothing even while it was useful. */
.welcome__mark { position: relative; z-index: 1; }

/* Per-word reveal. inline-block is required for transform to apply to a span,
   and the padding stops descenders/diacritics being clipped by the blur filter
   mid-animation — Arabic (تشكيل) shows this immediately without it. */
.welcome__title .word {
  display: inline-block;
  padding-block-end: 0.06em;
  /* No will-change. This selector matches EVERY word of the greeting, so the hint promoted
     one compositor layer per word — permanently, long after the one-shot reveal finished —
     and `filter` is the most expensive thing that list can contain. */
}

@media (prefers-reduced-motion: reduce) {
  .welcome__halo { animation: none !important; }
  .welcome__title .word { opacity: 1 !important; filter: none !important; transform: none !important; }
}
[data-motion="off"] .welcome__halo { animation: none !important; }
[data-motion="off"] .welcome__title .word { opacity: 1 !important; filter: none !important; transform: none !important; }

/* ── Micro-interaction polish ────────────────────────────────────────────────
   Small, consistent responses that make the whole shell feel built rather than
   assembled. Press feedback itself is JS (initPressFeedback) so it can spring;
   these are the hover/focus halves that belong in CSS. */
.chat-item { transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out); }
.chat-item:hover { transform: translateX(2px); }
[dir="rtl"] .chat-item:hover { transform: translateX(-2px); }

/* The composer is the primary surface — give focus a real, calm emphasis. */
.composer { transition: border-color var(--dur-base, .22s) var(--ease-out), box-shadow var(--dur-base, .22s) var(--ease-out); }
.composer.is-focused {
  border-color: color-mix(in srgb, var(--color-accent) 55%, var(--color-border-strong));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 12%, transparent);
}

/* Send button: reads as "armed" the moment there is something to send.
   The lift is gated to real hover — on touch, :hover sticks after the tap. */
.composer__send:not(:disabled) { transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  /* :not(:active) — this rule is LATER in source than the :active press scale, so
     without it the hover lift masks the CSS-fallback press while the mouse is down. */
  .composer__send:not(:disabled):not(:active):hover { transform: translateY(-1px); }
}

@media (prefers-reduced-motion: reduce) {
  .chat-item, .composer, .composer__send { transition: none !important; }
  .chat-item:hover, .composer__send:not(:disabled):hover,
  .landing__signin:hover, .guest-cta:hover { transform: none !important; }
}
[data-motion="off"] .chat-item:hover,
[data-motion="off"] .composer__send:not(:disabled):hover,
[data-motion="off"] .landing__signin:hover,
[data-motion="off"] .guest-cta:hover { transform: none !important; }


/* ═══════════════════════════════════════════════════════════════════════════
   FIRAS CODE — PROFESSIONAL PASS (v4, appended after cw--v3).
   The owner's note: the workspace "feels childish". The cause is the cw--v3
   skin's consumer-app vocabulary rather than the layout:
     · a blue→green GRADIENT blob for the project logo, and a gradient Run pill
     · glow box-shadows tinted with the accent on hover
     · translateY(-1px) lift + scale(.94/.97) bounce on nearly every control
     · pill radii (999px) on toolbar buttons, tabs and chips
     · a 34px SERIF display heading inside a code editor
     · emoji (▶ ⬇ 🕑) standing in for icons
   An IDE wants the opposite: flat surfaces, tight radii, one accent used once,
   and motion you notice only when something actually happens.

   Scoped to .cw--v3 so it beats that layer on specificity+order. NO class names
   change — the JS binds to every one of them.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Retune the v3 design tokens: tighter geometry, calmer elevation. */
.cw--v3 {
  --cwv-radius-chip: 6px;
  --cwv-radius-panel: 8px;
  --cwv-radius-card: 10px;
  --cwv-pad-bar: 7px 12px;
  --cwv-gap: 8px;
  --cwv-shadow-panel: 0 1px 2px rgba(0,0,0,.05), 0 4px 14px rgba(0,0,0,.05);
}
[data-theme="dark"] .cw--v3 {
  --cwv-shadow-panel: 0 1px 2px rgba(0,0,0,.35), 0 4px 14px rgba(0,0,0,.3);
}

/* ---- Toolbar ---- */
.cw--v3 .cw-bar { backdrop-filter: none; -webkit-backdrop-filter: none; background: var(--color-sidebar-bg, var(--color-surface)); }
/* The gradient logo tile was the loudest toy element in the workspace. */
.cw--v3 .cw-bar__logo {
  inline-size: 26px; block-size: 26px;
  border-radius: var(--cwv-radius-chip);
  background: var(--color-accent-soft);
  border: var(--border-hairline) solid color-mix(in srgb, var(--color-accent) 26%, transparent);
  color: var(--color-accent);
  font: var(--weight-semibold) 10px var(--font-mono, monospace);
  box-shadow: none;
}
.cw--v3 .cw-bar__home { inline-size: 30px; block-size: 30px; border-radius: var(--cwv-radius-chip); }
.cw--v3 .cw-bar__home:hover { transform: none; }
.cw--v3 .cw-bar__home:active { transform: none; }
.cw--v3 .cw-bar__name { font-weight: var(--weight-semibold); font-size: 13px; }
.cw--v3 .cw-bar__count { border-radius: var(--cwv-radius-chip); padding: 2px 7px; font-size: 10.5px; }

.cw--v3 .cw-bar__btn {
  display: inline-flex; align-items: center; gap: 6px;
  min-block-size: 30px;
  padding: 0 var(--space-3);
  border-radius: var(--cwv-radius-chip);
  background: transparent;
  /* Explicit width+style, not just a colour: these buttons inherited a dead
     `border:1px solid var(--color-border)`, so border-style was `none` and
     setting only border-color could never bring the edge back.
     Resolved straight from the theme token — --cwv-hair adds a level of
     indirection that measurably did NOT re-resolve on a theme switch here. */
  border: 1px solid var(--color-border-hairline);
  color: var(--color-text-secondary);
  font: var(--weight-medium) 12.5px var(--font-sans);
  transition: background var(--cwv-motion), border-color var(--cwv-motion), color var(--cwv-motion);
}
[dir="rtl"] .cw--v3 .cw-bar__btn { font-family: var(--font-arabic); }
.cw--v3 .cw-bar__btn:hover {
  background: var(--color-bg-subtle);
  border-color: var(--color-border-strong);
  color: var(--color-text-primary);
  transform: none;            /* no lift */
  box-shadow: none;           /* no accent glow */
}
.cw--v3 .cw-bar__btn:active { transform: none; }
.cw--v3 .cw-bar__btn:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
.cw--v3 .cw-bar__btn-ico { display: inline-flex; flex: 0 0 auto; }
.cw--v3 .cw-bar__btn-ico svg { inline-size: 14px; block-size: 14px; }

/* ═══════════ FIRAS CODE — TOOLBAR DOCK ═══════════
   Ported model: 21st.dev "Toolbar Dock" (ruixen.ui). Placed straight after the v3 toolbar rules
   so it wins on the shared .cw-bar__btn without needing a heavier selector. */
.cw-dock { position: relative; z-index: 30; display: flex; align-items: center; gap: 8px; flex: 0 1 auto; min-inline-size: 0; }
/* The clip is what makes the fit measurable: the strip keeps its max-content width however hard
   the flex row squeezes it, so strip.offsetWidth vs clip.clientWidth is a real question and not
   two numbers shrinking in step. The block padding exists only so this overflow does not slice
   the top off a focus ring. */
.cw-dock__clip { display: flex; flex: 1 1 auto; min-inline-size: 0; overflow: hidden; padding-block: 3px; margin-block: -3px; }
.cw-dock__strip { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.cw-dock__grp { display: flex; align-items: center; gap: 6px; }
.cw-dock__sep { display: block; inline-size: 1px; block-size: 18px; flex: 0 0 auto; border-radius: 1px; background: var(--color-border-hairline); }
/* .cw--v3 .cw-bar__btn sets display:inline-flex, which outranks the UA sheet's [hidden] rule —
   without this every folded button stays in the row and the overflow menu folds nothing. */
.cw-dock [hidden] { display: none !important; }
.cw-dock .cw-bar__btn { position: relative; }

/* Icon-only tier. The label is taken off screen, never removed: it stays the button's accessible
   name, so nothing has to keep an aria-label in step with the Refresh→Run flip. */
.cw-dock.is-compact .cw-dock__lbl { position: absolute; inline-size: 1px; block-size: 1px; margin: -1px; padding: 0; overflow: hidden; white-space: nowrap; border: 0; clip-path: inset(50%); }
.cw-dock.is-compact .cw-bar__btn { gap: 0; padding-inline: 9px; }

.cw-dock__more { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; inline-size: 30px; block-size: 30px; padding: 0; border-radius: var(--cwv-radius-chip, 10px); border: 1px solid var(--color-border-hairline); background: transparent; color: var(--color-text-muted); transition: background var(--cwv-motion, .18s ease), color var(--cwv-motion, .18s ease), border-color var(--cwv-motion, .18s ease); }
.cw-dock__more svg { inline-size: 15px; block-size: 15px; }
.cw-dock__more:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.cw-dock__more[aria-expanded="true"] { background: var(--color-accent-soft); color: var(--color-accent); border-color: color-mix(in srgb, var(--color-accent) 40%, var(--color-border-hairline)); }
.cw-dock__more:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }

/* THE RAIL — one surface for every label, revealed by clip-path rather than eight popovers.
   Inverted with text-primary on surface: the only pair in the palette that is still a real
   tooltip in both themes without inventing a token for it. */
.cw-dock__rail {
  position: absolute; inset-block-start: calc(100% + 9px);
  display: flex; align-items: stretch; inline-size: max-content;
  border-radius: 10px;
  background: var(--color-text-primary); color: var(--color-surface);
  box-shadow: 0 10px 26px rgba(0,0,0,.24);
  opacity: 0; pointer-events: none;
  clip-path: inset(0px 100% 0px 0px round 10px); -webkit-clip-path: inset(0px 100% 0px 0px round 10px);
  transition: transform .19s var(--ease-out, cubic-bezier(.22,1,.36,1)),
              clip-path .19s var(--ease-out, cubic-bezier(.22,1,.36,1)),
              -webkit-clip-path .19s var(--ease-out, cubic-bezier(.22,1,.36,1)),
              opacity .16s linear;
  will-change: transform, clip-path, opacity;
}
.cw-dock__rail.is-on { opacity: 1; }
.cw-dock__seg { display: flex; align-items: center; gap: 10px; padding-inline: 12px; white-space: nowrap; }
.cw-dock__txt { display: flex; flex-direction: column; gap: 1px; padding-block: 7px; }
.cw-dock__name { font: 600 12.5px var(--font-sans); }
.cw-dock__hint { font: 400 11px var(--font-sans); opacity: .74; max-inline-size: 250px; overflow: hidden; text-overflow: ellipsis; }
[dir="rtl"] .cw-dock__name, [dir="rtl"] .cw-dock__hint, [dir="rtl"] .cw-dock__cap, [dir="rtl"] .cw-dock__rname { font-family: var(--font-arabic); }
.cw-dock__keys, .cw-dock__rkeys { display: inline-flex; flex: 0 0 auto; gap: 3px; }
.cw-dock__keys kbd, .cw-dock__rkeys kbd { display: inline-flex; align-items: center; justify-content: center; min-inline-size: 17px; block-size: 17px; padding: 0 4px; border-radius: 5px; font: 500 10.5px var(--font-mono, monospace); }
.cw-dock__keys kbd { border: 1px solid color-mix(in srgb, var(--color-surface) 40%, transparent); color: var(--color-surface); }
.cw-dock__rkeys kbd { border: 1px solid var(--color-border-hairline); color: var(--color-text-muted); }

/* Overflow menu. Logical inset here and physical on the rail: this one is never transformed,
   so it can simply hug whichever edge of the dock is the end edge in the current direction. */
.cw-dock__menu { position: absolute; inset-block-start: calc(100% + 8px); inset-inline-end: 0; z-index: 40; min-inline-size: 210px; padding: 6px; border-radius: 14px; border: 1px solid var(--color-border-hairline); background: var(--color-surface); box-shadow: var(--cwv-shadow-float, 0 18px 44px rgba(0,0,0,.22)); }
.cw-dock__cap { padding: 5px 9px 7px; font: 500 10.5px var(--font-sans); color: var(--color-text-muted); }
.cw-dock__row { display: flex; align-items: center; gap: 9px; inline-size: 100%; padding: 8px 9px; border: 0; border-radius: 9px; background: transparent; color: var(--color-text-primary); font: 500 12.5px var(--font-sans); text-align: start; cursor: pointer; transition: background var(--cwv-motion, .18s ease); }
.cw-dock__row:hover { background: var(--color-bg-subtle); }
.cw-dock__row:focus-visible { outline: none; background: var(--color-bg-subtle); box-shadow: inset 0 0 0 2px var(--color-accent-ring); }
.cw-dock__rico { display: inline-flex; flex: 0 0 auto; color: var(--color-text-muted); }
.cw-dock__rico svg { inline-size: 14px; block-size: 14px; }
.cw-dock__rname { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* DEGRADE TO THE ROW THAT WAS THERE BEFORE. UI 2.0 draws its own chrome, and on a phone .cw-bar
   is a horizontal scroller where a hover rail and an overflow menu are both dead weight.
   display:contents dissolves the wrappers, so the buttons are direct flex children of .cw-bar
   again and every phone rule already written against them keeps working untouched. */
:root[data-ui="2"] .cw-dock,
:root[data-ui="2"] .cw-dock__clip,
:root[data-ui="2"] .cw-dock__strip,
:root[data-ui="2"] .cw-dock__grp { display: contents; }
:root[data-ui="2"] .cw-dock__rail,
:root[data-ui="2"] .cw-dock__menu,
:root[data-ui="2"] .cw-dock__more,
:root[data-ui="2"] .cw-dock__sep { display: none !important; }
@media (max-width: 640px) {
  .cw-dock, .cw-dock__clip, .cw-dock__strip, .cw-dock__grp { display: contents; }
  .cw-dock__rail, .cw-dock__menu, .cw-dock__more, .cw-dock__sep { display: none !important; }
  /* A dock that was compact when the window crossed 640px would otherwise arrive on the phone
     as eight unlabelled icons in a scroller. */
  .cw-dock.is-compact .cw-dock__lbl { position: static; inline-size: auto; block-size: auto; margin: 0; overflow: visible; clip-path: none; }
  .cw-dock.is-compact .cw-bar__btn { gap: 6px; padding-inline: 14px; }
}
@media (prefers-reduced-motion: reduce) {
  .cw-dock__rail, .cw-dock__more, .cw-dock__row { transition: none; }
}
:root[data-motion="off"] .cw-dock__rail,
:root[data-motion="off"] .cw-dock__more,
:root[data-motion="off"] .cw-dock__row { transition: none; }

/* Run loses its gradient — it is a utility, not the hero action. */
.cw--v3 .cw-bar__btn.cw-run {
  background: transparent;
  border-color: var(--color-border-hairline);
  color: var(--color-text-secondary);
  box-shadow: none;
}
.cw--v3 .cw-bar__btn.cw-run:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); box-shadow: none; }
/* RUN-A-.PY MODE — the button already reads "تشغيل / Run" instead of "تحديث / Refresh"; this
   makes the change of meaning visible and not merely readable. Deliberately a tint and not a
   fill: the rule above demoted Run to a utility precisely so "Improve" stays the one
   emphasised control in the bar, and executing a script must not take that slot back. Placed
   AFTER that rule at the same (0,3,0) weight — anywhere earlier and it silently loses. */
.cw--v3 .cw-bar__btn.cw-run--py {
  color: var(--color-accent);
  border-color: color-mix(in srgb, var(--color-accent) 42%, var(--color-border-hairline));
}
.cw--v3 .cw-bar__btn.cw-run--py:hover { color: var(--color-accent); background: var(--color-accent-soft); }
.cw--v3 .cw-bar__btn.cw-run[disabled] { opacity: .62; pointer-events: none; }

/* EXACTLY ONE emphasised control in the bar: "Improve" — the action that
   actually re-runs, inspects and develops the project. */
.cw--v3 .cw-bar__btn--primary,
.cw-bar__btn--primary {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-on-accent);
  font-weight: var(--weight-semibold);
  box-shadow: none;
}
.cw--v3 .cw-bar__btn--primary:hover,
.cw-bar__btn--primary:hover {
  background: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
  color: var(--color-on-accent);
  box-shadow: none;
}
.cw--v3 .cw-bar__btn--primary:disabled,
.cw--v3 .cw-bar__btn--primary.is-busy,
.cw-bar__btn--primary.is-busy { opacity: .6; cursor: progress; }
.cw--v3 .cw-bar__btn--primary.is-busy .cw-bar__btn-ico,
.cw-bar__btn--primary.is-busy .cw-bar__btn-ico { animation: cw-spin 1s linear infinite; }
@keyframes cw-spin { to { transform: rotate(360deg); } }

/* ---- Tabs + tree: square-ish, quiet ---- */
.cw--v3 .cw-tab { border-radius: var(--cwv-radius-chip); padding: 6px 12px; font-size: 12px; }
.cw--v3 .cw-tab.is-active { box-shadow: none; background: var(--color-bg-subtle); color: var(--color-text-primary); font-weight: var(--weight-semibold); }
.cw--v3 .cw-tree__newfolder:hover { transform: none; }
.cw--v3 .cw-file { padding-block: 7px; }

/* ---- Home screen: an editor start screen, not a marketing hero ---- */
.cw-home__mark {
  inline-size: 50px; block-size: 50px;
  border-radius: var(--radius-lg);
  border: var(--border-hairline) solid color-mix(in srgb, var(--color-accent) 28%, var(--color-border-hairline));
  background: var(--color-accent-soft);
  color: var(--color-accent);
  font: var(--weight-semibold) 19px/1 var(--font-mono, monospace);
  box-shadow: none;
}
.cw-home__hero h1 { font: var(--weight-semibold) 24px/1.2 var(--font-sans); letter-spacing: -0.01em; }
.cw-home__hero p { font-size: var(--text-sm); max-inline-size: 420px; }
.cw-home__card { border-radius: var(--radius-lg); }
.cw-home__tpl { border-width: var(--border-hairline); border-radius: var(--radius-md); min-block-size: 40px; }
.cw-home__create { border-radius: var(--radius-md); min-block-size: 44px; }
.cw-home__tpl:focus-visible,
.cw-home__name:focus-visible,
.cw-home__create:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }

/* ---- Editor surface follows the theme instead of a hardcoded white ---- */
.cw-ed { background: var(--color-surface); }

@media (prefers-reduced-motion: reduce) {
  .cw--v3 .cw-bar__btn { transition: none !important; }
  .cw--v3 .cw-bar__btn--primary.is-busy .cw-bar__btn-ico,
  .cw-bar__btn--primary.is-busy .cw-bar__btn-ico { animation: none !important; }
}
[data-motion="off"] .cw--v3 .cw-bar__btn--primary.is-busy .cw-bar__btn-ico { animation: none !important; }

/* ---- Responsive: the 3-column grid is unreadable before the mobile nav ---- */
/* The desktop ladder is bounded at BOTH ends so no two rules ever fight.
   Below 861px the IDE is single-pane, driven by the mobile bottom nav — an unbounded
   `max-width: 1100px` here leaks into that range and tiles three panes behind the nav,
   which is the same class of bug as the 900px rule below, just less visible. */
@media (min-width: 901px) and (max-width: 1100px) {
  .cw-main { grid-template-columns: 168px minmax(0, 1.15fr) minmax(0, 1fr); }
}

/* P0 — Firas Code was unusable on every tablet and half-screen window.
   This rule used to read `@media (max-width: 900px) { grid-template-columns: 150px
   minmax(0,1fr) }` and it broke the IDE in two separate ways:

   1. It also matched everything BELOW it. At ≤860px the app switches to one pane at a
      time (line 4087, `grid-template-columns:1fr`) driven by the mobile nav — but both
      selectors are plain `.cw-main`, media queries add no specificity, and this rule is
      ~1400 lines later, so source order handed it the win. From 641px to 860px the
      editor rendered as a 150px sliver — roughly fifteen characters per line — beside
      500-700px of empty background. Below 641px an `!important` flex reset rescued it,
      and above 900px it was fine, so the failure sat exactly on iPad portrait (768px).
   2. Even in its own band it declared TWO columns for THREE children (tree, editor,
      preview), so the preview wrapped into an implicit 150px-tall row.

   Fixed by bounding the band at both ends and keeping all three columns — which is what
   the 1100px rule directly above already does. Only the tree narrows. */
@media (min-width: 860.02px) and (max-width: 900px) {
  .cw-main { grid-template-columns: 138px minmax(0, 1.05fr) minmax(0, 1fr); }
}
/* The button padding was never part of the bug and still wants the full ≤900px range. */
@media (max-width: 900px) { .cw--v3 .cw-bar__btn { padding: 0 var(--space-2); } }

/* ---- Firas Code home: remove the splash-screen vocabulary ----------------
   A 72px teal→#5b8def gradient tile that BOUNCES forever (cwFloat, 5s infinite)
   under a 40px accent glow and a white inset bevel, beneath a 34px serif
   headline whose text is a clipped gradient. That is a kids'-app splash screen,
   not a developer tool — and the gradient text has no fallback, so if
   background-clip fails the product name renders invisible. */
.cw-home { background: none; }                     /* dotted-paper backdrop + glow */
.cw-home__mark {
  inline-size: 50px; block-size: 50px;
  border-radius: var(--radius-lg);
  background: var(--color-accent-soft);
  border: var(--border-hairline) solid color-mix(in srgb, var(--color-accent) 28%, var(--color-border-hairline));
  box-shadow: none;
  animation: none;                                  /* no idle bobbing */
}
.cw-home__mark-glyph { font: var(--weight-semibold) 19px/1 var(--font-mono, monospace); color: var(--color-accent); letter-spacing: 0; }
.cw-home__hero h1 {
  background: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
  -webkit-text-fill-color: currentColor;            /* restore a real, always-visible fill */
  color: var(--color-text-primary);
  font: var(--weight-semibold) 24px/1.2 var(--font-sans);
  letter-spacing: -0.01em;
}
.cw-home__card { border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); backdrop-filter: none; }
/* The same gradient blob followed you into the IDE toolbar. */
.cw-bar__logo {
  inline-size: 26px; block-size: 26px;
  border-radius: var(--cwv-radius-chip, var(--radius-sm));
  background: var(--color-accent-soft);
  border: var(--border-hairline) solid color-mix(in srgb, var(--color-accent) 26%, transparent);
  color: var(--color-accent);
  font: var(--weight-semibold) 10px var(--font-mono, monospace);
  box-shadow: none;
}
@media (prefers-reduced-motion: reduce) { .cw-home__mark { animation: none !important; } }

/* ════════════════════════════════════════════════════════════════════════════
   FIRAS BRAIN (.fb-*) — grounded answers over the user's OWN documents
   4th product alongside Firas AI / Agent / Code. Appended LAST on purpose:
   this file has ~six override layers that win on SOURCE ORDER at equal
   specificity, so anything inserted mid-file would silently lose. Additive
   only — no existing line is touched, no !important anywhere.

   Laws obeyed (each verified against the precedent named):
     · tokens only — no literal colours outside the --fb-* block + rgba() scrims
     · flex-fence law (.cw/.cw-bar/.cw-main, 3924-3946): one full-height flex
       column, fixed 0-0-auto fences top+bottom, exactly ONE 1-1-auto;min-height:0
     · body{overflow:hidden} (170) — every scroller declares its own overflow
     · `font:` shorthand always paired with a [dir="rtl"] font-family rule (223)
     · dvh restated after vh on every modal cap (4962-4967)
     · safe-area insets applied as PHYSICAL padding-left/right (4898-4938)
     · z-index picked inside the hand-maintained 1200-1400 band (840/886/904)
   The view root carries its own dir attribute (app.js, as renderCodeHome does),
   so [dir="rtl"] .fb-* matches the component root — NOT <html>, which is
   permanently ltr.
   ════════════════════════════════════════════════════════════════════════════ */

/* ── Component-local tokens (marker block, .cw--v3 precedent at 4042-4056) ──
   Declared on the workspace root so every .fb-* descendant inherits them; the
   reader overlay also carries var() fallbacks in case it is ever portaled out
   of this subtree. Values are derived from the global tokens so they follow
   the theme automatically; only the shadow/scrim opacities need a dark tweak. */
/* .fb-reader is in this selector list because app.js appends it to document.body (it is a
   modal, so it must escape the workspace's stacking/overflow context). Declaring the tokens
   ONLY on #brainWorkspace left every --fb-* var unresolved there — and an unresolvable var()
   invalidates the whole `border:` shorthand at computed-value time, so the panel lost its
   outline and its header divider entirely. */
#brainWorkspace, .fb-reader {
  --fb-hair:          var(--color-border-hairline);
  --fb-rail-w:        272px;
  --fb-gap:           10px;
  --fb-radius-row:    10px;
  --fb-ic:            36px;   /* rail tile — denser sibling of .file-card__icon */
  --fb-ic-lg:         40px;   /* answer tile — exactly .file-card__icon (2527) */
  --fb-motion:        160ms var(--ease-out);
  --fb-shadow-float:  0 20px 60px color-mix(in srgb, var(--color-text-primary) 22%, transparent);
  --fb-scrim:         rgba(0,0,0,0.46);      /* same weight as .preview-overlay */
  --fb-scrim-soft:    rgba(0,0,0,0.28);      /* drag-over veil */
  --fb-accent-line:   color-mix(in srgb, var(--color-accent) 30%, transparent);
  --fb-danger-soft:   color-mix(in srgb, var(--color-error) 12%, transparent);
}
[data-theme="dark"] #brainWorkspace, [data-theme="dark"] .fb-reader {
  --fb-shadow-float:  0 20px 60px rgba(0,0,0,0.55);
  --fb-scrim:         rgba(0,0,0,0.62);
  --fb-scrim-soft:    rgba(0,0,0,0.42);
}

/* ── View gate ────────────────────────────────────────────────────────────
   BOTH halves are required: the root is created at runtime by app.js, so it
   must be display:none until .product-brain is on the body.
   #tierSwitch AND #tierSelect are hidden. .product-code only hides the first
   (3272), so on phones — where 4767 swaps the segmented strip for the compact
   dropdown — the tier control reappears over the workspace. Not repeating that
   bug: `.product-brain #tierSelect` is (1,1,0) and beats the (0,1,0) media-query
   rule that reveals it, without needing !important. */
#brainWorkspace { display: none; }
.product-brain #brainWorkspace {
  display: flex; flex: 1; min-height: 0;
  flex-direction: column;
  background: var(--color-bg);
  position: relative;
}
.product-brain .chat-scroll,
.product-brain .composer-wrap { display: none; }
.product-brain #tierSwitch,
.product-brain #tierSelect { display: none; }
/* Kept visible for the same reason as Agent and Code — see the note in the VOICE CALL section. Brain hides the whole composer anyway, so this rule only mattered on the way in. */

/* ── Root: the one full-height flex column ────────────────────────────────
   overflow-x is clipped here so nothing in the tree can ever make the page
   scroll sideways (body{overflow:hidden} would clip it instead of scrolling). */
.fb {
  position: relative;
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
  inline-size: 100%; max-inline-size: 100%;
  overflow-x: hidden;
  background: var(--color-bg);
  color: var(--color-text-primary);
  box-sizing: border-box;
}
.fb *, .fb *::before, .fb *::after { box-sizing: border-box; }

/* ── Top fence (flex:0 0 auto) ────────────────────────────────────────────── */
.fb-bar {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--fb-gap);
  padding-block: 10px;
  padding-left:  max(var(--space-4), var(--safe-left));
  padding-right: max(var(--space-4), var(--safe-right));
  border-block-end: var(--border-hairline) solid var(--fb-hair);
  background: var(--color-surface);
}
.fb-bar__title {
  min-inline-size: 0;
  font: var(--weight-semibold) 14px var(--font-sans);
  letter-spacing: var(--tracking-tight);
  color: var(--color-text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
[dir="rtl"] .fb-bar__title { font-family: var(--font-arabic); }
/* Counts are LTR runs inside Arabic chrome — longhands (not the `font:`
   shorthand) so the mono family is a deliberate declaration, and isolated so
   "3/48" cannot reorder. Same recipe as .cw--v3 .cw-bar__count (4065). */
.fb-bar__count {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 11px; font-weight: var(--weight-semibold); line-height: 1.6;
  color: var(--color-text-muted);
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--fb-hair);
  border-radius: var(--radius-pill);
  padding: 2px 9px;
  direction: ltr; unicode-bidi: isolate; font-variant-numeric: tabular-nums;
}
.fb-bar__btn {
  flex: 0 0 auto;
  padding: 7px 13px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-primary);
  font: var(--weight-medium) 12.5px var(--font-sans);
  white-space: nowrap;
  transition: border-color var(--fb-motion), color var(--fb-motion),
              background var(--fb-motion);
}
[dir="rtl"] .fb-bar__btn { font-family: var(--font-arabic); }
.fb-bar__btn:hover { border-color: var(--color-accent); color: var(--color-accent); }
.fb-bar__btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fb-bar__btn[hidden] { display: none; }
/* pointer-events, not just the dimming: :hover still matches a disabled button, so the
   accent-hover rule below would keep lighting it up under the cursor and it would read as
   live while every click was being dropped. Killing hit-testing is what settles it. */
.fb-bar__btn:disabled { opacity: 0.45; pointer-events: none; }
/* The one filled control in a fence that is otherwise all hairlines. Once a document is
   indexed this is the panel's primary verb, and an outline button drawn like every other
   chip is a button nobody finds. The pair matters: .fb-bar__sum is (0,1,0) and loses to
   .fb-bar__btn:hover at (0,2,0), so without the second rule the accent fill would flip back
   to a transparent chip the moment the pointer touched it. The :hover twin ties (0,2,0) and
   comes later in source, which is what wins it. */
.fb-bar__sum {
  border-color: transparent;
  background: var(--color-accent);
  color: var(--color-on-accent);
}
.fb-bar__sum:hover {
  border-color: transparent;
  background: var(--color-accent-hover);
  color: var(--color-on-accent);
}
.fb-bar__spacer { flex: 1 1 auto; min-inline-size: 0; }

/* ── The single growing region (flex:1 1 auto; min-height:0) ──────────────── */
.fb-main {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: row; align-items: stretch;
  min-inline-size: 0;
  overflow: hidden;
}

/* ── Sources rail — bordered scrolling column, .cw-tree/.cw-file pattern
   (3283-3292, overrides 4071-4082): .is-active rows carry a leading accent
   border. Its own nested fence set: head / list(grow) / foot. ─────────────── */
.fb-rail {
  flex: 0 0 var(--fb-rail-w);
  inline-size: var(--fb-rail-w); max-inline-size: 100%;
  display: flex; flex-direction: column; min-height: 0; min-inline-size: 0;
  border-inline-end: var(--border-hairline) solid var(--fb-hair);
  background: var(--color-surface);
}
.fb-rail__head {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--space-2);
  padding: 12px 14px 9px;
  font: var(--weight-semibold) 10.5px var(--font-sans);
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--color-text-muted);
}
[dir="rtl"] .fb-rail__head { font-family: var(--font-arabic); letter-spacing: 0.02em; }
.fb-rail__count {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.6;
  color: var(--color-text-muted);
  background: var(--color-bg-subtle);
  border-radius: var(--radius-pill);
  padding: 2px 8px;
  direction: ltr; unicode-bidi: isolate; font-variant-numeric: tabular-nums;
}
/* The library has a real ceiling (20 documents, 3 for a guest). One slot left is a warning;
   full is a refusal waiting to happen — both are worth seeing before the upload is rejected.
   --color-error is defined in BOTH the light and dark blocks, so this reads correctly in
   either theme without a second rule. */
.fb-rail__count.is-near { color: var(--color-text-primary); }
.fb-rail__count.is-full {
  color: var(--color-error);
  background: color-mix(in srgb, var(--color-error) 12%, transparent);
}
.fb-rail__list {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  padding-block: 2px 6px;
}
.fb-rail__list[hidden] { display: none; }
.fb-rail__foot {
  flex: 0 0 auto;
  padding: 10px 12px;
  padding-block-end: max(10px, var(--safe-bottom));
  border-block-start: var(--border-hairline) solid var(--fb-hair);
  background: var(--color-surface);
}

/* ── One source row ───────────────────────────────────────────────────────── */
.fb-src {
  position: relative;
  display: flex; align-items: center; gap: var(--space-2);
  margin: 2px 6px;
  padding: 8px 10px;
  border-radius: var(--fb-radius-row);
  border-inline-start: var(--border-strong) solid transparent;
  color: var(--color-text-secondary);
  cursor: pointer;
  transition: background var(--fb-motion), color var(--fb-motion),
              opacity var(--fb-motion), border-color var(--fb-motion);
}
.fb-src:hover { background: var(--color-bg-subtle); }
.fb-src:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fb-src.is-active {
  background: var(--color-accent-soft);
  color: var(--color-accent);
  border-inline-start-color: var(--color-accent);
}
/* Deselected — excluded from retrieval: dimmed, and the accent is withdrawn. */
.fb-src.is-off {
  opacity: 0.45;
  background: transparent;
  color: var(--color-text-muted);
  border-inline-start-color: transparent;
}
.fb-src.is-off .fb-src__ic { background: var(--color-bg-subtle); color: var(--color-text-muted); }
.fb-src.is-busy { cursor: progress; }
/* 40px --color-accent-soft type tile from .file-card__icon (2527), one step
   denser because it repeats down a 272px rail. */
.fb-src__ic {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: var(--fb-ic); block-size: var(--fb-ic); flex: 0 0 auto;
  border-radius: var(--radius-md);
  background: var(--color-accent-soft);
  color: var(--color-accent);
}
.fb-src__ic svg { inline-size: 19px; block-size: 19px; }
.fb-src__main {
  flex: 1 1 auto; min-inline-size: 0;
  display: flex; flex-direction: column; gap: 2px;
}
.fb-src__name {
  font: var(--weight-medium) 12.5px var(--font-sans);
  color: inherit;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  text-align: start;
}
[dir="rtl"] .fb-src__name { font-family: var(--font-arabic); }
/* "24 صفحة · 1.2MB" — page counts must not reorder inside Arabic. */
.fb-src__meta {
  font-family: var(--font-mono);
  font-size: 10.5px; line-height: 1.5;
  color: var(--color-text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  direction: ltr; unicode-bidi: isolate; font-variant-numeric: tabular-nums;
}
.fb-src__x {
  flex: 0 0 auto;
  display: grid; place-items: center;
  inline-size: 22px; block-size: 22px;
  border-radius: 7px;
  color: var(--color-text-muted);
  font-size: 11px;
  opacity: 0;
  transition: opacity var(--fb-motion), background var(--fb-motion), color var(--fb-motion);
}
.fb-src:hover .fb-src__x, .fb-src__x:focus-visible { opacity: 1; }
.fb-src__x:hover { background: var(--fb-danger-soft); color: var(--color-error); }
@media (pointer: coarse) { .fb-src__x { opacity: 0.85; inline-size: 30px; block-size: 30px; } }
/* Pin — the delete ✕'s twin: same 22px cell, revealed on hover, always reachable on touch.
   The one difference is that an ON pin never hides, because a pin nobody can see is a filter
   with no explanation. */
.fb-src__pin {
  flex: 0 0 auto;
  display: grid; place-items: center;
  inline-size: 22px; block-size: 22px;
  border-radius: 7px;
  color: var(--color-text-muted);
  opacity: 0;
  transition: opacity var(--fb-motion), background var(--fb-motion), color var(--fb-motion);
}
.fb-src__pin svg { inline-size: 14px; block-size: 14px; }
.fb-src:hover .fb-src__pin, .fb-src__pin:focus-visible { opacity: 1; }
.fb-src__pin:hover { background: var(--color-accent-soft); color: var(--color-accent); }
.fb-src.is-pinned .fb-src__pin { opacity: 1; color: var(--color-accent); }
/* A pinned row no longer toggles, so take back the promise the row's cursor makes. The pin
   inside it keeps a pointer, because that one still does something. */
.fb-src.is-pinned { cursor: default; }
.fb-src.is-pinned .fb-src__pin { cursor: pointer; }
@media (pointer: coarse) { .fb-src__pin { opacity: 0.85; inline-size: 30px; block-size: 30px; } }
/* Term list — the row's third control, and the only one that is ever absent: a document
   nobody has asked about has no list, and a button that opens an empty panel teaches the
   reader that the button does nothing. Built as the pin's twin plus the count, because the
   count is the part worth reaching for. */
.fb-src__gloss {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 3px;
  block-size: 22px; padding-inline: 5px;
  border-radius: 7px;
  color: var(--color-text-muted);
  opacity: 0;
  transition: opacity var(--fb-motion), background var(--fb-motion), color var(--fb-motion);
}
/* display above out-ranks the UA rule for [hidden], so restate it (same call as
   .fb-scope__pop). An unasked document hides this outright. */
.fb-src__gloss[hidden] { display: none; }
.fb-src__gloss svg { inline-size: 13px; block-size: 13px; }
.fb-src__gloss-n {
  font-family: var(--font-mono);
  font-size: 9.5px; font-weight: var(--weight-semibold); line-height: 1;
}
.fb-src:hover .fb-src__gloss, .fb-src__gloss:focus-visible { opacity: 1; }
.fb-src__gloss:hover { background: var(--color-accent-soft); color: var(--color-accent); }
/* Reachable without hover, like the pin and the ✕ beside it — but 26px rather than their 30:
   this one neither deletes anything nor holds a filter, and it is the THIRD control on a row
   that has to keep a filename readable. */
@media (pointer: coarse) { .fb-src__gloss { opacity: 0.85; block-size: 26px; padding-inline: 6px; } }
/* ≤900px the rail is a sideways strip of 216px cards (8912). Four fixed controls do not fit
   one, and the filename is what loses — so the strip keeps the GLYPH, which says this
   document has a list and how to open it, and gives up the count, which the panel it opens
   states in its own header. Same call .fb-pins__lbl makes on a phone. */
@media (max-width: 900px) {
  .fb-src__gloss-n { display: none; }
  .fb-src__gloss { padding-inline: 4px; }
}
/* Study plan — the pin's twin again, glyph only. It has no count to show: a plan is not a
   quantity, it is one thing a document either has a shape for or does not. brainPlanOffered
   already refuses the button below 900px, where the rail is a 216px card that cannot hold a
   fourth control; the display:none here is the belt to that braces, so a stale `hidden=false`
   from a resize can never widen the card. */
.fb-src__plan {
  flex: 0 0 auto;
  display: grid; place-items: center;
  inline-size: 22px; block-size: 22px;
  border-radius: 7px;
  color: var(--color-text-muted);
  opacity: 0;
  transition: opacity var(--fb-motion), background var(--fb-motion), color var(--fb-motion);
}
.fb-src__plan[hidden] { display: none; }
.fb-src__plan svg { inline-size: 14px; block-size: 14px; }
.fb-src:hover .fb-src__plan, .fb-src__plan:focus-visible { opacity: 1; }
.fb-src__plan:hover { background: var(--color-accent-soft); color: var(--color-accent); }
@media (max-width: 900px) {
  .fb-src__plan { display: none; }
}
/* Exam mode — the study plan's twin, glyph only and with nothing to count: a document is
   either examinable or it is not. brainExamOffered already refuses the button below 900px,
   where the rail is a 216px card that cannot hold a fifth control; the display:none here is
   the belt to that braces, exactly as it is for .fb-src__plan above. */
.fb-src__exam {
  flex: 0 0 auto;
  display: grid; place-items: center;
  inline-size: 22px; block-size: 22px;
  border-radius: 7px;
  color: var(--color-text-muted);
  opacity: 0;
  transition: opacity var(--fb-motion), background var(--fb-motion), color var(--fb-motion);
}
.fb-src__exam[hidden] { display: none; }
.fb-src__exam svg { inline-size: 14px; block-size: 14px; }
.fb-src:hover .fb-src__exam, .fb-src__exam:focus-visible { opacity: 1; }
.fb-src__exam:hover { background: var(--color-accent-soft); color: var(--color-accent); }
@media (max-width: 900px) {
  .fb-src__exam { display: none; }
}
/* Reading coverage — the study plan’s chip, glyph and nothing else, NOT the term list’s chip
   with its count. The row is out of room: 272px of rail leaves 238px inside a row, of which the
   type tile, the pin and the ✕ take 80 before any of this, and the term list, the plan, exam
   mode and the gaps between them take nearly all of what is left. A figure on this control
   ("100٪") measures 44px more and the FILENAME is what pays for it — on the one surface whose
   whole job is telling one book from another. The percentage belongs to the panel, which has a
   header to put it in.

   Absent until an answer has cited the document at all; brainReadOffered is what decides, and
   the display:none below 900px is the belt to that braces, so a stale `hidden=false` left by a
   resize can never widen a 216px card. */
.fb-src__cov {
  flex: 0 0 auto;
  display: grid; place-items: center;
  inline-size: 22px; block-size: 22px;
  border-radius: 7px;
  color: var(--color-text-muted);
  opacity: 0;
  transition: opacity var(--fb-motion), background var(--fb-motion), color var(--fb-motion);
}
/* display above out-ranks the UA rule for [hidden], so restate it — the same call
   .fb-src__plan and .fb-src__exam both make. */
.fb-src__cov[hidden] { display: none; }
.fb-src__cov svg { inline-size: 14px; block-size: 14px; }
.fb-src:hover .fb-src__cov, .fb-src__cov:focus-visible { opacity: 1; }
.fb-src__cov:hover { background: var(--color-accent-soft); color: var(--color-accent); }
/* Reachable without hover on a touch screen — opacity only, never a bigger box: on a coarse
   tablet the rail is still 272px wide and the filename is still what any extra width costs. */
@media (pointer: coarse) { .fb-src__cov { opacity: 0.85; } }
@media (max-width: 900px) {
  .fb-src__cov { display: none; }
}
/* Ingest progress: absolutely positioned so appearing/disappearing can never
   change the row height (the rail sits next to a content-visibility:auto
   thread — no reflow allowed after first paint). */
.fb-src__bar {
  position: absolute; inset-inline: 10px; inset-block-end: 3px;
  block-size: 3px;
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
  overflow: hidden;
  display: none;
}
.fb-src.is-busy .fb-src__bar { display: block; }
.fb-src__bar-fill {
  display: block;
  block-size: 100%; inline-size: 0;
  border-radius: inherit;
  background: var(--color-accent);
  transition: inline-size var(--fb-motion);
}

/* ── Upload control (a <label> + a visually hidden <input type=file>) ─────── */
.fb-up { display: flex; flex-direction: column; gap: 6px; }
.fb-up input[type="file"] {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  padding: 0; margin: -1px; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.fb-up__btn {
  display: flex; align-items: center; justify-content: center; gap: var(--space-2);
  inline-size: 100%;
  padding: 10px 14px;
  border: var(--border-hairline) dashed var(--fb-accent-line);
  border-radius: var(--radius-md);
  background: var(--color-accent-soft);
  color: var(--color-accent);
  font: var(--weight-semibold) 12.5px var(--font-sans);
  cursor: pointer;
  transition: border-color var(--fb-motion), background var(--fb-motion);
}
[dir="rtl"] .fb-up__btn { font-family: var(--font-arabic); }
.fb-up__btn:hover { border-color: var(--color-accent); }
.fb-up__btn:focus-within, .fb-up__btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fb-up__btn svg { inline-size: 16px; block-size: 16px; }
/* Reserved height so status text arriving mid-upload cannot nudge the rail. */
.fb-up__status {
  min-block-size: 16px;
  font: var(--weight-regular) 11px/16px var(--font-sans);
  color: var(--color-text-muted);
  text-align: center;
  overflow-wrap: anywhere;
}
[dir="rtl"] .fb-up__status { font-family: var(--font-arabic); }

/* ── Answer thread — the scroller on the other side of .fb-main ──────────── */
.fb-thread {
  flex: 1 1 auto;
  min-inline-size: 0; min-block-size: 0; min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  display: flex; flex-direction: column; gap: var(--space-6);
  padding: var(--space-6) var(--space-5);
  padding-left:  max(var(--space-5), var(--safe-left));
  padding-right: max(var(--space-5), var(--safe-right));
}

/* ── Empty state ──────────────────────────────────────────────────────────── */
.fb-empty {
  margin: auto;
  display: flex; flex-direction: column; align-items: center; gap: var(--space-3);
  max-inline-size: 380px;
  text-align: center;
}
.fb-empty__ic {
  display: grid; place-items: center;
  inline-size: 56px; block-size: 56px;
  border-radius: var(--radius-lg);
  background: var(--color-accent-soft);
  border: var(--border-hairline) solid var(--fb-accent-line);
  color: var(--color-accent);
}
.fb-empty__ic svg { inline-size: 26px; block-size: 26px; }
.fb-empty__title {
  font: var(--weight-semibold) 17px/1.4 var(--font-sans);
  letter-spacing: var(--tracking-tight);
  color: var(--color-text-primary);
}
[dir="rtl"] .fb-empty__title { font-family: var(--font-arabic); }
.fb-empty__hint {
  font: var(--weight-regular) 13px/1.65 var(--font-sans);
  color: var(--color-text-muted);
}
[dir="rtl"] .fb-empty__hint { font-family: var(--font-arabic); }

/* ── Drag-over veil — absolute inside .fb, so it never escapes the workspace.
   Kept out of flow and out of the flex fence maths entirely. ─────────────── */
/* Visibility is driven by the `hidden` ATTRIBUTE (app.js toggles drop.hidden on dragenter /
   dragleave / drop). A base `display:none` + an `.is-open` reveal would never fire, since
   nothing adds that class — clearing `hidden` alone would leave the base rule matching. */
.fb-drop {
  position: absolute; inset: 0; z-index: 1200;
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-6);
  background: var(--fb-scrim-soft);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.fb-drop[hidden] { display: none; }
.fb-drop__in {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-2);
  padding: var(--space-8) var(--space-10);
  border: var(--border-strong) dashed var(--color-accent);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  box-shadow: var(--fb-shadow-float);
  color: var(--color-accent);
  font: var(--weight-semibold) 14px/1.5 var(--font-sans);
  text-align: center;
  max-inline-size: 100%;
}
[dir="rtl"] .fb-drop__in { font-family: var(--font-arabic); }

/* ── Bottom fence composer (flex:0 0 auto) ───────────────────────────────── */
.fb-ask {
  flex: 0 0 auto;
  /* #micRec is `position: absolute; inset: 0` and is MOVED into this card while Brain is
     dictating. Without a containing block here it would pin itself to the viewport instead — the
     waveform would cover the whole app. Nothing inside .fb-ask is positioned, so this is inert
     until the bar arrives (.fb-drop sits at z-index 1200 and .fb-reader at 1360, both well clear). */
  position: relative;
  display: flex; align-items: flex-end; gap: var(--space-2);
  padding-block-start: 12px;
  padding-block-end: max(12px, var(--safe-bottom));
  padding-left:  max(var(--space-4), var(--safe-left));
  padding-right: max(var(--space-4), var(--safe-right));
  border-block-start: var(--border-hairline) solid var(--fb-hair);
  background: var(--color-surface);
}
.fb-ask__in {
  flex: 1 1 auto; min-inline-size: 0;
  max-block-size: 160px;
  overflow-y: auto;
  resize: none;
  padding: 10px 14px;
  border: var(--border-hairline) solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  background: var(--color-bg-subtle);
  color: var(--color-text-primary);
  font: var(--weight-regular) 14px/1.55 var(--font-sans);
  transition: border-color var(--fb-motion), background var(--fb-motion);
}
[dir="rtl"] .fb-ask__in { font-family: var(--font-arabic); }
.fb-ask__in::placeholder { color: var(--color-text-muted); }
.fb-ask__in:focus { outline: none; border-color: var(--color-accent); background: var(--color-surface); }
/* Text-shaped, not an icon square: app.js writes the word ("إرسال" / "Send") into this button.
   A fixed 38x38 box would overflow on both sides, since the button reset sets font-family but
   not font-size, so the label renders at the UA default. */
.fb-ask__send {
  flex: 0 0 auto;
  /* flex, not grid: the .is-stop state adds a ::before square that must sit BESIDE the label,
     and grid would place both in the same cell. */
  display: inline-flex; align-items: center; justify-content: center;
  min-inline-size: 38px; block-size: 38px;
  padding-inline: 14px;
  white-space: nowrap;
  font: var(--weight-semibold) 12.5px var(--font-sans);
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-on-accent);
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .fb-ask__send { font-family: var(--font-arabic); }
.fb-ask__send svg { inline-size: 18px; block-size: 18px; }
.fb-ask__send:hover:not(:disabled) { background: var(--color-accent-hover); }
.fb-ask__send:active:not(:disabled) { transform: scale(0.96); }
.fb-ask__send:disabled {
  background: var(--color-border-strong);
  color: var(--color-text-muted);
  cursor: not-allowed;
}
/* Stop state — the SAME button while an answer is streaming. Reads as an interrupt (neutral
   surface + hairline) rather than the accent-filled primary action, so it is obvious the click
   cancels rather than sends. */
.fb-ask__send.is-stop {
  background: var(--color-surface);
  color: var(--color-text-primary);
  border: var(--border-hairline) solid var(--fb-hair);
}
.fb-ask__send.is-stop:hover:not(:disabled) { background: var(--color-bg-subtle); }
.fb-ask__send.is-stop::before {
  content: ""; inline-size: 9px; block-size: 9px;
  border-radius: 2px; background: currentColor;
  margin-inline-end: 7px;
}

/* ── Page-range scope ─────────────────────────────────────────────────────
   A window that is ON has to be visible, because it is the explanation for a
   thin answer. So the control BECOMES the chip instead of sitting beside one:
   quiet button when off, accent chip with a ✕ when on. The digits ride in
   .fb-num (7110) — a hyphen between two numbers reorders inside Arabic prose
   unless the run is isolated LTR. */
.fb-scope { flex: 0 0 auto; position: relative; align-self: flex-end; }
.fb-scope__btn {
  display: inline-flex; align-items: center; gap: 6px;
  block-size: 38px; padding-inline: 12px;
  white-space: nowrap;
  border: var(--border-hairline) solid var(--fb-hair);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font: var(--weight-medium) 12.5px var(--font-sans);
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .fb-scope__btn { font-family: var(--font-arabic); }
.fb-scope__btn:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }
.fb-scope__btn.is-on {
  color: var(--color-accent);
  border-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
}
.fb-scope__x { font-size: 11px; line-height: 1; opacity: .6; }
.fb-scope__btn:hover .fb-scope__x { opacity: 1; }
/* Opens UPWARD out of the composer; .fb-ask never clips, so nothing needs a portal. */
.fb-scope__pop {
  position: absolute;
  inset-block-end: calc(100% + 8px);
  inset-inline-start: 0;
  z-index: 5;
  display: flex; flex-direction: column; gap: 8px;
  inline-size: max-content;
  padding: 10px;
  border: var(--border-hairline) solid var(--fb-hair);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: var(--shadow-md);
}
/* display:flex would out-rank the UA rule for [hidden], so restate it. */
.fb-scope__pop[hidden] { display: none; }
.fb-scope__hint {
  color: var(--color-text-muted);
  font: var(--weight-regular) 11.5px var(--font-sans);
}
[dir="rtl"] .fb-scope__hint { font-family: var(--font-arabic); }
.fb-scope__row { display: flex; align-items: center; gap: 6px; }
/* The fields are type="text" + inputmode="numeric", NOT type="number": an Arabic keyboard
   produces ٤٠, which a number field refuses outright. So there are no spinners to suppress
   and no `appearance: textfield` to write. */
.fb-scope__from, .fb-scope__to {
  inline-size: 70px;
  padding: 6px 8px;
  text-align: center;
  border: var(--border-hairline) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-bg-subtle);
  color: var(--color-text-primary);
  font: var(--weight-regular) 13px var(--font-sans);
}
.fb-scope__from:focus, .fb-scope__to:focus {
  outline: none;
  border-color: var(--color-accent);
  background: var(--color-surface);
}
.fb-scope__dash { color: var(--color-text-muted); }
.fb-scope__ok {
  block-size: 30px; padding-inline: 12px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-on-accent);
  font: var(--weight-semibold) 12px var(--font-sans);
  transition: background var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .fb-scope__ok { font-family: var(--font-arabic); }
.fb-scope__ok:hover { background: var(--color-accent-hover); }
@media (max-width: 640px) {
  .fb-scope__btn { padding-inline: 9px; }
  /* 4212 pins every input to 16px on phones to stop the iOS focus-zoom; the `font:`
     shorthand above is (0,1,0) and would out-specify that (0,0,1) rule whatever the
     source order, so restate it here on purpose (same call as .fb-ask__in). */
  .fb-scope__from, .fb-scope__to { font-size: 16px; inline-size: 78px; }
}
/* ── Pinned sources ────────────────────────────────────────────────
   The page-range chip's grammar, one state shorter. That control has an off
   state because it is also the way IN; this one is hidden outright when
   nothing is pinned, so it is only ever drawn lit. The whole chip clears —
   the ✕ is what says so. */
.fb-pins { display: inline-flex; flex: 0 0 auto; min-inline-size: 0; align-self: flex-end; }
/* The display above would out-rank the UA rule for [hidden], so restate it (same call as
   .fb-scope__pop above). An empty pin set hides this wrapper outright. */
.fb-pins[hidden] { display: none; }
.fb-pins__btn {
  display: inline-flex; align-items: center; gap: 6px;
  max-inline-size: 190px;
  block-size: 38px; padding-inline: 12px;
  border: var(--border-hairline) solid color-mix(in srgb, var(--color-accent) 45%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
  color: var(--color-accent);
  font: var(--weight-medium) 12.5px var(--font-sans);
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .fb-pins__btn { font-family: var(--font-arabic); }
.fb-pins__btn:hover { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }
.fb-pins__ic { display: inline-flex; flex: 0 0 auto; }
.fb-pins__ic svg { inline-size: 14px; block-size: 14px; }
/* Usually a document TITLE, and titles are long: this is the only part allowed to shrink, so
   the glyph, the count and the ✕ can never be squeezed out of a chip sharing a row with a
   textarea. */
.fb-pins__lbl {
  flex: 1 1 auto; min-inline-size: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  text-align: start;
}
/* One pinned source names itself; the count would only repeat "1". */
.fb-pins.is-solo .fb-pins__n { display: none; }
.fb-pins__x { flex: 0 0 auto; font-size: 11px; line-height: 1; opacity: .6; }
.fb-pins__btn:hover .fb-pins__x { opacity: 1; }
@media (max-width: 640px) {
  /* Four controls already share this row on a phone. The chip keeps the glyph, the count and
     the ✕ — that a filter is on, how many, and how to end it — and gives up the title, which
     is the one piece the rail repeats one tap away. */
  .fb-pins__btn { max-inline-size: none; padding-inline: 9px; gap: 5px; }
  .fb-pins__lbl { display: none; }
  .fb-pins.is-solo .fb-pins__n { display: inline; }
}

/* ── COMPARE TWO DOCUMENTS ────────────────────────────────────────────
   The chip is .fb-scope__btn's twin — same 38px pill, same row, same on-state — because it is
   the same kind of thing: a standing narrowing of the NEXT question, which has to be visible
   while that question is being typed rather than folded into a menu. */
.fb-cmp { flex: 0 0 auto; align-self: flex-end; }
/* The button's inline-flex would out-rank the UA's [hidden] rule through this wrapper, the
   same call .fb-pins[hidden] makes above. */
.fb-cmp[hidden] { display: none; }
.fb-cmp__btn {
  display: inline-flex; align-items: center; gap: 6px;
  block-size: 38px; padding-inline: 12px;
  white-space: nowrap;
  border: var(--border-hairline) solid var(--fb-hair);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font: var(--weight-medium) 12.5px var(--font-sans);
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .fb-cmp__btn { font-family: var(--font-arabic); }
.fb-cmp__btn:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }
.fb-cmp__btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* Only ever disabled while an answer is streaming — the same reason .fb-bar__btn is. */
.fb-cmp__btn:disabled { opacity: 0.45; pointer-events: none; }
.fb-cmp__btn.is-on {
  color: var(--color-accent);
  border-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
}
.fb-cmp__ic { display: inline-flex; flex: 0 0 auto; }
.fb-cmp__ic svg { inline-size: 14px; block-size: 14px; }

/* ── The two answers, side by side ─────────────────────────────────────
   `min-inline-size: 0` on the track is load-bearing: without it a long unbroken run — a URL, a
   wide table, a formula — grows its own column instead of scrolling inside it, and the pair
   stops being two equal readings of one question.

   The GRID is scoped to the panel, the column is not. A clone of .msg-ai__body that leaves
   #brainWorkspace — the big-text overlay takes one, and every export clone is attached to
   the live document — then lays the two out as ordinary stacked blocks, each keeping its
   rule and its heading. That is the same fallback the CONTENT already encodes, rather than
   a two-track grid squeezed into a surface nobody measured for one. */
#brainWorkspace .fb-cmp-out {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-5);
  align-items: start;
}
.fb-cmp-out__col {
  min-inline-size: 0;
  padding-inline-start: var(--space-4);
  border-inline-start: var(--border-strong) solid var(--fb-hair);
}
/* --fb-measure is a reading column for ONE text; halved it holds about thirty characters.
   Carries the id so it out-ranks `.fb-thread > *`, which sets that measure on every turn. */
#brainWorkspace .fb-thread > .turn--cmp { max-inline-size: min(1080px, 100%); }
/* Below this the two columns are two very narrow columns, so they stack — and keep the rule,
   which is then what says where one document's answer ends and the other's begins. */
@media (max-width: 840px) {
  #brainWorkspace .fb-cmp-out { grid-template-columns: 1fr; gap: var(--space-6); }
  #brainWorkspace .fb-thread > .turn--cmp { max-inline-size: var(--fb-measure); }
}
/* Five controls already share the ask row on a phone. The label goes and the glyph stays,
   exactly as .fb-pins does above; brainRenderCmp keeps a real aria-label on the button, so
   the icon-only state is still named. */
@media (max-width: 640px) {
  .fb-cmp__btn { padding-inline: 9px; gap: 5px; }
  .fb-cmp__lbl { display: none; }
}
/* Voice dictation. Deliberately NOT the send button's shape: this one ADDS to the question rather
   than sending it, so it reads as a quiet icon and fills with accent only while it is listening —
   the same grammar as .composer__mic / .composer__mic.is-on, which is the same feature. */
.fb-ask__mic {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 38px; block-size: 38px;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-muted);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
/* The button reset gives this display:inline-flex, which out-ranks the UA's [hidden] rule — and
   brainBindView hides the button outright where the browser withholds the microphone APIs. */
.fb-ask__mic[hidden] { display: none; }
.fb-ask__mic svg { inline-size: 19px; block-size: 19px; }
.fb-ask__mic:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.fb-ask__mic.is-on { background: var(--color-accent); color: var(--color-on-accent); }
/* ── THE BAR, RESIZED FOR THIS CARD ─────────────────────────────────────────────
   #micRec is MOVED in here while Brain dictates, and it arrives carrying the CHAT composer's
   metrics: `.mic-rec { padding: 12px 14px }` and a 56px `.mic-rec__viz`, both sized for that
   composer's ~100px two-row card. Brain's ask card is ONE 38px row inside 12/8 padding — about
   58px — and `inset: 0` pins the bar to the card's height rather than to its content, so an 80px
   stack of waveform-plus-padding spilled ~11px past the rounded edge, above and below.

   Scoped to `.fb-ask`, so the chat composer keeps its own generous bar untouched, and at (0,2,0)
   so it wins over the composer's plain `.mic-rec` rules no matter where they sit in this file.
   The 44px cancel/done buttons the phone media block asks for still fit: the card is >=64px there,
   because .fb-ask__send takes its own 44px minimum in the same breakpoint. */
.fb-ask .mic-rec { padding: 0 var(--space-2); gap: var(--space-2); }
.fb-ask .mic-rec__viz { block-size: 34px; }
/* `[dir="rtl"] .fb-ask__in` is a DESCENDANT rule, so it only ever fired when the whole panel was
   Arabic. brainSyncAsk now stamps a real dir on the field itself from the dominant script, which
   is the only thing that makes an Arabic question dictated into an English interface read
   correctly — and without this the direction would flip while the letters stayed in the Latin
   stack. Same declaration, matched to the new attribute. */
.fb-ask__in[dir="rtl"] { font-family: var(--font-arabic); }

/* ── Inline citation chip [S1] — the .cw-thread__chip recipe (4181) ────────
   Mono longhands rather than the `font:` shorthand: the run is a bare index,
   it must stay mono + LTR-isolated in Arabic prose (same call as .wordmark 269
   and .msg-ai__badge 694). */
.fb-cite {
  display: inline-flex; align-items: center;
  vertical-align: baseline;
  margin-inline: 2px;
  padding: 0; border: none; background: none;
  line-height: inherit;
  cursor: pointer;
}
.fb-cite:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fb-cite__n {
  font-family: var(--font-mono);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.6;
  color: var(--color-accent);
  background: var(--color-accent-soft);
  border-radius: var(--radius-pill);
  padding: 1px 8px;
  white-space: nowrap;
  direction: ltr; unicode-bidi: isolate; font-variant-numeric: tabular-nums;
  transition: background var(--fb-motion), color var(--fb-motion);
}
.fb-cite:hover .fb-cite__n { background: var(--color-accent); color: var(--color-on-accent); }

/* ── "المصادر" block under an answer ──────────────────────────────────────
   Appended as a SIBLING of .md, so the 3740-3750 measure clamp (direct
   children of .msg-ai__body > .md only) does not reach it — it must state the
   reading measure itself or it spans wider than the prose column.
   .turn is content-visibility:auto (626): NO height transitions and no
   late-loading media in here, so nothing reflows after first paint. */
.fb-sources {
  max-inline-size: var(--reading-w);
  margin-block-start: var(--space-4);
  border: var(--border-hairline) solid var(--fb-hair);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  overflow: hidden;
}
.fb-sources__head {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 10px 14px;
  background: var(--color-sidebar-bg);
  border-block-end: var(--border-hairline) solid var(--fb-hair);
  font: var(--weight-semibold) 10.5px/1.6 var(--font-sans);
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--color-text-muted);
}
[dir="rtl"] .fb-sources__head { font-family: var(--font-arabic); letter-spacing: 0.02em; }
.fb-sources__list { display: flex; flex-direction: column; }

/* ── One cited source (file + exact page + the passage) ───────────────────── */
.fb-sourced {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: 11px 14px;
  border-block-start: var(--border-hairline) solid var(--fb-hair);
  border-inline-start: var(--border-strong) solid transparent;
  text-align: start;
  cursor: pointer;
  transition: background var(--fb-motion), border-color var(--fb-motion);
}
.fb-sourced:first-child { border-block-start: none; }
.fb-sourced:hover { background: var(--color-accent-soft); border-inline-start-color: var(--color-accent); }
.fb-sourced:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* Exactly .file-card__icon (2527) — 40px --color-accent-soft type tile. */
.fb-sourced__ic {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: var(--fb-ic-lg); block-size: var(--fb-ic-lg); flex: 0 0 auto;
  border-radius: var(--radius-md);
  background: var(--color-accent-soft);
  color: var(--color-accent);
}
.fb-sourced__ic svg { inline-size: 22px; block-size: 22px; }
.fb-sourced__main {
  flex: 1 1 auto; min-inline-size: 0;
  display: flex; flex-direction: column; gap: 3px;
}
.fb-sourced__name {
  font: var(--weight-semibold) 13px/1.4 var(--font-sans);
  color: var(--color-text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
[dir="rtl"] .fb-sourced__name { font-family: var(--font-arabic); }
/* "صفحة 12" — the page number is the whole point of this product; isolate it. */
/* P0 — this pill was eating the entire citation row on every phone.
   It is a flex item with no `flex` declaration (so flex:0 1 auto) and `white-space: nowrap`.
   A flex item's automatic minimum size floors it at its min-content width, and for unbroken
   text that is the WHOLE string — so it could not shrink and pushed everything else to zero.
   Its label carries a document heading sliced to 80 characters (app.js, brainAddFiles), so
   on a .docx the string is long. Measured at 375px: the filename and the snippet both
   computed to 0px wide while the pill rendered 275px and overflowed the card; at 320px the
   pill sat 67px outside it and was clipped away by .fb-sources{overflow:hidden}. The citation
   is the entire point of Firas Brain, and on a phone it showed nothing readable at all.

   `min-inline-size: 0` removes the automatic floor so the pill can shrink; the ellipsis keeps
   it legible when it does. On narrow screens it drops to its own line instead (below). */
.fb-sourced__pg {
  align-self: flex-start;
  min-inline-size: 0;
  max-inline-size: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--font-mono);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.6;
  color: var(--color-accent);
  background: var(--color-accent-soft);
  border-radius: var(--radius-pill);
  padding: 1px 8px;
  white-space: nowrap;
  direction: ltr; unicode-bidi: isolate; font-variant-numeric: tabular-nums;
}
/* Below 640px the row wraps: the name and snippet take the full width and the page label
   sits under them, which is far more readable than three items fighting for 320px. */
@media (max-width: 640px) {
  .fb-sourced { flex-wrap: wrap; }
  .fb-sourced__main { flex: 1 1 100%; min-inline-size: 0; }
  .fb-sourced__pg { white-space: normal; margin-block-start: 4px; }
}
/* Two deterministic lines — a fixed max-block-size backs the clamp so the
   block's height is known before the text lands. */
.fb-sourced__snip {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
  max-block-size: 2.9em;
  font: var(--weight-regular) 12px/1.45 var(--font-sans);
  color: var(--color-text-secondary);
  overflow-wrap: anywhere;
}
[dir="rtl"] .fb-sourced__snip { font-family: var(--font-arabic); }

/* ── Term list — one document's collected terms, drawn inside the passage
   reader's own overlay (.fb-gloss IS .fb-reader, plus these rows). Each row is
   a citation row with the TERM where the filename goes: same target, same page
   pill, same click, one step earlier in the same journey. ─────────────────── */
.fb-gloss__hint {
  margin-block-end: var(--space-4);
  color: var(--color-text-muted);
  font: var(--weight-regular) 12.5px/1.7 var(--font-sans);
}
[dir="rtl"] .fb-gloss__hint { font-family: var(--font-arabic); }
.fb-gloss__list { display: flex; flex-direction: column; }
.fb-term {
  display: flex; align-items: stretch; gap: var(--space-1);
  border-block-start: var(--border-hairline) solid var(--fb-hair);
}
.fb-term:first-child { border-block-start: none; }
.fb-term__open {
  flex: 1 1 auto; min-inline-size: 0;
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: 10px 8px;
  border-radius: var(--radius-md);
  text-align: start;
  cursor: pointer;
  transition: background var(--fb-motion);
}
.fb-term__open:hover { background: var(--color-accent-soft); }
.fb-term__open:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fb-term__main { flex: 1 1 auto; min-inline-size: 0; display: flex; flex-direction: column; gap: 3px; }
/* The term is the DOCUMENT's word and carries its own dir attribute, so the Arabic face has
   to key off the element ITSELF — `[dir="rtl"] .fb-term__w` needs an rtl ANCESTOR, and the
   overlay above it follows the interface, which is usually LTR when this matters most. */
.fb-term__w {
  font: var(--weight-semibold) 13.5px/1.5 var(--font-sans);
  color: var(--color-text-primary);
  overflow-wrap: anywhere;
}
.fb-term__w[dir="rtl"] { font-family: var(--font-arabic); }
/* Two deterministic lines, exactly .fb-sourced__snip's clamp — a fixed max-block-size backs
   it so the row's height is known before the text lands. */
.fb-term__s {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
  max-block-size: 2.9em;
  font: var(--weight-regular) 12px/1.45 var(--font-sans);
  color: var(--color-text-secondary);
  overflow-wrap: anywhere;
}
.fb-term__s[dir="rtl"] { font-family: var(--font-arabic); }
/* .fb-sourced__pg's pill, and the same min-inline-size:0 that stops an unbreakable label
   from flooring the row at its own width. Direction and tabular figures come from .fb-num. */
.fb-term__pg {
  align-self: flex-start;
  min-inline-size: 0; max-inline-size: 100%;
  overflow: hidden; text-overflow: ellipsis;
  font-family: var(--font-mono);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.6;
  color: var(--color-accent);
  background: var(--color-accent-soft);
  border-radius: var(--radius-pill);
  padding: 1px 8px;
  white-space: nowrap;
}
/* Forgetting one term — .fb-src__x's grammar, down to being reachable without hover. */
.fb-term__x {
  flex: 0 0 auto;
  align-self: center;
  display: grid; place-items: center;
  inline-size: 26px; block-size: 26px;
  border-radius: 7px;
  color: var(--color-text-muted);
  font-size: 11px;
  opacity: 0;
  transition: opacity var(--fb-motion), background var(--fb-motion), color var(--fb-motion);
}
.fb-term:hover .fb-term__x, .fb-term__x:focus-visible { opacity: 1; }
.fb-term__x:hover { background: var(--fb-danger-soft); color: var(--color-error); }
@media (pointer: coarse) { .fb-term__x { opacity: 0.85; inline-size: 32px; block-size: 32px; } }
/* Below 640px the row wraps the way a citation row does: the term and its line take the
   full width and the page pill drops beneath them. */
@media (max-width: 640px) {
  .fb-term__open { flex-wrap: wrap; }
  .fb-term__main { flex: 1 1 100%; }
  .fb-term__pg { margin-block-start: 4px; }
}

/* ── Study plan — one document dealt out over days, drawn inside the passage
   reader's own overlay (.fb-plan IS .fb-reader, plus these rows). A session row
   is a term row with the DAY where the term goes and a page RANGE where the page
   number goes: same three columns, same pill, same hairline between them. ──── */
.fb-plan__hint {
  margin-block-end: var(--space-3);
  color: var(--color-text-muted);
  font: var(--weight-regular) 12.5px/1.7 var(--font-sans);
}
[dir="rtl"] .fb-plan__hint { font-family: var(--font-arabic); }
/* The day count. A stepper rather than a field: every value it can hold is one tap away,
   and there is nothing to type wrong. */
.fb-plan__days {
  display: flex; align-items: center; gap: var(--space-2);
  margin-block-end: var(--space-4);
  padding-block-end: var(--space-3);
  border-block-end: var(--border-hairline) solid var(--fb-hair);
}
.fb-plan__days[hidden] { display: none; }
.fb-plan__days-lbl {
  flex: 1 1 auto; min-inline-size: 0;
  color: var(--color-text-secondary);
  font: var(--weight-medium) 12.5px var(--font-sans);
}
[dir="rtl"] .fb-plan__days-lbl { font-family: var(--font-arabic); }
.fb-plan__step {
  flex: 0 0 auto;
  display: grid; place-items: center;
  inline-size: 28px; block-size: 28px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: 8px;
  background: transparent;
  color: var(--color-text-primary);
  font: var(--weight-semibold) 14px var(--font-mono);
  transition: border-color var(--fb-motion), color var(--fb-motion), background var(--fb-motion);
}
.fb-plan__step:hover { border-color: var(--color-accent); color: var(--color-accent); }
.fb-plan__step:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* pointer-events with the dimming, not instead of it: :hover still matches a disabled
   button, so the accent rule above would keep lighting it under the cursor while every
   click was dropped. The same call .fb-bar__btn:disabled makes. */
.fb-plan__step:disabled { opacity: 0.4; pointer-events: none; }
.fb-plan__n {
  min-inline-size: 26px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 14px; font-weight: var(--weight-semibold);
  color: var(--color-text-primary);
}
/* Building, empty, or failed — one line in the body's own voice, never an empty panel. */
.fb-plan__wait {
  padding-block: var(--space-3);
  color: var(--color-text-muted);
  font: var(--weight-regular) 12.5px/1.7 var(--font-sans);
}
[dir="rtl"] .fb-plan__wait { font-family: var(--font-arabic); }
.fb-plan__wait:empty, .fb-plan__wait[hidden] { display: none; }
.fb-plan__list { display: flex; flex-direction: column; }
.fb-day {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 10px 8px;
  border-block-start: var(--border-hairline) solid var(--fb-hair);
}
.fb-day:first-child { border-block-start: none; }
/* Tabular figures, but NO direction override — see the note where the row is built. The
   label follows the panel, so "Day 1" and "اليوم 1" each read the way their language does. */
.fb-day__n {
  flex: 0 0 auto;
  font: var(--weight-semibold) 11.5px var(--font-sans);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-primary);
  white-space: nowrap;
}
[dir="rtl"] .fb-day__n { font-family: var(--font-arabic); }
.fb-day__main { flex: 1 1 auto; min-inline-size: 0; }
/* Two deterministic lines, exactly .fb-term__s's clamp, so a session whose headings run long
   cannot push the rows below it off a screen the reader is scanning. */
.fb-day__topics {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
  max-block-size: 2.9em;
  font: var(--weight-regular) 12.5px/1.45 var(--font-sans);
  color: var(--color-text-secondary);
  overflow-wrap: anywhere;
}
/* The headings carry their own dir attribute, so the Arabic face keys off the ELEMENT —
   `[dir="rtl"] .fb-day__topics` needs an rtl ANCESTOR, and the shell above is usually LTR
   exactly when this matters. Same call .fb-term__w makes. */
.fb-day__topics[dir="rtl"] { font-family: var(--font-arabic); }
.fb-day__topics:empty::before { content: "—"; color: var(--color-text-muted); }
/* .fb-term__pg's pill, unchanged: direction and tabular figures come from .fb-num. */
.fb-day__pg {
  flex: 0 0 auto;
  max-inline-size: 45%;
  overflow: hidden; text-overflow: ellipsis;
  font-family: var(--font-mono);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.6;
  color: var(--color-accent);
  background: var(--color-accent-soft);
  border-radius: var(--radius-pill);
  padding: 1px 8px;
  white-space: nowrap;
}
/* Hand this day's pages to the search. .fb-term__x's grammar — same cell, revealed on hover,
   always reachable on touch — and the arrow is mirrored rather than redrawn for RTL. */
.fb-day__go {
  flex: 0 0 auto;
  display: grid; place-items: center;
  inline-size: 26px; block-size: 26px;
  border-radius: 7px;
  color: var(--color-text-muted);
  opacity: 0;
  transition: opacity var(--fb-motion), background var(--fb-motion), color var(--fb-motion);
}
.fb-day__go[hidden] { display: none; }
.fb-day__go svg { inline-size: 15px; block-size: 15px; }
[dir="rtl"] .fb-day__go svg { transform: scaleX(-1); }
.fb-day:hover .fb-day__go, .fb-day__go:focus-visible { opacity: 1; }
.fb-day__go:hover { background: var(--color-accent-soft); color: var(--color-accent); }
@media (pointer: coarse) { .fb-day__go { opacity: 0.85; inline-size: 32px; block-size: 32px; } }
.fb-plan__foot {
  display: flex; align-items: center; gap: var(--space-2);
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-3);
  border-block-start: var(--border-hairline) solid var(--fb-hair);
}
.fb-plan__foot[hidden] { display: none; }
/* .fb-bar__btn's hairline pill for the quiet verb, .fb-scope__ok's fill for the one that
   spends something — the panel's two buttons must not read as equals. */
.fb-plan__btn {
  flex: 0 0 auto;
  padding: 7px 13px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-primary);
  font: var(--weight-medium) 12.5px var(--font-sans);
  white-space: nowrap;
  transition: border-color var(--fb-motion), color var(--fb-motion), background var(--fb-motion);
}
[dir="rtl"] .fb-plan__btn { font-family: var(--font-arabic); }
.fb-plan__btn:hover { border-color: var(--color-accent); color: var(--color-accent); }
.fb-plan__btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fb-plan__btn:disabled { opacity: 0.45; pointer-events: none; }
/* Refine, while its document sits outside the search. Dimmed rather than `disabled` and it
   keeps its pointer events on purpose: the click is what says why (see syncRefine). */
.fb-plan__btn.is-off { opacity: 0.55; }
.fb-plan__btn.is-key {
  margin-inline-start: auto;
  border-color: transparent;
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-weight: var(--weight-semibold);
}
.fb-plan__btn.is-key:hover { background: var(--color-accent-hover); color: var(--color-on-accent); }
/* Below 640px the session row wraps the way a citation row does: the day and its headings
   take the full width and the page pill drops beneath them. */
@media (max-width: 640px) {
  .fb-day { flex-wrap: wrap; }
  .fb-day__main { flex: 1 1 100%; order: 3; }
  .fb-day__pg { order: 2; margin-inline-start: auto; }
  .fb-day__go { order: 4; }
}

/* ── Tables — the rows a document keeps its data in, drawn inside the passage
   reader's own overlay (.fb-tbl IS .fb-reader, plus a picker and one grid). The
   picker is .fb-day__pg's pill turned into a chip; the grid is the only thing in
   this panel allowed to move sideways, because .fb-reader__body is overflow-x:
   hidden and a twelve-column table must never scroll the page. ──────────── */
/* The bar's second verb. brainTablesOffered already refuses it below 900px, where the bar has a
   filename, a count and the primary button to fit; this is the belt to that braces, so a stale
   `hidden=false` left behind by a resize can never widen the bar. */
@media (max-width: 900px) {
  .fb-bar__tbl { display: none; }
}
.fb-tbl__hint {
  margin-block-end: var(--space-3);
  color: var(--color-text-muted);
  font: var(--weight-regular) 12.5px/1.7 var(--font-sans);
}
[dir="rtl"] .fb-tbl__hint { font-family: var(--font-arabic); }
/* Reading, empty, or failed — one line in the body's own voice, never an empty panel.
   .fb-plan__wait's rule, including the :empty collapse. */
.fb-tbl__wait {
  padding-block: var(--space-3);
  color: var(--color-text-muted);
  font: var(--weight-regular) 12.5px/1.7 var(--font-sans);
}
[dir="rtl"] .fb-tbl__wait { font-family: var(--font-arabic); }
.fb-tbl__wait:empty, .fb-tbl__wait[hidden] { display: none; }
/* What was cut — rows past the render cap, chunks past the sweep. Quieter than the hint above
   it and never hidden behind a disclosure: an incomplete table that looks complete is the one
   thing this panel must not hand a student. */
.fb-tbl__note {
  margin-block-end: var(--space-3);
  color: var(--color-text-muted);
  font: var(--weight-regular) 11.5px/1.6 var(--font-sans);
}
[dir="rtl"] .fb-tbl__note { font-family: var(--font-arabic); }
.fb-tbl__note:empty, .fb-tbl__note[hidden] { display: none; }
.fb-tbl__picks {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin-block-end: var(--space-4);
  padding-block-end: var(--space-3);
  border-block-end: var(--border-hairline) solid var(--fb-hair);
}
/* display above out-ranks the UA rule for [hidden], so restate it — the same call
   .fb-src__gloss and .fb-plan__foot both make. A single table hides the picker outright. */
.fb-tbl__picks[hidden] { display: none; }
.fb-tbl__pick {
  flex: 0 0 auto;
  display: inline-flex; align-items: baseline; gap: var(--space-2);
  max-inline-size: 100%;
  padding: 5px 11px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-secondary);
  cursor: pointer;
  transition: border-color var(--fb-motion), color var(--fb-motion), background var(--fb-motion);
}
.fb-tbl__pick:hover { border-color: var(--color-accent); color: var(--color-accent); }
.fb-tbl__pick:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fb-tbl__pick.is-on {
  border-color: transparent;
  background: var(--color-accent-soft);
  color: var(--color-accent);
}
/* The chip's name is the DOCUMENT's word — a sheet name, a slide title — so the Arabic face
   keys off the element ITSELF: `[dir="rtl"] .fb-tbl__pick-t` needs an rtl ANCESTOR, and the
   shell above is usually LTR exactly when this matters. Same call .fb-day__topics makes. */
.fb-tbl__pick-t {
  min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: var(--weight-medium) 12px var(--font-sans);
}
.fb-tbl__pick-t[dir="rtl"] { font-family: var(--font-arabic); }
.fb-tbl__pick-t:empty { display: none; }
.fb-tbl__pick-n {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.6;
  white-space: nowrap;
}
/* THE ONE SIDEWAYS SCROLLER. A data table is as wide as its columns and the panel is 720px;
   without this the grid would either be squeezed into unreadable columns or push the overlay
   itself sideways. -webkit-overflow-scrolling matches .fb-reader__body's own momentum. */
.fb-tbl__wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: var(--border-hairline) solid var(--fb-hair);
  border-radius: var(--radius-md);
}
.fb-tbl__t {
  border-collapse: collapse;
  inline-size: max-content; min-inline-size: 100%;
}
/* caption-side + text-align:start rather than the UA's centred caption — it is a label, and a
   label sits where the reading starts in either direction. The display is deliberately LEFT
   ALONE: a caption given any display other than table-caption drops out of the table box in
   several engines and takes the grid's accessible name with it, so the flex row that pushes the
   page pill to the far end is one level in. */
.fb-tbl__cap {
  caption-side: top;
  padding: 8px 10px;
  text-align: start;
  background: var(--color-bg-subtle);
  border-block-end: var(--border-hairline) solid var(--fb-hair);
}
.fb-tbl__cap-in { display: flex; align-items: baseline; gap: var(--space-2); }
.fb-tbl__cap-t {
  min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: var(--weight-semibold) 12px var(--font-sans);
  color: var(--color-text-primary);
}
.fb-tbl__cap-t[dir="rtl"] { font-family: var(--font-arabic); }
.fb-tbl__cap-t:empty { display: none; }
.fb-tbl__cap-n {
  flex: 0 0 auto;
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.6;
  color: var(--color-accent);
}
.fb-tbl__t th, .fb-tbl__t td {
  max-inline-size: 320px;
  padding: 7px 10px;
  border: var(--border-hairline) solid var(--fb-hair);
  text-align: start;
  vertical-align: top;
  font: var(--weight-regular) 12.5px/1.6 var(--font-sans);
  color: var(--color-text-secondary);
  overflow-wrap: anywhere;
}
/* Each cell carries its OWN dir (a bilingual textbook has both in one row), so — again — the
   Arabic face is keyed to the element rather than to an ancestor. */
.fb-tbl__t th[dir="rtl"], .fb-tbl__t td[dir="rtl"] { font-family: var(--font-arabic); }
/* The `font:` SHORTHAND above resets font-variant-numeric, and at (0,1,1) it outranks
   .fb-num at (0,1,0) — so the tabular figures a numeric cell is given in JS never actually
   reached one, and a column of numbers was free to sit ragged. Restated at the cell's own
   weight. .fb-tbl__pick-n and .fb-tbl__cap-n need no such rule: both are written in
   longhands for exactly this reason, the same call .fb-bar__count documents. */
.fb-tbl__t th.fb-num, .fb-tbl__t td.fb-num { font-variant-numeric: tabular-nums; }
.fb-tbl__t th {
  background: var(--color-bg-subtle);
  font-weight: var(--weight-semibold);
  color: var(--color-text-primary);
  white-space: nowrap;
}
.fb-tbl__t tbody tr:hover td { background: var(--color-accent-soft); }
/* .fb-plan__foot, unchanged — two quiet verbs and the one that produces a file. */
.fb-tbl__foot {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-3);
  border-block-start: var(--border-hairline) solid var(--fb-hair);
}
.fb-tbl__foot[hidden] { display: none; }
.fb-tbl__btn {
  flex: 0 0 auto;
  padding: 7px 13px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-primary);
  font: var(--weight-medium) 12.5px var(--font-sans);
  white-space: nowrap;
  transition: border-color var(--fb-motion), color var(--fb-motion), background var(--fb-motion);
}
[dir="rtl"] .fb-tbl__btn { font-family: var(--font-arabic); }
.fb-tbl__btn:hover { border-color: var(--color-accent); color: var(--color-accent); }
.fb-tbl__btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fb-tbl__btn:disabled { opacity: 0.45; pointer-events: none; }
.fb-tbl__btn.is-key {
  margin-inline-start: auto;
  border-color: transparent;
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-weight: var(--weight-semibold);
}
.fb-tbl__btn.is-key:hover { background: var(--color-accent-hover); color: var(--color-on-accent); }

/* ── Formula sheet — every equation in one document, with the page it is on.
   Drawn in the passage reader's own overlay (.fb-fml IS .fb-reader, plus these
   rows), and an equation row is a session row with the formula where the day's
   headings went: same hairline between rows, same accent page pill. The button
   that opens it needs nothing here — it is a plain .fb-bar__btn. ──────────── */
.fb-fml__hint {
  margin-block-end: var(--space-3);
  color: var(--color-text-muted);
  font: var(--weight-regular) 12.5px/1.7 var(--font-sans);
}
[dir="rtl"] .fb-fml__hint { font-family: var(--font-arabic); }
/* Sweeping, empty, or failed — one line in the body's own voice, never an empty panel. The
   progress figures live in their own .fb-num child rather than in the sentence: "1500/4000"
   reorders inside Arabic prose unless it is isolated LTR, and the words around it must not be
   dragged into that isolate with it. */
.fb-fml__wait {
  display: flex; align-items: baseline; gap: var(--space-2);
  padding-block: var(--space-3);
  color: var(--color-text-muted);
  font: var(--weight-regular) 12.5px/1.7 var(--font-sans);
}
[dir="rtl"] .fb-fml__wait { font-family: var(--font-arabic); }
/* display above out-ranks the UA rule for [hidden], so restate it (the same call
   .fb-plan__days and .fb-scope__pop make). */
.fb-fml__wait[hidden] { display: none; }
.fb-fml__wait-n {
  font-family: var(--font-mono);
  font-size: 11.5px; font-weight: var(--weight-semibold);
}
.fb-fml__wait-n:empty { display: none; }
.fb-fml__list { display: flex; flex-direction: column; }
.fb-eq {
  display: flex; align-items: baseline; gap: var(--space-3);
  padding: 9px 8px;
  border-block-start: var(--border-hairline) solid var(--fb-hair);
}
.fb-eq:first-child { border-block-start: none; }
/* The equation itself, and the only thing on the row that must never be truncated. Mono,
   because that is what keeps "l" and "1" apart in a formula being copied by eye — swapped for
   the Arabic face on the rows that carry Arabic words, since the mono family has no Arabic.
   Keyed off the ELEMENT's own dir: the shell above is LTR exactly when this matters, so
   `[dir="rtl"] .fb-eq__t` would never fire. Same call .fb-day__topics makes. */
.fb-eq__t {
  flex: 1 1 auto; min-inline-size: 0;
  font-family: var(--font-mono);
  font-size: 12.5px; line-height: 1.65;
  color: var(--color-text-primary);
  overflow-wrap: anywhere;
  -webkit-user-select: text; user-select: text;
}
.fb-eq__t[dir="rtl"] { font-family: var(--font-arabic); }
/* How many times the book states it. Quiet on purpose: it ranks the row, it is not the row. */
.fb-eq__n {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.6;
  color: var(--color-text-muted);
}
.fb-eq__n[hidden] { display: none; }
/* .fb-day__pg's pill, unchanged: direction and tabular figures come from .fb-num. A chunk
   with no page of its own leaves it empty, and an empty accent chip reads as a broken one. */
.fb-eq__pg {
  flex: 0 0 auto;
  max-inline-size: 40%;
  overflow: hidden; text-overflow: ellipsis;
  font-family: var(--font-mono);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.6;
  color: var(--color-accent);
  background: var(--color-accent-soft);
  border-radius: var(--radius-pill);
  padding: 1px 8px;
  white-space: nowrap;
}
.fb-eq__pg:empty { display: none; }
.fb-fml__foot {
  display: flex; align-items: center; gap: var(--space-3);
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-3);
  border-block-start: var(--border-hairline) solid var(--fb-hair);
}
.fb-fml__foot[hidden] { display: none; }
/* The ceiling, said rather than silently applied — see where its text is set. */
.fb-fml__note {
  flex: 1 1 auto; min-inline-size: 0;
  color: var(--color-text-muted);
  font: var(--weight-regular) 11.5px/1.5 var(--font-sans);
}
[dir="rtl"] .fb-fml__note { font-family: var(--font-arabic); }
/* .fb-plan__copy's hairline pill. One verb, and it spends nothing — so nothing here is
   filled the way .fb-plan__btn.is-key is. */
.fb-fml__btn {
  flex: 0 0 auto;
  margin-inline-start: auto;
  padding: 7px 13px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-primary);
  font: var(--weight-medium) 12.5px var(--font-sans);
  white-space: nowrap;
  transition: border-color var(--fb-motion), color var(--fb-motion), background var(--fb-motion);
}
[dir="rtl"] .fb-fml__btn { font-family: var(--font-arabic); }
.fb-fml__btn:hover { border-color: var(--color-accent); color: var(--color-accent); }
.fb-fml__btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* brainFmlOffered already refuses the button below 900px; this is the belt to that braces,
   for a window resized while the panel is open. The equation takes the row to itself and the
   page pill drops beneath it, the way .fb-day wraps. */
@media (max-width: 640px) {
  .fb-eq { flex-wrap: wrap; }
  .fb-eq__t { flex: 1 1 100%; }
  .fb-eq__pg { margin-inline-start: auto; }
}

/* ── Exam mode — one document, under a clock, drawn inside the passage reader's own
   overlay (.fb-exam IS .fb-reader, plus these rows), the same way .fb-plan is. Three
   stages share one body: the setup rows are the study plan's stepper grammar, the
   question is a .qz sheet with exactly one question on it, and a missed row is a
   .fb-day with the page pill turned into the button that opens it. ────────────── */
.fb-exam__hint {
  margin-block-end: var(--space-3);
  color: var(--color-text-muted);
  font: var(--weight-regular) 12.5px/1.7 var(--font-sans);
}
[dir="rtl"] .fb-exam__hint { font-family: var(--font-arabic); }
/* The clock. Beside the title rather than over the question, because a countdown in the
   reading line is the thing the eye goes back to instead of the question. */
.fb-exam__clock {
  flex: 0 0 auto;
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
  font-family: var(--font-mono);
  font-size: 11px; font-weight: var(--weight-semibold); line-height: 1.6;
  transition: background var(--fb-motion), color var(--fb-motion);
}
/* The last half minute, and nothing louder: a flashing clock is read as an error. */
.fb-exam__clock.is-low {
  background: var(--fb-danger-soft);
  color: var(--color-error);
}
.fb-exam__row {
  display: flex; align-items: center; gap: var(--space-2);
  padding-block: 9px;
  border-block-start: var(--border-hairline) solid var(--fb-hair);
}
.fb-exam__row:first-child { border-block-start: none; }
.fb-exam__lbl {
  flex: 1 1 auto; min-inline-size: 0;
  color: var(--color-text-secondary);
  font: var(--weight-medium) 12.5px var(--font-sans);
}
[dir="rtl"] .fb-exam__lbl { font-family: var(--font-arabic); }
/* .fb-scope__from's field, unchanged — the page window is asked for in the same words here,
   so it is asked for with the same control. */
.fb-exam__in {
  flex: 0 0 auto;
  inline-size: 70px;
  padding: 6px 8px;
  text-align: center;
  border: var(--border-hairline) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-bg-subtle);
  color: var(--color-text-primary);
  font: var(--weight-regular) 13px var(--font-sans);
}
.fb-exam__in:focus {
  outline: none;
  border-color: var(--color-accent);
  background: var(--color-surface);
}
.fb-exam__step {
  flex: 0 0 auto;
  display: grid; place-items: center;
  inline-size: 28px; block-size: 28px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: 8px;
  background: transparent;
  color: var(--color-text-primary);
  font: var(--weight-semibold) 14px var(--font-mono);
  transition: border-color var(--fb-motion), color var(--fb-motion), background var(--fb-motion);
}
.fb-exam__step:hover { border-color: var(--color-accent); color: var(--color-accent); }
.fb-exam__step:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* pointer-events with the dimming, not instead of it: :hover still matches a disabled button,
   so the accent rule above would keep lighting it under the cursor. Same call .fb-plan__step
   makes one block up. */
.fb-exam__step:disabled { opacity: 0.4; pointer-events: none; }
.fb-exam__n {
  min-inline-size: 26px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 14px; font-weight: var(--weight-semibold);
  color: var(--color-text-primary);
}
/* Building, empty or failed — one line in the body's own voice, never an empty panel. */
.fb-exam__wait {
  padding-block: var(--space-3);
  color: var(--color-text-muted);
  font: var(--weight-regular) 12.5px/1.7 var(--font-sans);
}
[dir="rtl"] .fb-exam__wait { font-family: var(--font-arabic); }
.fb-exam__q {
  margin-block-end: var(--space-3);
  font: var(--weight-medium) 15px/1.7 var(--font-sans);
  color: var(--color-text-primary);
  overflow-wrap: anywhere;
}
/* The question carries its own dir attribute, so the Arabic face keys off the ELEMENT —
   `[dir="rtl"] .fb-exam__q` needs an rtl ANCESTOR, and the shell above is usually LTR exactly
   when this matters. Same call .fb-day__topics and .qz__ask make. */
.fb-exam__q[dir="rtl"] { font-family: var(--font-arabic); }
.fb-exam__opts { display: flex; flex-direction: column; gap: 6px; }
/* The whole row is the target, so a thumb landing anywhere on the line picks that choice. */
.fb-exam__opt {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 11px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  text-align: start;
  cursor: pointer;
  transition: background var(--fb-motion), border-color var(--fb-motion);
}
.fb-exam__opt:hover { background: color-mix(in srgb, var(--color-accent) 8%, transparent); }
.fb-exam__opt:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* Chosen, NOT correct. Nothing on this sheet says right or wrong until it is committed — the
   accent here means "this is the one you are holding" and it is deliberately the same weight
   the setup's focused field carries. */
.fb-exam__opt.is-on {
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}
.fb-exam__key {
  flex: 0 0 auto;
  min-inline-size: 18px;
  font-family: var(--font-mono);
  font-size: 11px; font-weight: var(--weight-bold); line-height: 1.7;
  color: var(--color-text-muted);
}
.fb-exam__opt.is-on .fb-exam__key { color: var(--color-accent); }
.fb-exam__ot {
  flex: 1 1 auto; min-inline-size: 0;
  font: var(--weight-regular) 13.5px/1.6 var(--font-sans);
  color: var(--color-text-primary);
  overflow-wrap: anywhere;
}
.fb-exam__ot[dir="rtl"] { font-family: var(--font-arabic); }
.fb-exam__score {
  font: var(--weight-semibold) 15px/1.7 var(--font-sans);
  color: var(--color-text-primary);
}
[dir="rtl"] .fb-exam__score { font-family: var(--font-arabic); }
.fb-exam__revhead {
  margin-block: var(--space-3) var(--space-2);
  padding-block-start: var(--space-3);
  border-block-start: var(--border-hairline) solid var(--fb-hair);
  color: var(--color-text-secondary);
  font: var(--weight-medium) 12.5px/1.6 var(--font-sans);
}
[dir="rtl"] .fb-exam__revhead { font-family: var(--font-arabic); }
.fb-exam__list { display: flex; flex-direction: column; }
/* A missed question. .fb-day's three columns — the text, then the pill, then the control
   that takes you somewhere — except that here the pill IS the control. */
.fb-miss {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: 11px 8px;
  border-block-start: var(--border-hairline) solid var(--fb-hair);
}
.fb-miss:first-child { border-block-start: none; }
.fb-miss__main { flex: 1 1 auto; min-inline-size: 0; display: flex; flex-direction: column; gap: 3px; }
.fb-miss__q {
  font: var(--weight-medium) 13px/1.6 var(--font-sans);
  color: var(--color-text-primary);
  overflow-wrap: anywhere;
}
.fb-miss__q[dir="rtl"] { font-family: var(--font-arabic); }
.fb-miss__ans { display: flex; flex-wrap: wrap; gap: 4px var(--space-3); }
.fb-miss__mine, .fb-miss__key, .fb-miss__why {
  font: var(--weight-regular) 12px/1.55 var(--font-sans);
  overflow-wrap: anywhere;
}
.fb-miss__mine[dir="rtl"], .fb-miss__key[dir="rtl"], .fb-miss__why[dir="rtl"] { font-family: var(--font-arabic); }
/* The verdict is carried on the WORDS, not on a fill: half these rows are red on a first
   attempt at anything worth testing, and a panel washed in it is a scolding. The same call
   .qz__q.is-right / .is-wrong makes one storey up. */
.fb-miss__mine { color: var(--color-error); }
.fb-miss__key { color: var(--color-success); }
.fb-miss__why { color: var(--color-text-muted); }
/* The way back to the page. Always visible, never hover-revealed like .fb-day__go: this one
   is the reason the reader is looking at this list at all. */
.fb-miss__go {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 8px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-pill);
  color: var(--color-accent);
  background: var(--color-accent-soft);
  transition: border-color var(--fb-motion), background var(--fb-motion);
}
.fb-miss__go:hover { border-color: var(--color-accent); }
.fb-miss__go:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fb-miss__pg {
  font-family: var(--font-mono);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.6;
  white-space: nowrap;
}
.fb-miss__ic { display: grid; place-items: center; }
.fb-miss__ic svg { inline-size: 14px; block-size: 14px; }
/* Mirrored rather than redrawn for RTL, exactly as .fb-day__go's arrow is. */
[dir="rtl"] .fb-miss__ic svg { transform: scaleX(-1); }
.fb-exam__foot {
  display: flex; align-items: center; gap: var(--space-2);
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-3);
  border-block-start: var(--border-hairline) solid var(--fb-hair);
}
/* .fb-bar__btn's hairline pill for the quiet verb, .fb-scope__ok's fill for the one that
   spends something — the panel's two buttons must not read as equals. */
.fb-exam__btn {
  flex: 0 0 auto;
  padding: 7px 13px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-primary);
  font: var(--weight-medium) 12.5px var(--font-sans);
  white-space: nowrap;
  transition: border-color var(--fb-motion), color var(--fb-motion), background var(--fb-motion);
}
[dir="rtl"] .fb-exam__btn { font-family: var(--font-arabic); }
.fb-exam__btn:hover { border-color: var(--color-accent); color: var(--color-accent); }
.fb-exam__btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fb-exam__btn:disabled { opacity: 0.45; pointer-events: none; }
.fb-exam__btn.is-key {
  margin-inline-start: auto;
  border-color: transparent;
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-weight: var(--weight-semibold);
}
.fb-exam__btn.is-key:hover { background: var(--color-accent-hover); color: var(--color-on-accent); }
/* The UA rule for [hidden] is display:none and an author `display` beats it, so every element
   this panel hides has to be named here. Without it the answer options would sit under the
   setup from the moment the panel opens — the one failure an exam cannot have. */
.fb-exam__hint[hidden], .fb-exam__clock[hidden], .fb-exam__setup[hidden], .fb-exam__row[hidden],
.fb-exam__wait[hidden], .fb-exam__run[hidden], .fb-exam__done[hidden], .fb-exam__btn[hidden],
.fb-miss__go[hidden] { display: none; }
/* Below 640px the missed row wraps the way a citation row does: the question takes the full
   width and the way back drops beneath it, still whole. */
@media (max-width: 640px) {
  .fb-miss { flex-wrap: wrap; }
  .fb-miss__main { flex: 1 1 100%; }
  .fb-miss__go { margin-inline-start: auto; }
  /* 4212 pins every input to 16px on phones to stop the iOS focus-zoom; the `font:`
     shorthand above is (0,1,0) and would out-specify that (0,0,1) rule whatever the source
     order, so restate it here on purpose — the same call .fb-scope__from makes. */
  .fb-exam__in { font-size: 16px; }
}

/* ── Passage reader — .preview-overlay / .preview-panel grammar (2743-2799),
   same .is-open open/close vocabulary. z-index 1360 sits in the hand-kept band
   between .ann-edit-overlay (1350) and .dev-modal (1400). ─────────────────── */
.fb-reader {
  position: fixed; inset: 0; z-index: 1360;
  display: grid; place-items: center;
  padding: var(--space-4);
  background: var(--fb-scrim, rgba(0,0,0,0.46));
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out),
              visibility 0s linear var(--dur-base);
}
.fb-reader.is-open {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: opacity var(--dur-base) var(--ease-out), visibility 0s;
}
.fb-reader[hidden] { display: none; }
.fb-reader__panel {
  inline-size: 100%; max-inline-size: 720px;
  max-height: 86vh;                 /* fallback first — engines without dvh */
  max-height: 86dvh;                /* iOS Safari: vh is the URL-bar-hidden box */
  display: flex; flex-direction: column; min-height: 0;
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--fb-hair);
  border-radius: var(--radius-xl);
  box-shadow: var(--fb-shadow-float, var(--shadow-lg));
  overflow: hidden;
  transform: translateY(8px) scale(0.99);
  transition: transform var(--dur-base) var(--ease-out);
}
.fb-reader.is-open .fb-reader__panel { transform: none; }
.fb-reader__head {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  border-block-end: var(--border-hairline) solid var(--fb-hair);
  background: var(--color-sidebar-bg);
}
.fb-reader__title {
  flex: 1 1 auto; min-inline-size: 0;
  font: var(--weight-semibold) 13px/1.5 var(--font-sans);
  color: var(--color-text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
[dir="rtl"] .fb-reader__title { font-family: var(--font-arabic); }
.fb-reader__pg {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 10.5px; font-weight: var(--weight-semibold); line-height: 1.6;
  color: var(--color-accent);
  background: var(--color-accent-soft);
  border-radius: var(--radius-pill);
  padding: 2px 9px;
  white-space: nowrap;
  direction: ltr; unicode-bidi: isolate; font-variant-numeric: tabular-nums;
}
.fb-reader__x {
  flex: 0 0 auto;
  display: grid; place-items: center;
  inline-size: 34px; block-size: 34px;
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
  transition: background var(--dur-fast), color var(--dur-fast);
}
.fb-reader__x:hover { background: var(--color-accent-soft); color: var(--color-accent); }
.fb-reader__x svg { inline-size: 17px; block-size: 17px; }
/* ── Citation — the head's second control, and the panel it folds open. The button is
   .fb-reader__x's twin (same 34px cell, same hover) because they are peers on the
   same row; the panel is a band between the head and the body, so what you are
   about to copy does not scroll away under the passage. ────────────────────── */
.fb-reader__cite {
  flex: 0 0 auto;
  display: grid; place-items: center;
  inline-size: 34px; block-size: 34px;
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
  transition: background var(--dur-fast), color var(--dur-fast);
}
/* display above out-ranks the UA rule for [hidden], so restate it — same call
   .fb-src__gloss and .fb-scope__pop make. An unnameable source hides this outright. */
.fb-reader__cite[hidden] { display: none; }
.fb-reader__cite:hover, .fb-reader__cite.is-on { background: var(--color-accent-soft); color: var(--color-accent); }
.fb-reader__cite svg { inline-size: 17px; block-size: 17px; }
/* An opening quotation mark points the way the text runs, so in Arabic it faces the
   other way. The glyph is the only thing mirrored; the cell keeps its own box. */
[dir="rtl"] .fb-reader__cite svg { transform: scaleX(-1); }
.fb-bib {
  flex: 0 0 auto;
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-4);
  border-block-end: var(--border-hairline) solid var(--fb-hair);
  background: var(--color-sidebar-bg);
  max-block-size: 40vh; overflow-y: auto;
}
.fb-bib[hidden] { display: none; }
.fb-bib__hint {
  margin: 0;
  color: var(--color-text-muted);
  font: var(--weight-regular) 12px/1.7 var(--font-sans);
}
[dir="rtl"] .fb-bib__hint { font-family: var(--font-arabic); }
.fb-bib__row { display: flex; align-items: flex-start; gap: var(--space-3); }
/* .fb-reader__pg's pill. "APA" is a name in both languages, so it is pinned ltr. */
.fb-bib__style {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.8;
  color: var(--color-accent);
  background: var(--color-accent-soft);
  border-radius: var(--radius-pill);
  padding: 2px 9px;
  white-space: nowrap;
  direction: ltr; unicode-bidi: isolate;
}
.fb-bib__lines { flex: 1 1 auto; min-inline-size: 0; display: flex; flex-direction: column; gap: 6px; }
.fb-bib__line { display: flex; align-items: baseline; gap: var(--space-2); }
.fb-bib__lbl {
  flex: 0 0 auto;
  font: var(--weight-medium) 11px/1.7 var(--font-sans);
  color: var(--color-text-muted);
  white-space: nowrap;
}
[dir="rtl"] .fb-bib__lbl { font-family: var(--font-arabic); }
/* The entry carries its own dir (the document's, not the interface's), so the Arabic
   face has to key off the ELEMENT — the same trap .fb-term__w documents — and the run
   is isolated so an Arabic title cannot drag the label and the button around it. */
.fb-bib__txt {
  flex: 1 1 auto; min-inline-size: 0;
  font: var(--weight-regular) 12.5px/1.75 var(--font-sans);
  color: var(--color-text-primary);
  overflow-wrap: anywhere;
  unicode-bidi: isolate;
  user-select: text;
}
.fb-bib__txt[dir="rtl"] { font-family: var(--font-arabic); }
.fb-bib__copy {
  flex: 0 0 auto;
  padding: 3px 10px;
  border: var(--border-hairline) solid var(--fb-hair);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font: var(--weight-medium) 11px/1.7 var(--font-sans);
  white-space: nowrap;
  transition: background var(--dur-fast), color var(--dur-fast);
}
[dir="rtl"] .fb-bib__copy { font-family: var(--font-arabic); }
.fb-bib__copy:hover { background: var(--color-accent-soft); color: var(--color-accent); }
.fb-bib__copy:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* Below 640px a label, a full bibliography entry and its button cannot share one line
   and leave the entry readable — so the entry takes the width and the button drops
   under the text it copies. Exactly what .fb-term__open does one panel over. */
@media (max-width: 640px) {
  .fb-bib__row { flex-wrap: wrap; }
  .fb-bib__line { flex-wrap: wrap; }
  .fb-bib__txt { flex: 1 1 100%; }
}
/* The ONE growing child of the panel column. */
.fb-reader__body {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  padding: var(--space-5);
  padding-block-end: max(var(--space-5), var(--safe-bottom));
}
/* Surrounding context — deliberately dimmer than the hit. */
.fb-reader__ctx {
  font: var(--weight-regular) 14px/1.9 var(--font-sans);
  color: var(--color-text-muted);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
[dir="rtl"] .fb-reader__ctx { font-family: var(--font-arabic); }
/* The cited passage itself — accent-soft field, leading accent border. */
.fb-reader__hit {
  display: block;
  margin-block: var(--space-3);
  padding: var(--space-3) var(--space-4);
  /* The cited passage is marked by a hairline rule and a tinted ground, not a 3px slab.
     A thick coloured edge on a callout is the most recognizable generated-UI tell there is,
     and the tint already carries the "this is the quoted passage" signal on its own. */
  border-inline-start: 1px solid var(--color-accent);
  border-radius: var(--radius-sm);
  background: var(--color-accent-soft);
  color: var(--color-text-primary);
  font: var(--weight-medium) 14px/1.9 var(--font-sans);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
[dir="rtl"] .fb-reader__hit { font-family: var(--font-arabic); }

/* ── The line that answers the question ──────────────────────────────────────
   Inside an opened passage, the sentence(s) whose wording overlaps the question most are
   wrapped in a real <mark> (app.js SPLITS the paragraph's text node; it never re-writes the
   text), so the highlight is nothing but a ground: selection, copy and find-in-page all still
   see the passage exactly as the document stored it.

   accent-RING, not accent-soft. The cited chunk already sits on an accent-soft field, and a
   second 8% tint on top of that is invisible — the mark has to read against the tint it lands
   on, in all six themes. */
.fb-mark {
  background: var(--color-accent-ring);
  color: var(--color-text-primary);
  border-radius: var(--radius-xs);
  padding-inline: 2px;
  margin-inline: -2px;      /* the padding must not shift the glyphs around it */
  /* Redundancy, not decoration: a tint alone is exactly the signal a colour-blind reader
     loses, and this one carries meaning. */
  box-shadow: inset 0 -1px 0 var(--color-accent);
  /* A marked sentence usually wraps. Without this the ground and the radius are drawn once
     around the whole broken box instead of once per line. */
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
/* The surrounding paragraphs are deliberately dimmed. A marked line inside one is not
   context any more, so it comes back to full strength. */
.fb-reader__ctx .fb-mark { color: var(--color-text-primary); }

/* ── Numeric isolation utility — attach to any run holding a page number,
   a count or a ratio that would otherwise reorder inside Arabic. ─────────── */
.fb-num { direction: ltr; unicode-bidi: isolate; font-variant-numeric: tabular-nums; }

/* ── Reading coverage — the document as one bar, drawn inside the passage reader’s own
   overlay (.fb-cov IS .fb-reader, plus a track and these rows). A gap row is a session row
   with the SIZE where the topics go and a page RANGE in the same pill: same three columns,
   same hairline between them. ──────────────────────────────────────────── */
.fb-cov__hint {
  margin-block-end: var(--space-3);
  color: var(--color-text-muted);
  font: var(--weight-regular) 12.5px/1.7 var(--font-sans);
}
[dir="rtl"] .fb-cov__hint { font-family: var(--font-arabic); }
/* The whole document, at the width of the panel. Marks are placed with LOGICAL insets, so
   page one sits at the right in an Arabic panel and at the left in an English one — the
   direction the reader’s own book runs. left/right here would be wrong in exactly the shell
   this product is built for. */
.fb-cov__track {
  position: relative;
  block-size: 10px;
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
  overflow: hidden;
}
.fb-cov__mark {
  position: absolute;
  inset-block: 0;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
}
.fb-cov__stat {
  margin-block: var(--space-2) var(--space-4);
  padding-block-end: var(--space-3);
  border-block-end: var(--border-hairline) solid var(--fb-hair);
  color: var(--color-text-secondary);
  font: var(--weight-medium) 12.5px/1.6 var(--font-sans);
}
[dir="rtl"] .fb-cov__stat { font-family: var(--font-arabic); }
/* Either the heading over the gap list, or the one line that says there is no list. */
.fb-cov__gaps {
  margin-block-end: var(--space-2);
  color: var(--color-text-muted);
  font: var(--weight-semibold) 11.5px/1.6 var(--font-sans);
}
[dir="rtl"] .fb-cov__gaps { font-family: var(--font-arabic); }
.fb-cov__gaps:empty { display: none; }
.fb-cov__list { display: flex; flex-direction: column; }
.fb-gap {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 10px 8px;
  border-block-start: var(--border-hairline) solid var(--fb-hair);
}
.fb-gap:first-child { border-block-start: none; }
/* How big the untouched stretch is. .fb-num like the rail row’s own meta line, which carries
   the same "<count> <unit>" shape for the same reason. */
.fb-gap__main {
  flex: 1 1 auto; min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: var(--weight-regular) 12.5px/1.45 var(--font-sans);
  color: var(--color-text-secondary);
}
/* .fb-day__pg’s pill, unchanged: direction and tabular figures come from .fb-num. */
.fb-gap__pg {
  flex: 0 0 auto;
  max-inline-size: 45%;
  overflow: hidden; text-overflow: ellipsis;
  font-family: var(--font-mono);
  font-size: 10px; font-weight: var(--weight-semibold); line-height: 1.6;
  color: var(--color-text-muted);
  background: var(--color-bg-subtle);
  border-radius: var(--radius-pill);
  padding: 1px 8px;
  white-space: nowrap;
}
/* Hand this range to the search. .fb-day__go’s grammar exactly — same cell, revealed on
   hover, always reachable on touch — and the arrow is mirrored rather than redrawn for RTL. */
.fb-gap__go {
  flex: 0 0 auto;
  display: grid; place-items: center;
  inline-size: 26px; block-size: 26px;
  border-radius: 7px;
  color: var(--color-text-muted);
  opacity: 0;
  transition: opacity var(--fb-motion), background var(--fb-motion), color var(--fb-motion);
}
.fb-gap__go[hidden] { display: none; }
.fb-gap__go svg { inline-size: 15px; block-size: 15px; }
[dir="rtl"] .fb-gap__go svg { transform: scaleX(-1); }
.fb-gap:hover .fb-gap__go, .fb-gap__go:focus-visible { opacity: 1; }
.fb-gap__go:hover { background: var(--color-accent-soft); color: var(--color-accent); }
@media (pointer: coarse) { .fb-gap__go { opacity: 0.85; inline-size: 32px; block-size: 32px; } }


/* ── Responsive ───────────────────────────────────────────────────────────
   ≤900px: .fb-main stacks to a column and the rail becomes a horizontal strip
   that scrolls sideways INSIDE ITSELF (never the page). Collapsing is driven
   by [hidden] on .fb-rail__list — an attribute, so no new structural class. */
@media (max-width: 900px) {
  .fb-main { flex-direction: column; }
  .fb-rail {
    flex: 0 0 auto;
    inline-size: 100%; max-inline-size: 100%;
    border-inline-end: none;
    border-block-end: var(--border-hairline) solid var(--fb-hair);
  }
  .fb-rail__head { cursor: pointer; padding-block: 10px 8px; }
  .fb-rail__list {
    flex: 0 0 auto;
    display: flex; flex-direction: row; align-items: stretch; gap: 6px;
    max-block-size: none;
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none;
    padding: 2px 8px 8px;
  }
  .fb-rail__list::-webkit-scrollbar { display: none; }
  .fb-src { flex: 0 0 auto; inline-size: 216px; margin: 0; }
  .fb-rail__foot { padding-block: 8px; }
  .fb-up { flex-direction: row; align-items: center; gap: var(--space-2); }
  .fb-up__btn { inline-size: auto; flex: 1 1 auto; }
  .fb-up__status { flex: 0 1 auto; text-align: start; }
  .fb-thread { padding-block: var(--space-5); }
}
@media (max-width: 640px) {
  .fb-bar { padding-block: 8px; gap: var(--space-2); }
  .fb-bar__count { display: none; }
  .fb-bar__btn { padding: 6px 11px; font-size: 12px; }
  .fb-thread {
    padding-left:  max(var(--space-4), var(--safe-left));
    padding-right: max(var(--space-4), var(--safe-right));
  }
  .fb-sources { max-inline-size: 100%; }
  /* Reader goes near-full-screen. dvh restated after vh, as everywhere else. */
  .fb-reader { padding: var(--space-2); }
  .fb-reader__panel {
    inline-size: 100%; max-inline-size: 100%;
    max-height: 94vh;
    max-height: 94dvh;
    border-radius: var(--radius-lg);
  }
  .fb-reader__body { padding: var(--space-4); }
  /* 4212 pins every input to 16px on phones to stop the iOS focus-zoom; the
     `font:` shorthand above is (0,1,0) and would out-specify that (0,0,1)
     rule no matter the source order, so restate it here on purpose. */
  .fb-ask__in { font-size: 16px; }
}

/* ── Firas Brain — bidi for genuinely mixed answers ───────────────────────────
   A Brain answer is the hardest bidi case this app has: Arabic prose that quotes an
   English document, so one reply carries English headings ("Slide 19 – Blood – CBC
   baseline"), Arabic sentences, Latin abbreviations (Hb, HCT, AFI, previa), lab values,
   ratios and dates — often inside a single paragraph.

   Setting one base direction for the whole answer cannot work: whichever one is chosen,
   the other script's runs get reordered against it, which is what made trailing terms
   and numbers land in the wrong place. `unicode-bidi: plaintext` is the CSS form of
   dir="auto" applied PER BLOCK — every paragraph, heading, list item and cell resolves
   its own base direction from its first strong character. An English heading then lays
   out LTR and the Arabic paragraph under it lays out RTL, in the same answer, with
   neither disturbing the other.

   Scoped to #brainWorkspace so the main chat's rendering is untouched. */
#brainWorkspace .md > p,
#brainWorkspace .md > h1, #brainWorkspace .md > h2, #brainWorkspace .md > h3,
#brainWorkspace .md > h4, #brainWorkspace .md > h5, #brainWorkspace .md > h6,
#brainWorkspace .md > ul > li, #brainWorkspace .md > ol > li,
#brainWorkspace .md > blockquote,
#brainWorkspace .md td, #brainWorkspace .md th {
  unicode-bidi: plaintext;
  text-align: start;
}
/* A heading that resolves LTR inside an RTL answer must keep the Latin face; the global
   [dir="rtl"] rule at 223 would otherwise force the Arabic family onto pure-Latin text. */
/* brainAutoDir() stamps dir="auto" on every block, so these attribute selectors resolve per
   block. A pure-Latin heading in an Arabic answer gets the Latin face; an Arabic heading in
   an English answer gets the Arabic one. [dir="auto"] alone is not enough — the browser
   resolves it, so match on :dir() and keep the attribute form as the fallback. */
#brainWorkspace .md :is(h1,h2,h3,h4,h5,h6,p,li):dir(ltr) { font-family: var(--font-sans); }
#brainWorkspace .md :is(h1,h2,h3,h4,h5,h6,p,li):dir(rtl) { font-family: var(--font-arabic); }

/* Isolate every atomic mixed-script run so it can never be split or reordered by the
   surrounding paragraph: inline code, and the citation chip itself (its inner number is
   already isolated, but the chip is what sits in the text flow). */
#brainWorkspace .md code { unicode-bidi: isolate; }
.fb-cite { unicode-bidi: isolate; }

/* Bare Latin/number runs inside Arabic prose — "AFI 5.3", "16/8/2026", "7.73×10³/µL" —
   keep tabular figures so columns of lab values do not jitter between lines. */
#brainWorkspace .md { font-variant-numeric: tabular-nums; }

/* The passage reader has the same mixed-script problem: a cited passage is usually in the
   document's language while the chrome around it follows the UI. Each paragraph already
   carries its own dir from JS; plaintext makes the fallback correct too. */
.fb-reader__body p { unicode-bidi: plaintext; text-align: start; }

/* Arabic tables and preformatted blocks. A source table frequently arrives as an Arabic
   markdown table (the prompt now demands a real one) or, when extraction flattened it, as a
   `pre` block. brainAutoDir() stamps a direction on both; these rules make the typography
   follow, so Arabic table cells are not rendered in a Latin monospace face and the header row
   reads from the correct side. */
#brainWorkspace .md table { inline-size: 100%; border-collapse: collapse; }
#brainWorkspace .md :is(td, th):dir(rtl) { font-family: var(--font-arabic); text-align: right; }
#brainWorkspace .md :is(td, th):dir(ltr) { font-family: var(--font-sans);   text-align: left; }
#brainWorkspace .md pre:dir(rtl) { direction: rtl; }
#brainWorkspace .md pre:dir(rtl) code { font-family: var(--font-arabic); unicode-bidi: plaintext; }
/* A wide table must scroll inside its own box, never widen the answer column. */
#brainWorkspace .md table { display: block; max-inline-size: 100%; overflow-x: auto; }

/* Force-vision toggle in the sources rail. Sits under the Add-files button because it changes
   how the NEXT upload is read, not anything already indexed. */
.fb-up__ocr {
  display: flex; align-items: flex-start; gap: 8px;
  flex-wrap: wrap; min-inline-size: 0;
  margin-block-start: var(--space-2);
  cursor: pointer;
  font: 400 11.5px/1.45 var(--font-sans);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}
[dir="rtl"] .fb-up__ocr { font-family: var(--font-arabic); }
.fb-up__ocr input { flex: 0 0 auto; margin-block-start: 2px; accent-color: var(--color-accent); cursor: pointer; }
.fb-up__ocr:hover { color: var(--color-text-secondary); }

/* ── Firas Brain — layout corrections ─────────────────────────────────────────
   Measured problems this fixes, at 1440x900 and 375x812:

   DESKTOP: the thread was 900px wide but its content clamped to a 704px reading
   measure and left-aligned inside it, leaving ~156px of dead space down the right
   and three different content edges (bubble 544, prose 704, sources 704, container
   860). Everything now shares ONE centred column, so question, answer and sources
   line up on the same two edges.

   MOBILE: the sources rail was permanently expanded and ate 176px — 27% of the
   usable height — leaving 474px for the actual conversation. It now starts
   collapsed to its header strip and opens on tap (app.js sets the attribute).  */
#brainWorkspace { --fb-measure: 740px; }

/* One centred column for every turn, the pending row, and the empty state. */
.fb-thread > * {
  inline-size: 100%;
  max-inline-size: var(--fb-measure);
  margin-inline: auto;
}
/* The global prose clamp (styles.css:3740) narrows .md children to --reading-w, which
   left the answer text visibly narrower than the Sources card directly beneath it.
   Inside Brain the column IS the measure, so let the prose fill it. */
#brainWorkspace .msg-ai__body > .md > * { max-inline-size: none; }
#brainWorkspace .fb-sources { max-inline-size: none; }
.fb-thread { gap: var(--space-5); }

/* The question should read as a question — a quiet lead-in to the answer, not a
   competing block. Full-width in the column, so both edges agree with the answer. */
#brainWorkspace .msg-user__bubble {
  inline-size: 100%; max-inline-size: none;
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--fb-hair);
  border-radius: var(--radius-lg);
  padding: 10px 14px;
}

@media (max-width: 900px) {
  /* Collapsed rail: the header strip stays as the control, and the SOURCE LIST folds
     away to reclaim the 176px the conversation needed.

     What must NOT fold away is the uploader. This rule used to read
     `.fb-rail__list[hidden] + .fb-rail__foot { display: none }`, and .fb-rail__foot is
     the container holding the entire "Add files" control. The rail starts collapsed on
     every narrow viewport, so a first-time user opening Firas Brain on a phone saw an
     empty thread, an ask box, and a 31px strip — with no way to add the documents the
     whole product exists to read. It was not merely off-screen: display:none also put
     it out of reach of keyboard and screen-reader users entirely.

     The uploader is one ~60px row. It stays. */
  #brainWorkspace .fb-rail { flex: 0 0 auto; }
  #brainWorkspace .fb-rail__list[hidden] + .fb-rail__foot { display: block; }
  #brainWorkspace .fb-rail__head::after {
    content: "";
    inline-size: 7px; block-size: 7px;
    margin-inline-start: auto;
    border-inline-end: 2px solid currentColor;
    border-block-end: 2px solid currentColor;
    transform: rotate(45deg) translate(-2px, -2px);
    opacity: .55;
    transition: transform var(--dur-fast) var(--ease-out);
  }
  #brainWorkspace .fb-rail:has(.fb-rail__list[hidden]) .fb-rail__head::after {
    transform: rotate(-45deg) translate(-1px, 1px);
  }
  .fb-thread { padding-block: var(--space-4); gap: var(--space-4); }
}

/* Answer actions (copy). Quiet until hovered — the answer is the content, these are tools. */
.fb-acts {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-block-start: var(--space-3);
}
.fb-act {
  font: var(--weight-medium) 12px var(--font-sans);
  color: var(--color-text-muted);
  background: transparent;
  border: var(--border-hairline) solid var(--fb-hair);
  border-radius: var(--radius-pill);
  padding: 4px 12px;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .fb-act { font-family: var(--font-arabic); }
.fb-act:hover {
  color: var(--color-text-primary);
  background: var(--color-bg-subtle);
  border-color: var(--color-border-strong);
}
.fb-act:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fb-act:active { transform: scale(0.97); }

/* The PDF action reads as the primary export — gold, matching the document it produces.
   Both colours used to be hard-coded (#C9A227 at rest, #E4C468 on hover) with no light
   variant, so in the light theme the label sat at ~2.4:1 and then VANISHED on hover at
   ~1.6:1 — the button appeared to empty itself under the pointer. --plan-gold is the
   project's existing themed gold (#B8862A light / #D8B45A dark) and already solves this
   for the subscription chips; the hover state now comes from the theme, not a second literal. */
.fb-act--gold {
  color: var(--plan-gold);
  border-color: color-mix(in srgb, var(--plan-gold) 45%, transparent);
}
.fb-act--gold:hover:not(:disabled) {
  color: var(--plan-gold);
  background: color-mix(in srgb, var(--plan-gold) 14%, transparent);
  border-color: var(--plan-gold);
}
.fb-act:disabled { opacity: .55; cursor: default; transform: none; }

/* ══════════════════════════════════════════════════════════════════════════════
   COPY YOUR OWN QUESTION (Firas Brain)

   The answer has always had a copy bar; the question had nothing. In Brain the question is
   often the laboriously typed part, and re-running a sweep means copying it, changing a word
   and sending it again. `.msg-user__bubble *` sets user-select:none, so on a phone there was
   no way to get the text back at all.

   Hidden until the turn is hovered or the button is focused, so it never competes with the
   answer — but always reachable by keyboard, which is why it fades rather than being display:none.
   ══════════════════════════════════════════════════════════════════════════════ */
/* IN FLOW, NOT FLOATED OVER THE BUBBLE. Absolute positioning was the first instinct and it was
   wrong: the user bubble spans the full column width (measured 600px of a 600px turn), so there
   is no margin to sit in — the button landed on top of the text. A row underneath costs 26px,
   overlaps nothing, needs no direction-specific rule, and matches the action row the ANSWER
   already has. */
.fb-umsg-acts {
  display: flex;
  justify-content: flex-end;
  margin-block-start: 4px;
  block-size: 26px;
}
.fb-umsg-copy {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 26px; block-size: 26px;
  padding: 0;
  color: var(--color-text-muted);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
/* :focus as well as :focus-visible — a keyboard Tab matches both, but a programmatic .focus()
   matches only :focus, and a control that cannot be revealed under test cannot be verified. */
.fb-thread .turn--user:hover .fb-umsg-copy,
.fb-umsg-copy:focus,
.fb-umsg-copy:focus-visible { opacity: 1; }
.fb-umsg-copy:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }
.fb-umsg-copy:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fb-umsg-copy:active { transform: translateY(1px); }
/* Touch has no hover, so the affordance would never appear — show it permanently, quietly. */
@media (hover: none) {
  .fb-umsg-copy { opacity: .55; }
}

/* The identity control sits beside the export button and carries a live swatch of the
   chosen paper. It is deliberately quieter than --gold: it configures the export, it is
   not the export. */
.fb-act--theme { display: inline-flex; align-items: center; gap: 7px; }
.fb-act__swatch {
  inline-size: 13px; block-size: 13px; flex: 0 0 auto;
  border-radius: 3px;
  border: 1.5px solid currentColor;   /* overridden inline with the identity's accent */
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12);
}

/* ══════════════════════════════════════════════════════════════════════════════
   PDF IDENTITY PICKER

   Each card is a miniature of the sheet it produces, not a colour chip: the thing being
   chosen is a document, so the chooser shows a document. The preview is built from the
   identity's own paper/ink/accent, which is why almost every colour here arrives inline.
   ══════════════════════════════════════════════════════════════════════════════ */
.bpk-card-wrap { max-inline-size: 720px; }
.bpk-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 12px;
  max-block-size: 62vh;
  overflow-y: auto;
  padding: 2px;                       /* so a focus ring is not clipped by the scroll box */
}
.bpk-card {
  display: flex; flex-direction: column; align-items: stretch; gap: 7px;
  padding: 8px; text-align: start;
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.bpk-card:hover { border-color: var(--color-border-strong); box-shadow: var(--shadow-md, 0 6px 18px rgba(0,0,0,.10)); transform: translateY(-1px); }
.bpk-card:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.bpk-card.is-on { border-color: var(--color-accent); box-shadow: 0 0 0 1px var(--color-accent); }

/* The miniature sheet. A4 is 1:1.414, so the preview keeps that ratio — a preview in the
   wrong proportion misrepresents how much of the page the title actually occupies. */
.bpk-sheet {
  display: block; position: relative;
  aspect-ratio: 1 / 1.414;
  border-radius: 3px;
  padding: 13px 11px;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px rgba(128, 128, 128, .22);
}
.bpk-kick { display: block; inline-size: 26px; block-size: 3px; border-radius: 2px; opacity: .9; }
.bpk-ttl  { display: block; margin-block-start: 9px; font-size: 13px; font-weight: 700; line-height: 1.1; }
.bpk-rule { display: block; inline-size: 22px; block-size: 2px; margin-block: 6px 8px; }
.bpk-ln   { display: block; block-size: 2px; border-radius: 2px; opacity: .34; margin-block-end: 5px; }
.bpk-ln--s { inline-size: 62%; }
.bpk-name { font: var(--weight-medium) 12.5px var(--font-sans); color: var(--color-text-primary); }
[dir="rtl"] .bpk-name, [dir="rtl"] .bpk-desc { font-family: var(--font-arabic); }
.bpk-desc { font: var(--weight-regular) 10.5px/1.45 var(--font-sans); color: var(--color-text-muted); }

@media (max-width: 560px) {
  .bpk-grid { grid-template-columns: repeat(auto-fill, minmax(124px, 1fr)); max-block-size: 56vh; }
}

/* Touch sizing for the Brain action row.
   `.fb-act` used to compute to ~24px tall: the `font:` shorthand omits line-height, so
   it resets to `normal` (~14.4px) and 4px padding either side plus hairlines lands at
   half the 44px minimum. These three buttons — Copy, Copy with page references, Export
   PDF — are how a Brain answer leaves the app, and a mis-tap fires a real PDF render.
   The row already wraps, so extra height costs nothing horizontally. */
/* `(pointer: coarse), (max-width: 560px)` — BOTH conditions, matching the rule the main
   chat composer already uses. Coarse-pointer alone misses a narrow desktop window, and
   width alone misses a tablet; the two products must not disagree about how big a thumb is. */
@media (pointer: coarse), (max-width: 560px) {
  .fb-act {
    min-block-size: 44px;
    display: inline-flex; align-items: center;
    padding-inline: var(--space-4);
  }
  /* Same bump for the composer's send/stop control, which is also the ONLY way to
     cancel a long answer. It was a hard 38px `block-size` — not a min — while the
     identical control on the main chat composer was deliberately raised to 44px. */
  .fb-ask__send { min-inline-size: 44px; min-block-size: 44px; block-size: auto; }
  .fb-ask__mic  { min-inline-size: 44px; min-block-size: 44px; block-size: auto; }
  .fb-src__x    { inline-size: 36px; block-size: 36px; }
  .fb-src__pin  { inline-size: 36px; block-size: 36px; }
  .fb-reader__x { inline-size: 40px; block-size: 40px; }
  .fb-reader__cite { inline-size: 40px; block-size: 40px; }
  /* Four of these stack in a scrolling band on a phone. 34px is the size the row wants and
     the size the block two rules up exists to refuse — .fb-act is here for the same reason. */
  .fb-bib__copy { min-block-size: 40px; display: inline-flex; align-items: center; padding-inline: var(--space-4); }
}

/* ── Firas Code — live progress inside the conversation ───────────────────────
   The engine narrates itself here instead of in the .cw-diff overlay: a headline for the
   current phase, then one line per file carrying its own state. It is appended to the
   transcript and STAYS there when the run ends, so the conversation holds the record of
   what was actually done. Appended last, per the .cw source-order convention. */
.cw-live {
  border: var(--border-hairline) solid var(--cwv-hair, var(--color-border-hairline));
  border-radius: var(--radius-lg);
  background: var(--color-bg-subtle);
  padding: 10px 12px;
  margin-block: 8px;
  font: 400 12.5px/1.6 var(--font-sans);
}
[dir="rtl"] .cw-live { font-family: var(--font-arabic); }
.cw-live.is-fail { border-color: color-mix(in srgb, var(--color-error) 40%, transparent); }
.cw-live.is-done { opacity: .92; }

.cw-live__head { display: flex; align-items: center; gap: 8px; min-inline-size: 0; }
.cw-live__head strong {
  font: var(--weight-semibold) 12.5px var(--font-sans);
  color: var(--color-text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
[dir="rtl"] .cw-live__head strong { font-family: var(--font-arabic); }
.cw-live__t {
  font-size: 11.5px; color: var(--color-text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-inline-size: 0;
}
.cw-live__sp { flex: 1 1 auto; }
.cw-live__btn {
  flex: 0 0 auto;
  font: var(--weight-medium) 11px var(--font-sans);
  color: var(--color-text-muted);
  background: transparent;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  padding: 2px 10px; cursor: pointer;
}
[dir="rtl"] .cw-live__btn { font-family: var(--font-arabic); }
.cw-live__btn:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }

.cw-live__g { flex: 0 0 auto; inline-size: 12px; text-align: center; font-size: 11px; line-height: 1.6; }
.cw-live__g--ok   { color: var(--color-accent); }
.cw-live__g--fail { color: var(--color-error); }
.cw-live__spin {
  flex: 0 0 auto;
  inline-size: 11px; block-size: 11px; border-radius: 50%;
  border: 1.6px solid var(--color-border-strong);
  border-top-color: var(--color-accent);
  animation: cwLiveSpin .7s linear infinite;
}
@keyframes cwLiveSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .cw-live__spin { animation: none; } }
/* A stopped run keeps its record and offers to continue from where it got to. */
.cw-live.is-stopped { border-color: color-mix(in srgb, var(--color-accent) 34%, transparent); }
.cw-live__resume {
  color: var(--color-accent);
  border-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
}
.cw-live__resume:hover {
  color: var(--color-accent);
  background: var(--color-accent-soft);
  border-color: var(--color-accent);
}

/* ════════════════════════════════════════════════════════════════════════════
   MOTION — the instrument reading a value
   Appended last on purpose: this file wins on source order at equal specificity.

   One authored idea, applied consistently, instead of an effect per component: an
   instrument reports a value by a GRADUATION arriving at it. So selection across the whole
   app — a conversation, a product, a model tier — is a brass tick that grows into place on
   the reading edge, and hover is that same tick previewed faintly. Nothing travels, nothing
   scales, nothing bounces; the only thing that moves is the thing being measured.

   Cost discipline:
   · Only `transform` and `opacity` are animated — both composite off the main thread, so
     none of this competes with streaming, KaTeX or highlight.js for frame time.
   · Every element animates FROM a visible resting state. If a stylesheet or a CDN fails,
     the UI is simply static — never invisible.
   · No `will-change` anywhere: it is a targeted hint for a known-expensive animation, not a
     baseline, and left on at rest it costs memory on every row of a long list.
   · Durations stay inside 120–320ms. Anything slower reads as lag on a control you clicked.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── The graduation tick ───────────────────────────────────────────────────── */
.chat-item, .product-switch, .tier-switch__btn { position: relative; }

.chat-item::before,
.product-switch::before {
  content: "";
  position: absolute;
  inset-block: 6px;
  inset-inline-start: 0;
  inline-size: 1px;                 /* a hairline graduation — never a slab */
  background: var(--color-line);
  transform: scaleY(0);
  transform-origin: 50% 50%;
  opacity: 0;
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
  pointer-events: none;
}
.chat-item:hover::before,
.product-switch:hover::before { transform: scaleY(0.45); opacity: 0.5; }
.chat-item.is-active::before   { transform: scaleY(1);    opacity: 1; }

/* The tier strip reads along the bottom edge — it is a horizontal scale. */
.tier-switch__btn::after {
  content: "";
  position: absolute;
  inset-inline: 22%;
  inset-block-end: 3px;
  block-size: 1px;
  background: var(--color-line);
  transform: scaleX(0);
  opacity: 0;
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
  pointer-events: none;
}
.tier-switch__btn:hover::after      { transform: scaleX(0.5); opacity: 0.45; }
.tier-switch__btn.is-active::after,
.tier-switch__btn[aria-selected="true"]::after { transform: scaleX(1); opacity: 1; }

/* ── Chrome that catches light ─────────────────────────────────────────────── */
/* Engraved metal brightens at its edge before it changes colour. One inset hairline is the
   whole effect — cheaper than a shadow and it never blurs text underneath. */
.icon-btn, .btn-newchat, .landing__feature, .fb-act {
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.icon-btn:hover, .btn-newchat:hover {
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--color-line) 34%, transparent);
}
/* The old rule nudged every conversation row 2px sideways on hover. Rows are a LIST — when
   one shifts, the eye reads the whole column as unstable. The tick reports hover instead. */
.chat-item:hover, [dir="rtl"] .chat-item:hover { transform: none; }

/* Feature cards lift by their shadow, not by moving. */
.landing__feature:hover {
  border-color: color-mix(in srgb, var(--color-line) 40%, var(--color-border-hairline));
  box-shadow: 0 2px 10px rgba(0,0,0,0.30);
  transform: none;
}

/* ── Focus: always visible, never animated away ────────────────────────────── */
/* A focus ring that fades in is a focus ring a keyboard user out-runs. It appears at once;
   only its colour eases. */
:where(button, a, input, textarea, select, [tabindex]):focus-visible {
  transition: outline-color var(--dur-fast) var(--ease-out);
}

/* ── Menus and overlays ────────────────────────────────────────────────────── */
/* Panels resolve from slightly compressed to true size — the way a mechanism seats — rather
   than sliding in from an edge. 160ms: long enough to read as deliberate, short enough that
   a second click is never queued behind it. */
.tools-menu__panel:not([hidden]),
.mode-select__menu:not([hidden]),   /* was .mode-select__panel — a dead selector; the JS emits __menu */
.product-menu:not([hidden]) {
  animation: seat var(--dur-base) var(--ease-out) both;
  transform-origin: top center;
}
@keyframes seat {
  from { opacity: 0; transform: scale(0.985) translateY(-2px); }
  to   { opacity: 1; transform: none; }
}

/* ── Streaming caret ───────────────────────────────────────────────────────── */
/* Deliberately NOT restyled here. The caret already exists further up this file, already
   animates opacity only, and already reads --color-accent, which now resolves to brass.
   Re-declaring it would have added a seventh styling era to a file whose stacked eras are
   the exact defect the audit flagged — a duplicate that looks like an improvement and is
   really just one more rule for the next reader to reconcile. Nothing to do. */

/* ════════════════════════════════════════════════════════════════════════════
   THE BEAM — the chosen mark, and its streaming animation
   Appended last: this file resolves equal specificity by source order.
   ══════════════════════════════════════════════════════════════════════════ */

/* The Beam is a SOLID green letterform, so it needs no tile behind it. The old marks were
   drawn in --color-on-accent on a filled accent square; painting a solid mark on a solid
   square of its own colour would erase it. Tile removed, mark drawn in the accent itself. */
/* `.nib.is-aurora` is specificity (0,2,0) and re-declares the gradient tile, so a plain
   `.nib` reset loses to it and the landing mark rendered as a green Beam on a green tile —
   invisible. Both selectors are listed, at matching specificity.

   `.product-agent .nib` is the SAME trap, found later: it is also (0,2,0), so inside Firas
   Agent the plain `.nib` reset (0,1,0) lost and the green→blue gradient tile survived, with
   the accent-coloured Beam drawn on top of it. Green mark on a green-blue tile is the
   reported "الشعار ما مبين زين" — the mark was there, just with almost no contrast against
   its own background. Listing it here removes the tile in Agent like everywhere else.
   The product-switch DOT keeps its gradient: it is a status dot, not a logo. */
.nib,
.nib.is-aurora,
.product-agent .nib,
.msg-ai__avatar,
.msg-ai__avatar--agent {
  background: none;
  box-shadow: none;
  border-radius: 0;
  overflow: visible;
}
.nib .fmark,
.msg-ai__avatar .fmark {
  inline-size: 100%; block-size: 100%;
  display: block;
  color: var(--color-accent);          /* the stem + upper arm inherit this */
}
.fmark__stem,
.fmark__arm--1 { fill: currentColor; }
.fmark__arm--2 { fill: var(--color-accent-deep); }

/* The wordmark sits beside a mark that is now taller than it is wide, so give the lockup a
   touch more breathing room than the old square tile needed. */
.logo-lockup { gap: var(--space-3); }

/* ── Streaming: the mark emits while the answer arrives ──────────────────────
   The behaviour the owner asked for: the mark beside a reply is alive while the model is
   generating, then settles when it stops.

   Built from the letterform's own logic rather than a generic spinner — the arms TAPER, so
   they animate as light travelling outward along each arm, staggered, while the stem holds
   steady. Nothing spins, nothing jumps.

   Cost: `transform` and `opacity` only, both composited off the main thread, so this never
   competes with the markdown/KaTeX/highlight work happening on the same frames.
   `transform-box: fill-box` is required — without it an SVG transform-origin resolves
   against the whole viewBox and the arms would swing instead of scale. */
.fmark__arm {
  transform-box: fill-box;
  transform-origin: left center;
}
[dir="rtl"] .fmark__arm { transform-origin: right center; }

.msg-ai.is-generating .fmark__arm--1 { animation: beam-emit 1.5s var(--ease-inout) infinite; }
.msg-ai.is-generating .fmark__arm--2 { animation: beam-emit 1.5s var(--ease-inout) 0.22s infinite; }
.msg-ai.is-generating .fmark__stem   { animation: beam-hold 1.5s var(--ease-inout) infinite; }

@keyframes beam-emit {
  0%, 100% { opacity: 0.38; transform: scaleX(0.82); }
  45%      { opacity: 1;    transform: scaleX(1); }
}
@keyframes beam-hold {
  0%, 100% { opacity: 0.72; }
  45%      { opacity: 1; }
}

/* Settling. When generation ends the class comes off and the arms return to rest through
   the same easing — no snap, and no lingering animation burning frames on a finished reply. */
.fmark__arm, .fmark__stem {
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}

/* Reduced motion: the mark still says "working", but by breathing rather than travelling.
   Freezing it entirely would remove the only signal that a long answer is still coming. */
@media (prefers-reduced-motion: reduce) {
  .msg-ai.is-generating .fmark__arm--1,
  .msg-ai.is-generating .fmark__arm--2,
  .msg-ai.is-generating .fmark__stem {
    animation-name: rm-working !important;
    animation-duration: 1.6s !important;
    animation-iteration-count: infinite !important;
    transform: none !important;
  }
}
:root[data-motion="off"] .msg-ai.is-generating .fmark__arm--1,
:root[data-motion="off"] .msg-ai.is-generating .fmark__arm--2,
:root[data-motion="off"] .msg-ai.is-generating .fmark__stem {
  animation-name: rm-working !important;
  animation-duration: 1.6s !important;
  animation-iteration-count: infinite !important;
  transform: none !important;
}

/* ── Product transition ─────────────────────────────────────────────────────
   Tunes the cross-fade the View Transitions API performs when setProduct() swaps the shell.
   Both snapshots animate on the compositor, so this costs no layout and no paint.
   The old view leaves slightly faster than the new one arrives — an even cross-fade dips
   through a visibly empty middle, while an offset one reads as one thing becoming another. */
::view-transition-old(root) {
  animation: vt-out 170ms var(--ease-out) both;
}
::view-transition-new(root) {
  animation: vt-in 260ms var(--ease-out) both;
}
@keyframes vt-out { to   { opacity: 0; transform: scale(0.994); } }
@keyframes vt-in  { from { opacity: 0; transform: scale(1.006); } }

/* The setProduct() call is already gated on motionEnabled(), so a reduced-motion visitor
   never starts a transition. This is belt-and-braces for a transition begun elsewhere. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) { animation: none !important; }
}
:root[data-motion="off"] ::view-transition-old(root),
:root[data-motion="off"] ::view-transition-new(root) { animation: none !important; }

/* ── Drawn status icons (were typed glyphs) ─────────────────────────────────
   The Firas Code progress column and the product-menu tick used "✓ ✗ ▸ ·" as characters.
   A character inherits the UI font, so the column jumped between Latin and Arabic metrics
   and each mark landed on a different baseline at a different weight. These are SVG now and
   need a box rather than a font-size. */
.cw-live__g {
  flex: 0 0 auto;
  inline-size: 13px; block-size: 13px;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 0;
}
.cw-live__g svg { inline-size: 100%; block-size: 100%; display: block; }

.product-menu__check {
  inline-size: 16px; block-size: 16px;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 0;
}
.product-menu__check svg { inline-size: 100%; block-size: 100%; display: block; }


/* ── Overlays: one seating motion for all nine ──────────────────────────────
   Every modal in the app (memory, plans, redeem, admin, settings, Brain reader…) is built
   from the same .mem-overlay / .mem-card scaffold, copy-pasted nine times. The scrim already
   fades; the card only ever had `transform:none` applied on open, with no FROM state — so it
   appeared instantly inside a fading scrim, which reads as a jump.

   Giving the card a resting transform means the existing `.is-open` rule finally has
   something to animate away from — no JS change, all nine inherit it at once. */
.mem-card {
  transform: translateY(8px) scale(0.985);
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
  opacity: 0;
}
.mem-overlay.is-open .mem-card { transform: none; opacity: 1; }

/* The scrim resolves a touch slower than the card so the card leads and the background
   settles behind it, rather than both arriving flat together. */
.mem-overlay { transition: opacity var(--dur-base) var(--ease-out); }

/* Reduced motion: the card still arrives, it just does not travel. Removing the opacity
   change too would make a modal appear with no transition at all, which is more jarring,
   not less. */
@media (prefers-reduced-motion: reduce) {
  .mem-card { transform: none !important; }
}
:root[data-motion="off"] .mem-card { transform: none !important; }

/* ── iOS: never zoom the page just because someone tapped a field ───────────
   Mobile Safari zooms the viewport when a focused input computes below 16px. It is not a
   preference and there is no way to opt out per-element — the ONLY reliable fix is to keep
   every field at 16px or larger.

   Measured before this rule, at 375px: 5 of 7 reachable fields were under 16px, and all four
   sign-in fields were exactly 15px — which is why the login screen was the worst offender.

   Deliberately NOT solved with `maximum-scale=1` / `user-scalable=no` in the viewport meta.
   That does stop the zoom, but it also removes pinch-to-zoom entirely, which is a real
   accessibility loss for anyone who needs to magnify text — trading a small annoyance for a
   barrier. 16px costs nothing and leaves manual zoom working.

   Scoped to `(pointer: coarse)` so it reaches phones AND tablets (an iPad is ~768-1024px
   wide and still auto-zooms), plus a narrow-width branch, matching the convention already
   used by the touch-target rules in this file. A bare element selector is intentional: it
   catches fields in surfaces that do not exist until their product renders — Brain's ask
   box, the Code AI bar, settings, admin, redeem — without having to enumerate them. */
@media (pointer: coarse), (max-width: 560px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  textarea,
  select {
    font-size: 16px;
  }
  /* The search field was 13px and sets its own height from that size; keep it visually
     compact now that the text is larger. */
  .sidebar__search input { padding-block: 9px; }
}

/* ── Buttons are buttons, not text ──────────────────────────────────────────
   On iOS a press-and-hold anywhere selectable raises the native selection / Copy callout.
   The action row under a reply (Copy · Regenerate · Retry with Firas Max) was being caught
   by it, so a normal tap selected the label instead of firing the button.

   Applied to EVERY control in the app rather than to that one row: any button, any product,
   any surface added later. Text the user might legitimately want to copy — message bodies,
   code, cited passages — is deliberately untouched. */
button,
[role="button"],
.icon-btn,
.msg-actions button,
.fb-act,
.cw-bar__btn,
.cw-live__btn,
.tier-select__opt,
.tier-switch__btn,
.product-menu__item,
.mode-select__item,
.tools-menu__item,
.plan-cta,
.chat-item__actions button {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;   /* suppresses the long-press Copy/Look Up sheet on iOS */
}
/* …but never gag the content itself. */
.md, .msg-user__bubble, .fb-reader__hit, .code-card, pre, code {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}

/* ── The mark, animating harder ─────────────────────────────────────────────
   The first pass was too polite to notice: arms moving between 0.38 and 1.0 opacity across
   1.5s reads as "static" at the 24px the avatar actually renders at.

   Now the WHOLE mark breathes on a circular arc while the arms emit inside it — the mark
   swells, eases back, and the two arms travel through it out of phase. Still transform and
   opacity only, still compositor-only, but the amplitude is now large enough to read at
   thumbnail size. */


.msg-ai.is-generating .fmark__arm--1 { animation: beam-emit 1.25s var(--ease-inout) infinite; }
.msg-ai.is-generating .fmark__arm--2 { animation: beam-emit 1.25s var(--ease-inout) 0.30s infinite; }
.msg-ai.is-generating .fmark__stem   { animation: beam-hold 1.25s var(--ease-inout) infinite; }
@keyframes beam-emit {
  0%, 100% { opacity: 0.22; transform: scaleX(0.62); }
  46%      { opacity: 1;    transform: scaleX(1); }
}
@keyframes beam-hold {
  0%, 100% { opacity: 0.55; }
  46%      { opacity: 1; }
}

/* ── "Thinking…" before the first character ─────────────────────────────────
   Between pressing send and the first text arriving there was nothing on screen at all —
   and on this server that gap is the WHOLE wait, because replies arrive in one chunk.
   The label is driven by a data attribute so it stays localized. */
.msg-ai.is-thinking .msg-ai__body::before {
  content: attr(data-thinking);
  display: inline-flex; align-items: center;
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
  animation: rm-working 1.5s var(--ease-inout) infinite;
}
[dir="rtl"] .msg-ai.is-thinking .msg-ai__body::before { font-family: var(--font-arabic); }

@media (prefers-reduced-motion: reduce) {
  .msg-ai.is-generating .fmark { animation: none !important; transform: none !important; }
}
:root[data-motion="off"] .msg-ai.is-generating .fmark { animation: none !important; transform: none !important; }

/* ════════════════════════════════════════════════════════════════════════════
   GENERATION INDICATOR — radiating, in place, trailing the text
   Replaces the earlier orbit. Three corrections, all requested:
     · NO rotation and no swing. The mark never turns.
     · The motion travels OUTWARD FROM THE CENTRE — a radial emit, not a wobble.
     · It rides at the END OF THE TEXT and moves as the text grows, then stops.
   ══════════════════════════════════════════════════════════════════════════ */

/* The avatar beside the reply is now completely still — the living part belongs at the
   text tip, where the writing is actually happening. */
.msg-ai.is-generating .fmark { animation: none; transform: none; }

/* The tip itself: the Beam, drawn as a MASK so it takes `background-color` and therefore
   follows the theme's accent — a plain SVG data-URI would freeze one colour into the file. */
.beam-tip {
  display: inline-block;
  inline-size: 0.92em; block-size: 0.92em;
  margin-inline-start: 0.28em;
  vertical-align: -0.14em;
  background-color: var(--color-accent);
  -webkit-mask: var(--beam-mask) center / contain no-repeat;
          mask: var(--beam-mask) center / contain no-repeat;
  /* Scale about the centre = the shape grows outward in every direction at once. */
  transform-origin: 50% 50%;
  animation: beam-radiate 1.15s var(--ease-inout) infinite;
  pointer-events: none;
}
:root {
  --beam-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath d='M18 10h6.5v44H18Z'/%3E%3Cpath d='M24.5 10H50l-5 8.5H24.5Z'/%3E%3Cpath d='M24.5 27H42l-4.5 8.5H24.5Z'/%3E%3C/svg%3E");
}
@keyframes beam-radiate {
  0%, 100% { transform: scale(0.68); opacity: 0.30; }
  48%      { transform: scale(1.10); opacity: 1; }
}

/* A second, softer ring pushing outward behind it — this is what reads as "radiating"
   rather than "throbbing". Pure transform+opacity, no blur, no shadow. */
.beam-tip::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--color-accent);
  opacity: 0;
  animation: beam-halo 1.15s var(--ease-out) infinite;
}
.beam-tip { position: relative; }
@keyframes beam-halo {
  0%   { transform: scale(0.5); opacity: 0.28; }
  70%  { transform: scale(1.9); opacity: 0; }
  100% { transform: scale(1.9); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .beam-tip { animation: rm-working 1.6s var(--ease-inout) infinite; transform: none !important; }
  .beam-tip::after { display: none; }
}
:root[data-motion="off"] .beam-tip { animation: rm-working 1.6s var(--ease-inout) infinite; transform: none !important; }
:root[data-motion="off"] .beam-tip::after { display: none; }

/* The avatar is STILL. Two earlier layers animate its arms and stem during generation; those
   selectors target the arm elements, so a rule on the parent .fmark cannot override them.
   Stopped explicitly here, last in source order.
   Rationale: the generation indicator now rides at the END OF THE TEXT, where the writing is
   happening and where it can follow the text as it grows. An avatar pinned at the top of the
   reply cannot do that, and running both puts two competing pulses on screen for one event. */
.msg-ai.is-generating .fmark__arm--1,
.msg-ai.is-generating .fmark__arm--2,
.msg-ai.is-generating .fmark__stem,
.msg-ai.is-generating .fmark {
  animation: none !important;
  transform: none !important;
  opacity: 1 !important;
}

/* ── The mark sits BELOW the text, and descends with it ─────────────────────
   Correction: it was riding inline at the end of the last sentence. The owner asked for
   Claude Code's placement — the mark on its OWN LINE beneath the answer, moving down the
   page as the answer grows, then stopping when generation ends.

   The tip is now a SIBLING of .md inside .msg-ai__body (see placeBeamTip), not a node
   inside the rendered markdown. That is what makes the position stable: its indent comes
   from .md's box, which does not change, instead of from whichever paragraph or list item
   happened to be last that frame. It is also created once per turn and never replaced, so
   the animation below actually runs to completion rather than restarting every paint.

   Motion is deliberate: a pure centre-out radiate. No rotation, no left/right travel. */
/* TWO ANIMATIONS MUST NOT SHARE A PROPERTY.
   This carried `beam-radiate` (infinite, animates transform) and `beam-tip-in` (260ms,
   `both`, also animated transform). Later name wins on a conflicting property, and `both`
   retains the end state forever — so 260ms after the mark appeared, `transform: scale(1)`
   was pinned permanently and the radiate pulse stopped dead. The mark looked completely
   static, which is exactly what was reported: "مابيها انميشن حالياً".

   The entrance now touches OPACITY ONLY. `beam-radiate` owns `transform` alone, with
   nothing competing for it, so the pulse runs for as long as the answer is being written.

   Size: 1.4em, up from 1.05em — Claude's writing indicator is a clearly-visible mark, not
   a speck. At the assistant body's 16px this renders ~22px. */
.beam-tip {
  display: block;
  inline-size: 1.4em; block-size: 1.4em;
  margin-inline: 0;
  margin-block-start: 0.5em;
  vertical-align: baseline;
  /* ONE animation, deliberately. beam-radiate already animates BOTH transform and opacity,
     so any second animation here would collide on one of them and — with a `both` fill —
     freeze it permanently. That is the bug this replaced, and a separate entrance buys
     nothing: the cycle begins at scale(0.68)/opacity(0.30), which IS a soft appearance. */
  animation: beam-radiate 1.15s var(--ease-inout) infinite;
}
[dir="rtl"] .beam-tip { margin-inline: 0; }

/* The halo ring reads more strongly at this size, so it is scaled to match. */
.beam-tip::after { animation: beam-halo 1.25s var(--ease-out) infinite; }

/* Reduced motion: keep the mark (it carries real state — "still writing") but drop the
   travel, leaving a calm opacity breathe. Gentler, not zero. */
@media (prefers-reduced-motion: reduce) {
  .beam-tip { animation: rm-working 1.6s var(--ease-inout) infinite; transform: none !important; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   HELD IMAGES — a cross-origin image inside a reply does not fetch itself.
   See installPurifyHooks() in app.js: the src is parked in data-src so a URL that
   smuggles conversation text out cannot fire on paint. This makes the parked state
   look like an offer rather than a failure, and names the host so the choice is real.
   ══════════════════════════════════════════════════════════════════════════════ */
img.md-img-held {
  display: block;
  inline-size: min(100%, 320px);
  min-block-size: 92px;
  border: 1px dashed var(--color-border-strong, rgba(0,0,0,.18));
  border-radius: var(--radius-md, 10px);
  background:
    radial-gradient(closest-side, var(--color-accent-soft) 0%, transparent 100%),
    var(--color-surface-sunken, rgba(0,0,0,.03));
  cursor: pointer;
  position: relative;
  transition: border-color .18s var(--ease-out), background-color .18s var(--ease-out);
}
img.md-img-held::after {
  content: "🖼  اضغط لعرض الصورة — " attr(data-host);
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-3, 12px);
  font: var(--weight-medium, 500) 0.82rem/1.45 var(--font-arabic, system-ui);
  color: var(--color-text-muted);
  text-align: center;
  word-break: break-all;
}
img.md-img-held:hover,
img.md-img-held:focus-visible {
  border-color: var(--color-accent);
  background-color: var(--color-accent-soft);
}
img.md-img-held:hover::after { color: var(--color-accent); }
@media (prefers-reduced-motion: reduce) { img.md-img-held { transition: none; } }

/* ══════════════════════════════════════════════════════════════════════════════
   WELCOME — OPTICAL CENTRING

   The greeting was pinned to the top of the scroll area: on a 812px phone it sat at
   y≈56 with 408px of nothing between it and the composer. Two things went wrong at once,
   and they share a cause.

   · The empty state read as "the page failed to load the rest", because the eye expects
     content to be balanced in the space it is given, not stacked against the ceiling.
   · The halo is positioned relative to .welcome, so with .welcome only 148px tall and
     glued to the top, the glow spilled downward and ended up centred roughly 250px BELOW
     the mark it exists to sit behind. That detached green blob floating in dead space is
     the "نشاز" that was reported twice.

   Centring the block in its scroll area fixes both: the greeting lands in the optical
   centre above the composer, and the halo — being anchored to it — travels with it and
   sits behind the mark again, which is the only place it ever made sense.

   `min-block-size` rather than `block-size`, so a taller empty state (starter chips, a
   notice) still grows and scrolls instead of being clipped. `.hidden` carries !important
   so the thread view is unaffected.
   ══════════════════════════════════════════════════════════════════════════════ */
#welcome {
  min-block-size: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* The 64px top padding existed only to push the greeting off the ceiling. Centring does
   that now, and keeping it would just bias the block downward. A small remainder is left
   so the mark never kisses the header on a short viewport. */
#welcome > .welcome {
  padding-block-start: var(--space-6);
  padding-block-end: var(--space-6);
  inline-size: 100%;
}
@media (max-width: 560px) {
  #welcome > .welcome { padding-block-start: var(--space-4); }
}

/* HALO ANCHORING.
   `inset-block-start: -26%` resolves against .welcome's own HEIGHT — a box whose height is
   whatever the greeting happens to be, and which changed the moment the block was centred.
   The result was a 435px glow whose centre sat ~136px BELOW the mark it is supposed to be
   radiating from: a detached green blob sitting in empty space, reported twice as "نشاز".

   Centring it on the block instead removes the dependency entirely. The glow now sits
   behind the mark and the greeting at every viewport and every string length, because
   50%/-50% is self-correcting in a way that a tuned negative percentage never is. */
.welcome__halo {
  inset-block-start: 50%;
  translate: -50% -50%;
}

/* RTL — THE HALO WAS OFF-SCREEN IN ARABIC.

   `inset-inline-start: 50%` + `translate: -50%` centres correctly in LTR only. In RTL
   `inset-inline-start` maps to `right`, so the box is measured from the opposite edge and
   the -50% translate then pushes it further the SAME way instead of back. Measured on a
   375px viewport with dir="rtl": the halo's centre sat at x = -248 while the mark sat at
   x = 337 — the glow was almost entirely off the left edge of the screen, in the app's
   primary language. This predates the centring work; it has been wrong the whole time,
   which is the likeliest reason the greeting kept reading as "نشاز" no matter what the
   gradient itself was doing.

   `inset-inline: 0` + `margin-inline: auto` centres an absolutely-positioned box with a
   definite width in BOTH directions, with no translate to get the sign wrong. */
.welcome__halo {
  inset-inline: 0;
  margin-inline: auto;
  translate: 0 -50%;
}

/* The secondary highlight sat at a PHYSICAL `62% 62%`, so it stayed on the same side of
   the screen when the layout mirrored — the light appeared to come from the far side of
   the text in Arabic. Driving its x through a variable lets it follow the reading
   direction, so the glow always falls on the side the greeting starts from. */
.welcome__halo { --halo-hx: 62%; }
[dir="rtl"] .welcome__halo { --halo-hx: 38%; }
.welcome__halo {
  background:
    radial-gradient(closest-side circle at 50% 50%,
      color-mix(in srgb, var(--color-accent) 18%, transparent) 0%,
      color-mix(in srgb, var(--color-accent) 13%, transparent) 20%,
      color-mix(in srgb, var(--color-accent) 8%,  transparent) 38%,
      color-mix(in srgb, var(--color-accent) 4%,  transparent) 54%,
      color-mix(in srgb, var(--color-accent) 1.6%,transparent) 68%,
      color-mix(in srgb, var(--color-accent) 0.4%,transparent) 80%,
      transparent 88%),
    radial-gradient(closest-side circle at var(--halo-hx) 62%,
      color-mix(in srgb, var(--color-accent) 9%,  transparent) 0%,
      color-mix(in srgb, var(--color-accent) 5%,  transparent) 28%,
      color-mix(in srgb, var(--color-accent) 2%,  transparent) 50%,
      color-mix(in srgb, var(--color-accent) 0.5%,transparent) 70%,
      transparent 84%);
}

/* The glow is deliberately WIDER than its column (435px in a 327px content box), and for an
   over-constrained absolutely-positioned box `margin-inline: auto` cannot centre — the
   used value collapses toward the inline start, which is why LTR and RTL each sat 30px off
   centre in opposite directions. `left` + a -50% translate are PHYSICAL, so they centre
   identically regardless of direction. That is the right choice here precisely because a
   decorative wash should NOT mirror; only its internal highlight follows the text, and
   that is handled by --halo-hx above. */
.welcome__halo {
  inset-inline: auto;
  left: 50%;
  margin-inline: 0;
  translate: -50% -50%;
}

/* ══════════════════════════════════════════════════════════════════════════════
   ONE GENERATION INDICATOR, NOT TWO.

   `.stream-caret::after` painted a 2px blinking bar at the end of the answer while the
   Beam pulsed on the line below it. Both said the same thing — "still writing" — so the
   pair competed for the same glance, and because the caret is `display:inline-block` on a
   block container it also opened its own line box: a stray green tick floating in the gap
   between the last sentence and the mark. Measured: 53px of dead space inside .md that had
   no content in it.

   The Beam is the chosen indicator, so the caret goes. The class itself is left in place —
   setStreamingUI still toggles it and it remains a useful state hook — only its paint is
   dropped, so nothing that reads the class breaks.
   ══════════════════════════════════════════════════════════════════════════════ */
.stream-caret::after { content: none; }

/* ══════════════════════════════════════════════════════════════════════════════
   TOGGLE KNOB — LOGICAL *AND* ON THE COMPOSITOR.

   An earlier pass moved this knob from `transform` to `inset-inline-start` for a good
   reason: the physical transform needed a hand-negated RTL twin, so the travel distance
   lived in three places and drifted whenever the track or knob size changed.

   But `inset-inline-start` is a layout property. Animating it makes the browser reflow on
   every frame of a control people tap constantly — the one place motion must be free.

   Both goals are reachable at once: keep ONE travel constant, take the direction from a
   variable rather than a duplicated selector, and animate `translate`, which is
   composited. The RTL twin below only flips a sign; it never restates the distance.
   ══════════════════════════════════════════════════════════════════════════════ */
.tools-menu__switch::after {
  --knob-travel: 14px;          /* 34 track - 16 knob - 2 rest - 2 end */
  --knob-dir: 1;
  inset-inline-start: 2px;
  transition: translate var(--dur-fast) var(--ease-out);
  will-change: translate;
}
[dir="rtl"] .tools-menu__switch::after { --knob-dir: -1; }
.tools-menu__item.is-on .tools-menu__switch::after,
[dir="rtl"] .tools-menu__item.is-on .tools-menu__switch::after {
  inset-inline-start: 2px;      /* the rest position never changes — only the translate */
  transform: none;
  translate: calc(var(--knob-travel) * var(--knob-dir));
}
@media (prefers-reduced-motion: reduce) {
  .tools-menu__switch::after { transition-duration: 1ms; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   HISTORY — "COULD NOT LOAD" IS A DIFFERENT STATE FROM "NOTHING HERE".

   The failed-load state reuses the empty-state layout but must not look like it: an empty
   history is neutral, a failed one is a problem the user can act on. So the icon takes a
   warning tint and the retry button is a real, reachable control rather than a hint.
   ══════════════════════════════════════════════════════════════════════════════ */
.history__empty--error .history__empty-ic {
  opacity: 1;
  color: var(--color-warn, #C08A2E);
}
.history__retry {
  margin-block-start: var(--space-2);
  padding: 7px 14px;
  min-block-size: 34px;
  border: var(--border-hairline) solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-primary);
  font: var(--weight-medium) var(--text-sm) / 1 var(--font-sans);
  cursor: pointer;
  /* Composited only, and inside the 300ms UI budget. */
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out);
}
.history__retry:hover:not(:disabled) {
  background: var(--color-accent-soft);
  border-color: var(--color-accent);
}
.history__retry:focus-visible {
  outline: 2px solid var(--color-accent-ring);
  outline-offset: 2px;
}
.history__retry:disabled { opacity: 0.55; cursor: default; }
/* Touch target floor — this is the only way out of the state, so it must be easy to hit. */
@media (pointer: coarse), (max-width: 560px) {
  .history__retry { min-block-size: 44px; padding-inline: 18px; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   THE GENERATION MARK — REDESIGNED

   Three changes, all requested directly:

   1. NOT A SCALE PULSE. The mark used to bounce 0.68→1.10 every 1.15s. On an element
      that is now permanent that reads as fidgeting, and per the motion rules a
      CONSTANT, ongoing animation is `linear` — an ease curve implies a start and an end,
      which a "still working" indicator does not have. It is now a highlight that sweeps
      across the glyph at constant speed: the mark holds its size and position, and only
      the light moves. Calmer, and unmistakably "in progress" rather than "throbbing".

   2. IT STAYS WHEN GENERATION ENDS. The animation is gated on `.is-generating`, which is
      already toggled per turn, so the shimmer simply stops and the mark remains as a
      signature under the finished answer. No element is created or destroyed.

   3. A DIVIDER ABOVE IT. The mark now sits under a hairline rule spanning the text column,
      which is what turns it from a loose floating glyph into a deliberate end-of-answer
      mark. The rule is on the tip itself (full width), the glyph is drawn as its ::before.
   ══════════════════════════════════════════════════════════════════════════════ */
.beam-tip {
  display: block;
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: auto;
  margin-block-start: 1.15em;
  padding-block-start: 0.85em;
  border-block-start: var(--border-hairline) solid var(--color-border-hairline);
  background: none;
  -webkit-mask: none;
          mask: none;
  animation: none;                 /* the glyph animates, not the container */
  transform: none;
  position: relative;
  pointer-events: none;
}
[dir="rtl"] .beam-tip { margin-inline: 0; }

/* The glyph. Same Beam mask as everywhere else, so it stays one logo. */
.beam-tip::before {
  content: "";
  display: block;
  inline-size: 1.25em;
  block-size: 1.25em;
  background-color: var(--color-accent);
  -webkit-mask: var(--beam-mask) center / contain no-repeat;
          mask: var(--beam-mask) center / contain no-repeat;
}

/* The old halo ring belonged to the pulse; with a sweep it just muddies the glyph. */
.beam-tip::after { content: none; }

/* THE SWEEP. A light band travels across the glyph while — and only while — the turn is
   generating. `linear`, because constant motion has no start or end to ease. 1.6s is slow
   enough to read as breathing rather than blinking. */
.msg-ai.is-generating .beam-tip::before {
  background-image: linear-gradient(100deg,
    transparent 24%,
    color-mix(in srgb, var(--color-text-primary) 65%, transparent) 46%,
    transparent 68%);
  background-size: 280% 100%;
  background-repeat: no-repeat;
  animation: beam-sweep 1.6s linear infinite;
}
@keyframes beam-sweep {
  from { background-position: 175% 0; }
  to   { background-position: -75% 0; }
}

/* Reduced motion: the mark still reports work, without travel. */
@media (prefers-reduced-motion: reduce) {
  .msg-ai.is-generating .beam-tip::before {
    background-image: none;
    animation: rm-working 1.8s var(--ease-inout) infinite;
  }
}
:root[data-motion="off"] .msg-ai.is-generating .beam-tip::before {
  background-image: none;
  animation: rm-working 1.8s var(--ease-inout) infinite;
}

/* ══════════════════════════════════════════════════════════════════════════════
   PER-BLOCK DIRECTION IN THE MAIN CHAT — equations stop fighting the prose.

   #brainWorkspace has carried `unicode-bidi: plaintext` for a while and it works; the main
   chat was deliberately left out. That exemption is what produced the scrambled answers:
   a numbered list where one item is an English sentence, the next is Arabic, and several
   carry inline LaTeX was laid out with ONE forced RTL base direction for every line. The
   Latin runs and their parentheses were then reordered against that base, so a step read
   as "(Euler's totient function for a prime modulus) .12" with the closing clause leading,
   and Arabic lines interleaved their equations mid-sentence.

   `plaintext` is the CSS form of dir="auto" applied PER BLOCK: each paragraph, heading,
   list item and cell resolves its own base direction from its first strong character. An
   English step lays out LTR, the Arabic step under it lays out RTL, and neither disturbs
   the other — which is exactly the "المعادلات تنفصل عن الشرح" that was asked for, achieved
   at the layout level rather than by hoping the model formats around the bug.

   Display math is additionally forced to its own line and centred, so an equation is never
   a participant in a bidi run at all — the strongest form of separation available.
   ══════════════════════════════════════════════════════════════════════════════ */
/* `unicode-bidi: plaintext` USED TO BE HERE and has been removed on purpose. It gave each
   block its own base direction from its FIRST STRONG CHARACTER, which fixed equations but
   broke lists: it re-orders inline content without changing the `direction` property, and the
   list marker's side resolves against `direction`. A bullet starting with a Latin word laid
   its text out to the left while its dot stayed on the right — the reported
   "النقاط تدخل بالكلام". autoDirBlocks() in app.js now stamps a real `dir` on every
   block instead, deciding by DOMINANT script rather than first character (so "Hb 8.5 غ/دل عند
   القبول" stays Arabic), which moves alignment, padding-inline-start AND the marker together.
   Keeping both would be worse than either: the text would be ordered by one rule and aligned
   by the other. */
.msg-ai__body .md > p,
.msg-ai__body .md > h1, .msg-ai__body .md > h2, .msg-ai__body .md > h3,
.msg-ai__body .md > h4, .msg-ai__body .md > h5, .msg-ai__body .md > h6,
.msg-ai__body .md > ul > li, .msg-ai__body .md > ol > li,
.msg-ai__body .md > blockquote,
.msg-ai__body .md td, .msg-ai__body .md th {
  text-align: start;
}

/* An equation on its own line is never part of a mixed run, so it can never be reordered
   against the sentence around it. This is the separation the owner asked for, enforced in
   layout rather than left to the model's formatting. */
.msg-ai__body .md .katex-display {
  display: block;
  direction: ltr;
  unicode-bidi: isolate;
  text-align: center;
  margin-block: var(--space-4);
  overflow-x: auto;
  overflow-y: hidden;
}
/* Inline math keeps its own LTR run and cannot leak its direction into the Arabic around
   it — `isolate` (not `embed`) so the surrounding neutrals resolve against the paragraph,
   not against the formula. */
.msg-ai__body .md :not(.katex-display) > .katex {
  direction: ltr;
  unicode-bidi: isolate;
}

/* ══════════════════════════════════════════════════════════════════════════════
   ANNOUNCEMENT VIDEO

   When an announcement carries a video it IS the announcement and the text is its caption,
   so it leads the panel at full width. The trailer is 1920x1080, and `aspect-ratio` reserves
   its box BEFORE the metadata arrives — without it the panel reflows and the caption jumps
   the moment the first frame loads.
   ══════════════════════════════════════════════════════════════════════════════ */
.ann-reader-video {
  display: block;
  inline-size: 100%;
  aspect-ratio: 16 / 9;
  block-size: auto;
  margin-block-end: var(--space-4);
  border-radius: var(--radius-md, 10px);
  background: #000;                    /* letterbox, not the page ground, while it loads */
  outline: var(--border-hairline) solid var(--color-border-hairline);
  outline-offset: -1px;
}
/* A tall phone gives a 16:9 video very little height; capping by viewport keeps the caption
   visible without the user having to scroll past a full-bleed player to reach the words. */
@media (max-width: 560px) {
  .ann-reader-video { max-block-size: 42vh; object-fit: contain; }
}

/* PINNED — the badge says why this item is first. Without it a pinned announcement just
   looks like the newest one, and the ordering reads as a bug the first time something newer
   appears below it. */
.ann-pin {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px;
  margin-inline-end: var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-accent-soft);
  color: var(--color-accent);
  font: var(--weight-medium) 11px/1.6 var(--font-sans);
  vertical-align: middle;
}
/* At 11px this is small text, so it owes 4.5:1. The accent on its own 14% wash measures
   3.97:1 against the dark card — the lighter accent takes it to 4.9:1. Light theme already
   clears it (4.7:1) because the wash sits on a near-white card, so it is left alone. */
[data-theme="dark"] .ann-pin { color: var(--color-accent-hover); }

/* The same pill in a quieter register: a row that opens a 52-second video should not look
   identical to a row of text, but "Video" is a fact about the row, not a call to action —
   the accent belongs to Pinned alone.
   Two class selectors, not one: the dark-theme accent rule above is (0,2,0) and would
   otherwise repaint this pill teal. Equal specificity, and this comes later in the file. */
.ann-pin.ann-pin--vid {
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
}
/* Own row, above the title. `gap` owns the spacing here, so the pill's own inline margin —
   which it needs when it sits inline in the reader — is cancelled. */
.ann-item-badges { display: flex; align-items: center; gap: 6px; margin-block-end: 1px; }
.ann-item-badges .ann-pin { margin-inline-end: 0; }

/* ══════════════════════════════════════════════════════════════════════════════
   ANNOUNCEMENT COMPOSER — the added controls.
   ══════════════════════════════════════════════════════════════════════════════ */
.ann-pin-lbl {
  display: inline-flex; align-items: center; gap: 6px;
  font: var(--weight-regular) var(--text-sm) / 1 var(--font-sans);
  color: var(--color-text-secondary);
  cursor: pointer; user-select: none;
}
.ann-pin-lbl input { accent-color: var(--color-accent); cursor: pointer; }

/* DESTRUCTIVE, so it does not look like the other buttons. Quiet until hover: a red button
   sitting permanently next to Publish invites the mis-click it is meant to prevent. */
.ann-clear-all {
  display: block;
  inline-size: 100%;
  margin-block-start: var(--space-3);
  padding: 9px 14px;
  min-block-size: 38px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md, 10px);
  background: transparent;
  color: var(--color-text-muted);
  font: var(--weight-medium) var(--text-sm) / 1 var(--font-sans);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.ann-clear-all:hover:not(:disabled) {
  color: var(--color-danger, #C0483A);
  border-color: var(--color-danger, #C0483A);
  background: color-mix(in srgb, var(--color-danger, #C0483A) 8%, transparent);
}
.ann-clear-all:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
.ann-clear-all:disabled { opacity: .55; cursor: default; }

/* ============================================================================
   TCLAMP — collapse-with-"عرض المزيد" for a very long USER message.

   NO ABSOLUTE POSITIONING ANYWHERE. The fade is a mask-image on the clamped
   element itself and the gradient axis is VERTICAL (to bottom), so there is
   nothing to mirror; the button is a normal-flow inline-flex sibling inside the
   bubble, which sets `text-align: start`. Both render identically under
   dir="ltr" and dir="rtl" — which matters because applyShellLang hard-codes
   html.dir="ltr" and only per-element dir carries RTL. Measured in Chrome: the
   button's offset from the bubble's START edge is 17px in BOTH directions.

   SPECIFICITY: `.msg-user__text.is-tclamped` is (0,2,0) and this block sits at
   the end of the file. Nothing anywhere in styles.css sets max-height,
   max-block-size or overflow on .msg-user__bubble or its text child, and there
   is no `#id *` rule in the file, so these rules win.

   --tclamp-cut is written by JS as an exact pixel boundary snapped to a whole
   line, and never lands inside a .katex-display block.
============================================================================ */
.msg-user__text.is-tclamped {
  max-block-size: var(--tclamp-cut, 60vh);
  overflow: hidden;
  /* Alpha ramp over the last ~2.6 lines. Vertical only — RTL-safe by construction. */
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 2.6em), rgba(0,0,0,.3) calc(100% - 1em), transparent 100%);
          mask-image: linear-gradient(to bottom, #000 calc(100% - 2.6em), rgba(0,0,0,.3) calc(100% - 1em), transparent 100%);
}
/* No mask support (very old engines): a clean hard cut. The button still works. */
@supports not (mask-image: linear-gradient(#000, #000)) {
  .msg-user__text.is-tclamped { -webkit-mask-image: none; mask-image: none; }
}

.msg-user__bubble .tclamp__more {
  display: inline-flex; align-items: center; gap: 6px;
  margin-block-start: var(--space-2);
  padding: 5px 11px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: transparent;              /* the bubble is already a tinted panel */
  color: var(--color-text-secondary);
  font: var(--weight-medium) var(--text-sm) / 1.2 var(--font-sans);
  white-space: nowrap;                  /* the bubble sets pre-wrap; the pill must not wrap */
  cursor: pointer;
  -webkit-user-select: none; user-select: none;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.msg-user__bubble .tclamp__more[dir="rtl"] { font-family: var(--font-arabic); }
.msg-user__bubble .tclamp__more:hover {
  color: var(--color-text-primary);
  border-color: var(--color-border-strong);
  background: var(--color-surface);
}
.msg-user__bubble .tclamp__more:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
/* ICONS.caret points RIGHT; rotation has no notion of writing direction, so this
   is correct in Arabic and English alike. */
.msg-user__bubble .tclamp__chev {
  display: inline-flex; inline-size: 13px; block-size: 13px;
  transition: transform var(--dur-base) var(--ease-out);
  transform: rotate(90deg);
}
.msg-user__bubble .tclamp__chev svg { inline-size: 100%; block-size: 100%; }
.msg-user__bubble .tclamp__more[aria-expanded="true"] .tclamp__chev { transform: rotate(-90deg); }
@media (prefers-reduced-motion: reduce) {
  .msg-user__bubble .tclamp__more, .msg-user__bubble .tclamp__chev { transition: none; }
}
@media (pointer: coarse), (max-width: 560px) {
  .msg-user__bubble .tclamp__more { min-block-size: 40px; padding-inline: 14px; }
}

/* The English fields read as a secondary pair, so the Arabic ones stay the primary input. */
.ann-title-en, .ann-body-en { opacity: .92; }
@media (pointer: coarse), (max-width: 560px) {
  .ann-clear-all { min-block-size: 44px; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   THE TIER PICKER DOES NOT BELONG TO FIRAS CODE

   Firas Code picks its own engine — the workspace routes builds to the coder tier
   itself (requestTier = "ultra" on a code turn), so the topbar's Mini/Pro/Ultra/Max
   control was offering a choice that Code does not honour. It leaked into BOTH Code
   screens: the "Build with AI" home and the IDE.

   ONLY HALF OF IT WAS HIDDEN. `.product-code #tierSwitch{display:none}` has been in this
   file since Deck v2 — but the tier control has TWO presentations of the same state:
   #tierSwitch, the desktop strip, and #tierSelect, the phone dropdown. Code hid the strip
   and never the dropdown, so the control was invisible on a laptop and fully present on a
   phone. That is why it took a phone screenshot to surface, and why it appeared on BOTH
   Code screens (home and IDE) — nothing about the screen was ever the variable.
   The .product-brain block below already hides the pair and its comment names this exact
   asymmetry as an open bug; this closes it.

   Layout is safe: `.topbar__spacer` is the flex growth element that positions this
   group, so removing the control lets the remaining buttons close up normally —
   no gap, no reflow of the wordmark.
   ══════════════════════════════════════════════════════════════════════════════ */
body.product-code #tierSwitch,
body.product-code #tierSelect { display: none !important; }

/* ----------------------------------------------------------------------------
   Video card — a generated clip. Deliberately the same visual language as
   .image-card (frame, glow, shimmer, action bar) so the two read as one family;
   only the aspect ratio and the player differ.
---------------------------------------------------------------------------- */
/* These two were defined beside the image card and moved here when its loader was rebuilt as
   a dot field. The VIDEO card is now their only consumer, and an animation naming keyframes
   that no longer exist is silently inert — no error, just a loader that stops moving. Keeping
   the definition next to its single use is what stops that happening again. */
@keyframes imgGlow {
  0%, 100% { box-shadow: 0 0 0 1px var(--color-accent-soft), 0 8px 24px -14px var(--color-accent); }
  50%      { box-shadow: 0 0 0 2px var(--color-accent), 0 16px 44px -10px var(--color-accent); }
}
@keyframes imgShimmer { from { background-position: 220% 0; } to { background-position: -220% 0; } }

.video-card {
  inline-size: 100%; max-inline-size: 520px;
  margin-block: var(--space-3) var(--space-1);
}
.video-card__frame {
  position: relative; overflow: hidden;
  inline-size: 100%; aspect-ratio: 16 / 9;
  border-radius: var(--radius-lg);
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--color-border-hairline);
}
.video-card.is-loading .video-card__frame {
  border-color: transparent;
  box-shadow: 0 0 0 1.5px var(--color-accent-soft), 0 10px 34px -12px var(--color-accent);
  animation: imgGlow 1.7s ease-in-out infinite;
}
.video-card.is-loading .video-card__frame::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(110deg, transparent 35%, color-mix(in srgb, var(--color-accent) 22%, transparent) 50%, transparent 65%);
  background-size: 220% 100%;
  animation: imgShimmer 1.5s linear infinite;
}
.video-card__loader {
  position: absolute; inset: 0; z-index: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-2);
  color: var(--color-text-secondary);
  font-family: var(--font-sans); font-size: var(--text-sm);
  text-align: center; padding-inline: var(--space-4);
}
[dir="rtl"] .video-card__loader { font-family: var(--font-arabic); }
.video-card.is-done .video-card__loader { display: none; }
.video-card__spin {
  inline-size: 28px; block-size: 28px; border-radius: 50%;
  border: 3px solid color-mix(in srgb, var(--color-accent) 22%, transparent);
  border-top-color: var(--color-accent);
  /* auth-spin is the file's existing rotation keyframe. There is no `spin` keyframe in this
     stylesheet; naming one would have failed silently. The image card used to share this
     spinner and no longer does — it has the aperture loader above. */
  animation: auth-spin 0.7s linear infinite;
}
.video-card.is-error .video-card__spin { display: none; }
.video-card__vid {
  position: absolute; inset: 0;
  inline-size: 100%; block-size: 100%;
  object-fit: contain; background: #000;
  opacity: 0; transition: opacity var(--dur-base) var(--ease-out);
}
.video-card.is-done .video-card__vid { opacity: 1; }
.video-card.is-error .video-card__frame { box-shadow: none; animation: none; border-color: var(--color-border-hairline); }
.video-card.is-error .video-card__frame::after { content: none; }
.video-card.is-error .video-card__loadtext { color: var(--color-text-muted); }
.video-card__bar {
  display: flex; align-items: center; gap: var(--space-2);
  margin-block-start: var(--space-2);
}
.video-card__cap {
  flex: 1 1 auto; min-inline-size: 0;
  font-family: var(--font-sans); font-size: var(--text-xs); color: var(--color-text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
[dir="rtl"] .video-card__cap { font-family: var(--font-arabic); }
.video-card__dl {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: 6px 10px; border-radius: var(--radius-pill);
  border: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-surface); color: var(--color-text-secondary);
  font-family: var(--font-sans); font-size: var(--text-xs);
  transition: background var(--dur-fast), border-color var(--dur-fast), color var(--dur-fast);
}
[dir="rtl"] .video-card__dl { font-family: var(--font-arabic); }
.video-card__dl:hover { background: var(--color-bg-subtle); border-color: var(--color-border-strong); color: var(--color-text); }
.video-card__dl svg { inline-size: 15px; block-size: 15px; }
@media (prefers-reduced-motion: reduce) {
  .video-card.is-loading .video-card__frame,
  .video-card.is-loading .video-card__frame::after,
  .video-card__spin { animation: none; }
  .video-card__vid { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════════
   UI 2.0 — a skin, not a rewrite
   ═══════════════════════════════════════════════════════════════════════════════

   WHAT THIS IS. Everything below activates only under `:root[data-ui="2"]`, set by one switch in
   Settings. Not a single line of markup or application logic changes: the same DOM, the same
   engines, the same features, the same behaviour. Turn it off and the app is byte-for-byte the
   product it was a second earlier.

   WHY IT IS DONE THIS WAY. A real rewrite of a 2.4 MB single-file app means re-testing every
   feature in it — the call, the Agent card, Code's IDE, Brain's citations, six themes, RTL and LTR.
   A skin cannot break any of them, because it cannot reach them. The trade is honest: this changes
   how the product LOOKS and FEELS, not how it is built.

   THE METHOD. The design system already runs on tokens, and the project's own convention is to
   REPLACE THE VALUES, NOT THE NAMES — so most of this file is one block of new values, and the
   whole app re-tunes at once. Only the few surfaces that need more than a token get their own rule.

   THE DIRECTION, AND WHY IT DIFFERS. UI 1 is a deliberate aesthetic: "an engraved instrument is
   cut, not moulded" — 2–9px radii, machined edges, deliberately flat shadows. That is a real point
   of view and it is left completely intact. UI 2.0 argues the other side: generous geometry, light
   that falls in layers instead of a single flat drop, and motion with weight. Contemporary premium
   software — the reference set on 21st.dev reads the same way — earns "expensive" through space and
   restraint rather than through edge.
   ═══════════════════════════════════════════════════════════════════════════════ */

:root[data-ui="2"] {
  /* ---------- GEOMETRY ----------
     The single largest change in the file. Every panel, button, field, card, menu and sheet in the
     app reads one of these six values, so widening them re-cuts the entire product in one edit.
     Scaled, not flattened: the ratio between steps is kept so hierarchy survives. */
  --radius-xs:  6px;
  --radius-sm:  10px;
  --radius-md:  14px;
  --radius-lg:  18px;
  --radius-xl:  24px;

  /* ---------- LIGHT ----------
     UI 1 uses one flat drop shadow. Real depth is two lights: a tight contact shadow that says an
     object is RESTING on something, and a wide ambient one that says it is ABOVE it. Both are
     softer per-layer than the single shadow they replace, so the result reads deeper without
     reading heavier. */
  --shadow-xs:    0 1px 2px rgba(0,0,0,.05);
  --shadow-sm:    0 1px 2px rgba(0,0,0,.05), 0 4px 10px -2px rgba(0,0,0,.06);
  --shadow-md:    0 2px 4px rgba(0,0,0,.05), 0 10px 22px -4px rgba(0,0,0,.09);
  --shadow-lg:    0 4px 8px rgba(0,0,0,.05), 0 24px 48px -12px rgba(0,0,0,.14);
  --shadow-composer: 0 2px 6px rgba(0,0,0,.05), 0 16px 40px -8px rgba(0,0,0,.12);
  /* A focus ring with room to breathe around a rounder corner. */
  --shadow-focus: 0 0 0 3.5px var(--color-accent-ring);

  /* ---------- MOTION ----------
     Slightly longer and springier. UI 1's 120ms with a standard ease is efficient and slightly
     mechanical; a little overshoot is what makes a surface feel like it has mass. */
  --dur-fast:   150ms;
  --dur-base:   210ms;
  --dur-slow:   320ms;
  --ease-out:   cubic-bezier(.16, 1, .3, 1);
  --ease-inout: cubic-bezier(.65, 0, .35, 1);
}

/* Deeper still in dark mode: the same shadow that reads as depth on a light ground disappears on a
   dark one, so it is strengthened rather than left to vanish. */
:root[data-ui="2"][data-theme="dark"],
:root[data-ui="2"]:not([data-theme="light"]) {
  --shadow-sm:    0 1px 2px rgba(0,0,0,.3), 0 4px 10px -2px rgba(0,0,0,.28);
  --shadow-md:    0 2px 4px rgba(0,0,0,.32), 0 10px 22px -4px rgba(0,0,0,.4);
  --shadow-lg:    0 4px 8px rgba(0,0,0,.34), 0 24px 48px -12px rgba(0,0,0,.5);
  --shadow-composer: 0 2px 6px rgba(0,0,0,.3), 0 16px 40px -8px rgba(0,0,0,.45);
}

/* ── THE COMPOSER ── the one surface the eye returns to on every turn.
   It stops being a bordered box on the page and becomes an object floating above it: frosted, so
   the conversation stays faintly visible underneath, and lifted, so it reads as the thing you act
   with rather than part of what you are reading. */
:root[data-ui="2"] .composer {
  background: color-mix(in srgb, var(--color-surface) 82%, transparent);
  backdrop-filter: blur(20px) saturate(1.35);
  -webkit-backdrop-filter: blur(20px) saturate(1.35);
  border-color: color-mix(in srgb, var(--color-border) 55%, transparent);
  transition: border-color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
:root[data-ui="2"] .composer:focus-within {
  border-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
  box-shadow: var(--shadow-composer), 0 0 0 3px color-mix(in srgb, var(--color-accent) 12%, transparent);
}

/* ── THE SIDEBAR ── a hard 1px rule down the page is the most dated thing in most app shells.
   Recede it instead: the panel sits slightly back in the stack and the eye reads the boundary from
   the change in ground, which is how depth works everywhere else. */
:root[data-ui="2"] .sidebar {
  border-inline-end: 0;
  background: color-mix(in srgb, var(--color-sidebar-bg) 92%, var(--color-text-primary) 3%);
}
:root[data-ui="2"] .topbar {
  border-block-end: 0;
  background: color-mix(in srgb, var(--color-bg) 78%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

/* ── LIST ROWS ── taller, pill-shaped, and they lift on hover instead of just tinting. */
:root[data-ui="2"] .chat-item {
  block-size: 40px;
  border-radius: var(--radius-md);
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
/* THE ONE CONTEXT WHERE 40px DOES NOT APPLY. On a phone the row carries a second line — the four
   44px action buttons, which cannot be hidden there (see the note in the touch-targets block: they
   are always-present so every row's actions are reachable and so the row's geometry does not change
   under a finger mid-tap). A fixed height clips that line out of its own row.

   It lives HERE, next to the declaration it overrides, rather than in the phone block 237,000
   characters earlier — that block matched this selector's specificity and still lost, because at
   equal specificity the later rule wins and this one is later. Adjacency is also the documentation:
   the exception is visible to anyone reading the rule. */
@media (hover: none), (max-width: 560px) {
  :root[data-ui="2"] .chat-item { block-size: auto; min-block-size: 48px; }
}
:root[data-ui="2"] .chat-item:hover { transform: none; }

/* ── PRESS ── every control acknowledges a press by sinking slightly. One rule, whole app.
   Excludes the composer and the call orb, which own their own press behaviour. */
:root[data-ui="2"] button:not(.composer):not(.call__orb):active { transform: scale(.975); }
:root[data-ui="2"] button { transition: transform var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }

/* ── CARDS AND SHEETS ── the settings panel, menus and modals inherit the new geometry from the
   tokens; this only softens their edge so a 24px corner is not outlined by a hard hairline. */
:root[data-ui="2"] .set-card,
:root[data-ui="2"] .set-shell,
:root[data-ui="2"] .msg-menu,
:root[data-ui="2"] .product-menu,
/* __panel, NOT .tools-menu. `.tools-menu` is the WRAPPER around the "+" trigger, not the dropdown
   — so this shadow was landing on a 40x40 box with border-radius:0, drawing a hard-cornered
   rectangle behind the one button in a row of bare circles. Reported as "هذا بي نشاز", and it was:
   five ghost controls and one with a square shadow. The dropdown is what wanted the lift. */
:root[data-ui="2"] .tools-menu__panel {
  border-color: color-mix(in srgb, var(--color-border) 50%, transparent);
  box-shadow: var(--shadow-lg);
}

/* ── TYPE ── a touch more air between lines in body copy, and tighter tracking on display sizes.
   Long-form Arabic in particular reads better with the extra leading. */
:root[data-ui="2"] .md,
:root[data-ui="2"] .msg-body { line-height: 1.78; }
:root[data-ui="2"] h1, :root[data-ui="2"] h2, :root[data-ui="2"] h3 { letter-spacing: -.015em; }

/* Motion is a preference, not a decoration. */
@media (prefers-reduced-motion: reduce) {
  :root[data-ui="2"] button:active { transform: none; }
  :root[data-ui="2"] .chat-item:hover { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════════
   UI 2.0 · PART TWO — the surfaces
   ═══════════════════════════════════════════════════════════════════════════════

   Part one re-tuned the design tokens, which changes how everything FEELS. This part changes what
   the five products LOOK like — chat, Agent, Code, Brain and the call each get a different visual
   idea, not a rounder version of the same one.

   Still a skin. Still only under [data-ui="2"]. Still not one line of markup or logic touched, so
   every feature behaves identically and the switch reverts the product exactly.

   The thesis, in one line per surface:
     · chat    — a document, not a messenger
     · agent   — a timeline, not a box
     · code    — a cockpit, not a page
     · brain   — the citation is the interface
     · call    — the orb carries it; everything else gets out of the way
   ═══════════════════════════════════════════════════════════════════════════════ */

/* ── CHAT · A DOCUMENT, NOT A MESSENGER ─────────────────────────────────────────
   The single biggest perceptual change in UI 2.0.

   A filled bubble pushed to one side is a MESSAGING idiom — it says "two people texting". What
   actually happens here is a person asking and a long, considered answer coming back, often with
   headings, code and citations. That is a document.

   So the user's turn stops being a coloured pill and becomes a quiet inset with a rule down its
   leading edge — the way a quotation is set in print. It still reads unmistakably as "this is what
   I said", but it no longer competes with the answer, which is the part worth reading. */
/* REVISED. The paragraph above argued the user's turn should stop being a bubble and become a
   quiet inset with a rule down its leading edge. On screen it was wrong twice over: it put the
   message on the LEFT while everything the reader writes belongs on their own side, and it threw
   away --user-fill / --user-ink / --user-sheen — the tokens the six themes actually tune the user
   turn with — for one flat 7% tint. A message you sent should look like something you sent.

   So it is a bubble again, on the trailing edge as it always was, and built properly:

   · a real gradient instead of a flat fill, light falling from the top, so it reads as a surface
     rather than a rectangle of colour;
   · a bright inner hairline along the top edge — the one detail that separates a lit object from
     a filled shape;
   · one tightened corner at the bottom trailing side, the shape that says whose turn this is;
   · a shadow tinted with the accent rather than neutral black, so the bubble sits ON the page in
     the theme's own light.

   Every value comes from an existing token, so all six themes and both light and dark follow. */
:root[data-ui="2"] .msg-user { justify-content: flex-end; }
:root[data-ui="2"] .msg-user__bubble {
  max-inline-size: min(78%, 520px);
  padding: 13px 18px;
  color: var(--user-ink);
  /* An ANGLE, not `to inline-end`: a directional gradient re-aims itself under RTL content and the
     highlight would jump to the other corner mid-thread. 160deg is absolute and holds. */
  background:
    linear-gradient(160deg,
      color-mix(in srgb, #ffffff 9%, var(--user-fill)) 0%,
      var(--user-fill) 52%,
      color-mix(in srgb, #000000 7%, var(--user-fill)) 100%);
  border: var(--border-hairline) solid color-mix(in srgb, #ffffff 15%, var(--user-edge));
  border-radius: 20px;
  /* PHYSICAL, deliberately. The bubble's position comes from a flex container in a shell that is
     fixed LTR, so it sits on the right whatever the message's own direction is. The logical
     property would flip this corner for an Arabic message and leave the tail pointing away from
     the side the bubble is actually on. */
  border-bottom-right-radius: 7px;
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, #ffffff 16%, transparent),
    0 1px 2px rgba(0, 0, 0, .16),
    0 10px 24px -8px color-mix(in srgb, var(--color-accent-deep) 42%, transparent);
}
/* The turn is the thread's widest element, so it also sets how far the eye has to travel back to
   the start of the answer. A little air under it, and none of the base rule's extra top margin. */
:root[data-ui="2"] .msg-user { margin-block-end: 2px; }

/* The answer's header was avatar + name + badge on every single turn — chrome repeated dozens of
   times down a page. Kept, because the identity matters, but quieted to a mark: smaller, circular,
   and the name set in the same small caps as a byline rather than a title. */
:root[data-ui="2"] .msg-ai__head { margin-bottom: var(--space-2); opacity: .9; }
:root[data-ui="2"] .msg-ai__avatar { inline-size: 20px; block-size: 20px; border-radius: 50%; }
:root[data-ui="2"] .msg-ai__name {
  font-size: 12px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--color-text-secondary);
}
/* Long-form answers get real measure and leading — this is a reading surface. */
:root[data-ui="2"] .msg-ai__body { font-size: 15.5px; }

/* Turn actions stop being always-on chrome and fade in with the turn they belong to. */
:root[data-ui="2"] .msg-actions { opacity: .55; transition: opacity var(--dur-base) var(--ease-out); }
:root[data-ui="2"] .msg-ai:hover .msg-actions,
:root[data-ui="2"] .msg-actions:focus-within { opacity: 1; }

/* ── AGENT · A TIMELINE, NOT A BOX ──────────────────────────────────────────────
   A mission is a sequence of stations over time, and the card was drawing it as a bordered
   rectangle with a bar in it. Given a spine down the leading edge whose colour IS the phase, the
   card reads as progress at a glance from across the room — which is exactly how it is used, since
   the whole point of a background mission is that you are not staring at it. */
:root[data-ui="2"] .agent-card {
  border: 0;
  border-inline-start: 3px solid color-mix(in srgb, var(--color-accent) 60%, transparent);
  border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
  background: color-mix(in srgb, var(--color-accent) 4%, var(--color-surface));
  padding: 16px 18px 14px;
  box-shadow: var(--shadow-sm);
}
:root[data-ui="2"] [dir="rtl"] .agent-card { border-radius: var(--radius-lg) 0 0 var(--radius-lg); }
:root[data-ui="2"] .agent-card.is-done {
  border-inline-start-color: color-mix(in srgb, var(--color-accent) 85%, transparent);
  background: var(--color-surface);
}
/* The progress bar becomes a hairline the full width of the card: present when you look for it,
   invisible when you are reading the steps. */
:root[data-ui="2"] .agent-card__prog-bar { block-size: 3px; border-radius: 999px; overflow: hidden; }
:root[data-ui="2"] .agent-card__prog-fill { border-radius: 999px; transition: inline-size var(--dur-slow) var(--ease-out); }
:root[data-ui="2"] .agent-card__phase {
  font-size: 11.5px; letter-spacing: .05em; text-transform: uppercase;
  opacity: .85;
}

/* ── CODE · A COCKPIT, NOT A PAGE ───────────────────────────────────────────────
   An IDE is instruments around a work surface. The toolbar stops being a strip with a border under
   it and becomes a floating segmented control: grouped, recessed into its own tray, so the eye
   reads "these are the controls" as one object instead of six loose buttons. */
:root[data-ui="2"] .cw-bar {
  border-block-end: 0;
  background: color-mix(in srgb, var(--color-bg-subtle) 70%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  gap: 4px;
}
:root[data-ui="2"] .cw-bar__btn {
  border-radius: var(--radius-sm);
  border-color: transparent;
  background: transparent;
}
:root[data-ui="2"] .cw-bar__btn:hover { background: color-mix(in srgb, var(--color-text-primary) 7%, transparent); }
:root[data-ui="2"] .cw-bar__btn--primary {
  background: var(--color-accent); color: var(--color-on-accent);
  box-shadow: var(--shadow-sm);
}
/* The instruction bar is where the work is directed — it gets the same floating treatment the
   chat composer got, so the two ways of talking to Firas look like the same gesture. */
:root[data-ui="2"] .cw-ai {
  border-radius: var(--radius-xl);
  border-color: color-mix(in srgb, var(--color-border) 50%, transparent);
  box-shadow: var(--shadow-md);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

/* ── BRAIN · THE CITATION IS THE INTERFACE ──────────────────────────────────────
   Brain's entire promise is "the answer, and the page it came from". The citation was a small
   inline number; here it is the most designed element on the surface — a chip you can see from a
   glance away, because it is the thing that makes the answer trustworthy. */
:root[data-ui="2"] .fb-cite {
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-accent) 30%, transparent);
  padding-inline: 8px;
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
:root[data-ui="2"] .fb-cite:hover {
  background: color-mix(in srgb, var(--color-accent) 22%, transparent);
  transform: translateY(-1px);
}
:root[data-ui="2"] .fb-cite__n { font-variant-numeric: tabular-nums; font-weight: 600; }
:root[data-ui="2"] .fb-ask {
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-md);
  border-color: color-mix(in srgb, var(--color-border) 50%, transparent);
}
:root[data-ui="2"] .fb-bar { border-block-end: 0; background: transparent; }
/* The empty state is the first thing a new user meets — given air instead of a boxed panel. */
:root[data-ui="2"] .fb-empty { border: 0; background: transparent; }
:root[data-ui="2"] .fb-empty__ic { opacity: .55; }

/* ── CALL · THE ORB CARRIES IT ──────────────────────────────────────────────────
   The shader orb is the most expensive thing on screen and everything else should defer to it.
   The name goes quiet and wide-tracked, the status becomes a whisper, and the controls sit lower
   so the orb owns the optical centre. */
:root[data-ui="2"] .call__name {
  font-size: 22px; letter-spacing: .04em; font-weight: 600;
  opacity: .92;
}
:root[data-ui="2"] .call__status { font-size: 13.5px; letter-spacing: .03em; opacity: .8; }
:root[data-ui="2"] .call__controls { margin-top: 30px; }
:root[data-ui="2"] .call__btn { inline-size: 62px; block-size: 62px; }

/* ── EVERYTHING ELSE ────────────────────────────────────────────────────────────
   Inputs and selects inherit the new geometry from the tokens but keep UI 1's heavier border;
   softened here so a 14px corner is not traced by a hard line. */
:root[data-ui="2"] input,
:root[data-ui="2"] textarea,
:root[data-ui="2"] select {
  border-color: color-mix(in srgb, var(--color-border) 60%, transparent);
}
:root[data-ui="2"] input:focus,
:root[data-ui="2"] textarea:focus,
:root[data-ui="2"] select:focus {
  border-color: color-mix(in srgb, var(--color-accent) 50%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  :root[data-ui="2"] .fb-cite:hover { transform: none; }
  :root[data-ui="2"] .agent-card__prog-fill { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════════
   UI 2.0 · PART THREE — the product rail
   ═══════════════════════════════════════════════════════════════════════════════

   The first part of UI 2.0 that is not a skin. Parts one and two re-tuned tokens and surfaces;
   this one adds a column to the application grid and puts the four products in it permanently.

   Desktop only, and deliberately. On a phone the drawer already IS the navigation and the four
   products live in its switcher; a second permanent rail would eat 76px of a 360px screen to
   duplicate something one tap away. The rule is width, not device: the moment there is room for
   a rail, there is a rail.
   ═══════════════════════════════════════════════════════════════════════════════ */

:root[data-ui="2"] { --rail-w: 76px; }

/* Below the drawer breakpoint the rail is not rendered as a column at all. It still exists in the
   DOM (the same nav, the same handlers) — it is simply not given a place, so nothing shifts. */
.rail { display: none; }

@media (min-width: 768.02px) {
  /* One extra track. Everything else in the shell keeps its own column and needs no change. */
  :root[data-ui="2"] .app { grid-template-columns: var(--rail-w) var(--sidebar-w) 1fr; }
  /* Collapsing the conversation list must NOT collapse the rail — the products are the one thing
     that should survive every collapse, since they are how you get anywhere else. */
  :root[data-ui="2"] body.sidebar-collapsed .app { grid-template-columns: var(--rail-w) 0 1fr; }

  :root[data-ui="2"] .rail {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 14px 8px;
    /* A shade back from the sidebar, which is itself a shade back from the thread. Three grounds,
       receding — the depth cue that replaces the 1px rules UI 1 draws between panels. */
    background: color-mix(in srgb, var(--color-sidebar-bg) 86%, var(--color-text-primary) 6%);
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: none;
  }
  :root[data-ui="2"] .rail::-webkit-scrollbar { display: none; }

  :root[data-ui="2"] .rail__item {
    inline-size: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    padding: 10px 4px 8px;
    border: 0;
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--color-text-secondary);
    cursor: pointer;
    /* Colour and ground move together on hover; the press-sink comes from the global button rule
       in part one, so the rail behaves like every other control without restating it. */
    transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
  }
  :root[data-ui="2"] .rail__item:hover {
    background: color-mix(in srgb, var(--color-text-primary) 7%, transparent);
    color: var(--color-text-primary);
  }
  :root[data-ui="2"] .rail__item:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
  }
  /* The active product is the only filled thing in the rail, so "where am I" is answered from the
     corner of the eye without reading a word. */
  :root[data-ui="2"] .rail__item.is-active {
    background: color-mix(in srgb, var(--color-accent) 16%, transparent);
    color: var(--color-accent);
  }

  :root[data-ui="2"] .rail__ico { display: grid; place-items: center; inline-size: 22px; block-size: 22px; }
  :root[data-ui="2"] .rail__ico svg { inline-size: 22px; block-size: 22px; }

  :root[data-ui="2"] .rail__label {
    font-size: 10.5px;
    line-height: 1.2;
    letter-spacing: .01em;
    text-align: center;
    /* Arabic at 10.5px needs the Arabic face explicitly — the Latin stack renders it with the
       fallback and the labels come out visibly heavier than the English ones. */
    font-family: var(--font-arabic), var(--font-ui);
  }
  :root[data-ui="2"] .rail__item.is-active .rail__label { font-weight: 600; }

  /* With the rail present, the dropdown in the sidebar is a second control for the same state.
     Removed rather than left as a duplicate — two ways to switch product is one too many. */
  :root[data-ui="2"] .product-switch { display: none; }
  /* Which frees the sidebar head to sit tighter against the new-chat button. */
  :root[data-ui="2"] .sidebar__head { margin-block-end: 4px; }
}

/* ═══════════════════════════════════════════════════════════════════════════════
   UI 2.0 · PART FOUR — the inspector
   ═══════════════════════════════════════════════════════════════════════════════

   A fourth grid track, added only while there is something in it. `.has-inspector` is set by
   renderInspector() rather than inferred with :has(), so the layout is driven by the same code
   that decides the panel's contents and the two can never disagree.

   Desktop only, for the same reason the rail is: 300px of pinned column on a 360px phone is the
   whole screen. On a phone the card in the thread IS the panel, which is where it started.
   ═══════════════════════════════════════════════════════════════════════════════ */

:root[data-ui="2"] { --insp-w: 300px; }

@media (min-width: 768.02px) {
  :root[data-ui="2"] .app.has-inspector { grid-template-columns: var(--rail-w) var(--sidebar-w) 1fr var(--insp-w); }
  :root[data-ui="2"] body.sidebar-collapsed .app.has-inspector { grid-template-columns: var(--rail-w) 0 1fr var(--insp-w); }

  /* Below ~1100px the thread would be squeezed thinner than the panel beside it. The reading
     column wins that fight every time — a source list is worth nothing next to a 40-character
     measure. */
  @media (max-width: 1100px) {
    :root[data-ui="2"] .app.has-inspector { grid-template-columns: var(--rail-w) var(--sidebar-w) 1fr; }
    :root[data-ui="2"] .inspector { display: none; }
  }
  /* Collapsing the conversation list hands 268px back, and the panel is worth more than that
     empty gutter. So the cut-off is not one number: it is where the READING COLUMN would get
     too narrow, and that depends on what else is on screen. With the sidebar away, the thread
     still gets 500px+ at 880 — comfortably more than it had at 1100 with the sidebar open. */
  @media (min-width: 880px) and (max-width: 1100px) {
    :root[data-ui="2"] body.sidebar-collapsed .app.has-inspector { grid-template-columns: var(--rail-w) 0 1fr var(--insp-w); }
    :root[data-ui="2"] body.sidebar-collapsed .inspector { display: flex; }
  }
  @media (max-width: 879px) {
    :root[data-ui="2"] body.sidebar-collapsed .app.has-inspector { grid-template-columns: var(--rail-w) 0 1fr; }
  }

  :root[data-ui="2"] .inspector {
    display: flex;
    flex-direction: column;
    gap: 22px;
    padding: 18px 16px 24px;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Same recession as the rail, from the other side — the thread sits forward between two
       quieter grounds, which is the whole point of putting it in the middle. */
    background: color-mix(in srgb, var(--color-sidebar-bg) 90%, var(--color-text-primary) 3%);
  }

  :root[data-ui="2"] .insp-sec { display: flex; flex-direction: column; gap: 8px; }

  :root[data-ui="2"] .insp-h {
    display: flex; align-items: center; justify-content: space-between;
    gap: 8px; margin-block-end: 2px;
  }
  :root[data-ui="2"] .insp-h__t {
    font-size: 11.5px; letter-spacing: .07em; text-transform: uppercase;
    font-weight: 600; color: var(--color-text-secondary);
  }
  /* Tabular figures so 3/7 and 11/12 do not jitter the header as a mission advances. */
  :root[data-ui="2"] .insp-h__n {
    font-size: 11.5px; font-variant-numeric: tabular-nums;
    color: var(--color-accent); font-weight: 600;
  }

  :root[data-ui="2"] .insp-bar {
    block-size: 3px; border-radius: 999px; overflow: hidden;
    background: color-mix(in srgb, var(--color-text-primary) 10%, transparent);
  }
  :root[data-ui="2"] .insp-bar__fill {
    block-size: 100%; border-radius: 999px; background: var(--color-accent);
    transition: inline-size var(--dur-slow) var(--ease-out);
  }

  /* ── STEPS ──
     A spine down the leading edge with the marks sitting on it: the shape says "sequence" before
     a single word is read, which is what a glance at a running mission is for. */
  :root[data-ui="2"] .insp-steps {
    list-style: none; margin: 6px 0 0; padding: 0 0 0 0;
    position: relative;
  }
  :root[data-ui="2"] .insp-steps::before {
    content: ""; position: absolute; inset-block: 9px;
    inset-inline-start: 9px; inline-size: 1.5px;
    background: color-mix(in srgb, var(--color-text-primary) 12%, transparent);
  }
  :root[data-ui="2"] .insp-step {
    position: relative;
    display: flex; align-items: flex-start; gap: 10px;
    padding: 6px 6px 6px 0;
    padding-inline-start: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out);
  }
  :root[data-ui="2"] .insp-step:hover { background: color-mix(in srgb, var(--color-text-primary) 6%, transparent); }

  :root[data-ui="2"] .insp-step__mark {
    flex: 0 0 auto;
    inline-size: 19px; block-size: 19px; margin-block-start: 1px;
    display: grid; place-items: center;
    border-radius: 50%;
    /* line-height:1 is what actually centres it. place-items centres the LINE BOX, and an
       inherited 1.5 line-height makes that box taller than the digit — so the box is centred
       perfectly and the number still sits low. Collapsing the leading to the glyph makes the two
       the same thing. */
    line-height: 1;
    font-size: 10.5px; font-weight: 700; font-variant-numeric: tabular-nums;
    /* Opaque, because it sits ON the spine and a translucent mark would show the line through it. */
    background: var(--color-sidebar-bg);
    border: 1.5px solid color-mix(in srgb, var(--color-text-primary) 16%, transparent);
    color: var(--color-text-secondary);
  }
  :root[data-ui="2"] .insp-step.is-done .insp-step__mark {
    background: var(--color-accent); border-color: var(--color-accent); color: var(--color-on-accent);
  }
  :root[data-ui="2"] .insp-step.is-fail .insp-step__mark {
    background: var(--color-danger, #c0392b); border-color: var(--color-danger, #c0392b); color: #fff;
  }
  /* The running step is the only animated thing in the panel, so the eye finds it with no search. */
  :root[data-ui="2"] .insp-step.is-run .insp-step__mark {
    border-color: var(--color-accent);
    border-inline-start-color: transparent;
    animation: insp-spin .8s linear infinite;
  }
  @keyframes insp-spin { to { transform: rotate(360deg); } }

  :root[data-ui="2"] .insp-step__t {
    font-size: 12.5px; line-height: 1.45;
    color: var(--color-text-secondary);
    /* Two lines, then ellipsis. A nine-step mission with wrapped four-line titles is a wall. */
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  }
  :root[data-ui="2"] .insp-step.is-run .insp-step__t { color: var(--color-text-primary); font-weight: 600; }
  :root[data-ui="2"] .insp-step.is-done .insp-step__t { color: var(--color-text-primary); }

  /* ── SOURCES ──
     Brain's whole promise, given a card each: the number that appears in the answer, the file it
     came from, and the page. Pressing one opens the passage itself. */
  :root[data-ui="2"] .insp-src {
    display: flex; align-items: flex-start; gap: 10px;
    inline-size: 100%; text-align: start;
    padding: 10px 11px;
    border: 1px solid color-mix(in srgb, var(--color-border) 45%, transparent);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
  }
  :root[data-ui="2"] .insp-src:hover {
    border-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
    box-shadow: var(--shadow-sm);
  }
  :root[data-ui="2"] .insp-src:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
  :root[data-ui="2"] .insp-src__n {
    flex: 0 0 auto;
    inline-size: 20px; block-size: 20px; margin-block-start: 1px;
    display: grid; place-items: center;
    border-radius: 6px;
    background: color-mix(in srgb, var(--color-accent) 15%, transparent);
    color: var(--color-accent);
    font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums;
  }
  :root[data-ui="2"] .insp-src__box { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
  :root[data-ui="2"] .insp-src__t {
    font-size: 12.5px; line-height: 1.4; color: var(--color-text-primary);
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  }
  :root[data-ui="2"] .insp-src__p {
    font-size: 11px; color: var(--color-text-secondary); font-variant-numeric: tabular-nums;
  }

  /* Pressing a row in the panel has to LAND somewhere visible, or it reads as a dead control.
     A brief ring on the card it scrolled to is the whole feedback. */
  :root[data-ui="2"] .is-inspector-flash {
    animation: insp-flash 1.2s var(--ease-out);
  }
  @keyframes insp-flash {
    0%, 100% { box-shadow: var(--shadow-sm); }
    25%      { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 40%, transparent); }
  }
}

@media (prefers-reduced-motion: reduce) {
  :root[data-ui="2"] .insp-step.is-run .insp-step__mark { animation: none; border-inline-start-color: var(--color-accent); }
  :root[data-ui="2"] .insp-bar__fill { transition: none; }
  :root[data-ui="2"] .is-inspector-flash { animation: none; }
}

/* ── RAIL ACTIVITY ──────────────────────────────────────────────────────────────
   The dot that says a product is working while you are looking at a different one.
   Small enough to ignore, animated enough to catch, and it carries a count once more than one
   thing is in flight — "3 running" is a different situation from "something is running". */
@media (min-width: 768.02px) {
  :root[data-ui="2"] .rail__item { position: relative; }
  :root[data-ui="2"] .rail__busy {
    position: absolute;
    inset-block-start: 7px;
    /* Trailing corner, so it never collides with the icon in either direction. */
    inset-inline-end: 9px;
    min-inline-size: 7px; block-size: 7px;
    display: grid; place-items: center;
    padding-inline: 0;
    border-radius: 999px;
    background: var(--color-accent);
    /* A ring in the rail's own ground keeps the dot legible on top of an active item's fill. */
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-sidebar-bg) 86%, var(--color-text-primary) 6%);
    font-size: 9px; font-weight: 700; line-height: 1;
    color: var(--color-on-accent);
    animation: rail-pulse 1.9s var(--ease-inout) infinite;
  }
  /* With a count in it the dot has to grow into a pill, and stop pulsing — a number that throbs
     is unreadable. */
  :root[data-ui="2"] .rail__busy:not(:empty) {
    min-inline-size: 14px; block-size: 14px;
    padding-inline: 3px;
    animation: none;
  }
  @keyframes rail-pulse {
    0%, 100% { opacity: 1;   transform: scale(1); }
    50%      { opacity: .45; transform: scale(.82); }
  }
  /* The label goes to the accent too, so the state reads even for someone who cannot see the dot
     against the ground. */
  :root[data-ui="2"] .rail__item.is-busy:not(.is-active) .rail__label { color: var(--color-accent); }
}
@media (prefers-reduced-motion: reduce) {
  :root[data-ui="2"] .rail__busy { animation: none; }
}

/* ── STILL WORKING ──────────────────────────────────────────────────────────────
   A conversation whose answer is still being produced — in this tab, or on the server after the
   tab was closed. Deliberately NOT scoped to UI 2.0: the rail and the inspector are a look, but
   "which of my forty conversations is still running" is a capability, and putting a capability
   behind an aesthetic switch is the wrong trade.

   `.chat-item` is already a flex row with an auto-width title, so the dot claims its own 6px and
   the title reflows around it — no absolute positioning to keep in sync with a hover state. */
.chat-item__live {
  flex: 0 0 auto;
  inline-size: 6px; block-size: 6px;
  border-radius: 50%;
  background: var(--color-accent);
  animation: chat-live-pulse 1.9s var(--ease-inout) infinite;
}
@keyframes chat-live-pulse {
  0%, 100% { opacity: 1;  transform: scale(1); }
  50%      { opacity: .4; transform: scale(.7); }
}
/* The title lifts to full contrast too — a 6px dot alone is not enough to find a row by while
   scrolling a long list, and it is the only cue for anyone who cannot pick the colour out. */
.chat-item.is-working .chat-item__title { color: var(--color-text-primary); font-weight: var(--weight-medium); }

@media (prefers-reduced-motion: reduce) {
  .chat-item__live { animation: none; }
}

/* ── LIST METADATA (UI 2.0) ─────────────────────────────────────────────────────
   A stamp per row and a count per heading. Together they turn the sidebar from a list of titles
   into an inventory you can read at a glance — which is the difference between a chat app's
   history and a workspace's index of work. */
@media (min-width: 768.02px) {
  :root[data-ui="2"] .chat-item__stamp {
    flex: 0 0 auto;
    font-size: 10.5px;
    font-variant-numeric: tabular-nums;
    color: var(--color-text-tertiary, var(--color-text-secondary));
    opacity: .65;
    transition: opacity var(--dur-fast) var(--ease-out);
  }
  /* The row's actions live in the same slot and appear on hover or when active. Two things in one
     place is a collision, so the stamp yields — the time is context, the controls are intent. */
  :root[data-ui="2"] .chat-item:hover .chat-item__stamp,
  :root[data-ui="2"] .chat-item.is-active .chat-item__stamp { opacity: 0; inline-size: 0; overflow: hidden; }

  :root[data-ui="2"] .history__group-label {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
  }
  :root[data-ui="2"] .history__group-n {
    font-size: 10.5px; font-variant-numeric: tabular-nums; font-weight: 600;
    color: var(--color-text-secondary);
    background: color-mix(in srgb, var(--color-text-primary) 8%, transparent);
    border-radius: 999px;
    padding: 1px 6px;
    /* Reset the heading's own tracking — letter-spaced digits inside a 20px pill read as a gap. */
    letter-spacing: 0;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════════
   THE COMPOSER, REBUILT — after 21st.dev "Input Bar" (Agent Elements)
   ═══════════════════════════════════════════════════════════════════════════════

   WHAT WAS WRONG. Everything lived on ONE flex row: the textarea, the mode picker, the tools
   menu, and a four-button action tray. The buttons are fixed-width, so the textarea got whatever
   was left — measured at 790px wide, the placeholder "اسأل فِراس..." wrapped onto THREE lines
   inside a composer that was mostly chrome. The stylesheet already conceded this below 560px,
   where `flex-wrap` broke the textarea onto its own line. The phone layout was the right layout.

   WHAT IT IS NOW. The shape every serious AI composer converged on, and the one 21st.dev's own
   Agent Elements ships: the prompt owns a full-width row, and every control sits on a second row
   beneath it, split by intent —

     leading  → what you ADD to the message   (attach · tools · mode)
     trailing → how you SEND it               (dictate · call · send)

   Attach moved out of the trailing cluster in the process. It had been sitting directly beside
   Send, which made the most destructive mis-tap in the composer also the easiest one.

   THE THEME IS OURS, NOT THEIRS. Not one hex value is copied. Every colour, radius, shadow,
   duration and easing below is an existing Firas token, so the composer re-themes with the other
   six themes and both UI skins for free. What is taken from 21st.dev is the STRUCTURE — row
   split, control grouping, the 0fr→1fr tray reveal, and the send button's three states.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* The card itself. A touch more air than before, because the two rows need room to read as two
   rows rather than as a taller box. */
.composer {
  padding: var(--space-3) var(--space-3) var(--space-2);
  gap: 2px;
}

/* ── ROW 1 · THE PROMPT ─────────────────────────────────────────────────────────
   No siblings any more, so nothing competes with it for width. */
.composer__row {
  display: block;
  inline-size: 100%;
}
.composer__textarea {
  display: block;
  inline-size: 100%;
  padding: 6px var(--space-2) 4px;
  /* Was 200px. With the buttons out of the way the prompt can breathe — but it still stops well
     short of swallowing the thread it sits over. */
  max-block-size: 208px;
}

/* ── ROW 2 · THE CONTROLS ───────────────────────────────────────────────────────
   One row, two intents, pushed to opposite ends. */
.composer__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  inline-size: 100%;
  padding-inline: 2px;
  /* MEASURED, not defensive. Every control is floored by min-inline-size and every cluster is
     flex:0 0 auto, so nothing here shrinks — the row simply overflows. At 320px the budget is
     264px of content box against roughly 314px of controls once the mode pill is counted. The old
     one-row composer survived that only because `.composer__row { flex-wrap: wrap }` bailed it out
     below 560px; that wrap belonged to a row this one replaces, so the escape hatch has to come
     with it. Wrapping costs nothing at any width where it is not needed. */
  flex-wrap: wrap;
  row-gap: var(--space-2);
}
.composer__tools {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* min-inline-size:0 so a long mode label truncates instead of shoving the send button off the
     trailing edge — the send button is the one control that must never move. */
  min-inline-size: 0;
  flex: 0 1 auto;
}
/* The action tray's pill background went with the four-button cluster it existed to hold. Three
   bare controls with one filled Send reads cleaner and matches the leading side's weight. */
.composer__actions {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  padding: 0;
  background: transparent;
  border-radius: 0;
  /* Once the bar wraps, `justify-content: space-between` has nothing to push against — a lone
     cluster on the second line sits at the START edge. This keeps Send pinned to the trailing
     edge on whichever line it lands on, which is the one position it must never lose. */
  margin-inline-start: auto;
}

/* Attach now lives on the LEADING side, so the rules that styled it inside .composer__actions no
   longer reach it. Restated here at the same 34px as its new neighbours (the tools trigger and
   the mode pill), so the leading cluster is one consistent height. */
.composer__tools .composer__attach {
  inline-size: 34px; block-size: 34px; min-inline-size: 34px;
  border-radius: var(--radius-pill);
  border: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-bg);
  color: var(--color-text-secondary);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.composer__tools .composer__attach svg { inline-size: 18px; block-size: 18px; }
.composer__tools .composer__attach:hover {
  color: var(--color-text-primary);
  border-color: var(--color-border-strong);
  background: var(--color-surface);
}

/* Mic and call: ghost buttons that only take a ground on hover, so Send is the single filled
   thing on the row and the eye never has to choose between three highlights. */
.composer__actions .composer__mic,
.composer__actions .composer__call {
  inline-size: 34px; block-size: 34px; min-inline-size: 34px;
  border-radius: var(--radius-pill);
  border: var(--border-hairline) solid transparent;
  background: transparent;
  color: var(--color-text-secondary);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.composer__actions .composer__mic svg,
.composer__actions .composer__call svg { inline-size: 18px; block-size: 18px; }
.composer__actions .composer__mic:hover,
.composer__actions .composer__call:hover {
  color: var(--color-text-primary);
  background: var(--color-bg-subtle);
  border-color: var(--color-border-hairline);
}
/* Recording keeps its filled accent state — it is a MODE, not a hover, and it has to be obvious
   from across the screen that the microphone is live. */
.composer__actions .composer__mic.is-on {
  color: var(--color-on-accent);
  background: var(--color-accent);
  border-color: var(--color-accent);
}

/* Send: the one filled control. Three states, exactly as the reference ships them —
   idle (nothing to send) reads as unavailable, ready reads as the primary action, and streaming
   swaps the arrow for a stop square without moving or resizing. */
.composer__actions .composer__send {
  inline-size: 34px; block-size: 34px; min-inline-size: 34px;
  border-radius: var(--radius-pill);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.composer__actions .composer__send svg { inline-size: 17px; block-size: 17px; }

/* ── THE ATTACHMENT TRAY ────────────────────────────────────────────────────────
   0fr → 1fr on a grid row. The trick from the reference, and the only way to animate a height
   that is not known in advance: the row's track collapses to nothing while the content keeps its
   natural size inside an overflow:hidden wrapper. `display:none` cannot be transitioned, and a
   hard-coded max-height either clips two rows of thumbnails or leaves a gap after one. */
.attach-tray-outer {
  display: grid;
  /* OPEN by default and closed by :has(), never the reverse. A browser without :has() then falls
     back to "always open" — which costs nothing, because an EMPTY tray is zero-height and
     zero-padding anyway. Defaulting to closed would have hidden real attachments on those
     browsers, which is the difference between a missing animation and a missing feature. */
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--dur-base) var(--ease-out);
}
.attach-tray-outer:has(> .attach-tray:empty) { grid-template-rows: 0fr; }

.attach-tray {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  /* Both are required by the grid trick: min-block-size:0 lets the track actually reach 0fr
     (a grid item's automatic minimum size is its content, which would pin it open), and
     overflow:hidden clips the content while the track closes around it. */
  min-block-size: 0;
  overflow: hidden;
  padding: 2px var(--space-2) var(--space-2);
}
/* Padding survives a collapsed track, so an empty tray would leave a permanent gap above the
   prompt. Zero it with the same :empty test the old rule used. */
.attach-tray:empty { padding: 0; }

/* ── RECORDING ──────────────────────────────────────────────────────────────────
   The bar already overlays the whole card (position:absolute; inset:0), which survives the
   restructure untouched. What is added is the card's own reaction: while the mic is live the
   composer's edge picks up the accent, so the state is legible from the page, not only from
   inside the bar. Purely decorative, so browsers without :has() simply do not get it. */
.composer:has(.mic-rec:not([hidden])) {
  border-color: color-mix(in srgb, var(--color-accent) 55%, transparent);
  box-shadow: var(--shadow-composer), 0 0 0 3px color-mix(in srgb, var(--color-accent) 14%, transparent);
}
/* #micRec is a child of the FORM, not of the row, and it is pinned with `inset: 0` — so it covers
   whatever the composer's height becomes. That was invisible while the form was one ~60px row and
   the bar happened to line up with it. Two rows make the form ~100px, and a 34px waveform floating
   in a 100px card reads as a bug rather than a state.

   The fix is to USE the height rather than fight it: the waveform grows into it and the bar gets
   real vertical padding, so the recording surface looks like something that was designed at this
   size. No repositioning, no new containing block — the overlay still covers the whole card, which
   is exactly right, because while the mic is live the card IS the recorder. */
.mic-rec {
  align-items: center;
  padding: 12px 14px;
  gap: 12px;
}
.mic-rec__viz { block-size: 56px; }

/* ── NARROW ─────────────────────────────────────────────────────────────────────
   The old ≤560px rules existed to FORCE this layout with flex-wrap. It is the layout now, at
   every width, so they are neutralised rather than left to fight the new structure. What a phone
   still needs is bigger targets and a leading cluster that may shrink. */
@media (max-width: 560px) {
  .composer__row { flex-wrap: nowrap; }
  .composer__textarea { flex: none; inline-size: 100%; order: 0; }
  .composer__bar { gap: var(--space-1); }
  .composer__tools { flex: 0 1 auto; }
  /* NOT 0. The old rule set this to `auto` to push the cluster right inside a wrapped
     .composer__row; zeroing it here to "neutralise the old layout" also killed the identical job
     it does in the new one — measured at 320px, the trailing cluster snapped back to x=31, i.e.
     Send landed on the LEADING edge of the wrapped line. On a wrapped line `space-between` has
     nothing to push against, so this margin is the only thing holding Send in place. */
  .composer__actions { margin-inline-start: auto; }
}
@media (pointer: coarse), (max-width: 560px) {
  /* 40px, not 44: the row now holds five controls plus a mode pill, and 44 pushes the leading
     cluster into the trailing one on a 360px screen. 40 clears the touch-target floor with the
     6px gaps counted in. */
  .composer__tools .composer__attach,
  .composer__actions .composer__mic,
  .composer__actions .composer__call,
  .composer__actions .composer__send {
    inline-size: 40px; block-size: 40px; min-inline-size: 40px;
  }
  .composer__actions .composer__send svg { inline-size: 19px; block-size: 19px; }
}

/* ── UI 2.0 ─────────────────────────────────────────────────────────────────────
   The new geometry and the frosted, lifted composer from part one apply on top of this without
   restating anything — the structure is shared, the skin is not. Only the tray's reveal needs the
   longer UI 2.0 duration so it matches everything else that opens. */
:root[data-ui="2"] .attach-tray { transition-duration: var(--dur-slow); }

/* ═══════════════════════════════════════════════════════════════════════════════
   BRAIN AND CODE — the same composer, spoken in their own rooms
   ═══════════════════════════════════════════════════════════════════════════════

   Firas has three places you type into, and until now they were three different objects:

     chat   a card holding the field                     (rebuilt above, after 21st.dev)
     brain  a full-width strip, hairline on top, with a separately-bordered field inside it
     code   the same strip again, with a 1.5px pill field and a text button

   Two of them were still the older idiom — a bar across the bottom with a bordered input dropped
   into it, so the eye reads TWO nested boxes where there is one thing to do. The rebuilt composer
   reads as one surface: the card IS the field, and the controls live inside it.

   These bring the other two into that language. Structure is untouched — Brain's ask bar and
   Code's instruction bar have two children each and never needed a second row — so this is
   purely about which element carries the border. Not one line of markup or JS changes, which is
   also why it cannot break either product.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* ── BRAIN ──────────────────────────────────────────────────────────────────────
   The closest surface to chat — you ask, it answers at length — so it gets the closest
   treatment: the strip stops spanning the panel and becomes a floating card on the same
   --column-w measure the composer uses, and the field inside gives up its own border. */
.fb-ask {
  align-items: flex-end;
  gap: var(--space-2);
  /* Was a full-bleed strip with a rule on top. Now a card, inset from the panel edges. */
  border-block-start: 0;
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-composer);
  margin-inline: auto;
  inline-size: min(100% - 2 * var(--space-4), var(--column-w));
  /* The safe-area allowance moves from padding to margin: it has to sit OUTSIDE the card, or the
     card grows a phantom home-indicator-sized floor on iOS. */
  margin-block: 0 max(var(--space-3), var(--safe-bottom));
  padding: var(--space-3) var(--space-3) var(--space-2);
  padding-inline-start: var(--space-3);
  padding-inline-end: var(--space-3);
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
/* focus-within, not :focus on the field — the whole card lights, the way the chat composer does. */
.fb-ask:focus-within {
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-composer), var(--shadow-focus);
}
/* The field is now INSIDE a bordered card, so its own border and fill are the second box that
   made the old bar read as two nested things. Both go; the card carries them. */
.fb-ask__in {
  border-color: transparent;
  background: transparent;
  border-radius: var(--radius-md);
  padding: 6px var(--space-2);
}
.fb-ask__in:focus {
  border-color: transparent;
  background: transparent;
}

/* ── CODE ───────────────────────────────────────────────────────────────────────
   The IDE is a cockpit — instruments around a work surface — so its instruction bar KEEPS its
   rule against the editor rather than floating free. What changes is the field: the same
   hairline-and-radius as everywhere else instead of a 1.5px pill, and a real focus ring so the
   one place you talk to Firas in Code answers a keystroke the way the other two do. */
.cw-ai {
  align-items: center;
  gap: var(--space-2);
  padding: 10px 12px;
}
.cw-ai__in {
  border-width: var(--border-hairline);
  border-color: var(--color-border-hairline);
  border-radius: var(--radius-lg);
  padding: 10px 14px;
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.cw-ai__in:focus {
  border-color: var(--color-accent);
  background: var(--color-surface);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 12%, transparent);
}
/* Match the send button's height to the field so the pair sits on one baseline instead of the
   button floating a couple of pixels proud of it. */
.cw-ai__go {
  block-size: 40px;
  display: inline-flex; align-items: center; justify-content: center;
}

/* UI 2.0 lifts all three the same way — the frosted, deeper card from part one — so the only
   thing restated here is Brain's, which does not inherit .composer's rules. */
:root[data-ui="2"] .fb-ask {
  background: color-mix(in srgb, var(--color-surface) 84%, transparent);
  backdrop-filter: blur(20px) saturate(1.35);
  -webkit-backdrop-filter: blur(20px) saturate(1.35);
  border-color: color-mix(in srgb, var(--color-border) 55%, transparent);
}
:root[data-ui="2"] .fb-ask:focus-within {
  border-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
  box-shadow: var(--shadow-composer), 0 0 0 3px color-mix(in srgb, var(--color-accent) 12%, transparent);
}
/* UI 2.0 part one softens the border on EVERY input/textarea/select, and
   `:root[data-ui="2"] textarea` (0,1,1) outranks a bare `.fb-ask__in` (0,1,0) — so the field kept
   a visible outline inside the new card and the bar still read as two nested boxes, which is the
   exact thing this section exists to remove. Restated at matching specificity. */
:root[data-ui="2"] .fb-ask__in,
:root[data-ui="2"] .fb-ask__in:focus {
  border-color: transparent;
  background: transparent;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   ONE ROW, ONE MATERIAL — fixing the discord in the control bar
   ═══════════════════════════════════════════════════════════════════════════════

   MEASURED, not guessed. Six controls sat on the new toolbar in three different materials and
   two different heights:

     attach     40x40   fill #262624   hairline border      ← my rule
     tools "+"  44x44   fill #262624   hairline border      ← styles.css:5564, pre-existing
     mode pill  50x44   fill #1F1E1D   hairline border      ← styles.css:2179 + 5565, DARKER fill
     mic        40x40   transparent    transparent border   ← my rule
     call       40x40   transparent    transparent border   ← my rule
     send       40x40   filled accent  no border            ← correct, it is the primary

   So the leading three read as raised buttons and the trailing two as bare icons, the middle one
   was a shade darker than its neighbours, and the first was 4px shorter than the two beside it.
   Six controls, one row — that is the نشاز.

   THE REFERENCE ALREADY ANSWERS THIS. 21st.dev's Input Bar gives its attachment button and its
   toolbar pills `bg-transparent` with `hover:bg-neutral-100`, and fills exactly one control: Send.
   A toolbar is a set of equals with one primary; giving four of them their own ground makes four
   primaries and no hierarchy.

   So: every control on this row is a ghost that takes a ground only on hover, at one shared size.
   Send stays filled — it is the only thing here that commits anything. States that MEAN something
   (a tool switched on, the menu open, the mic live) keep their colour, because those are the only
   moments a second highlight is information rather than noise.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* One selector list, so the six can never drift apart again. */
.composer__bar .composer__attach,
.composer__bar .tools-menu__trigger,
.composer__bar .mode-select__trigger,
.composer__bar .composer__mic,
.composer__bar .composer__call {
  border-color: transparent;
  background: transparent;
  color: var(--color-text-secondary);
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.composer__bar .composer__attach:hover,
.composer__bar .tools-menu__trigger:hover,
.composer__bar .mode-select__trigger:hover,
.composer__bar .composer__mic:hover,
.composer__bar .composer__call:hover {
  background: var(--color-bg-subtle);
  border-color: transparent;
  color: var(--color-text-primary);
}

/* ── HEIGHT ──
   The icon buttons are circles, so width and height move together; the mode pill is wider because
   it carries a label, but its HEIGHT has to match or the row has two baselines. */
.composer__bar .composer__attach,
.composer__bar .tools-menu__trigger,
.composer__bar .composer__mic,
.composer__bar .composer__call,
.composer__bar .composer__send {
  inline-size: 34px; block-size: 34px; min-inline-size: 34px;
}
.composer__bar .mode-select__trigger {
  block-size: 34px; min-block-size: 34px;
}

@media (pointer: coarse), (max-width: 560px) {
  /* 40 for ALL six. styles.css:5564-5565 raised only the tools trigger and the mode pill to 44,
     which is where the 4px height difference came from. */
  .composer__bar .composer__attach,
  .composer__bar .tools-menu__trigger,
  .composer__bar .composer__mic,
  .composer__bar .composer__call,
  .composer__bar .composer__send {
    inline-size: 40px; block-size: 40px; min-inline-size: 40px;
  }
  .composer__bar .mode-select__trigger {
    block-size: 40px; min-block-size: 40px;
  }
}

/* ── THE STATES THAT STILL EARN A COLOUR ──
   Restated because the ghost rule above would otherwise flatten them: a menu that is open, a tool
   that is switched on, and a microphone that is live are all things the user needs to see WITHOUT
   looking for them. Everything else is quiet. */
.composer__bar .tools-menu.is-open .tools-menu__trigger,
.composer__bar .mode-select.is-open .mode-select__trigger,
.composer__bar .mode-select__trigger[aria-expanded="true"] {
  color: var(--color-accent);
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}
.composer__bar .composer__mic.is-on {
  color: var(--color-on-accent);
  background: var(--color-accent);
  border-color: var(--color-accent);
}

/* Send is the exception the rest of the row exists to set off. */
.composer__bar .composer__send {
  background: var(--color-accent);
  color: var(--color-on-accent);
  border: 0;
}
.composer__bar .composer__send:disabled {
  background: var(--color-border-strong);
  color: var(--color-text-muted);
}
.composer__bar .composer__send:hover:not(:disabled) { background: var(--color-accent-hover); }

/* ═══════════════════════════════════════════════════════════════════════════════
   TOOL CALLS SECTION — 21st.dev, ported class-for-class
   ═══════════════════════════════════════════════════════════════════════════════

   Every number below is the original's, read off its Tailwind classes rather than re-judged:

     w-fit max-w-[35rem] ....... width: fit-content; max-inline-size: 35rem
     gap-2 / gap-1 ............. 8px / 4px
     py-2 / pt-1 / pt-2 ........ 8px / 4px / 8px
     min-h-8 min-w-8 .......... 32px          (the icon slot)
     p-1 rounded-lg ........... 4px / 8px     (the icon tile)
     icon size 21 ............. 21px glyph inside that 32px tile
     -space-x-2 ............... -8px overlap between stacked tiles
     rotate 8deg / -8deg ...... alternating tilt, single icon stays straight
     size-7 rounded-lg ........ 28px "+N" chip
     w-px min-h-4 ............. 1px connector, 16px minimum run
     text-xs .................. 12px / 1.333
     text-[11px] .............. 11px
     rounded-xl p-3 mt-2 mb-3 . 12px radius, 12px padding, 8px above, 12px below
     duration-200 ............. 200ms

   ONLY THE PALETTE IS OURS. zinc-200/800 (tiles), zinc-300/700 (connector), zinc-500 (label),
   zinc-600/400 (row text), zinc-400/500 (category), zinc-100/800-50 (detail panel) each map to
   the token that plays that role here — so the section follows all six Firas themes and both
   light and dark instead of shipping a second grey scale beside ours.
   ═══════════════════════════════════════════════════════════════════════════════ */

.tc {
  inline-size: fit-content;
  max-inline-size: 35rem;
}

/* ── header ── flex items-center gap-2 text-zinc-500 py-2 ─────────────────────── */
.tc-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-block: 8px;
  border: 0;
  background: transparent;
  cursor: pointer;
  color: var(--color-text-muted);
  transition: color 200ms var(--ease-out);
}
.tc-head:hover { color: var(--color-text-primary); }   /* hover:text-zinc-900 dark:hover:text-white */

/* ── the stack ── flex min-h-8 items-center -space-x-2 ───────────────────────── */
.tc-stack {
  display: flex;
  align-items: center;
  min-block-size: 32px;
}
.tc-stack__i {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 32px;
}
/* -space-x-2: every tile after the first pulls 8px back onto the one before it. Logical, so the
   pile leans the same way in Arabic as in English. */
.tc-stack__i + .tc-stack__i,
.tc-stack__i + .tc-more { margin-inline-start: -8px; }

/* p-1 min-w-8 min-h-8 bg-zinc-200 dark:bg-zinc-800 rounded-lg text-zinc-600 backdrop-blur */
.tc-tile {
  display: grid;
  place-items: center;
  inline-size: 32px; block-size: 32px;
  min-inline-size: 32px; min-block-size: 32px;
  padding: 4px;
  border-radius: 8px;
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}
.tc-tile svg { inline-size: 21px; block-size: 21px; }

/* z-0 size-7 rounded-lg bg-zinc-200 dark:bg-zinc-700/60 text-xs text-zinc-600 font-normal */
.tc-more {
  position: relative;
  z-index: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 28px; block-size: 28px;
  min-inline-size: 28px; min-block-size: 28px;
  border-radius: 8px;
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
  font-size: 12px;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

/* text-xs font-medium transition-all duration-200 */
.tc-label {
  font-size: 12px;
  line-height: 1.334;
  font-weight: 500;
  white-space: nowrap;
  transition: all 200ms var(--ease-out);
}
[dir="rtl"] .tc-label { font-family: var(--font-arabic); }

/* transition-transform duration-200 + rotate-180 when expanded */
.tc-chev {
  display: inline-flex;
  transition: transform 200ms var(--ease-out);
}
.tc.is-open > .tc-head .tc-chev { transform: rotate(180deg); }
.tc-row.is-open .tc-t .tc-chev { transform: rotate(180deg); }

/* ── collapsible content ── max-h-0/opacity-0 → max-h-[2000px]/opacity-100 ────── */
.tc-body {
  overflow: hidden;
  max-block-size: 0;
  opacity: 0;
  transition: max-block-size 200ms var(--ease-out), opacity 200ms var(--ease-out);
}
.tc.is-open > .tc-body {
  max-block-size: 2000px;
  opacity: 1;
}
.tc-list { padding-block-start: 4px; }   /* space-y-0 pt-1 — rows deliberately have no gap */

/* ── a row ── flex items-stretch gap-2 ───────────────────────────────────────── */
.tc-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
}
/* flex flex-col items-center self-stretch */
.tc-rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  align-self: stretch;
}
/* min-h-8 min-w-8 flex items-center justify-center shrink-0 */
.tc-rail__slot {
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: 32px; min-inline-size: 32px;
  flex-shrink: 0;
}
/* w-px flex-1 bg-zinc-300 dark:bg-zinc-700 min-h-4 */
.tc-line {
  inline-size: 1px;
  flex: 1;
  min-block-size: 16px;
  background: var(--color-border);
}

/* flex-1 min-w-0 */
.tc-col {
  flex: 1;
  min-inline-size: 0;
}

/* flex items-center gap-1 group/parent */
.tc-t {
  display: flex;
  align-items: center;
  gap: 4px;
  border: 0;
  background: transparent;
  padding: 0;
  text-align: start;
  cursor: default;
  color: inherit;
}
.tc-t.has-detail { cursor: pointer; }
/* text-xs text-zinc-600 dark:text-zinc-400 font-medium */
.tc-title {
  font-size: 12px;
  line-height: 1.334;
  font-weight: 500;
  color: var(--color-text-secondary);
  transition: color 200ms var(--ease-out);
}
[dir="rtl"] .tc-title { font-family: var(--font-arabic); }
/* group-hover/parent:text-zinc-900 dark:group-hover/parent:text-white — only when it opens */
.tc-t.has-detail:hover .tc-title { color: var(--color-text-primary); }
.tc-t .tc-chev { color: var(--color-text-muted); }

/* text-[11px] text-zinc-400 dark:text-zinc-500 capitalize */
.tc-cat {
  margin: 0;
  font-size: 11px;
  line-height: 1.4;
  color: var(--color-text-muted);
}
[dir="rtl"] .tc-cat { font-family: var(--font-arabic); }

/* mt-2 space-y-2 text-[11px] bg-zinc-100 dark:bg-zinc-800/50 rounded-xl p-3 mb-3 w-fit */
.tc-detail {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-block: 8px 12px;
  padding: 12px;
  border-radius: 12px;
  background: var(--color-bg-subtle);
  font-size: 11px;
  inline-size: fit-content;
  max-inline-size: 100%;
}
.tc-detail[hidden] { display: none; }
/* flex flex-col — the Input / Output pair */
.tc-detail__g { display: flex; flex-direction: column; min-inline-size: 0; }
/* text-zinc-400 dark:text-zinc-500 font-medium mb-1 */
.tc-detail__h {
  margin-block-end: 4px;
  font-weight: 500;
  color: var(--color-text-muted);
}
[dir="rtl"] .tc-detail__h { font-family: var(--font-arabic); }
.tc-detail__v {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--color-text-secondary);
  overflow-wrap: anywhere;
}
/* The output is real markdown — the component's CompactMarkdown slot. Held at the panel's own
   11px so a rendered heading cannot blow the compact scale apart. */
.tc-out { font-size: 11px; line-height: 1.65; overflow-wrap: anywhere; }
.tc-out > :first-child { margin-block-start: 0; }
.tc-out > :last-child { margin-block-end: 0; }
.tc-out pre { max-inline-size: 100%; overflow-x: auto; }

/* A running step is the one thing here the original had no state for — it renders finished calls.
   The tile picks up the accent so a mission in flight still says where it is. */
.tc-row.is-run .tc-tile {
  background: var(--color-accent-soft);
  color: var(--color-accent);
}
.tc-row.is-run .tc-title { color: var(--color-text-primary); }
.tc-row.is-fail .tc-tile {
  background: color-mix(in srgb, var(--color-danger, #c0392b) 15%, transparent);
  color: var(--color-danger, #c0392b);
}
/* Steps not started yet recede, so "done" and "waiting" are not the same weight. */
.tc-row.is-todo .tc-tile { opacity: .5; }
.tc-row.is-todo .tc-title { color: var(--color-text-muted); }

@media (prefers-reduced-motion: reduce) {
  .tc-chev, .tc-body, .tc-label, .tc-title { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════════
   THE COMPOSER BAR, ARRANGED — UI 2.0 only
   ═══════════════════════════════════════════════════════════════════════════════

       (+)   [ برو · تلقائي ]                        (mic)   (primary)

   Six controls down to four. Attach folds into the "+" sheet, the mode selector becomes a model
   pill, and Call and Send merge into one primary that swaps identity with the input.

   The palette is entirely ours — jade accent, our surfaces, our Arabic face — so the arrangement
   travels across all six themes instead of arriving with someone else's greys.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* Attach and the mode selector leave the bar: one is now a row in the "+" sheet, the other is
   replaced by the pill. Removed from the LAYOUT, not from the DOM — every handler still exists and
   the sheet drives the very same elements. */
:root[data-ui="2"] .composer__bar .composer__attach,
:root[data-ui="2"] .composer__bar .mode-select { display: none; }

/* ── THE MODEL PILL ── "برو تلقائي": the tier at full contrast, the mode muted behind it. */
:root[data-ui="2"] .cs-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  block-size: 34px;
  padding-inline: 12px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
  cursor: pointer;
  white-space: nowrap;
  min-inline-size: 0;
  transition: background var(--dur-fast) var(--ease-out);
}
:root[data-ui="2"] .cs-pill:hover { background: color-mix(in srgb, var(--color-text-primary) 10%, var(--color-bg-subtle)); }
:root[data-ui="2"] .cs-pill__a {
  font-size: 13px; font-weight: 600;
  color: var(--color-text-primary);
  overflow: hidden; text-overflow: ellipsis;
}
:root[data-ui="2"] .cs-pill__b {
  font-size: 13px; font-weight: 500;
  color: var(--color-text-muted);
}
:root[data-ui="2"] [dir="rtl"] .cs-pill__a,
:root[data-ui="2"] [dir="rtl"] .cs-pill__b { font-family: var(--font-arabic); }

@media (pointer: coarse), (max-width: 560px) {
  :root[data-ui="2"] .cs-pill { block-size: 40px; }
  /* On a narrow bar the DEFAULT mode is the first thing worth losing — the tier is the
     identity, and "Auto" tells nobody anything. A mode the reader chose is the opposite:
     the composer's own mode button is hidden under UI 2.0, so this badge is the only
     thing on screen saying Firas will plan first. Dropping it made Plan mode look like
     it had done nothing. */
  @media (max-width: 400px) {
    :root[data-ui="2"] .cs-pill__b[data-mode="auto"] { display: none; }
    :root[data-ui="2"] .cs-pill__a { max-inline-size: 9ch; }
  }
}

/* ── THE PRIMARY ── one circle, two identities.
   Empty box  -> the call, wearing a waveform: "talk to Firas".
   Any text   -> Send.
   Driven entirely by #sendBtn's own `disabled`, which the app already maintains, so there is no
   new state and no handler to keep in sync. Where :has() is missing both simply show, which is
   exactly the UI 1 behaviour — a missing merge, not a missing button. */
:root[data-ui="2"] .composer__bar .composer__call .ico-wave { display: none; }
:root[data-ui="2"] .composer__actions:has(.composer__send:disabled) .composer__call .ico-phone { display: none; }
:root[data-ui="2"] .composer__actions:has(.composer__send:disabled) .composer__call .ico-wave { display: block; }

/* While the box is empty the call IS the primary, so it takes the filled treatment. */
:root[data-ui="2"] .composer__actions:has(.composer__send:disabled) .composer__call {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-on-accent);
}
:root[data-ui="2"] .composer__actions:has(.composer__send:disabled) .composer__call:hover {
  background: var(--color-accent-hover);
  color: var(--color-on-accent);
}
/* …and a disabled Send has nothing left to say, so it steps out rather than sitting there greyed
   beside a live primary. */
:root[data-ui="2"] .composer__bar .composer__send:disabled { display: none; }
/* The moment there is something to send, the call does not go quiet — it GOES.

   Quieting it left three circles on the row (mic, call, send) where the empty state had two, so
   the trailing cluster grew by one the instant a key was pressed and everything shuffled sideways.
   Two icons, always: the microphone, and one primary that IS the call while the box is empty and
   IS Send the moment it is not.

   That also states the rule honestly rather than hiding it: you place a call from an empty box.
   A half-typed message and a voice call are two different intentions, and the composer should not
   offer both at once as though you could have them together. */
/* IT STEPS BACK; IT DOES NOT LEAVE. This used to be `display: none`, which — together with the
   waveform that replaces the phone while the box is empty — meant a phone icon never appeared under
   UI 2.0 at all, and the call read as a removed feature. The intention behind the rule was right:
   a half-typed message and a voice call are different intentions and should not compete. Quieting
   the button says that; deleting it says the feature is gone. */
:root[data-ui="2"] .composer__actions:has(.composer__send:not(:disabled)) .composer__call {
  opacity: .55;
  transform: scale(.92);
}
/* Agent and Code hide the call button entirely (a pre-existing rule), so there Send must stay
   visible even with nothing typed — otherwise those composers lose their primary altogether. */
/* CORRECT ORDER. `:root` IS <html>, so `body … :root` can never match and this rule did nothing
   for as long as it has existed — the Agent's composer really has had no primary button while
   empty. The product class lives on <body>, which IS a descendant of the root, so the root
   attribute has to come first. Firas Code is not in the pair any more: .product-code hides
   .composer-wrap outright (see the rule near the Code workspace), so it has no Send to re-show. */
:root[data-ui="2"] body.product-agent .composer__bar .composer__send:disabled { display: grid; }

/* ═══ SHEETS ═══════════════════════════════════════════════════════════════════
   Same construction as the share sheet already in this stylesheet: a dimmed ground and a card
   that rises from the bottom edge. Grouped rounded cards inside, which is what makes a settings
   list read as sections rather than as one long list. */
.cs-sheet {
  position: fixed; inset: 0; z-index: 1300;
  display: flex; align-items: flex-end; justify-content: center;
  background: rgba(0, 0, 0, .42);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  opacity: 0;
  transition: opacity .18s var(--ease-out);
}
.cs-sheet.is-open { opacity: 1; }
.cs-sheet__card {
  inline-size: min(96vw, 460px);
  max-block-size: min(84vh, 720px);
  margin-block-end: max(14px, env(safe-area-inset-bottom));
  display: flex; flex-direction: column;
  padding: 10px 12px 16px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: 22px;
  background: var(--color-surface);
  box-shadow: 0 18px 60px rgba(0, 0, 0, .34);
  transform: translateY(16px);
  transition: transform .2s var(--ease-out);
}
.cs-sheet.is-open .cs-sheet__card { transform: none; }
/* The grabber. Purely a signal that this thing came from the bottom edge. */
.cs-sheet__card::before {
  content: "";
  inline-size: 38px; block-size: 4px;
  margin: 2px auto 8px;
  border-radius: 999px;
  background: var(--color-border-strong);
}
.cs-sheet__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
  padding: 2px 2px 12px;
}
.cs-sheet__x, .cs-sheet__sp {
  inline-size: 32px; block-size: 32px; flex: 0 0 auto;
}
.cs-sheet__x {
  display: grid; place-items: center;
  border: 0; border-radius: 50%;
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.cs-sheet__x:hover { background: var(--color-border-strong); color: var(--color-text-primary); }
.cs-sheet__title {
  flex: 1 1 auto; text-align: center;
  font-size: 16px; font-weight: 600;
  color: var(--color-text-primary);
}
[dir="rtl"] .cs-sheet__title { font-family: var(--font-arabic); }
.cs-sheet__body {
  display: flex; flex-direction: column; gap: 12px;
  overflow-y: auto; overscroll-behavior: contain;
  padding-block-end: 2px;
}
/* A COLUMN FLEX CONTAINER SHRINKS ITS CHILDREN BEFORE IT SCROLLS, and a sheet must never do
   that. Measured at 375x812 with five groups open: the models card held 290px of rows and
   rendered at 235px, slicing the fourth model in half; the response-style card lost its last
   subtitle. The `overflow-y: auto` above never engaged, because the children yielded first -
   nothing overflowed, so nothing scrolled. Every child now keeps its natural height and the
   body scrolls, which is what that overflow rule was always meant to produce. Every sheet in
   the app inherits the fix. */
.cs-sheet__body > * { flex: 0 0 auto; }

/* A label over a card, for a group whose rows do not say what they are together.
   The model tiers need none - four product names under "Select model" explain
   themselves - but two rows reading "Auto" and "Plan" need to say which axis. */
.cs-sec {
  font: 600 12.5px/1.3 var(--font-sans);
  color: var(--color-text-secondary);
  padding-inline: 4px;
  margin-block: 14px 7px;
}

/* A grouped card of rows, with hairlines between rows and none at the ends. */
.cs-card {
  border-radius: 16px;
  background: var(--color-bg-subtle);
  overflow: hidden;
}
.cs-row {
  inline-size: 100%;
  display: flex; align-items: center; gap: 12px;
  padding: 14px 14px;
  border: 0; background: transparent;
  text-align: start;
  cursor: pointer;
  color: var(--color-text-primary);
  transition: background var(--dur-fast) var(--ease-out);
}
.cs-row + .cs-row { box-shadow: inset 0 1px 0 color-mix(in srgb, var(--color-text-primary) 8%, transparent); }
.cs-row:hover { background: color-mix(in srgb, var(--color-text-primary) 5%, transparent); }
.cs-row__ic { flex: 0 0 auto; display: grid; place-items: center; color: var(--color-text-secondary); }
.cs-row__mid { flex: 1 1 auto; min-inline-size: 0; display: flex; flex-direction: column; gap: 2px; }
.cs-row__t { font-size: 15px; font-weight: 500; }
.cs-row__sub { font-size: 12.5px; color: var(--color-text-muted); }
[dir="rtl"] .cs-row__t, [dir="rtl"] .cs-row__sub { font-family: var(--font-arabic); }
.cs-row__val { flex: 0 0 auto; font-size: 13.5px; color: var(--color-text-muted); }
[dir="rtl"] .cs-row__val { font-family: var(--font-arabic); }
.cs-row__chev { flex: 0 0 auto; display: grid; place-items: center; color: var(--color-text-muted); }
.cs-row__check { flex: 0 0 auto; display: grid; place-items: center; color: var(--color-accent); }

/* The switch. Ours, in the accent — the reference's blue would be the one foreign colour on the
   sheet. */
.cs-sw {
  flex: 0 0 auto;
  position: relative;
  inline-size: 44px; block-size: 26px;
  border-radius: 999px;
  background: var(--color-border-strong);
  transition: background var(--dur-base) var(--ease-out);
}
.cs-sw::after {
  content: "";
  position: absolute; inset-block-start: 3px; inset-inline-start: 3px;
  inline-size: 20px; block-size: 20px;
  border-radius: 50%;
  background: #fff;
  transition: transform var(--dur-base) var(--ease-out);
}
.cs-sw.is-on { background: var(--color-accent); }
/* translate, not inset-inline-start, so the knob slides the correct way in Arabic without a
   second rule — a physical `left` here would run the wrong direction under RTL content. */
.cs-sw.is-on::after { transform: translateX(18px); }
[dir="rtl"] .cs-sw.is-on::after { transform: translateX(-18px); }

@media (prefers-reduced-motion: reduce) {
  .cs-sheet, .cs-sheet__card, .cs-sw, .cs-sw::after { transition: none; }
}

/* ═══ THE COMPOSER'S FOCUS RING, AND THE ROOM TO WRITE IN ═══════════════════════

   THREE THINGS, ONE PLACE.

   1 · THE NEON WAS CROSSING THE BUTTONS. Measured at 390px: the text row ends at y=681 and the
       control row begins at y=683, while the global `:focus-visible` rule (styles.css:440) draws
       a 3px ring on the textarea itself — reaching y=684. Two pixels INTO the buttons.
       It was also a duplicate: `.composer.is-focused` already lights the whole card, so focusing
       the box drew two rings, and the inner one was the one that collided. The card keeps the
       ring — it is the larger, clearer indicator — and the textarea gives its up. No accessibility
       is lost: the focus state is still unmistakable, and now it is unmistakably ONE thing.

   2 · A REGRESSION OF MINE. Adding the waveform icon to the call button gave it two SVGs, and only
       UI 2.0 had a rule choosing between them — so UI 1 drew the handset AND the waveform on top of
       each other inside one 40px circle. Confirmed by measurement, not by eye. Hidden by default
       now; UI 2.0's empty state is the only thing that reveals it.

   3 · MORE ROOM TO WRITE, AND HIGHER. An empty box was exactly one 36px line, so the prompt sat
       almost against the controls. It gets a real minimum and the two rows get air between them —
       which is also what stops the ring and the buttons from ever being neighbours again.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* 2 — the waveform is UI 2.0's empty-state icon, and nothing else's. */
.composer__call .ico-wave { display: none; }

/* 1 — the card is the focus indicator; the field inside it is not a second one. */
.composer__textarea:focus-visible,
.composer__textarea:focus {
  outline: none;
  box-shadow: none;
}

/* 3 — room to write. */
:root[data-ui="2"] .composer__textarea {
  /* Was one 36px line. 52 gives an empty prompt visible depth before a word is typed, and keeps
     autoGrow's ceiling (152px, set in app.js) comfortably above it. min-block-size and not
     block-size, so autoGrow still owns the height once the text passes two lines. */
  min-block-size: 52px;
  padding-block: 10px 6px;
}
:root[data-ui="2"] .composer {
  /* 2px was the gap between the prompt and the controls. Six is what makes them read as two rows
     rather than as one crowded one — and it is the space the focus ring was borrowing. */
  gap: 6px;
  padding-block-start: 14px;
}

/* ═══ A STACKING CONTEXT I CREATED, AND WHAT IT TRAPPED ═════════════════════════

   REPRODUCED AND MEASURED. The dictation menu opened UNDER the greeting: "مساءً سعيدًا يا Falcon"
   and the F mark printed straight through it.

   The chain:
     .welcome__title / .welcome__mark   position: relative; z-index: 1   (root context)
     .mic-menu                          position: absolute; z-index: 40
     .composer                          position: relative; z-index: auto  + backdrop-filter
     .composer-wrap                     position: absolute; z-index: auto

   `backdrop-filter` creates a stacking context. UI 2.0 put one on `.composer` for the frosted
   look, and that instantly re-scoped the menu's z-index 40 to INSIDE the composer — where it now
   only outranks the composer's own children. Against the page, the whole composer subtree competed with
   `z-index: auto`, i.e. 0. The greeting's 1 beat all of it.

   Before UI 2.0 there was no context, so 40 competed at the root and won. The menu did not change;
   its ceiling did.

   The fix is to raise the CONTEXT rather than the menu: one z-index on the wrapper lifts the
   composer and everything that opens out of it above the thread in a single move — which also
   covers the tools popover and the mode menu, both of which were one open away from the same bug.
   5 clears the greeting's 1 and stays far below the drawer scrim (390) and the sheets (1300).
   ═══════════════════════════════════════════════════════════════════════════════ */
:root[data-ui="2"] .composer-wrap { z-index: 5; }

/* ═══ THE HALO GOES, AND THE TOPBAR STOPS REPEATING THE COMPOSER ════════════════

   1 · NO MORE CIRCULAR NEON — in BOTH interfaces, on request.

   `.welcome__halo` is a 720px radial gradient sitting behind the greeting and the mark, starting
   26% above them. It was tuned carefully (closest-side, so its ramp completes at the nearest edge
   and leaves no visible boundary) and the tuning worked — but a tuned glow is still a glow, and
   the greeting reads cleaner as type on a ground.

   Hidden rather than deleted: the element and the reasoning behind its gradient stay in place, so
   turning it back on is one line if this is ever missed. It is decorative and aria-hidden, so
   nothing about the screen's meaning changes.

   2 · ONE MODEL CONTROL, NOT TWO.

   UI 2.0 put the model in the composer, where it belongs: a pill reading "ماكس تلقائي" that opens
   the full picker. Leaving Mini / Pro / Ultra / Max ALSO strung across the top bar is the same
   mistake the product switcher made — two controls for one piece of state, so the user has to
   learn that they are the same thing.

   NOT hidden in Firas Code and Firas Brain. Those two replace the main composer with their own
   bar, so the pill does not exist there — removing the top bar's switcher would leave them with no
   way to change model at all. The rule keeps the control exactly where it is still the only one.
   ═══════════════════════════════════════════════════════════════════════════════ */

.welcome__halo { display: none; }

:root[data-ui="2"] body:not(.product-code):not(.product-brain) #tierSelect,
:root[data-ui="2"] body:not(.product-code):not(.product-brain) #tierSwitch { display: none; }

/* ═══ THE MARK ABOVE THE GREETING ═══════════════════════════════════════════════
   Removed on request, in both interfaces, alongside the halo that was behind it. The greeting
   stands on its own — and it is not as if the brand is missing from the screen: the same mark is
   in the sidebar lockup, in the top bar when the sidebar is collapsed, and on every answer Firas
   gives. Hidden rather than deleted, so it is one line to bring back.
   ═══════════════════════════════════════════════════════════════════════════════ */
.welcome__mark { display: none; }

/* ═══ FIRAS CODE COULD NOT SCROLL — ANYWHERE ═══════════════════════════════════

   Reported as "من دزيتله رسالة ما اكدر انزل" — send it a message and you cannot scroll down.
   It was not the chat panel. It was every panel in the IDE.

   MEASURED at a 1100x800 viewport, on a project with 8 thread turns:

     .cw-main            height 592px   grid-template-rows: 2236px   <-- the fault
       .cw-tree          2236px
       .cw-ed            2236px
       .cw-right         2236px
         .cw-thread      2180px
           .cw-thread__list  height 2180, scrollHeight 2180  -> NOTHING to scroll

   `.cw-main` is a grid and declares `grid-template-columns` only. With no `grid-template-rows`
   the implicit row is `auto`, which sizes to CONTENT — so the row grew to 2236px inside a
   container clamped at 592px, and because `.cw-main` is `overflow: visible` the extra 1644px
   simply spilled off-screen with no scrollbar anywhere to reach it.

   Every panel then inherited the lie: `.cw-thread__list` has `overflow-y: auto` and is honestly
   scrollable in principle, but it had been stretched to exactly its own content height, so there
   was no overflow left for it to scroll. The scrollbars were all correct and all useless.

   TWO declarations fix it, and both are required:

     grid-template-rows: minmax(0, 1fr)  pins the row to the container instead of the content.
     min-block-size: 0                   a grid item's AUTOMATIC minimum size is its content, so
                                         even a 1fr row cannot shrink it without this. `.cw-ed`
                                         already carries an explicit min-height and so was never
                                         the one holding the row open.

   After: rows 592.4px, all three columns 592px, and the thread gets 1643px of real scroll range.
   ═══════════════════════════════════════════════════════════════════════════════ */
.cw-main { grid-template-rows: minmax(0, 1fr); }
.cw-tree, .cw-right { min-block-size: 0; }

/* ═══ FIRAS CODE — DRAGGABLE COLUMNS (ported from 21st.dev's Splitter / Ark UI) ═══════════

   The reference drops its ResizeTrigger into a flex gap between two panels. .cw-main has no gap,
   and its track list is rewritten by four separate media queries — a real fourth and fifth column
   would have meant editing every one of them, and getting one wrong is a pane on its own row.
   So each handle is a grid item placed INTO the cell of the pane it precedes and pulled back over
   the seam by a negative inline margin. It owns no track, and since every track here is a length
   or a minmax(…, Nfr) — never a content-sized `auto` — an item lying on top of one cannot widen
   it. Placing the three panes explicitly is the other half of that: with two items given cells by
   hand, auto-placement would go looking for the first free cell and push the editor and the
   preview onto rows of their own.

   Everything is bounded at 861px. Below it .cw-main is `display: flex !important` with one pane
   on screen behind the mobile nav, so a handle would be a flex child separating nothing and
   --cw-split would be re-imposing three columns on a layout that deliberately has one.

   `.cw-main[data-split]` carries an attribute for two reasons: it out-specifies every plain
   `.cw-main` rule in this file without having to be the last one (this block is followed by more
   appended layers, and source order is not a thing to bet a layout on), and it does not exist at
   all until the user has dragged — an untouched workspace keeps the ladder it has today.
   ════════════════════════════════════════════════════════════════════════════════════════ */
.cw-split { display: none; }

@media (min-width: 860.02px) {
  .cw-main[data-split] { grid-template-columns: var(--cw-split); }
  .cw-main > .cw-tree  { grid-area: 1 / 1; }
  .cw-main > .cw-chat  { grid-area: 1 / 2; }   /* the editor moved into the deck; the conversation holds the middle */
  .cw-main > .cw-right { grid-area: 1 / 3; }

  /* Of the 9px hit area only 3 reach back over the previous pane: that edge is where .cw-tree
     parks its 9px scrollbar and where the editor's line numbers end. The 6px on this side land
     inside .cw-etabs' and .cw-tabs' own padding, so the handle covers no control at all.
     z-index sits under the find (16) and outline (15) panels on purpose — a floating panel
     belongs over a seam, and both of those inset 10px from the edge and never reach it anyway. */
  .cw-split {
    display: block; grid-row: 1; z-index: 4;
    justify-self: start; align-self: stretch;
    /* PHYSICAL, and it has to be. justify-self resolves in the GRID's axis, but an inline
       margin resolves against the HANDLE's OWN direction — and this handle carries
       dir="rtl" for its Arabic label. Under the Arabic UI the logical version became
       margin-right and slid the whole 9px grab zone off the seam into the editor: measured at
       1280px the box went from [197,206] to [200,209] with the seam at 200, so the bar no
       longer sat on the border it is drawn over and the 3px reaching back over the previous
       pane was gone. .cw-main is always LTR — setLang writes html.dir="ltr"
       unconditionally and nothing between <html> and this grid overrides it — so left IS
       the seam side, and physical is the only margin the element's own label cannot flip. */
    inline-size: 9px; margin-left: -3px;
    background: none; border: 0; padding: 0;
    cursor: col-resize; touch-action: none;
  }
  .cw-split[data-i="0"] { grid-column: 2; }
  .cw-split[data-i="1"] { grid-column: 3; }

  .cw-split > i {
    display: block;
    inline-size: 3px; block-size: calc(100% - 28px);
    margin-block: 14px; margin-inline: auto;
    border-radius: var(--radius-pill);
    background: transparent;
    transition: background var(--dur-fast) var(--ease-out);
  }
  /* The reference's handle rests in a visible grey because it sits in an empty gap. Here the seam
     is ALREADY drawn — .cw-tree and .cw-ed each carry a border-inline-end — so a second permanent
     bar on top of it reads as a double rule. The hairline is the resting state; the accent is
     kept for hover, focus and the drag itself, which is what the reference animates too. */
  .cw-split:hover > i,
  .cw-split:focus-visible > i,
  .cw-main.is-splitting .cw-split:active > i { background: var(--color-accent); }
  .cw-split:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

  .cw-main.is-splitting { cursor: col-resize; }
  /* Pointer capture already routes the drag back to the handle. This stops the iframe from ALSO
     reacting to a pointer crossing it — a hover left lit inside the user's own page — and stops
     the drag from painting a text selection across the tree and the editor on its way past. */
  .cw-main.is-splitting .cw-preview { pointer-events: none; }
  .cw-main.is-splitting * { user-select: none; -webkit-user-select: none; }
}

/* A tablet in landscape is past 861px and still being driven by a finger. */
@media (min-width: 860.02px) and (pointer: coarse) {
  .cw-split { inline-size: 20px; margin-left: -8px; }   /* physical — see the note above */
}

/* The motion attribute is read off :root as the LEFTMOST compound — :root IS <html>, so any
   ancestor in front of it makes the rule unmatchable. */
@media (prefers-reduced-motion: reduce) { .cw-split > i { transition: none; } }
:root[data-motion="off"] .cw-split > i { transition: none; }

/* ═══ THE CODE FILE RAIL — ported from 21st.dev's FileTree ══════════════════════
   The list it replaces printed "css/styles.css" on one row and "js/app.js" on the next: the
   directory repeated on every line, the filename — the only part being scanned for — pushed to
   the end of the string, one generic coloured dot for every language, no indent, no collapse.

   What is taken from the reference: a per-extension badge carrying the language's own brand
   colour, a rotating chevron with open/closed folder states, and a real indent so the hierarchy
   is visible without drawing a single connector line.

   Firas's tokens do the rest, so this follows all six themes. The one exception is the badge
   colour, which is deliberately NOT a token — #f7df1e is what JavaScript looks like, in every
   theme, and translating it into the palette would throw away the only thing that makes the
   badge readable at 16px.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* ── the language badge ── 16px tile, brand colour at 13% ground, full strength label */
.cw-badge {
  flex: 0 0 auto;
  inline-size: 16px; block-size: 16px;
  display: grid; place-items: center;
  border-radius: 4px;
  font: 700 8.5px/1 var(--font-mono);
  letter-spacing: -.02em;
}

/* ── folders ── a real row, not an emoji with inline styles */
.cw-folder {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 8px;
  cursor: pointer;
  user-select: none;
  color: var(--color-text-secondary);
  font: 600 12px var(--font-sans);
  border-radius: var(--radius-sm);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.cw-folder:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.cw-folder:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.cw-folder__chev {
  flex: 0 0 auto;
  opacity: .55;
  transform: rotate(90deg);
  transition: transform var(--dur-fast) var(--ease-out);
}
.cw-folder.is-collapsed .cw-folder__chev { transform: rotate(0deg); }
.cw-folder > svg:nth-of-type(2) { flex: 0 0 auto; color: var(--color-accent); opacity: .85; }
.cw-folder__p {
  flex: 1 1 auto; min-inline-size: 0;
  direction: ltr; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cw-folder__n {
  flex: 0 0 auto;
  font: 600 10px var(--font-mono);
  color: var(--color-text-muted);
  background: var(--color-bg-subtle);
  border-radius: 999px;
  padding: 1px 6px;
}
.cw-folder__add {
  flex: 0 0 auto;
  inline-size: 20px; block-size: 20px;
  display: grid; place-items: center;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: inherit;
  font: 600 15px/1 var(--font-sans);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.cw-folder:hover .cw-folder__add,
.cw-folder__add:focus-visible { opacity: .9; }
.cw-folder__add:hover { background: var(--color-surface); }

/* ── files ── the row keeps its contract; only the contents changed */
.cw-file {
  border-bottom: 0;                     /* the hairline-per-row look went with the flat list */
  border-radius: var(--radius-sm);
  gap: 8px;
  padding-block: 6px;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
/* A collapsed folder HIDES its files, it does not remove them — three call sites index into
   querySelectorAll(".cw-file") and need that list to keep matching files[]. */
.cw-file.is-hidden { display: none; }
.cw-file__p { font: 500 12.5px var(--font-mono); }
.cw-file__size {
  flex: 0 0 auto;
  font: 500 10px var(--font-mono);
  color: var(--color-text-muted);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.cw-file:hover .cw-file__size, .cw-file.is-active .cw-file__size { opacity: .7; }
.cw-file__x {
  flex: 0 0 auto;
  inline-size: 20px; block-size: 20px;
  display: grid; place-items: center;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--color-text-muted);
  font-size: 11px; cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.cw-file:hover .cw-file__x, .cw-file__x:focus-visible { opacity: 1; }
.cw-file__x:hover { background: color-mix(in srgb, #e5484d 15%, transparent); color: #e5484d; }

/* The rail itself gets a little air so the indent has something to indent from. */
.cw-tree__list { padding: 6px; display: flex; flex-direction: column; gap: 1px; }

/* ═══ THE FILE RAIL, PART TWO — the filter bar ═════════════════════════════
   The 21st.dev reference takes its query as a prop and leaves the box to the host; this is that
   box. STICKY, not merely first in the list: `.cw-tree` is the scroller, so a static bar scrolls
   away at exactly the list length that makes it worth having. The z-index is not decoration —
   every .cw-file is position:relative for its trailing slot, and two positioned boxes paint in
   DOM order, which would put every row on top of the bar. */
.cw-tfilter {
  position: sticky;
  inset-block-start: 0;
  z-index: 2;
  padding: 5px 8px 6px;
  background: var(--color-surface);
  border-block-end: 1px solid var(--color-border-hairline);
}
.cw-tfilter__row {
  display: flex; align-items: center; gap: 6px;
  padding: 3px 6px;
  border: 1px solid var(--color-border-hairline);
  border-radius: var(--radius-sm);
  background: var(--color-bg-subtle);
  transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.cw-tfilter:focus-within .cw-tfilter__row { border-color: var(--color-accent); box-shadow: var(--shadow-focus); }
.cw-tfilter__ico { flex: 0 0 auto; display: grid; place-items: center; color: var(--color-text-muted); }
.cw-tfilter__ico svg { inline-size: 12px; block-size: 12px; }
.cw-tfilter__in {
  flex: 1 1 auto; min-inline-size: 0;
  /* 24px is the floor a pointer can be relied on to hit — WCAG 2.5.8. Measured at 132x20 before
     this, inside a 28px row, so the height comes out of slack that was already there. */
  min-block-size: 24px;
  padding: 2px 0;
  border: 0; outline: none;
  background: transparent;
  color: var(--color-text-primary);
  font: 500 12px var(--font-mono);
}
.cw-tfilter__in::placeholder { color: var(--color-text-muted); }
/* "3/8" — digits and a slash, and the element carries its own dir="ltr", because inherited from
   an Arabic UI the pair is read back as 8/3. */
.cw-tfilter__n {
  flex: 0 0 auto;
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  background: var(--color-accent-soft);
  color: var(--color-accent);
  font: 600 10px var(--font-mono);
}
.cw-tfilter__x {
  flex: 0 0 auto;
  inline-size: 18px; block-size: 18px;
  display: grid; place-items: center;
  border: 0; border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-muted);
  font-size: 10px; line-height: 1; cursor: pointer;
}
.cw-tfilter__x:hover { background: var(--color-surface); color: var(--color-text-primary); }
/* Nothing to clear, nothing to click — an always-present ✕ next to an empty box reads as a
   control that does something else. */
.cw-tfilter:not(.is-on) .cw-tfilter__x { display: none; }
.cw-tfilter__none {
  margin-block-start: 6px;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  background: var(--color-bg-subtle);
  color: var(--color-text-muted);
  font: 500 11px var(--font-sans);
}
/* The key legend is worth its ~14px only while the box has the caret; at rest the rail is short
   and every line of it belongs to a filename. */
.cw-tfilter__keys {
  display: none;
  margin-block-start: 5px;
  color: var(--color-text-muted);
  font: 500 10px var(--font-sans);
}
.cw-tfilter:focus-within .cw-tfilter__keys { display: block; }
[dir="rtl"] .cw-tfilter__none, [dir="rtl"] .cw-tfilter__keys { font-family: var(--font-arabic); }
.cw-tfilter__n[hidden], .cw-tfilter__none[hidden] { display: none; }

/* The matched run inside the name. <mark> arrives with a yellow UA background, so both halves of
   its colour have to be replaced, not just one. */
.cw-tfilter__hit {
  background: var(--color-accent-soft);
  color: var(--color-accent);
  border-radius: 3px;
  padding: 0 1px;
}

/* Filtered OUT hides. The fold a row may also be under is never rewritten, so clearing the box
   restores exactly the folds the user had — the reference mutates its expanded set here and
   leaves every folder the query touched hanging open afterwards.
   :not(.is-tout) is load-bearing: without it the un-fold rule out-specifies the hide rule and a
   collapsed row that the query rejected comes back on screen. */
.cw-tree__list.is-tfilter .cw-file.is-hidden:not(.is-tout) { display: flex; }
.cw-tree__list .cw-file.is-tout, .cw-tree__list .cw-folder.is-tout { display: none; }
/* The chevron follows the override. A closed chevron over children the query has just put on
   screen is the fold lying about itself. */
.cw-tree__list.is-tfilter .cw-folder.is-collapsed .cw-folder__chev { transform: rotate(90deg); }

/* The reference's hover highlight, as a state on the row rather than a measured floating rect —
   mAutoAnimate is already moving these rows, and a second box chasing their bounds would spend
   half its life one frame behind. */
.cw-file:focus-visible, .cw-folder:focus-visible {
  outline: none;
  background: var(--color-accent-soft);
  box-shadow: var(--shadow-focus);
}

@media (prefers-reduced-motion: reduce) {
  .cw-tfilter__row { transition: none; }
}

@media (prefers-reduced-motion: reduce) {
  .cw-folder__chev { transition: none; }
}

/* ── the trailing slot ── it floats, so it costs the filename nothing at rest.
   Fading the old inline controls hid them but kept their ~40px, which on a 138px rail left
   thirteen pixels for the name. Absolute positioning is the only way to actually give it back. */
.cw-file { position: relative; }
.cw-file__trail {
  position: absolute;
  inset-inline-end: 4px;
  inset-block: 0;
  display: flex; align-items: center; gap: 4px;
  padding-inline-start: 18px;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-out);
  /* A short scrim so the controls read cleanly over the tail of a long filename instead of
     colliding with it. Angle, not `to inline-end`, so it does not re-aim under RTL content. */
  background: linear-gradient(90deg, transparent, var(--color-bg-subtle) 40%);
}
[dir="rtl"] .cw-file__trail { background: linear-gradient(270deg, transparent, var(--color-bg-subtle) 40%); }
.cw-file:hover .cw-file__trail,
.cw-file:focus-within .cw-file__trail { opacity: 1; pointer-events: auto; }
.cw-file.is-active .cw-file__trail { background: linear-gradient(90deg, transparent, var(--color-accent-soft) 40%); }
[dir="rtl"] .cw-file.is-active .cw-file__trail { background: linear-gradient(270deg, transparent, var(--color-accent-soft) 40%); }
/* The two children no longer fade on their own — the slot owns that now. */
.cw-file__size, .cw-file:hover .cw-file__size, .cw-file.is-active .cw-file__size { opacity: .7; }
.cw-file__x, .cw-file:hover .cw-file__x { opacity: 1; }
/* And the name finally has somewhere to go. */
.cw-file__p { flex: 1 1 auto; min-inline-size: 0; }

/* ── the narrow bands ──
   The rail is a deliberate ladder: 200px above 1100, 168px from 901-1100, 138px from 861-900.
   At 138 the row has 113px of content box, and the tree's own furniture — 26px of indent, 12px of
   end padding, the 16px badge and an 8px gap — leaves 51px for a name that wants 75. So in the
   tight bands the FURNITURE gives way, not the name: half the indent (the folder heading above
   already says which directory this is, so the indent only has to be legible, not generous), a
   smaller end pad and a tighter gap. That buys ~20px, which is the difference between "styl…" and
   "styles.cs…".

   Ellipsis at 138px is correct, not a defect — every file rail does it. The full path is on the
   row's title, and the badge already carries the extension, which is the part an ellipsis eats. */
/* The indent itself: base step 20px, matching the reference. Declared HERE, immediately before
   the band that halves it — a copy appended later in the file would have the same specificity,
   win on order, and silently cancel the override at every width. */
.cw-file { padding-inline-start: calc(var(--cw-depth, 0) * 20px + 6px); }

@media (min-width: 860.02px) and (max-width: 1100px) {
  .cw-file { gap: 6px; padding-inline-end: 6px; }
  /* Half the indent step. One calc, any depth — the row publishes --cw-depth and the band picks
     the multiplier, so this cannot drift out of step with the renderer the way matching on an
     inline pixel string would. */
  .cw-file { padding-inline-start: calc(var(--cw-depth, 0) * 10px + 6px); }
  .cw-folder { padding-inline: 6px; gap: 5px; }
  .cw-file__p { font-size: 12px; }
}

/* ═══ JUMP TO A QUESTION ════════════════════════════════════════════════════════
   An index of your own questions, for the conversation that got long. It sits beside the
   scroll-to-latest pill because the topbar is already the thing that overflows on a phone. */
.jump-btn { inset-inline-start: auto; inset-inline-end: 0; }
.jump-panel {
  position: fixed;
  z-index: 1200;
  inline-size: min(88vw, 300px);
  max-block-size: min(60vh, 420px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 6px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: 16px;
  background: var(--color-surface);
  box-shadow: 0 4px 10px rgba(0,0,0,.08), 0 18px 48px rgba(0,0,0,.24);
}
.jump-panel__h {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 10px 6px;
  font: 700 10.5px var(--font-sans);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--color-text-muted);
}
[dir="rtl"] .jump-panel__h { font-family: var(--font-arabic); letter-spacing: 0; }
.jump-panel__n {
  font: 600 10.5px var(--font-mono);
  color: var(--color-accent);
  font-variant-numeric: tabular-nums;
}
.jump-panel__empty { padding: 14px 10px; font: 400 12.5px var(--font-sans); color: var(--color-text-muted); text-align: center; }
[dir="rtl"] .jump-panel__empty { font-family: var(--font-arabic); }
.jump-row {
  inline-size: 100%;
  display: flex; align-items: flex-start; gap: 9px;
  padding: 8px 10px;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--color-text-secondary);
  text-align: start; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.jump-row:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.jump-row:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.jump-row__n {
  flex: 0 0 auto;
  inline-size: 18px; block-size: 18px;
  display: grid; place-items: center;
  border-radius: 6px;
  background: var(--color-bg-subtle);
  font: 600 10px var(--font-mono);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
.jump-row__t {
  flex: 1 1 auto; min-inline-size: 0;
  font: 400 12.5px/1.45 var(--font-sans);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
[dir="rtl"] .jump-row__t { font-family: var(--font-arabic); }

/* ═══ THE SHAPE OF THE CONVERSATION ════════════════════════════════════════════
   The jump panel's box, because this is the same kind of object: a popover pinned to a control,
   over a body that is overflow:hidden. Differing there would only produce two panels that are
   almost the same. What is different is inside it - no text in the rows at all, so every rule
   below is about making forty bars comparable at a glance.

   Written in logical properties throughout, and that is load-bearing rather than tidy: this is
   the one popover in the app that stamps its own dir, so the same rules have to lay the map out
   from the right in Arabic and from the left in English. */
.tshape {
  position: fixed;
  z-index: 1200;
  inline-size: min(88vw, 292px);
  max-block-size: min(62vh, 440px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 6px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: 16px;
  background: var(--color-surface);
  box-shadow: 0 4px 10px rgba(0,0,0,.08), 0 18px 48px rgba(0,0,0,.24);
}
.tshape__h {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 10px 2px;
  font: 700 10.5px var(--font-sans);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--color-text-muted);
}
/* Arabic is cursive and has no case, so the tracking and the caps above are for the Latin label
   only. The selector is `.tshape[dir="rtl"]` and not `[dir="rtl"] .tshape`: the shell is pinned
   dir="ltr", so an ancestor form has nothing to match and this panel would never get its font. */
.tshape[dir="rtl"] .tshape__h,
.tshape[dir="rtl"] .tshape__sub { font-family: var(--font-arabic); letter-spacing: 0; text-transform: none; }
.tshape__n {
  font: 600 10.5px var(--font-mono);
  color: var(--color-accent);
  font-variant-numeric: tabular-nums;
}
.tshape__sub {
  padding: 0 10px 8px;
  font: 400 11px var(--font-sans);
  color: var(--color-text-muted);
}
.tshape__list { display: flex; flex-direction: column; gap: 1px; }
.tshape__row {
  inline-size: 100%;
  display: flex; align-items: center; gap: 8px;
  padding: 5px 8px;
  border: 0; border-radius: var(--radius-sm);
  background: transparent;
  text-align: start; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.tshape__row:hover { background: var(--color-bg-subtle); }
.tshape__row:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* Where the reader is standing right now, marked at the moment the panel opened. Held apart
   from :hover by the accent on its bar below, not by a second background - two identical
   highlights on one list read as a rendering fault. */
.tshape__row.is-here { background: var(--color-bg-subtle); }
.tshape__k {
  flex: 0 0 auto; inline-size: 20px;
  font: 600 9.5px var(--font-mono);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
  text-align: end;
}
.tshape__ask {
  flex: 0 0 auto; inline-size: 10px;
  font: 700 12px var(--font-mono);
  line-height: 1;
  color: var(--color-text-muted); opacity: .35;
  text-align: center;
}
.tshape__ask.is-q { opacity: 1; color: var(--color-text-secondary); }
.tshape__track {
  flex: 1 1 auto; min-inline-size: 0;
  display: flex;
  block-size: 6px;
  border-radius: 999px;
  background: var(--color-bg-subtle);
  overflow: hidden;
}
.tshape__fill {
  block-size: 100%;
  border-radius: 999px;
  background: var(--color-text-muted);
  opacity: .5;
  transition: background var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}
.tshape__row:hover .tshape__fill,
.tshape__row.is-here .tshape__fill { background: var(--color-accent); opacity: 1; }
/* Reserved whether or not anything lands in it - see the JS: bars that stop in different places
   because one row happens to carry an icon are bars nobody can compare. */
.tshape__marks {
  flex: 0 0 auto;
  inline-size: 28px;
  display: flex; align-items: center; justify-content: flex-end; gap: 3px;
}
.tshape__b { inline-size: 12px; block-size: 12px; color: var(--color-text-muted); }
.tshape__b svg { inline-size: 100%; block-size: 100%; display: block; }
.tshape__row:hover .tshape__b,
.tshape__row.is-here .tshape__b { color: var(--color-accent); }
@media (prefers-reduced-motion: reduce) {
  .tshape__row, .tshape__fill { transition: none; }
}
/* The motion attribute is read off :root as the LEFTMOST compound, because :root IS <html> -
   put any ancestor selector in front of it and the rule can never match anything. */
:root[data-motion="off"] .tshape__row,
:root[data-motion="off"] .tshape__fill { transition: none; }

/* THE LANDING FLASH, UNGATED. inspectorScrollTo has always added .is-inspector-flash, but the
   keyframes were declared only under :root[data-ui="2"] — so in UI 1 the jump repositioned the
   page with no signal that anything had happened. Same animation, available to both. */
.is-inspector-flash { animation: insp-flash 1.2s var(--ease-out); }
@media (prefers-reduced-motion: reduce) { .is-inspector-flash { animation: none; } }

/* ═══ CODE WORKSPACE TABS — the segmented control, after 21st.dev's Tabs ════════
   معاينة / كونسول / دردشة are three mutually exclusive VIEWS of the same pane, but they were
   drawn as three loose pills — three things you can press, rather than one control with three
   positions. The reference's TabsList/TabsTrigger says it properly: a recessed track holding the
   set, and the active one RAISED out of it on the surface colour with a soft shadow.

   Converted from its Tailwind classes: `bg-muted p-0.5 rounded-lg` on the list becomes the track;
   `rounded-md px-3 py-1.5 text-sm font-medium transition-all` on the trigger; and
   `data-[state=active]:bg-background data-[state=active]:shadow-sm` becomes the lift.

   The lift is NEUTRAL, not accent-tinted. In a dark IDE a jade fill on the active tab competes
   with the accent that Send, the run button and the error dot are all already using — the raised
   surface says "selected" without spending the one colour that means "act".

   UI 2.0 only. UI 1 keeps the pills it has.
   ═══════════════════════════════════════════════════════════════════════════════ */
:root[data-ui="2"] .cw-tabs {
  gap: 2px;                                    /* p-0.5 between triggers, not a pill gap */
  padding: 2px;
  margin: 6px 8px;
  border-block-end: 0;                         /* the track replaces the rule under the row */
  border-radius: 10px;
  background: var(--color-bg-subtle);
  inline-size: fit-content;
}
:root[data-ui="2"] .cw-tab {
  padding: 5px 13px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--color-text-muted);
  font: 500 12.5px var(--font-sans);
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
[dir="rtl"] :root[data-ui="2"] .cw-tab { font-family: var(--font-arabic); }
:root[data-ui="2"] .cw-tab:hover { color: var(--color-text-secondary); }
:root[data-ui="2"] .cw-tab:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* The lift. `shadow-black/5` in the reference — a shadow this soft only reads on a track that is
   itself recessed, which is why both halves have to change together. */
:root[data-ui="2"] .cw-tab.is-active {
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .06), 0 1px 3px rgba(0, 0, 0, .10);
}
[data-theme="dark"] :root[data-ui="2"] .cw-tab.is-active,
:root[data-ui="2"]:not([data-theme="light"]) .cw-tab.is-active {
  box-shadow: 0 1px 2px rgba(0, 0, 0, .35), 0 2px 6px rgba(0, 0, 0, .3);
}
/* The error dot moves in with the tab it belongs to; on a 7px-radius tile the old 4px offset
   sat on the corner arc. */
:root[data-ui="2"] .cw-tab.has-errors::after { inset-block-start: 3px; inset-inline-end: 3px; }

/* ═══════════ FIRAS CODE — FIND & REPLACE (Ctrl+F · Ctrl+Shift+F) ═══════════
   The widget FLOATS over the top of the editor column, the way every IDE's find box does, so
   opening it never re-lays-out the code underneath and never steals a column from the tree or
   the preview. .cw-ed was position:static, so without the rule below the panel would have
   anchored to .cw itself and covered the file tree as well.

   z-index sits deliberately UNDER .cw-diff (20) and .cw-snap (22): the AI's change review and
   the version-history sheet are modal, and a find box must never float on top of them. It is
   comfortably above CodeMirror's own layers, which top out around 6. */
.cw-ed { position: relative; }
.cw-fr {
  position: absolute; inset-inline: 10px; inset-block-start: 8px; z-index: 16;
  display: flex; flex-direction: column;
  max-block-size: min(64%, 430px);
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--cwv-radius-panel, var(--radius-lg));
  box-shadow: var(--cwv-shadow-float, var(--shadow-lg));
  overflow: hidden;
  opacity: 0; transform: translateY(-8px);
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.cw-fr.is-open { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .cw-fr { transition: none; } }

.cw-fr__bar { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border-block-end: 1px solid var(--color-border-hairline); background: var(--color-bg-subtle); }
.cw-fr__col { flex: 1 1 auto; min-inline-size: 0; display: flex; flex-direction: column; gap: 7px; }
.cw-fr__row { display: flex; align-items: center; gap: 6px; }
.cw-fr__q, .cw-fr__r {
  flex: 1 1 auto; min-inline-size: 0; padding: 7px 11px;
  border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  background: var(--color-surface); color: var(--color-text-primary);
  font: 500 12.5px var(--font-mono);
}
.cw-fr__q:focus, .cw-fr__r:focus { outline: none; border-color: var(--color-accent); box-shadow: 0 0 0 3px var(--color-accent-soft); }
.cw-fr__chip {
  flex: 0 0 auto; min-inline-size: 32px; block-size: 30px; padding: 0 7px;
  border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  background: transparent; color: var(--color-text-muted);
  font: 600 11px var(--font-mono); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.cw-fr__chip:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }
.cw-fr__chip.is-on { background: var(--color-accent-soft); border-color: var(--color-accent); color: var(--color-accent); }
.cw-fr__go {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 7px; padding: 7px 15px;
  border: 1px solid var(--color-accent); border-radius: var(--radius-pill);
  background: var(--color-accent); color: var(--color-on-accent);
  font: 600 12px var(--font-sans); cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .cw-fr__go { font-family: var(--font-arabic); }
.cw-fr__go:hover:not(:disabled) { background: var(--color-accent-hover); border-color: var(--color-accent-hover); }
.cw-fr__go:disabled { cursor: default; background: transparent; color: var(--color-text-muted); border-color: var(--color-border); }
.cw-fr__gon { font: 700 11px var(--font-mono); opacity: .85; unicode-bidi: isolate; }
.cw-fr__gon:empty { display: none; }

.cw-fr__side { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; gap: 7px; }
[dir="rtl"] .cw-fr__side { align-items: flex-start; }
.cw-fr__scope { display: inline-flex; padding: 2px; border-radius: var(--radius-pill); background: var(--color-bg); border: 1px solid var(--color-border-hairline); }
.cw-fr__sc { padding: 5px 11px; border: none; border-radius: var(--radius-pill); background: transparent; color: var(--color-text-muted); font: 600 11px var(--font-sans); cursor: pointer; white-space: nowrap; transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
[dir="rtl"] .cw-fr__sc { font-family: var(--font-arabic); }
.cw-fr__sc.is-on { background: var(--color-surface); color: var(--color-text-primary); box-shadow: var(--shadow-xs); }
.cw-fr__nav { display: inline-flex; align-items: center; gap: 4px; }
.cw-fr__count { min-inline-size: 54px; text-align: center; font: 600 11px var(--font-mono); color: var(--color-text-muted); font-variant-numeric: tabular-nums; unicode-bidi: isolate; }
.cw-fr__pg { inline-size: 26px; block-size: 26px; border: none; border-radius: var(--radius-sm); background: transparent; color: var(--color-text-muted); font-size: 12px; cursor: pointer; transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
.cw-fr__pg:hover { background: var(--color-accent-soft); color: var(--color-accent); }

.cw-fr__out { flex: 1 1 auto; min-block-size: 0; overflow-y: auto; padding: 6px; }
.cw-fr__empty { padding: 22px 12px; text-align: center; font: 400 12.5px var(--font-sans); color: var(--color-text-muted); }
[dir="rtl"] .cw-fr__empty { font-family: var(--font-arabic); }
.cw-fr__file { margin-block-end: 4px; }
.cw-fr__fhead { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: var(--radius-sm); cursor: pointer; font: 600 11.5px var(--font-mono); color: var(--color-text-secondary); }
.cw-fr__fhead:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.cw-fr__fp { flex: 1 1 auto; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; }
.cw-fr__fn { flex: 0 0 auto; padding: 1px 7px; border-radius: var(--radius-pill); background: var(--color-bg-subtle); font: 700 10px var(--font-mono); color: var(--color-text-muted); unicode-bidi: isolate; }
.cw-fr__hit {
  display: flex; align-items: baseline; gap: 9px; inline-size: 100%;
  /* LOGICAL padding: the result's indent has to hang off the START edge, and a physical
     padding-left would put it on the wrong side of every Arabic layout. */
  padding-block: 4px; padding-inline: 20px 8px;
  border: none; border-radius: var(--radius-sm); background: transparent;
  cursor: pointer; text-align: start; font: 400 12px var(--font-mono); color: var(--color-text-secondary);
}
.cw-fr__hit:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.cw-fr__hit.is-active { background: var(--color-accent-soft); color: var(--color-text-primary); }
.cw-fr__ln { flex: 0 0 auto; min-inline-size: 34px; text-align: end; font-size: 10.5px; color: var(--color-text-muted); font-variant-numeric: tabular-nums; unicode-bidi: isolate; }
.cw-fr__tx { flex: 1 1 auto; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: pre; text-align: start; }
.cw-fr__mk { background: color-mix(in srgb, var(--color-accent) 28%, transparent); color: inherit; border-radius: 3px; padding: 0 1px; }

/* The results list, browsed — after 21st.dev's Tree View (ddoemonn). Two visual ideas come
   from it: a caret that turns as the group opens, and a row the KEYBOARD is on that is RINGED
   rather than filled — the filled look is already spoken for by .is-active, which means the
   match the editor is currently showing, and the two are allowed to be different rows. */
.cw-fr__fold {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  inline-size: 18px; block-size: 18px; padding: 0; margin-inline-start: -2px;
  border: none; border-radius: var(--radius-xs); background: transparent;
  color: var(--color-text-muted); cursor: pointer;
}
.cw-fr__fold:hover { background: var(--color-surface); color: var(--color-text-primary); }
.cw-fr__fold:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--color-accent); }
/* Open, the caret points DOWN in both directions. Shut, it points along the reading direction,
   which is a MIRROR and not a rotation — rotating it would aim it out of an Arabic panel. */
.cw-fr__fold svg { transform: rotate(90deg); transition: transform var(--dur-fast) var(--ease-out); }
.cw-fr__file.is-fold .cw-fr__fold svg { transform: none; }
[dir="rtl"] .cw-fr__file.is-fold .cw-fr__fold svg { transform: scaleX(-1); }
.cw-fr__hits { display: block; }
/* The chevron moves the filename 26px inward, so its rows move with it — and ONLY where a
   chevron was actually drawn, or the phone sheet would indent rows past nothing. */
.cw-fr__file.can-fold .cw-fr__hits { padding-inline-start: 14px; }
.cw-fr__file.is-fold .cw-fr__hits { display: none; }
.cw-fr__hit.is-cursor, .cw-fr__hit:focus-visible,
.cw-fr__fold.is-cursor { outline: none; box-shadow: inset 0 0 0 1px var(--color-accent); }
@media (prefers-reduced-motion: reduce) { .cw-fr__fold svg { transition: none; } }
@media (pointer: coarse) { .cw-fr__fold { inline-size: 26px; block-size: 26px; } }

.cw-fr__foot { display: flex; align-items: center; gap: 12px; padding: 7px 12px; border-block-start: 1px solid var(--color-border-hairline); background: var(--color-bg-subtle); font: 400 10.5px var(--font-sans); color: var(--color-text-muted); }
[dir="rtl"] .cw-fr__foot { font-family: var(--font-arabic); }
.cw-fr__note { flex: 1 1 auto; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cw-fr__keys { flex: 0 0 auto; opacity: .8; white-space: nowrap; }

@media (max-width: 860px) {
  .cw-fr { inset-inline: 6px; inset-block-start: 6px; max-block-size: min(72%, 460px); }
  .cw-fr__bar { flex-direction: column; align-items: stretch; }
  .cw-fr__side { flex-direction: row; align-items: center; justify-content: space-between; }
  .cw-fr__keys { display: none; }
}
@media (pointer: coarse) {
  .cw-fr__chip { block-size: 36px; min-inline-size: 38px; }
  .cw-fr__pg { inline-size: 34px; block-size: 34px; }
  .cw-fr__sc { padding: 8px 13px; }
  .cw-fr__hit { padding-block: 8px; }
}

/* ═══ GO TO SYMBOL — the outline panel ═══════════════════════════════════════
   Same host as find (.cw-ed is already position:relative) and the same z-index band,
   but anchored to the BOTTOM inline-end corner instead of the top edge, so find and
   outline can be open together without either sitting on the other. It stays under
   .cw-diff (20) and .cw-snap (22) for exactly the reason find does: a change review and
   the version-history sheet are modal, and a symbol list must never float over them.

   Every offset here is logical (inset-inline-end, padding-inline, text-align:start), so
   the panel docks to the correct edge in Arabic without a mirrored copy of the rules. */
.cw-ol {
  position: absolute; inset-inline-end: 10px; inset-block-end: 10px; z-index: 15;
  inline-size: min(320px, 64%);
  display: flex; flex-direction: column;
  max-block-size: min(70%, 520px);
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--cwv-radius-panel, var(--radius-lg));
  box-shadow: var(--cwv-shadow-float, var(--shadow-lg));
  overflow: hidden;
  opacity: 0; transform: translateY(8px);
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.cw-ol.is-open { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .cw-ol { transition: none; } }

.cw-ol__head {
  display: flex; align-items: center; gap: 8px;
  padding-block: 9px; padding-inline: 12px 8px;
  border-block-end: 1px solid var(--color-border-hairline); background: var(--color-bg-subtle);
}
.cw-ol__ttl { flex: 0 0 auto; font: 650 11.5px var(--font-sans); color: var(--color-text-primary); }
[dir="rtl"] .cw-ol__ttl { font-family: var(--font-arabic); }
.cw-ol__path {
  flex: 1 1 auto; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-align: start; font: 500 10.5px var(--font-mono); color: var(--color-text-muted); unicode-bidi: isolate;
}
.cw-ol__n {
  flex: 0 0 auto; padding-block: 1px; padding-inline: 7px; border-radius: var(--radius-pill);
  background: var(--color-bg); font: 700 10px var(--font-mono); color: var(--color-text-muted); unicode-bidi: isolate;
}
.cw-ol__n:empty { display: none; }
.cw-ol__x {
  flex: 0 0 auto; inline-size: 24px; block-size: 24px; border: none; border-radius: var(--radius-sm);
  background: transparent; color: var(--color-text-muted); font-size: 12px; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.cw-ol__x:hover { background: var(--color-accent-soft); color: var(--color-accent); }

.cw-ol__qrow { padding-block: 8px; padding-inline: 10px; border-block-end: 1px solid var(--color-border-hairline); }
.cw-ol__q {
  inline-size: 100%; padding-block: 6px; padding-inline: 10px;
  border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  background: var(--color-surface); color: var(--color-text-primary);
  font: 500 12px var(--font-mono);
}
.cw-ol__q:focus { outline: none; border-color: var(--color-accent); box-shadow: 0 0 0 3px var(--color-accent-soft); }

.cw-ol__out { flex: 1 1 auto; min-block-size: 0; overflow-y: auto; padding: 5px; }
.cw-ol__empty { padding: 20px 12px; text-align: center; font: 400 12px var(--font-sans); color: var(--color-text-muted); line-height: 1.6; }
[dir="rtl"] .cw-ol__empty { font-family: var(--font-arabic); }

.cw-ol__row {
  inline-size: 100%; display: flex; align-items: center; gap: 8px;
  padding-block: 5px; padding-inline: 7px;
  border: none; border-radius: var(--radius-sm); background: transparent;
  color: var(--color-text-secondary); font: 500 12px var(--font-mono);
  cursor: pointer; text-align: start;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.cw-ol__row--in { padding-inline-start: 24px; }
.cw-ol__row:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.cw-ol__row.is-active { background: var(--color-accent-soft); color: var(--color-text-primary); }
.cw-ol__row:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: -2px; }
.cw-ol__kd {
  flex: 0 0 auto; min-inline-size: 20px; block-size: 16px;
  display: inline-flex; align-items: center; justify-content: center;
  padding-inline: 3px; border-radius: 4px;
  background: var(--color-bg-subtle); color: var(--color-text-muted);
  font: 700 9.5px var(--font-mono); unicode-bidi: isolate;
}
.cw-ol__kd--fn, .cw-ol__kd--method { background: var(--color-accent-soft); color: var(--color-accent); }
.cw-ol__kd--cls { background: color-mix(in srgb, var(--color-accent-deep) 16%, transparent); color: var(--color-accent-deep); }
.cw-ol__kd--at { background: color-mix(in srgb, var(--color-success) 15%, transparent); color: var(--color-success); }
.cw-ol__kd--id { background: color-mix(in srgb, var(--color-accent) 14%, transparent); color: var(--color-accent); }
.cw-ol__nm { flex: 1 1 auto; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; }
.cw-ol__ln { flex: 0 0 auto; font-size: 10.5px; color: var(--color-text-muted); font-variant-numeric: tabular-nums; unicode-bidi: isolate; }

.cw-ol__foot {
  display: flex; align-items: center; gap: 10px;
  padding-block: 6px; padding-inline: 11px;
  border-block-start: 1px solid var(--color-border-hairline); background: var(--color-bg-subtle);
  font: 400 10px var(--font-sans); color: var(--color-text-muted);
}
[dir="rtl"] .cw-ol__foot { font-family: var(--font-arabic); }
.cw-ol__more { flex: 1 1 auto; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cw-ol__keys { flex: 0 0 auto; opacity: .85; white-space: nowrap; }

/* Three classes, so this beats .cw--v3 .cw-bar__btn and its :hover without !important. */
.cw-bar__btn.cw-outbtn.is-on { background: var(--color-accent-soft); border-color: var(--color-accent); color: var(--color-accent); }
/* MEASURED, NOT ASSUMED. .cw-bar is a single flex row with no wrap and no scroll; it only
   gains `flex-wrap: wrap` at ≤860px. And #codeWorkspace is a SIBLING of .chat-scroll inside
   <main>, so the bar never gets the whole viewport — it gets the viewport minus the app
   sidebar. At 1180px that is roughly 900px of bar for eight labelled buttons, which is
   already over: the buttons would shrink below their nowrap text and spill into each other,
   and in Arabic (--font-arabic, longer words) sooner still.
   So the word is OFF by default — the button keeps its title and aria-label and reads as a
   real control either way — and comes back only where there is room for it: a genuinely wide
   screen, or ≤860px where the row is allowed to wrap onto a second line. */
.cw-outbtn__lbl { display: none; }
@media (min-width: 1400px) { .cw-outbtn__lbl { display: inline; } }
@media (max-width: 860px)  { .cw-outbtn__lbl { display: inline; } }

@media (max-width: 860px) {
  .cw-ol { inset-inline: 6px; inset-block-end: 6px; inline-size: auto; max-block-size: min(62%, 420px); }
  .cw-ol__keys { display: none; }
}
@media (pointer: coarse) {
  .cw-ol__row { padding-block: 9px; }
  .cw-ol__x { inline-size: 32px; block-size: 32px; }
}

/* The undo toast. showToast() takes a string and nothing else, and an undoable replace needs
   a button the user can press — so this is its own element in the same place, with the same
   shape and timing as the app toast, plus one action. pointer-events stay ON, unlike .toast. */
.cw-undo {
  position: fixed; inset-block-end: var(--space-8); inset-inline: 0; margin-inline: auto;
  inline-size: max-content; max-inline-size: 92vw; z-index: var(--z-toast);
  display: flex; align-items: center; gap: 14px;
  padding-block: var(--space-3); padding-inline: var(--space-5) var(--space-4);
  background: var(--color-text-primary); color: var(--color-bg);
  border-radius: var(--radius-pill); box-shadow: var(--shadow-lg);
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: var(--weight-medium);
  opacity: 0; transform: translateY(8px);
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
/* NOT `[dir="rtl"] .cw-undo`. This element is appended to document.body and <html> is pinned
   dir="ltr" for the whole shell, so an ancestor-dir selector can never match it — the toast
   carries its own dir attribute, so match on that. */
.cw-undo[dir="rtl"] { font-family: var(--font-arabic); }
.cw-undo.is-open { opacity: 1; transform: none; }
.cw-undo__t { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cw-undo__b {
  flex: 0 0 auto; padding: 5px 15px;
  border: 1px solid color-mix(in srgb, var(--color-bg) 45%, transparent);
  border-radius: var(--radius-pill); background: transparent; color: var(--color-bg);
  font: 600 12px var(--font-sans); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .cw-undo__b { font-family: var(--font-arabic); }
.cw-undo__b:hover { background: color-mix(in srgb, var(--color-bg) 20%, transparent); }
@media (prefers-reduced-motion: reduce) { .cw-undo { transition: none; } }

/* ═══════════════════════════════════════════════════════════════════════════════
   SIDEBAR SEARCH — MATCHES INSIDE MESSAGES
   ═══════════════════════════════════════════════════════════════════════════════

   A SEPARATE GROUP, ON PURPOSE. The rows above are conversations whose TITLE matched;
   these are TURNS whose text matched. Same query, two different answers — folding them
   into one list would hide which one you got, and only one of them can tell you where in
   the conversation to look. So this reads as its own section, with its own heading, its
   own honest statement of what it covers, and its own action.

   Everything here is built from the existing token scale, so the six themes and the UI 2.0
   skin (which redefines the radius and duration tokens) carry it without a second rule. */

.history__group-label--hits {
  display: flex; align-items: center; gap: var(--space-2);
  color: var(--color-accent);
}
/* Under UI 1 the group count has no styling of its own — :root[data-ui="2"] .history__group-n
   is more specific than this pair, so UI 2.0 still wins and keeps its pill. */
.history__group-label--hits .history__group-n {
  font-size: 10.5px; font-variant-numeric: tabular-nums; letter-spacing: 0;
  color: var(--color-text-muted);
}
/* The honest scope line. Muted and small: it is a caveat on the heading, not a heading. */
.hits-scope {
  padding: 0 var(--space-3) var(--space-2);
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-muted);
  opacity: 0.85;
}
.hits-none {
  padding: var(--space-1) var(--space-3) var(--space-2);
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-muted);
}

.msg-hit {
  display: flex; flex-direction: column; gap: 3px;
  inline-size: 100%;
  padding: var(--space-2) var(--space-3);
  margin-block-end: 2px;
  border-radius: var(--radius-sm);
  text-align: start;
  color: var(--color-text-secondary);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.msg-hit:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.msg-hit:focus-visible {
  outline: none;
  background: var(--color-bg-subtle);
  box-shadow: 0 0 0 2px var(--color-accent-ring);
}
.msg-hit__top { display: flex; align-items: baseline; gap: var(--space-2); min-inline-size: 0; }
.msg-hit__title {
  flex: 1 1 auto; min-inline-size: 0;
  font-size: var(--text-xs); line-height: var(--leading-xs);
  font-weight: var(--weight-medium);
  color: var(--color-text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.msg-hit__who, .msg-hit__i {
  flex: 0 0 auto;
  font-size: 10px; line-height: var(--leading-xs);
  color: var(--color-text-muted);
}
.msg-hit__i { font-variant-numeric: tabular-nums; opacity: 0.8; }

/* Two lines of context, no more: this is a POINTER to a turn, not the turn itself. The clamp
   is what keeps a 40-hit list scannable instead of a wall. */
.msg-hit__snip {
  font-size: var(--text-xs); line-height: 17px;
  color: var(--color-text-muted);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}
/* The phrase itself. Tinted rather than boxed — a hard highlight at this size reads as an
   error state, and the accent already means "this is the thing you asked for" everywhere else. */
.msg-hit__mark {
  background: var(--color-accent-soft);
  color: var(--color-text-primary);
  border-radius: var(--radius-xs);
  padding: 0 1px;
  font-weight: var(--weight-semibold);
}

/* The action foot: progress while a wide search runs, the offer to start one otherwise. */
.hits-foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3) var(--space-5);
}
.hits-foot__lbl {
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-muted);
}
.hits-foot__lbl--warn { color: var(--color-accent); }
.hits-foot__n {
  font-size: var(--text-xs); line-height: var(--leading-xs);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
  opacity: 0.85;
}
.hits-foot__b {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px var(--space-3);
  border: var(--border-hairline) solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-secondary);
  font-size: var(--text-xs); line-height: var(--leading-xs); font-weight: var(--weight-medium);
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.hits-foot__b:hover {
  background: var(--color-bg-subtle);
  border-color: var(--color-accent);
  color: var(--color-text-primary);
}
.hits-foot__b--go { inline-size: 100%; justify-content: space-between; }
.hits-foot__b--go .hits-foot__n { opacity: 0.7; }

/* A real bar, not a spinner: this load has a known total, and "112 of 240" is the only thing
   that makes waiting for it a decision rather than a gamble. */
.hits-bar {
  flex: 1 1 100%;
  block-size: 3px;
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
  overflow: hidden;
}
.hits-bar__f {
  display: block; block-size: 100%;
  background: var(--color-accent);
  border-radius: var(--radius-pill);
  transition: inline-size var(--dur-slow) var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  .hits-bar__f { transition: none; }
}


/* === FIRAS CODE - OPEN-FILE TABS =============================================
   The 21st.dev "Code block" strip rebuilt on our tokens: ghost chips, one filled active tab,
   a language badge matching the file tree, and a copy button that becomes a tick. */
.cw-etabs{
  flex:0 0 auto; display:flex; align-items:center; gap:6px;
  padding:5px 6px; min-height:34px;
  background:var(--color-bg-subtle);
  border-bottom:1px solid var(--color-border-hairline);
}
.cw-etabs[hidden]{display:none}
/* The scroller is the ONLY thing that scrolls. A copy button living inside an overflow-x box
   scrolls away with the tabs; pushing it out with margin-inline-start:auto would also flip
   sides under RTL. Two boxes, one of them fixed, has neither problem. */
.cw-etabs__scroll{
  flex:1 1 auto; min-width:0; display:flex; align-items:center; gap:4px;
  overflow-x:auto; overflow-y:hidden; scrollbar-width:none;
}
.cw-etabs__scroll::-webkit-scrollbar{height:0}
.cw-etab{
  flex:0 0 auto; display:inline-flex; align-items:center; gap:6px;
  max-width:190px; padding:4px 8px; border:1px solid transparent; border-radius:7px;
  background:transparent; color:var(--color-text-muted);
  font:500 12px/1.4 inherit; cursor:pointer;
  transition:background .14s ease, color .14s ease, border-color .14s ease;
}
.cw-etab:hover{background:var(--color-surface); color:var(--color-text-primary)}
.cw-etab.is-active{
  background:var(--color-surface); color:var(--color-text-primary);
  border-color:var(--color-border-hairline);
}
.cw-etab__n{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
/* The close mark holds its width at all times. Revealing it on hover alone would reflow the
   strip under the pointer and slide the next tab out from under a click aimed at it. */
.cw-etab__x{
  flex:0 0 auto; width:14px; height:14px; line-height:14px; text-align:center;
  border-radius:4px; font-size:10px; opacity:0; color:var(--color-text-muted);
  transition:opacity .12s ease, background .12s ease;
}
.cw-etab:hover .cw-etab__x,.cw-etab.is-active .cw-etab__x{opacity:.65}
.cw-etab__x:hover{opacity:1; background:var(--color-border-hairline); color:var(--color-text-primary)}
.cw-etabs__copy{
  flex:0 0 auto; width:26px; height:26px; display:grid; place-items:center;
  border:1px solid transparent; border-radius:7px; background:transparent;
  color:var(--color-text-muted); cursor:pointer; transition:background .14s ease, color .14s ease;
}
.cw-etabs__copy svg{width:14px; height:14px}
.cw-etabs__copy:hover{background:var(--color-surface); color:var(--color-text-primary)}
.cw-etabs__copy.is-done{color:var(--color-success,#3f9d6b)}
[data-theme="dark"] .cw-etabs{background:#262624; border-bottom-color:#3a3a37}
[data-theme="dark"] .cw-etab:hover,[data-theme="dark"] .cw-etab.is-active{background:#1e1e1c; color:#e8e6df}
[data-theme="dark"] .cw-etab.is-active{border-color:#3a3a37}

/* ----- Composer "/" commands --------------------------------------------------
   Above the composer card, like .mic-menu and for the same reason: on a phone the composer
   IS the bottom of the viewport, so there is nowhere below it to open into. */
.slashm {
  position: absolute; bottom: calc(100% + 10px); left: 8px; z-index: var(--z-dropdown, 40);
  width: 300px; max-width: calc(100% - 16px);
  max-height: 320px; overflow: auto; overscroll-behavior: contain;
  padding: 6px; border: 1px solid var(--color-border-hairline); border-radius: 14px;
  background: var(--color-surface);
  box-shadow: 0 4px 10px rgba(0,0,0,.08), 0 16px 40px rgba(0,0,0,.16);
  animation: micMenuIn .16s ease;
}
/* PHYSICAL left/right, deliberately. The app shell is pinned LTR, so inset-inline-start
   resolves to `left` even while the caret is sitting at the right edge of an Arabic line —
   the menu would open across the box from the word it is completing. slashRender toggles
   this class off the textarea's own stamped dir instead. */
.slashm--end { left: auto; right: 8px; }
.slashm[hidden] { display: none; }
.slashm__t {
  padding: 8px 10px 6px; font: 700 11px var(--font-sans); letter-spacing: .06em;
  text-transform: uppercase; color: var(--color-text-muted);
}
/* The dir is stamped ON these elements, so the attribute selector carries NO descendant
   space — `[dir="rtl"] .slashm__t` would need an RTL ancestor, and in this shell there is
   never one. */
[dir="rtl"].slashm__t, [dir="rtl"].slashm__lb, [dir="rtl"].slashm__hn, [dir="rtl"].slashm__cmd {
  font-family: var(--font-arabic);
}
[dir="rtl"].slashm__t { letter-spacing: 0; }
.slashm__it {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; border: 0; border-radius: 10px; cursor: pointer;
  background: transparent; color: var(--color-text-secondary); text-align: start;
  font: 500 13.5px var(--font-sans); transition: background .12s ease, color .12s ease;
}
.slashm__it:hover { background: var(--color-bg-subtle); }
.slashm__it.is-active { background: var(--color-accent-soft); color: var(--color-accent); }
.slashm__ico { flex: 0 0 auto; display: inline-flex; color: var(--color-text-muted); }
.slashm__ico svg { width: 18px; height: 18px; display: block; }
.slashm__it.is-active .slashm__ico { color: var(--color-accent); }
.slashm__txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.slashm__lb { font-weight: 600; color: var(--color-text-primary); overflow-wrap: anywhere; }
.slashm__it.is-active .slashm__lb { color: var(--color-accent); }
.slashm__hn { font-size: 11.5px; color: var(--color-text-muted); overflow-wrap: anywhere; }
.slashm__cmd {
  flex: 0 0 auto; padding: 2px 7px; border-radius: 999px;
  background: var(--color-bg-subtle); color: var(--color-text-muted);
  font: 600 11.5px var(--font-mono); white-space: nowrap;
}
.slashm__it.is-active .slashm__cmd { background: var(--color-surface); color: var(--color-accent); }

/* ═══════════ FIRAS CODE — PREVIEW DEVICE CHASSIS (appended: source-order wins) ═══════════ */
/* The device switcher already existed; what it drew was a narrow iframe with a shadow, which
   reads as "a narrow window", not as a phone. This is the chassis around it — and the fit box
   that lets the viewport be a real 390×844 instead of 390 × however tall the pane happens to be.
   Every size here is inline from cwApplyDevice; the stylesheet is skin and rounding only. */
.cw-devfit{position:relative;flex:1 1 auto;min-width:0;width:100%;height:100%}
.cw-prevstage.is-framed .cw-devfit{flex:0 0 auto}
/* transform-origin is top-left and the fit box is sized to the SCALED result: with `center` the
   parent would have to know the pre-scale size to keep it centred, and the scrollbars that
   produced were worse than the arithmetic. Centring is the stage's flexbox, not a margin:auto —
   auto margins are one of the three things that quietly break under [dir=rtl]. */
.cw-devframe{position:absolute;top:0;left:0;box-sizing:border-box;width:100%;height:100%;transform-origin:top left;overflow:hidden}
.cw-devframe[data-dev="mobile"],.cw-devframe[data-dev="tablet"]{
  background:color-mix(in srgb,#17171a 92%,var(--color-accent) 8%);
  box-shadow:0 18px 44px color-mix(in srgb,var(--color-text-primary) 26%,transparent),
             inset 0 0 0 1px color-mix(in srgb,#ffffff 13%,transparent);
}
/* The chassis stays dark in both themes on purpose: a phone body that turns white in dark mode
   stops reading as a phone. The console above it makes the same call for the same reason. */
[data-theme="dark"] .cw-devframe[data-dev="mobile"],[data-theme="dark"] .cw-devframe[data-dev="tablet"]{
  box-shadow:0 18px 44px rgba(0,0,0,.55),inset 0 0 0 1px color-mix(in srgb,#ffffff 16%,transparent);
}
.cw-devframe[data-dev="mobile"]{border-radius:42px}
.cw-devframe[data-dev="tablet"]{border-radius:26px}
/* Four classes, so this beats the pre-chassis `.cw-prevstage.is-framed .cw-preview` rule that
   still wants to put its own border and shadow on the glass — the chassis owns both now. */
.cw-prevstage .cw-devframe .cw-preview{display:block;width:100%;height:100%;border:0;background:#fff}
.cw-prevstage.is-framed .cw-devframe .cw-preview{border:0;box-shadow:none;max-width:none;border-radius:0}
/* Four classes on these two as well, or the reset one line up out-ranks them and the glass keeps
   square corners. That is not only cosmetic: Safari has never reliably clipped a COMPOSITED child
   — an iframe is one — to an ancestor's overflow+border-radius, so on iOS the white iframe would
   square off against the bezel however round the chassis is. The glass carries its own corners. */
.cw-prevstage .cw-devframe[data-dev="mobile"] .cw-preview{border-radius:31px}
.cw-prevstage .cw-devframe[data-dev="tablet"] .cw-preview{border-radius:11px}
.cw-prevstage.is-framed{background:radial-gradient(120% 120% at 50% 0%,var(--color-bg-subtle),var(--color-bg) 72%)}
/* Slit and home bar live INSIDE the wall (top/bottom 3px of an 11px bezel), never over the
   glass: a dynamic-island style overlay would sit on top of the page being debugged. */
.cw-devframe__notch,.cw-devframe__bar{position:absolute;display:none;pointer-events:none;background:color-mix(in srgb,#ffffff 46%,transparent)}
.cw-devframe[data-dev="mobile"] .cw-devframe__notch{display:block;top:3px;left:50%;transform:translateX(-50%);width:64px;height:5px;border-radius:3px}
.cw-devframe[data-dev="mobile"] .cw-devframe__bar{display:block;bottom:3px;left:50%;transform:translateX(-50%);width:34%;height:5px;border-radius:3px}
.cw-devframe[data-dev="tablet"] .cw-devframe__notch{display:block;top:5px;left:50%;transform:translateX(-50%);width:5px;height:5px;border-radius:50%}
.cw-devframe[data-dev="tablet"] .cw-devframe__bar{display:block;bottom:5px;left:50%;transform:translateX(-50%);width:22%;height:5px;border-radius:3px}
/* Rotated, the hardware rotates with it. left/right are physical here by intent — the frame
   carries dir="ltr" precisely so the body never mirrors with the interface language. */
.cw-devframe.is-land[data-dev="mobile"] .cw-devframe__notch{top:50%;left:3px;transform:translateY(-50%);width:5px;height:64px}
.cw-devframe.is-land[data-dev="mobile"] .cw-devframe__bar{top:50%;bottom:auto;left:auto;right:3px;transform:translateY(-50%);width:5px;height:34%}
.cw-devframe.is-land[data-dev="tablet"] .cw-devframe__notch{top:50%;left:5px;transform:translateY(-50%)}
.cw-devframe.is-land[data-dev="tablet"] .cw-devframe__bar{top:50%;bottom:auto;left:auto;right:5px;transform:translateY(-50%);width:5px;height:22%}
/* .cw-prevbtn is display:inline-flex, which the UA's [hidden]{display:none} loses to. */
.cw-prevrot[hidden]{display:none}
.cw-prevrot{padding:0;min-width:26px;font-size:13px;line-height:1}
.cw-prevrot.is-land{color:var(--color-accent);border-color:color-mix(in srgb,var(--color-accent) 45%,transparent);background:var(--color-accent-soft)}
/* The pill used to say "834px · Tablet" and now says "834 × 1112 · 38% · Tablet". Measured: below a
   ~560px preview pane the longer run wraps to a second line and pushes the whole browser chrome
   8px taller, which on a drag-resize reads as the toolbar jumping. It is flex:1 with a
   min-width:80px floor already, so it shrinks; it just needed to be told to stay on one line.
   Truncation eats the END, i.e. the device name -- which the lit-up segment button already says,
   while the geometry the pill exists for sits at the start of the run. */
.cw-prevaddr{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}


/* ----------------------------------------------------------------------------
   FIRAS CODE — CONSOLE TOOLBAR (levels · clock · filter · clear)
   Model ported from 21st.dev #10635 "Interactive Logs Table": counted level chips
   above a filtered, timestamped log body.

   Why so few raw tokens here: .cw-console is a FIXED dark surface (#141412) in all
   six themes, and the theme greys are painted for the app's own surfaces. Dropped
   in raw they fail on this one — --color-text-muted is #6E6C64 under the light
   theme, which is 2.4:1 on #141412, and --color-accent is a dark teal there. So the
   tokens are still the source of every colour, mixed toward white by the amount the
   dark ground needs. --color-accent-soft is already alpha (rgba .08–.15) and layers
   over the terminal as-is.

   It does NOT rely on being last in the file. Several patches land on this same tail
   anchor, so source order here is whatever the serialiser happened to pick. The one rule
   it has to beat is the v3 skin's `.cw--v3 .cw-conrow` / `.cw--v3 .cw-conrow.is-msg`
   padding shorthand, and `.cw--v3 .cw-console.is-ts .cw-conrow` outweighs both on
   specificity (0,4,0 vs 0,2,0 and 0,3,0) — so it wins wherever it is pasted.
   -------------------------------------------------------------------------- */
.cw-conbar{
  position:sticky; top:0; z-index:2;
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  padding:7px 10px; background:#141412; border-bottom:1px solid #ffffff14;
}
.cw--v3 .cw-conbar{background:#171715}   /* the v3 console is a gradient; match its top stop */
.cw-conf{display:flex; align-items:center; gap:4px; flex-wrap:wrap}
.cw-conf__chip{
  display:inline-flex; align-items:center; gap:5px;
  padding:3px 9px; border:1px solid transparent; border-radius:var(--radius-pill);
  background:transparent; color:color-mix(in srgb, var(--color-text-muted) 45%, #ffffff);
  font:600 10.5px/1.45 var(--font-sans); cursor:pointer;
  transition:background .14s ease, color .14s ease, border-color .14s ease;
}
[dir="rtl"] .cw-conf__chip{font-family:var(--font-arabic)}
.cw-conf__chip:hover{background:var(--color-accent-soft); color:#e8e6df}
.cw-conf__chip.is-active{
  background:color-mix(in srgb, var(--color-accent) 20%, transparent);
  border-color:color-mix(in srgb, var(--color-accent) 42%, transparent);
  color:color-mix(in srgb, var(--color-accent) 55%, #ffffff);
}
/* An active severity chip answers in the same colour its rows are written in, so the eye
   does not have to re-learn what red means one line below. */
.cw-conf__chip[data-lvl="error"].is-active{background:#c0392b26; border-color:#ff8a8055; color:#ff8a80}
.cw-conf__chip[data-lvl="warn"].is-active{background:#b9770e26; border-color:#ffd54f55; color:#ffd54f}
.cw-conf__chip.is-none{opacity:.45}
.cw-conf__n{
  min-width:15px; padding:0 4px; border-radius:var(--radius-pill);
  background:#ffffff14; color:inherit;
  font:600 9.5px/15px var(--font-mono); text-align:center;
}
.cw-consearch{
  flex:1 1 110px; min-width:88px; max-width:210px;
  padding:4px 10px; border:1px solid #ffffff14; border-radius:var(--radius-pill);
  background:#ffffff08; color:#e8e6df; font:500 11px var(--font-sans);
  transition:border-color .14s ease, background .14s ease;
}
[dir="rtl"] .cw-consearch{font-family:var(--font-arabic)}
.cw-consearch::placeholder{color:color-mix(in srgb, var(--color-text-muted) 40%, #ffffff); opacity:.8}
.cw-consearch:focus{outline:none; border-color:var(--color-accent); background:#ffffff12}
.cw-consearch::-webkit-search-cancel-button{filter:invert(1); opacity:.45}
.cw-conts,.cw-conwipe{
  flex:0 0 auto; width:24px; height:24px; display:grid; place-items:center;
  border:1px solid transparent; border-radius:7px; background:transparent;
  color:color-mix(in srgb, var(--color-text-muted) 45%, #ffffff);
  font-size:11.5px; line-height:1; cursor:pointer;
  transition:background .14s ease, color .14s ease, border-color .14s ease;
}
/* Safe to use an inline-logical margin HERE, unlike on the rows: .cw-conbar carries a real
   dir attribute, so `start` resolves to the right under Arabic. The rows below only ever get
   unicode-bidi:plaintext, which reorders inline content and leaves `direction` at ltr — every
   logical property on them still lands on the physical left. */
.cw-conts{margin-inline-start:auto}
.cw-conts:hover,.cw-conwipe:hover{background:var(--color-accent-soft); color:#e8e6df; border-color:#ffffff14}
.cw-conts[aria-pressed="true"]{
  background:color-mix(in srgb, var(--color-accent) 20%, transparent);
  border-color:color-mix(in srgb, var(--color-accent) 42%, transparent);
  color:color-mix(in srgb, var(--color-accent) 55%, #ffffff);
}
/* Filtered out, not deleted — the fix-errors palette action still reads every error row. */
.cw-console__list .cw-conrow.is-off{display:none}
/* The clock lives in the row's attribute and is painted here, never in its textContent:
   that text is what the fix-errors action sends to the model. Physical left/px because
   .cw-conrow is direction:ltr by decree and always will be — stdout is. */
.cw-console.is-ts .cw-conrow{padding-left:68px}
.cw-console.is-ts .cw-conrow::after{
  content:attr(data-t); position:absolute; left:18px; top:2px;
  font:10px/1.5 var(--font-mono); color:#ffffff3d; letter-spacing:.02em;
}
.cw--v3 .cw-console.is-ts .cw-conrow{padding-left:76px}
.cw--v3 .cw-console.is-ts .cw-conrow::after{left:26px; top:3px}
.cw-conempty{
  padding:16px 16px 20px; text-align:center;
  font:500 11.5px/1.75 var(--font-sans);
  color:color-mix(in srgb, var(--color-text-muted) 42%, #ffffff);
}
[dir="rtl"].cw-conempty{font-family:var(--font-arabic)}
/* Same bump the rest of the pane already gets (.cw-conclear is 34px, .cw-tab is 38px): a
   24px icon button is not a target on the phone, and the console has its own mobnav tab so
   it IS reached by thumb. Left off the chips' width on purpose — they size from their label
   and a min-height is all they need. */
@media (pointer: coarse){
  .cw-conts,.cw-conwipe{width:34px; height:34px; font-size:14px}
  .cw-conf__chip{padding:8px 11px; min-height:34px; font-size:11.5px}
  .cw-conf__n{font-size:10.5px; line-height:16px; min-width:16px}
}
@media (max-width: 560px){
  .cw-conbar{padding:6px 8px; gap:6px}
  .cw-consearch{max-width:none}
  .cw-console.is-ts .cw-conrow{padding-left:58px}
  .cw-console.is-ts .cw-conrow::after{left:16px; font-size:9px}
  .cw--v3 .cw-console.is-ts .cw-conrow{padding-left:64px}
  .cw--v3 .cw-console.is-ts .cw-conrow::after{left:22px}
}

/* ═══ RETRY ON A STRONGER MODEL — the pair strip and the side-by-side sheet ═══════════════
   The strip is chrome on a turn, not a call to action: it exists to explain why one question
   has two answers. Tinted like .msg-action--continue on the retry itself so that half reads
   as the new one, plain on the original so a thread full of retries is not a wall of accent.
   Every offset is logical — the strip sits inside a turn that is stamped rtl in Arabic. */
.retry-pair {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  margin-block-end: var(--space-2);
  font: 500 12px/1.5 var(--font-sans);
  color: var(--color-text-muted);
}
.retry-pair__label { color: var(--color-text-secondary); }
.retry-pair--new .retry-pair__label { color: var(--color-accent); }
.retry-pair__tier {
  padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--color-border-hairline);
  background: var(--color-bg-subtle); color: var(--color-text-muted);
  font-size: 11px;
}
.retry-pair__btn {
  padding: 2px 8px; border-radius: var(--radius-sm);
  border: 1px solid transparent; background: transparent;
  color: var(--color-accent); font: inherit; cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.retry-pair__btn:hover { background: var(--color-accent-soft); color: var(--color-accent-hover); }

/* The sheet. Shaped after .mem-overlay / .mem-card — same scrim, same tokens, same resting
   transform — so it arrives the way every other modal in the app arrives. */
.rtc-overlay {
  position: fixed; inset: 0; z-index: 1200;
  display: flex; align-items: center; justify-content: center; padding: 20px;
  background: rgba(0,0,0,.55);
  opacity: 0; transition: opacity var(--dur-base) var(--ease-out);
}
.rtc-overlay.is-open { opacity: 1; }
.rtc-card {
  inline-size: min(1080px, 100%);
  block-size: min(88vh, 100%); block-size: min(88dvh, 100%);
  display: flex; flex-direction: column;
  background: var(--color-surface); color: var(--color-text-primary);
  border: 1px solid var(--color-border-strong); border-radius: var(--radius-lg);
  box-shadow: 0 24px 64px rgba(0,0,0,.45);
  transform: translateY(8px) scale(.985); opacity: 0;
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
}
.rtc-overlay.is-open .rtc-card { transform: none; opacity: 1; }
.rtc-head {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 16px 20px; border-block-end: 1px solid var(--color-border-hairline);
}
.rtc-head__tx { flex: 1; min-inline-size: 0; }
.rtc-head h3 { margin: 0; font: 600 17px/1.3 var(--font-sans); }
.rtc-head p { margin: 3px 0 0; font: 400 13px/1.45 var(--font-sans); color: var(--color-text-secondary); }
.rtc-x {
  flex: 0 0 auto; inline-size: 32px; block-size: 32px;
  display: grid; place-items: center;
  border: none; background: transparent; color: var(--color-text-muted);
  border-radius: var(--radius-sm); cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.rtc-x:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }

/* EACH COLUMN SCROLLS ITSELF. One shared scroller would mean scrolling past the whole of the
   first answer to reach the second — the opposite of a comparison. min-block-size:0 on both
   the grid and the column is what lets a flex/grid child actually shrink and scroll instead
   of growing the card past the viewport. */
.rtc-cols {
  flex: 1; min-block-size: 0;
  display: grid; grid-template-columns: 1fr 1fr;
  overflow: hidden;
}
.rtc-col { display: flex; flex-direction: column; min-inline-size: 0; min-block-size: 0; }
/* An INLINE border, not border-left: the two columns swap sides in Arabic and a physical
   left border would end up on the outside edge of the sheet instead of between them. */
.rtc-col + .rtc-col { border-inline-start: 1px solid var(--color-border-hairline); }
.rtc-col__head {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 8px;
  padding: 10px 18px; border-block-end: 1px solid var(--color-border-hairline);
  font: 600 12px/1.4 var(--font-sans); color: var(--color-text-secondary);
}
.rtc-col__tier {
  padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--color-border-hairline);
  background: var(--color-bg-subtle); color: var(--color-text-muted);
  font-weight: 500; font-size: 11px;
}
.rtc-col__body {
  flex: 1; min-block-size: 0; overflow: auto;
  padding: 14px 18px 22px;
  /* `.md > p { text-align: start }` is scoped to .msg-ai__body, and this markdown is not in
     a turn. Set on the container so it inherits down to blocks that carry their own dir. */
  text-align: start;
}

/* Two columns of prose need real width. Below that they stack, the card takes the whole
   height, and the OUTER box becomes the single scroller — nested scrollers on a phone means
   the inner one eats the flick meant for the page. */
@media (max-width: 860px) {
  .rtc-cols { grid-template-columns: 1fr; overflow: auto; }
  .rtc-col { min-block-size: auto; }
  .rtc-col + .rtc-col {
    border-inline-start: 0;
    border-block-start: 1px solid var(--color-border-hairline);
  }
  .rtc-col__body { overflow: visible; }
  .rtc-col__head { position: sticky; inset-block-start: 0; background: var(--color-surface); z-index: 1; }
}

@media (prefers-reduced-motion: reduce) { .rtc-card { transform: none !important; } }
:root[data-motion="off"] .rtc-card { transform: none !important; }

/* ── ANY TWO ANSWERS ──────────────────────────────────────────────────────────────────────
   The sheet IS the one above: .rtc-overlay / .rtc-card / .rtc-cols / .rtc-col are reused
   whole, so a comparison arrives the same way whichever control opened it — and the phone
   stacking, the reduced-motion rules and the per-column scrollers all come with them. Only
   the three parts the retry sheet has no use for live here: the chooser that stands in for a
   column until an answer is picked, the control that turns a chosen column back into one, and
   the footer that hands both answers to the composer.

   Every button below inherits `cursor:pointer; border:none; background:none` and the shared
   :focus-visible ring from the reset, so only the departures from those are written out. */
.cmp-swap {
  /* At the far edge of the head, whichever edge that is — the sheet's dir decides, and a
     physical margin-left would pin it to the OUTSIDE of the Arabic layout. */
  margin-inline-start: auto;
  flex: 0 0 auto;
  padding-block: 2px; padding-inline: var(--space-2);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font: inherit; font-weight: var(--weight-medium);
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.cmp-swap:hover { background: var(--color-accent-soft); border-color: var(--color-accent-ring); color: var(--color-accent); }

.cmp-list { display: flex; flex-direction: column; gap: var(--space-2); }
.cmp-row {
  display: flex; align-items: flex-start; gap: var(--space-2);
  inline-size: 100%;
  padding-block: var(--space-2); padding-inline: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text-primary);
  /* start, not left: the row is laid out from whichever edge the chooser's dir made the first
     one, and its three parts have to begin there too. */
  text-align: start;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.cmp-row:hover { background: var(--color-bg-subtle); border-color: var(--color-border-strong); }
.cmp-row__k {
  flex: 0 0 auto;
  min-inline-size: 20px;
  font-family: var(--font-sans);
  font-size: var(--text-xs); line-height: var(--leading-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-muted);
}
.cmp-row__tier {
  flex: 0 0 auto;
  padding-block: 1px; padding-inline: 7px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle); color: var(--color-text-muted);
  font-family: var(--font-sans);
  font-size: var(--text-xs); line-height: var(--leading-sm);
  font-weight: var(--weight-medium);
}
/* Two lines of the answer and no more: the row is here to be RECOGNISED, and the column beside
   it is where the answer is actually read. min-inline-size:0 is what lets it clamp at all. */
.cmp-row__tx {
  flex: 1 1 auto; min-inline-size: 0;
  font-size: var(--text-sm); line-height: var(--leading-sm);
  color: var(--color-text-secondary);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
/* The face follows the SNIPPET's own script, not the panel's — an English answer listed inside
   the Arabic sheet must not be set in the Arabic face. Same split .askag makes. */
.cmp-row__tx[dir="rtl"] { font-family: var(--font-arabic); }
.cmp-row__tx[dir="ltr"] { font-family: var(--font-sans); }

.cmp-foot {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--space-3);
  padding-block: var(--space-3); padding-inline: var(--space-5);
  border-block-start: var(--border-hairline) solid var(--color-border-hairline);
}
/* display:flex would otherwise win over the `hidden` attribute and the footer would sit there
   empty-handed while only one column had an answer in it. */
.cmp-foot[hidden] { display: none; }
.cmp-foot[dir="rtl"] { font-family: var(--font-arabic); }
.cmp-foot__h {
  flex: 1 1 auto; min-inline-size: 0;
  font-size: var(--text-xs); line-height: var(--leading-sm);
  color: var(--color-text-muted);
}
.cmp-foot__b {
  flex: 0 0 auto;
  block-size: 34px;
  padding-inline: var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-family: inherit;
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out);
}
.cmp-foot__b:hover { background: var(--color-accent-hover); }

/* Below this the two columns stack and .rtc-cols becomes the single scroller. The footer is a
   SIBLING of that scroller, not a child of it — .rtc-card is a flex column of a fixed 88dvh, so
   flex already pins the footer under the scrolling area and it cannot scroll away. Kept as
   belt-and-braces for the day the card itself is allowed to grow: this is the one control the
   reader opened the sheet to press, and it must not leave the screen. */
@media (max-width: 860px) {
  .cmp-foot {
    position: sticky; inset-block-end: 0;
    background: var(--color-surface);
  }
}

/* The motion attribute is read off :root as the LEFTMOST compound, because :root IS <html> —
   put any ancestor selector in front of it and the rule can never match anything. */
@media (prefers-reduced-motion: reduce) {
  .cmp-row, .cmp-swap, .cmp-foot__b { transition: none; }
}
:root[data-motion="off"] .cmp-row,
:root[data-motion="off"] .cmp-swap,
:root[data-motion="off"] .cmp-foot__b { transition: none; }



/* ═══════════════════════════════════════════════════════════════════════════════
   SIDEBAR FOLDERS  (.hist-foldbar / .hist-fold / .hist-pick)
   ═══════════════════════════════════════════════════════════════════════════════
   A folder header is a row in the same family as .chat-item — same height, same
   radius, same hover — because it sits directly above chat rows and anything that
   looked like a different species there would read as a different list.

   The header frame stays LTR in both languages (app.js explains why); only the
   name and the count carry a direction, so nothing here needs an RTL mirror. */
.hist-foldbar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
}
.hist-foldbar__add {
  display: grid; place-items: center;
  inline-size: 22px; block-size: 22px; flex: 0 0 auto;
  border-radius: var(--radius-xs);
  color: var(--color-text-muted);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.hist-foldbar__add svg { inline-size: 13px; block-size: 13px; }
.hist-foldbar__add:hover { background: var(--color-surface); color: var(--color-text-primary); }
/* No folders yet: the whole line is the affordance, and it says so — dashed, quiet,
   and the only thing on screen that this feature costs a user who never wants it. */
.hist-foldbar.is-empty {
  margin-block-start: var(--space-2);
  padding-block: var(--space-2);
  border: 1px dashed var(--color-border-hairline);
  border-radius: var(--radius-sm);
  cursor: pointer; text-transform: none; letter-spacing: 0;
  opacity: .72;
  transition: opacity var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.hist-foldbar.is-empty:hover { opacity: 1; border-color: var(--color-border-strong); }

.hist-fold { margin-block-end: 2px; }
.hist-fold__head {
  position: relative;
  display: flex; align-items: center; gap: var(--space-2);
  block-size: 34px;
  padding-inline: var(--space-2);
  border-radius: var(--radius-sm);
  cursor: pointer;
  color: var(--color-text-secondary);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.hist-fold__head:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.hist-fold__head:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: -2px; }
.hist-fold__chev { display: inline-flex; flex: 0 0 auto; color: var(--color-text-muted); transition: transform var(--dur-fast) var(--ease-out); }
.hist-fold__chev svg { inline-size: 13px; block-size: 13px; }
/* Points along the row when the folder is shut, down into it when it is open. Rotated the
   same way in both languages: the header frame never flips. */
.hist-fold.is-collapsed .hist-fold__chev { transform: rotate(-90deg); }
.hist-fold__ic { display: inline-flex; flex: 0 0 auto; color: var(--color-accent); opacity: .85; }
.hist-fold__ic svg { inline-size: 14px; block-size: 14px; }
.hist-fold__name {
  flex: 1; min-inline-size: 0;
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hist-fold__in {
  inline-size: 100%; background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-strong); border-radius: var(--radius-xs);
  padding: 2px 6px; font-size: var(--text-sm); color: var(--color-text-primary);
}
.hist-fold__n {
  flex: 0 0 auto;
  font-size: 10.5px; font-variant-numeric: tabular-nums; font-weight: 600;
  color: var(--color-text-secondary);
  background: color-mix(in srgb, var(--color-text-primary) 8%, transparent);
  border-radius: 999px; padding: 1px 6px; letter-spacing: 0;
}
/* Same reveal rule as .chat-item__actions, and for the same reason: on a touch screen the
   first tap synthesises :hover, and a control that appears then reflows the row mid-gesture. */
.hist-fold__acts { display: none; gap: 2px; flex: 0 0 auto; }
@media (hover: hover) and (pointer: fine) {
  .hist-fold__head:hover .hist-fold__acts { display: flex; }
}
@media (hover: none) { .hist-fold__acts { display: flex; } }
.hist-fold.is-picking .hist-fold__acts { display: flex; }
.hist-fold__acts button {
  display: grid; place-items: center;
  inline-size: 26px; block-size: 26px;
  border-radius: var(--radius-xs); color: var(--color-text-muted);
  transition: background var(--dur-fast), color var(--dur-fast);
}
.hist-fold__acts button:hover { background: var(--color-surface); color: var(--color-text-secondary); }
.hist-fold__acts svg { inline-size: 14px; block-size: 14px; }
/* The count and the controls share the end of the row; the count yields, exactly as the
   UI 2.0 row stamp does — the number is context, the buttons are intent. */
@media (hover: hover) and (pointer: fine) {
  .hist-fold__head:hover .hist-fold__n { display: none; }
}
.hist-fold.is-picking .hist-fold__n { display: none; }

/* Drop state. The dashed ring is drawn with an inset outline so the row does not change
   size when it lights up — a target that grows under the pointer moves itself away. */
.hist-fold__head.is-drop {
  background: color-mix(in srgb, var(--color-accent) 14%, transparent);
  color: var(--color-text-primary);
  outline: 2px dashed var(--color-accent); outline-offset: -2px;
}
.chat-item.is-dragging { opacity: .45; }

.hist-fold__body { padding-inline-start: var(--space-3); }
.hist-fold.is-collapsed .hist-fold__body { display: none; }
.hist-fold__hint {
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-muted);
  border: 1px dashed var(--color-border-hairline); border-radius: var(--radius-sm);
  margin-block: 2px;
}

/* The "add conversations" panel — a list, not a popover: it is rendered inside the folder
   so a background stream finishing (renderHistory) cannot pull it out from under a tap. */
.hist-pick {
  margin-block: var(--space-1) var(--space-2);
  padding: var(--space-2);
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-sm);
}
.hist-pick__head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  font-size: var(--text-xs); color: var(--color-text-muted);
  margin-block-end: var(--space-2);
}
.hist-pick__done {
  flex: 0 0 auto; padding: 2px 8px; border-radius: var(--radius-pill, 999px);
  font-size: var(--text-xs); color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}
.hist-pick__q {
  inline-size: 100%; block-size: 30px; margin-block-end: var(--space-2);
  padding-inline: var(--space-2);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-xs);
  color: var(--color-text-primary); font-size: var(--text-sm);
}
.hist-pick__q:focus { outline: none; border-color: var(--color-border-strong); }
.hist-pick__rows { max-block-size: 240px; overflow-y: auto; }
.hist-pick__row {
  display: flex; align-items: center; gap: var(--space-2);
  inline-size: 100%; min-block-size: 30px;
  padding-inline: var(--space-2);
  border-radius: var(--radius-xs);
  color: var(--color-text-secondary); font-size: var(--text-sm);
  text-align: start; cursor: pointer;
}
.hist-pick__row:hover { background: var(--color-surface); color: var(--color-text-primary); }
.hist-pick__tick { display: grid; place-items: center; flex: 0 0 auto; inline-size: 16px; block-size: 16px; opacity: 0; color: var(--color-accent); }
.hist-pick__tick svg { inline-size: 13px; block-size: 13px; }
.hist-pick__row.is-on .hist-pick__tick { opacity: 1; }
.hist-pick__row.is-on { color: var(--color-text-primary); }
.hist-pick__tx { flex: 1; min-inline-size: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hist-pick__none { padding: var(--space-3) var(--space-2); font-size: var(--text-xs); color: var(--color-text-muted); text-align: center; }

/* Same budget as the conversation rows below: real targets on a touch screen, and the folder
   header carries three controls next to a name that can ellipsise, not four. */
@media (pointer: coarse), (max-width: 560px) {
  .hist-fold__head { block-size: auto; min-block-size: 44px; }
  .hist-fold__acts { gap: 0; }
  .hist-fold__acts button { inline-size: 40px; block-size: 40px; }
  .hist-foldbar__add { inline-size: 40px; block-size: 40px; }
  .hist-pick__row { min-block-size: 44px; }
  .hist-pick__q { block-size: 40px; }
}

/* ═════════════════════════════════════════════════════════════════════════════
   BS · SELECTING SEVERAL CONVERSATIONS  (.hist-bs / .chat-item__bs)
   ═════════════════════════════════════════════════════════════════════════════
   Off, this is one quiet line in the family of .hist-foldbar above it. On, it is the same
   panel .hist-pick is — a filled block that owns the top of the list — because at that point
   it is the thing being used and the rows below it are what it is being used on.

   Logical properties throughout. The BAR carries a real dir and flips whole in Arabic; the
   conversation rows below it never do, so nothing here needs an RTL mirror. */
.hist-bs {
  display: flex; flex-direction: column; gap: var(--space-2);
  margin-block: var(--space-1);
}
.hist-bs__go {
  display: flex; align-items: center; gap: var(--space-2);
  inline-size: 100%; min-block-size: 30px;
  padding-inline: var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--color-text-muted); font-size: var(--text-xs);
  text-align: start; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.hist-bs__go:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.hist-bs__go svg { inline-size: 14px; block-size: 14px; flex: 0 0 auto; }

.hist-bs.is-on {
  gap: var(--space-2);
  padding: var(--space-2);
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-sm);
}
.hist-bs__head {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-xs); color: var(--color-text-muted);
}
.hist-bs__n { flex: 0 0 auto; font-weight: var(--weight-semibold); color: var(--color-text-primary); }
.hist-bs__lb { min-inline-size: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hist-bs__all {
  margin-inline-start: auto; flex: 0 0 auto;
  padding: 2px 8px; border-radius: var(--radius-pill, 999px);
  font-size: var(--text-xs); color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}
.hist-bs__done {
  flex: 0 0 auto; padding-inline: var(--space-1);
  font-size: var(--text-xs); color: var(--color-text-muted);
  transition: color var(--dur-fast) var(--ease-out);
}
.hist-bs__done:hover { color: var(--color-text-primary); }
/* Dimmed, never disabled: with nothing picked these still answer, and what they answer is the
   one sentence that explains the state — "nothing is selected". */
.hist-bs__acts { display: flex; align-items: center; gap: 2px; }
.hist-bs__acts.is-off { opacity: .45; }
.hist-bs__acts button {
  display: grid; place-items: center;
  inline-size: 30px; block-size: 30px; flex: 0 0 auto;
  border-radius: var(--radius-xs);
  color: var(--color-text-muted);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.hist-bs__acts button:hover { background: var(--color-surface); color: var(--color-text-primary); }
.hist-bs__acts svg { inline-size: 15px; block-size: 15px; }
.hist-bs__acts .is-danger:hover { color: var(--color-error, #C0483A); }
.hist-bs__panel { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.hist-bs__rows { max-block-size: 200px; overflow-y: auto; }
.hist-bs__none {
  padding: var(--space-2); text-align: center;
  font-size: var(--text-xs); color: var(--color-text-muted);
}

/* The box on the row. It leads the row in BOTH languages — the frame stays LTR, which is what
   keeps one conversation drawn one way in a sidebar that holds both scripts. */
.chat-item__bs {
  display: grid; place-items: center; flex: 0 0 auto;
  inline-size: 18px; block-size: 18px;
  color: var(--color-text-muted);
}
.chat-item__bs svg { inline-size: 16px; block-size: 16px; }
.chat-item.is-bsel .chat-item__bs { color: var(--color-accent); }
.chat-item.is-bsel { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
/* The row's own buttons are gone for as long as the mode is on. In here a tap MEANS pick, and
   a delete sitting under a finger that expects a checkbox is the one mistake this must never
   make. Placed after every rule that shows them — including the touch one — so it wins. */
.chat-item.is-bulk .chat-item__actions { display: none; }

/* Same budget as the rows below: real targets on a touch screen, where this mode is the only
   way to file anything at all because dragging does not exist there. */
@media (pointer: coarse), (max-width: 560px) {
  .hist-bs__go { min-block-size: 40px; }
  .hist-bs__acts { gap: 0; }
  .hist-bs__acts button { inline-size: 40px; block-size: 40px; }
  .chat-item__bs { inline-size: 22px; block-size: 22px; }
  .chat-item__bs svg { inline-size: 19px; block-size: 19px; }
}

/* ----------------------------------------------------------------------------
   PINNED TURNS — the strip at the top of a thread, and the control on each turn
   The strip is a child of the SCROLLER (#chatScroll, or .fb-thread which is its own
   scroller) and never of .chat-inner: `position: sticky` resolves against the nearest
   scrollport, so inside the reading column it would scroll away with the turns it indexes.
---------------------------------------------------------------------------- */
.pinmsg-bar {
  position: sticky; inset-block-start: 0; z-index: 4;
  /* .fb-thread is `display:flex; flex-direction:column` AND the scroller, so the strip is a flex
     item there: without this it is a shrink candidate the moment the turns overflow. */
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 8px;
  padding-block: 6px;
  padding-inline: var(--space-4);
  /* OPAQUE, not a blur. backdrop-filter would make this element a containing block and trap its
     own stacking context — the trap the sheets layer already paid for once. */
  background: var(--color-bg);
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
}
/* Align the chips with the reading column rather than the window: it is that column they index. */
.chat-scroll > .pinmsg-bar {
  padding-inline: max(var(--space-6), calc((100% - var(--column-w)) / 2));
}
.pinmsg-bar__ic { flex: 0 0 auto; display: inline-flex; color: var(--color-accent); }
.pinmsg-bar__ic svg { inline-size: 14px; block-size: 14px; }
.pinmsg-bar__list {
  flex: 1 1 auto; min-inline-size: 0;
  display: flex; align-items: center; gap: 6px;
  overflow-x: auto; overflow-y: hidden;
  /* Without this a flick along a full strip scrolls the THREAD out from under the reader once
     the chips run out — the strip is inside the scroller it is pinned to. */
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  padding-block: 2px;
}
.pinmsg-bar__list::-webkit-scrollbar { display: none; }

.pinmsg-chip {
  flex: 0 0 auto; display: inline-flex; align-items: stretch;
  max-inline-size: 260px;
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.pinmsg-chip:hover { border-color: var(--color-border-strong); }
.pinmsg-chip__go {
  display: inline-flex; align-items: center; gap: 6px;
  min-inline-size: 0;
  padding-block: 3px; padding-inline: 10px 4px;
  background: transparent; border: 0; cursor: pointer;
  font: var(--weight-regular) 12px/18px var(--font-sans);
  color: var(--color-text-secondary);
}
.pinmsg-chip__who { flex: 0 0 auto; color: var(--color-text-muted); font-weight: var(--weight-medium); }
.pinmsg-chip__tx { min-inline-size: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
/* The Arabic face follows the bar, but the excerpt carries its OWN dir — a pinned English line
   in an Arabic session is the common case, and it must not be set in the Arabic stack. */
[dir="rtl"] .pinmsg-chip { font-family: var(--font-arabic); }
.pinmsg-chip__tx[dir="ltr"] { font-family: var(--font-sans); }
.pinmsg-chip__x {
  flex: 0 0 auto; inline-size: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 0; cursor: pointer;
  font-size: 11px; line-height: 1; color: var(--color-text-muted);
}
.pinmsg-chip__x:hover { color: var(--color-text-primary); background: var(--color-bg-subtle); }
.pinmsg-chip__go:focus-visible,
.pinmsg-chip__x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ----- the control on a turn ---------------------------------------------- */
.pinmsg-row { display: flex; }
.pinmsg-b {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 26px; block-size: 26px; padding: 0;
  background: transparent; border: 0; cursor: pointer;
  border-radius: var(--radius-sm);
  color: var(--color-text-muted); opacity: .55;
  transition: opacity var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.pinmsg-b svg { inline-size: 15px; block-size: 15px; }
.pinmsg-b:hover { opacity: 1; color: var(--color-text-secondary); background: var(--color-bg-subtle); }
.pinmsg-b:focus-visible { outline: none; opacity: 1; box-shadow: var(--shadow-focus); }
.pinmsg-b.is-on { opacity: 1; color: var(--color-accent); }

/* The chat's own question has no action row, so its control is ORDERED into the gutter before
   the bubble. .msg-user is justify-content:flex-end and the bubble takes up to 85% of the
   column, so a control appended after it would be crushed against the edge. */
.msg-user > .pinmsg-row { order: -1; align-self: center; margin-inline-end: 4px; }
/* A question is quieter than an answer: the control stays out of the flow of reading until the
   turn is hovered or focused, or until it is actually pinned. :focus as well as :focus-visible,
   because a programmatic .focus() matches only the former and a control that cannot be revealed
   under test cannot be verified. */
.msg-user > .pinmsg-row .pinmsg-b,
.fb-thread .turn--user .pinmsg-b { opacity: 0; }
.msg-user:hover > .pinmsg-row .pinmsg-b,
.msg-user > .pinmsg-row .pinmsg-b:focus,
.msg-user > .pinmsg-row .pinmsg-b.is-on,
.fb-thread .turn--user:hover .pinmsg-b,
.fb-thread .turn--user .pinmsg-b:focus,
.fb-thread .turn--user .pinmsg-b.is-on { opacity: 1; }
/* Touch has no hover, so a reveal-on-hover control would never appear at all. */
@media (hover: none) {
  .msg-user > .pinmsg-row .pinmsg-b,
  .fb-thread .turn--user .pinmsg-b { opacity: .55; }
}

/* ----- the private note on an answer --------------------------------------- */
/* Tokens only, no literal colour: six themes in this file work by REDEFINING these, so one
   hard-coded hex would be the single surface in the thread that ignores five of them. */
.msgnote {
  margin-block: var(--space-2) 0;
  padding: 9px 12px;
  border-radius: var(--radius-md);
  border-inline-start: 2.5px solid color-mix(in srgb, var(--color-accent) 45%, transparent);
  background: var(--color-bg-subtle);
}
.msgnote__h { display: flex; align-items: center; gap: 7px; margin-block-end: 5px; }
.msgnote__ic { flex: 0 0 auto; display: inline-flex; color: var(--color-accent); }
.msgnote__ic svg { inline-size: 13px; block-size: 13px; }
.msgnote__t { font: var(--weight-semibold) 11.5px var(--font-sans); color: var(--color-text-muted); }
/* dir is stamped on the CARD and the shell above it is fixed LTR, so the Arabic face has to be
   selected from the card's own attribute — an ancestor form here would never match. */
.msgnote[dir="rtl"] .msgnote__t { font-family: var(--font-arabic); }
.msgnote__x {
  flex: 0 0 auto; margin-inline-start: auto;
  inline-size: 22px; block-size: 22px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--radius-sm); background: none; cursor: pointer;
  color: var(--color-text-muted); font-size: 12px; line-height: 1;
  transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.msgnote__x:hover { color: var(--color-text-primary); background: var(--color-bg-subtle); }
.msgnote__x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* No border and no well of its own: this has to read as writing in the margin, not as a form
   field parked under the answer. The frame around it is already the card. */
.msgnote__tx {
  display: block; inline-size: 100%; box-sizing: border-box;
  min-block-size: 42px; max-block-size: 260px;
  margin: 0; padding: 0; border: 0; background: none; resize: none; overflow-y: auto;
  font: var(--weight-regular) 13.5px/1.65 var(--font-sans);
  color: var(--color-text-primary);
}
.msgnote__tx[dir="rtl"] { font-family: var(--font-arabic); }
.msgnote__tx:focus { outline: none; }
.msgnote__tx::placeholder { color: var(--color-text-muted); opacity: 1; }
/* The control wears the row's own button class (.msg-action in chat, .fb-act in Brain) and adds
   only what neither row has: room for an icon beside the word. */
.msgnote-b { display: inline-flex; align-items: center; gap: 6px; }
.msgnote-b__ic { display: inline-flex; }
.fb-act.msgnote-b .msgnote-b__ic svg { inline-size: 14px; block-size: 14px; }
.msgnote-b__t[dir="rtl"] { font-family: var(--font-arabic); }
.msgnote-b.is-on, .msgnote-b.is-on .msgnote-b__ic { color: var(--color-accent); }

/* ══════════════════════════ THREAD TABLE OF CONTENTS ══════════════════════════
   A minimap of your own questions, standing in the gutter beside the 744px column. Ticks only:
   the question's text arrives on hover, in a label that is absolutely positioned OUT of the
   rail, so revealing it costs no layout and moves nothing under the pointer.

   PHYSICAL right here, deliberately, against this file's logical-property habit. <html> is
   pinned dir="ltr" and only content carries dir, so this gutter is the same gutter in both
   languages - there is nothing to mirror, and a logical inset would hand the rail away to
   whichever element later stamps dir="rtl" on an ancestor of it. */
.ttoc {
  /* display:none until tocFit() has measured a real gutter. A rail with nowhere to stand must
     not draw at all - which is also, deliberately, what happens if the script never runs. */
  display: none;
  position: absolute;
  right: 10px;
  /* Pinned to the BAND between the topbar and the composer, both written by tocScale() off the
     real boxes - NOT centred on .main. The composer is much the taller of the two, so a
     .main-centred rail sits about fifty pixels low and its last ticks land under #jumpBtn,
     which shares this right edge and paints at z-index 200. The fallbacks are the two boxes at
     their resting heights, for the frame before the first measurement. */
  top: var(--ttoc-top, 68px);
  bottom: var(--ttoc-bot, 206px);
  z-index: 4;
  flex-direction: column; align-items: flex-end; justify-content: center;
  padding-block: 4px;
  /* The rail's own box must never eat a click meant for the text behind it. Only ticks live. */
  pointer-events: none;
}
.ttoc.is-fit { display: flex; }
/* No hover, no preview - and a tick tapped blind is a jump to somewhere you were never shown.
   Those readers get the jump panel, which was built for exactly this. */
@media (hover: none) {
  .ttoc.is-fit { display: none; }
}

.ttoc__tick {
  position: relative;
  flex: 0 0 auto;
  pointer-events: auto;
  inline-size: 26px;
  /* Set per rail by tocScale(): sixty questions get a shorter tick, never a scrollbar. */
  block-size: var(--ttoc-h, 20px);
  display: grid; place-items: center end;
  padding: 0; border: 0; background: transparent;
  -webkit-appearance: none; appearance: none;
  cursor: pointer;
}
.ttoc__tick:focus-visible { outline: none; border-radius: var(--radius-sm); box-shadow: var(--shadow-focus); }
.ttoc__dot {
  inline-size: 13px; block-size: 2px;
  border-radius: 2px;
  background: color-mix(in srgb, var(--color-text-muted) 42%, transparent);
  transition: inline-size var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.ttoc__tick:hover .ttoc__dot { inline-size: 20px; background: var(--color-text-secondary); }
.ttoc__tick.is-active .ttoc__dot { inline-size: 20px; background: var(--color-accent); }

/* The label grows LEFTWARDS out of the rail, over the column, and is out of flow - so a rail of
   sixty ticks never reflows just because one of them was hovered. */
.ttoc__lbl {
  position: absolute;
  right: calc(100% + 4px);
  top: 50%;
  transform: translateY(-50%);
  display: flex; align-items: flex-start; gap: 7px;
  inline-size: max-content; max-inline-size: min(320px, 30vw);
  padding: 7px 10px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: 12px;
  background: var(--color-surface);
  box-shadow: 0 2px 6px rgba(0,0,0,.08), 0 12px 32px rgba(0,0,0,.22);
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur-fast) var(--ease-out), visibility var(--dur-fast);
}
.ttoc__tick:hover .ttoc__lbl,
.ttoc__tick:focus-visible .ttoc__lbl { opacity: 1; visibility: visible; }
.ttoc__n {
  flex: 0 0 auto;
  font: 600 10px/1.6 var(--font-mono);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
.ttoc__t {
  flex: 1 1 auto; min-inline-size: 0;
  font: 400 12.5px/1.45 var(--font-sans);
  color: var(--color-text-primary);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* SELF, not descendant. The dir is stamped on this very span by syncThreadToc(), and the shell
   above it is pinned dir="ltr" forever - so `[dir="rtl"] .ttoc__t` has no ancestor to match and
   the label would quietly keep the Latin stack for Arabic. (L493's bare `[dir="rtl"]` does
   match, but it ties on specificity with .ttoc__t's own `font` shorthand and loses to it by
   source order, so it cannot be leaned on either.) */
.ttoc__t[dir="rtl"] { font-family: var(--font-arabic); }
@media (prefers-reduced-motion: reduce) {
  .ttoc__dot, .ttoc__lbl { transition: none; }
}


/* ══ FTL · the Agent's timeline step ═══════════════════════════════════════════
   The rail is a flex column BESIDE the text, never an absolutely-positioned line: this exact block
   is stamped dir="rtl" for an Arabic mission, and an inset-inline-start rail plus a translate is
   the one pairing that lands on the wrong edge there. Flex order follows dir for free — which is
   why .tc-rail, the only other rail in this file, is built the same way. */
.ftl { margin-block: 0 var(--space-4); }
.ftl[dir="rtl"] { font-family: var(--font-arabic); }
.ftl__e { display: flex; align-items: stretch; gap: 10px; }
.ftl__rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  align-self: stretch;
  flex: 0 0 auto;
  inline-size: 10px;
}
/* The dot sits on the date's optical line, not on the top of the box. */
.ftl__dot {
  flex: 0 0 auto;
  inline-size: 9px; block-size: 9px;
  margin-block-start: 5px;
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-accent-soft);
}
/* min-block-size so a one-line event still joins the next one instead of leaving a gap. */
.ftl__line {
  flex: 1 1 auto;
  inline-size: 1px;
  min-block-size: 14px;
  margin-block-start: 4px;
  background: var(--color-border-hairline);
}
.ftl__c { flex: 1 1 auto; min-inline-size: 0; padding-block-end: 14px; }
.ftl__e:last-child .ftl__c { padding-block-end: 0; }
.ftl__d {
  display: block;
  font-size: 11.5px;
  font-weight: var(--weight-bold);
  letter-spacing: .01em;
  color: var(--color-accent);
}
/* The date is stamped dir="ltr" so a range keeps its reading order, and that also makes its own
   `start` edge the LEFT one. Measured in an RTL card: the date landed at the column's left rim
   while its headline sat at the right rim, 300px apart, on every Arabic timeline. text-align is
   therefore set from the BLOCK's direction, not from the date's own. */
.ftl[dir="rtl"] .ftl__d { text-align: right; }
.ftl[dir="ltr"] .ftl__d { text-align: left; }
.ftl__t {
  margin-block-start: 2px;
  font-size: 14px;
  font-weight: var(--weight-semibold);
  line-height: 1.45;
  color: var(--color-text-primary);
}
.ftl__x {
  margin-block-start: 3px;
  font-size: 13px;
  line-height: 1.75;
  color: var(--color-text-secondary);
}
/* The tool-calls panel runs its whole detail pane at 11px. A timeline opened in there has to come
   down with it, or one step's output sets a type scale the fourteen around it do not. */
.tc-out .ftl__t { font-size: 12.5px; }
.tc-out .ftl__x { font-size: 11.5px; }

/* == FDC - the Agent's decision step ===========================================
   A framed card rather than loose prose, because the whole point of this kind is that it COMMITS:
   one question at the top, the options and the criteria side by side, one recommendation in a
   band of its own at the bottom. Loose in the flow it read as another paragraph and the verdict
   stopped looking like a verdict.

   The two columns wrap at 190px instead of collapsing at a breakpoint: this block renders inside
   the thread, inside a 340px UI 2.0 detail pane and inside a shared page, and a media query keyed
   to the VIEWPORT gets the narrow ones wrong in all three. */
.fdc{
  margin-block:0 var(--space-4);
  padding:14px 16px;
  border:var(--border-hairline) solid var(--color-border-hairline);
  border-radius:var(--radius-lg);
  background:var(--color-bg-subtle);
}
.fdc[dir="rtl"]{ font-family:var(--font-arabic) }
.fdc__k{
  display:block; margin-block-end:5px;
  font-size:10.5px; font-weight:var(--weight-bold); letter-spacing:.06em; text-transform:uppercase;
  color:var(--color-text-muted);
}
/* letter-spacing is the one property that takes an Arabic word apart: the letters are JOINED, and
   spacing them breaks every join into a separate glyph. Cleared for the whole RTL card, not just
   this label, so nothing added here inherits it by accident. */
.fdc[dir="rtl"] .fdc__k{ letter-spacing:0; text-transform:none }
.fdc__qt{ margin:0 0 13px; font-size:14.5px; font-weight:var(--weight-semibold); line-height:1.5; color:var(--color-text-primary) }
.fdc__cols{ display:flex; flex-wrap:wrap; gap:14px 22px; margin-block-end:13px }
.fdc__col{ flex:1 1 190px; min-inline-size:0 }
.fdc__l{ display:flex; flex-direction:column; gap:5px }
.fdc__i{ display:flex; align-items:flex-start; gap:7px; font-size:12.5px; line-height:1.55; color:var(--color-text-secondary) }
.fdc__i.is-pick{ color:var(--color-text-primary); font-weight:var(--weight-semibold) }
.fdc__dot{
  flex:0 0 auto; display:grid; place-items:center;
  inline-size:14px; block-size:14px; margin-block-start:3px;
  border-radius:50%; border:var(--border-hairline) solid var(--color-border-hairline);
  color:var(--color-on-accent);
}
.fdc__i.is-pick .fdc__dot{ background:var(--color-accent); border-color:var(--color-accent) }
.fdc__dot svg{ inline-size:9px; block-size:9px }
.fdc__it{ flex:1 1 auto; min-inline-size:0 }
.fdc__p{ padding-block-start:12px; border-block-start:var(--border-hairline) solid var(--color-border-hairline) }
.fdc__badge{
  display:inline-block; margin-block-end:5px;
  padding:2px 9px; border-radius:var(--radius-pill);
  background:var(--color-accent-soft); color:var(--color-accent);
  font-size:10.5px; font-weight:var(--weight-bold);
}
.fdc__pt{ margin:0; font-size:15px; font-weight:var(--weight-bold); line-height:1.4; color:var(--color-text-primary) }
.fdc__why{ margin:5px 0 0; font-size:12.5px; line-height:1.7; color:var(--color-text-secondary) }
/* The tool-calls panel runs its whole detail pane at 11px. A decision opened in there has to come
   down with it, or one step's output sets a type scale the fourteen around it do not. */
.tc-out .fdc{ padding:11px 12px }
.tc-out .fdc__qt{ font-size:12.5px }
.tc-out .fdc__pt{ font-size:13px }
.tc-out .fdc__i, .tc-out .fdc__why{ font-size:11.5px }

/* ══ FRV · the Agent's requirements ledger ═══════════════════════════════════════
   A flex ROW per requirement with the mark first — the same shape .ftl uses and for the same
   reason: this block is stamped dir="rtl" for an Arabic mission, flex order follows dir for free,
   and an absolutely-positioned mark would need an inset that lands on the wrong edge there. Every
   inset in the block is logical; there is not one `left` in it. */
.frv { margin-block: 0 var(--space-4); }
.frv[dir="rtl"] { font-family: var(--font-arabic); }
.frv__head {
  display: flex;
  align-items: baseline;
  gap: 7px;
  margin-block-end: 10px;
  padding-block-end: 8px;
  border-block-end: 1px solid var(--color-border-hairline);
}
.frv__score { font-size: 15px; font-weight: var(--weight-bold); color: var(--color-accent); }
.frv__lbl { font-size: 12px; color: var(--color-text-secondary); }
.frv__i { display: flex; align-items: flex-start; gap: 9px; padding-block: 7px; }
.frv__i + .frv__i { border-block-start: 1px solid var(--color-border-hairline); }
/* A fixed box so the marks stay in one column however long the requirements run. */
.frv__m {
  flex: 0 0 auto;
  inline-size: 16px;
  block-size: 16px;
  margin-block-start: 2px;
  color: var(--color-text-secondary);
}
.frv__m svg { display: block; inline-size: 100%; block-size: 100%; }
.frv__i.is-met .frv__m,     .frv__i.is-met .frv__t     { color: var(--color-success); }
.frv__i.is-miss .frv__m,    .frv__i.is-miss .frv__t    { color: var(--color-error); }
.frv__i.is-partial .frv__m, .frv__i.is-partial .frv__t { color: var(--color-accent); }
.frv__c { flex: 1 1 auto; min-inline-size: 0; }
.frv__q {
  font-size: 13.5px;
  font-weight: var(--weight-semibold);
  line-height: 1.5;
  color: var(--color-text-primary);
}
/* The state word rides at the END of the requirement instead of in a column of its own: a fourth
   column is what makes a table like this unreadable at 360px, and the word is two syllables. */
.frv__t {
  margin-inline-start: 7px;
  font-size: 10.5px;
  font-weight: var(--weight-bold);
  white-space: nowrap;
}
.frv__n {
  margin-block-start: 2px;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--color-text-secondary);
}
/* The fix is set off by a rail rather than by an arrow: an arrow has to be mirrored by hand for
   Arabic and a border-inline-start mirrors itself. */
.frv__f {
  margin-block-start: 4px;
  padding-inline-start: 9px;
  border-inline-start: 2px solid var(--color-accent-soft);
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--color-text-secondary);
}
/* The UI 2.0 tool-calls pane runs its whole detail at 11px; a ledger opened in there comes down
   with it, exactly as the timeline above does. */
.tc-out .frv__q { font-size: 12.5px; }
.tc-out .frv__n, .tc-out .frv__f { font-size: 11.5px; }


/* ══ FGL · the Agent's glossary step ═══════════════════════════════════════════
   One line per term, and the line IS the design: the term, an em dash, the definition. No grid and
   no two columns, because the two halves regularly disagree about direction — an Arabic glossary
   defining "HTTP" would want the term column on the right with that term's own text running left,
   and a column that flips per row is not a column. Inline, with each half isolated by its own dir
   attribute, the browser's bidi algorithm places them correctly for free.

   The faces hang off [dir] on the two halves rather than on .fgl, for the same reason they do in
   .askag: the term and the definition are frequently in different scripts from each other and from
   the block around them. */
.fgl { margin-block: 0 var(--space-4); }
.fgl[dir="rtl"] { font-family: var(--font-arabic); }
.fgl__r {
  margin-block-end: 7px;
  font-size: 13.5px; line-height: 1.75;
  color: var(--color-text-secondary);
}
.fgl__r:last-child { margin-block-end: 0; }
.fgl__t { font-weight: var(--weight-semibold); color: var(--color-text-primary); }
.fgl__t[dir="rtl"] { font-family: var(--font-arabic); }
.fgl__t[dir="ltr"] { font-family: var(--font-sans); }
.fgl__s { color: var(--color-text-muted); }
.fgl__d[dir="rtl"] { font-family: var(--font-arabic); }
.fgl__d[dir="ltr"] { font-family: var(--font-sans); }
/* The tool-calls panel runs its whole detail pane at 11px. A glossary opened in there has to come
   down with it, or one step's output sets a type scale the fifteen around it do not. */
.tc-out .fgl__r { font-size: 11.5px; }

/* == FBG - the Agent's budget step =============================================
   This lands INSIDE `.md`, unlike `.agent-tbl`, which the step card appends beside the rendered
   answer rather than into it. That is the whole difficulty: `.md table` sets `display: block` (so
   a wide markdown table scrolls itself) and `.md th, .md td` put a full border on every cell, and
   both reach this table. Both are undone below - `display: block` collapses the column layout and
   with it the sticky header, and the cell borders draw a second grid over the hairlines. This
   table brings its own scroll container instead.

   The figure carries a REAL dir (buildBudgetEl stamps it), so every logical property here resolves
   from the BLOCK and not from the fixed-LTR shell above it. */
.fbg { margin-block: 0 var(--space-4); border: 1px solid var(--color-border-hairline); border-radius: 10px; background: var(--color-bg-subtle); overflow: hidden; }
.fbg[dir="rtl"] { font-family: var(--font-arabic); }
.fbg__bar { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 10px; border-bottom: 1px solid var(--color-border-hairline); }
.fbg__sum { display: inline-flex; align-items: baseline; gap: 7px; min-inline-size: 0; }
.fbg__sumk { font: 600 11px var(--font-sans); color: var(--color-text-muted); }
.fbg__sumv { font: 700 13px var(--font-sans); color: var(--color-accent); font-variant-numeric: tabular-nums; }
.fbg__copy { flex: 0 0 auto; padding: 4px 12px; border: 1px solid var(--color-border-hairline); border-radius: 999px; cursor: pointer; background: var(--color-surface, transparent); color: var(--color-text-secondary); font: 600 11px var(--font-sans); transition: color var(--dur-fast, .15s) var(--ease-out, ease-out), border-color var(--dur-fast, .15s) var(--ease-out, ease-out); }
.fbg__copy:hover { color: var(--color-accent); border-color: color-mix(in srgb, var(--color-accent) 45%, transparent); }
.fbg__copy[dir="rtl"] { font-family: var(--font-arabic); }
.fbg__scroll { max-block-size: 420px; overflow: auto; }
.fbg table { display: table; inline-size: 100%; border-collapse: collapse; margin: 0; overflow: visible; font: 12.5px/1.55 var(--font-sans); }
/* A cell widens (and the container scrolls) rather than chopping a word in half. */
.fbg th, .fbg td { border: 0; border-bottom: 1px solid var(--color-border-hairline); padding: 6px 11px; text-align: start; overflow-wrap: break-word; word-break: normal; hyphens: none; }
/* The box-shadow IS the header rule, not decoration. Under `border-collapse: collapse` the border
   belongs to the TABLE and not to the cell, so it stays behind while the sticky cell travels: a
   scrolled grid loses its underline and merges into the rows sliding beneath it. An inset shadow
   is painted by the cell itself, so it comes along. (`.agent-tbl th` documents the same fix;
   restated because these two tables share no selector.) */
.fbg th { position: sticky; inset-block-start: 0; z-index: 1; background: var(--color-bg-subtle); color: var(--color-text-primary); font-weight: 700; white-space: nowrap; box-shadow: inset 0 -1px 0 var(--color-border-hairline); }
.fbg td { color: var(--color-text-secondary); }
.fbg tbody tr:last-child td { border-bottom: 0; }
/* Each cell carries its own dir, so each picks its own face. */
.fbg th[dir="rtl"], .fbg td[dir="rtl"] { font-family: var(--font-arabic); }
/* EXPLICIT `right`, never `end`. Every figure cell is stamped dir="ltr" so a grouped number keeps
   its reading order - which makes its own `end` the RIGHT edge. But the header above it is Arabic
   text with dir="rtl", whose `end` is the LEFT edge: written logically, the head of the cost column
   would sit at the far side of its own column from the digits it labels. So the column picks one
   physical edge and every cell in it uses that, which is also what lines the decimal points up. */
.fbg th.fbg__n, .fbg td.fbg__n { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
/* The total is not a row of the breakdown, and it must survive the scroll that hides it: pinned to
   the bottom of the same container the header is pinned to the top of. The rule above it is an
   inset shadow for the same reason the header's is. */
.fbg tfoot td { position: sticky; inset-block-end: 0; z-index: 1; background: var(--color-bg-subtle); color: var(--color-text-primary); font-weight: 700; border-bottom: 0; box-shadow: inset 0 1px 0 var(--color-border-hairline); }
/* The tool-calls panel runs its whole detail pane at 11px. A budget opened in there has to come
   down with it, or one step's output sets a type scale the fourteen around it do not. */
.tc-out .fbg table { font-size: 11.5px; }
.tc-out .fbg__scroll { max-block-size: 300px; }

/* ══ AGENT CHECKLIST ═════════════════════════════════════════════════════════
   A checklist step renders CONTROLS where every other step renders prose, so it borrows
   .agent-step__out's inset and frame and none of its markdown metrics. Inside the UI 2.0 detail
   panel the frame is dropped: that panel already IS the frame, and two nested wells read as a bug.
   The inset is LOGICAL rather than the physical pair .agent-step__out uses, so it needs no RTL
   companion rule and cannot be left behind by one. */
.agent-ck { margin-block: 8px 6px; margin-inline: 28px 0; padding: 10px 12px; border-inline-start: 2.5px solid color-mix(in srgb, var(--color-accent) 45%, transparent); background: var(--color-bg-subtle); border-radius: 8px; }
.tc-detail .agent-ck { margin: 0; padding: 0; border-inline-start: 0; background: none; min-inline-size: min(320px, 62vw); }
.agent-ck__head { display: flex; align-items: center; gap: 7px; margin-block-end: 7px; }
.agent-ck__n { font: var(--weight-bold) 11.5px var(--font-sans); color: var(--color-accent); font-variant-numeric: tabular-nums; }
.agent-ck__lbl { font: var(--weight-semibold) 11.5px var(--font-sans); color: var(--color-text-muted); }
/* dir is stamped on the ELEMENT here, not on an ancestor — the shell is LTR, so the usual
   `[dir="rtl"] .x` ancestor form would never match on these two. */
.agent-ck__lbl[dir="rtl"], .agent-ck__reset[dir="rtl"] { font-family: var(--font-arabic); }
.agent-ck__reset { margin-inline-start: auto; padding: 3px 10px; border: var(--border-hairline) solid var(--color-border-hairline); border-radius: var(--radius-pill); background: none; color: var(--color-text-muted); font: var(--weight-semibold) 11px var(--font-sans); cursor: pointer; }
.agent-ck__reset:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }
.agent-ck__reset[hidden] { display: none; }
.agent-ck__bar { block-size: 4px; border-radius: var(--radius-pill); background: color-mix(in srgb, var(--color-text-muted) 20%, transparent); overflow: hidden; margin-block-end: 9px; }
.agent-ck__bar i { display: block; block-size: 100%; border-radius: var(--radius-pill); background: var(--color-accent); transition: width .35s var(--ease-out); }
.agent-ck__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px; }
.agent-ck__it { margin: 0; }
/* The whole row is the label, so a thumb landing anywhere on the line ticks the box. */
.agent-ck__it > label { display: flex; align-items: flex-start; gap: 9px; padding: 6px 7px; border-radius: 7px; cursor: pointer; }
.agent-ck__it > label:hover { background: color-mix(in srgb, var(--color-accent) 9%, transparent); }
.agent-ck__in { flex: 0 0 auto; margin: 1px 0 0; inline-size: 16px; block-size: 16px; accent-color: var(--color-accent); cursor: pointer; }
.agent-ck__in:disabled { cursor: default; opacity: .6; }
.agent-ck__t { flex: 1 1 auto; min-inline-size: 0; font-size: 13px; line-height: 1.55; color: var(--color-text-primary); overflow-wrap: anywhere; }
.agent-ck__t[dir="rtl"] { font-family: var(--font-arabic); }
.agent-ck__it.is-on .agent-ck__t { color: var(--color-text-muted); text-decoration: line-through; text-decoration-thickness: 1px; }
.agent-ck__md { margin-block: 6px; font-size: 13px; }
.agent-ck__md > :first-child { margin-block-start: 0; }
.agent-ck__md > :last-child { margin-block-end: 0; }
.tc-detail .agent-ck__t, .tc-detail .agent-ck__md { font-size: 11.5px; }

/* ══ FOL · the Agent's outline step ══════════════════════════════════════════
   Tokens only, like every other drawn step. The indent IS the content here, so it is carried by
   a rail rather than by padding alone: three levels down, whitespace on its own stops telling
   you which parent you are under. */
.fol { margin-block: 0 var(--space-4); }
.fol[dir="rtl"] { font-family: var(--font-arabic); }
.fol__bar { display: flex; margin-block-end: 6px; }
.fol__all {
  padding: 3px 10px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: none; cursor: pointer;
  font: var(--weight-semibold) 11px var(--font-sans); color: var(--color-text-muted);
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.fol__all[dir="rtl"] { font-family: var(--font-arabic); }
.fol__all:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }
.fol__all:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fol__kids {
  margin-inline-start: 9px;
  padding-inline-start: 12px;
  border-inline-start: var(--border-hairline) solid var(--color-border-hairline);
}
.fol__sm, .fol__leaf { display: flex; align-items: baseline; gap: 7px; padding-block: 4px; }
/* Both markers go: Safari draws ::-webkit-details-marker and every other engine draws the
   list-item marker, and either one would sit beside our own chevron. */
.fol__sm { cursor: pointer; list-style: none; }
.fol__sm::-webkit-details-marker { display: none; }
.fol__sm:hover .fol__t { color: var(--color-accent); }
.fol__sm:focus-visible { outline: none; border-radius: var(--radius-sm); box-shadow: var(--shadow-focus); }
.fol__chev {
  flex: 0 0 auto; display: inline-flex; color: var(--color-text-muted);
  transition: transform var(--dur-fast) var(--ease-out);
}
.fol__chev svg { inline-size: 12px; block-size: 12px; }
/* The closed chevron points INTO the reading direction, so in Arabic it points left; open, it
   points down in both. Rotation, not scaleX, because a mirrored transform composes backwards
   with the open state and sends the arrow up. */
.fol[dir="rtl"] .fol__chev { transform: rotate(180deg); }
.fol__s[open] > .fol__sm .fol__chev,
.fol[dir="rtl"] .fol__s[open] > .fol__sm .fol__chev { transform: rotate(90deg); }
/* Tabular figures: down a column of section numbers, 1.10 must not sit narrower than 1.2. */
.fol__n {
  flex: 0 0 auto;
  font: var(--weight-bold) 11.5px var(--font-sans);
  color: var(--color-accent);
  font-variant-numeric: tabular-nums;
}
.fol__t { font-size: 13.5px; font-weight: var(--weight-semibold); color: var(--color-text-primary); overflow-wrap: anywhere; }
.fol__d { font-size: 12.5px; color: var(--color-text-muted); overflow-wrap: anywhere; }
/* A leaf has no chevron, so it is indented by one to sit under the headings above it. */
.fol__leaf { padding-inline-start: 19px; }
/* On a phone the one-line gloss stops being a gloss and becomes the wrap that hides the
   structure — so it drops under its heading instead of standing beside it. */
@media (max-width: 560px) {
  .fol__sm, .fol__leaf { flex-wrap: wrap; }
  .fol__d { flex-basis: 100%; margin-inline-start: 19px; }
}
/* Inside the UI 2.0 detail panel everything is one step down, like .tc-out already does. */
.tc-out .fol__t { font-size: 12px; }
.tc-out .fol__d, .tc-out .fol__n { font-size: 11px; }

/* ══ FMAP · the Agent's concept-map step ═════════════════════════════════════
   Tokens only, no literal colour — six themes in this file work by REDEFINING these tokens, so
   one hard-coded hex would be the single surface in the figure that ignores five of them. The
   SVG carries NO fills of its own for the same reason: everything is painted from here.

   The <text> direction is set HERE and not in buildMapEl, because SVG text has no dir attribute
   to be given one. `unicode-bidi: isolate` is what makes `direction` bite at all — with the
   default `normal` the property is ignored and an Arabic label ending in a bracket or a digit
   puts it on the wrong end. */
.fmap { margin-block: 0 var(--space-4); }
.fmap[dir="rtl"] { font-family: var(--font-arabic); }
.fmap[dir="rtl"] .fmap__svg text { direction: rtl; unicode-bidi: isolate; }
.fmap__ttl {
  margin-block-end: var(--space-2);
  font-size: var(--text-sm); font-weight: 600; color: var(--color-text-primary);
}
/* The figure scrolls inside its OWN box. A concept map has a natural width, and letting the
   column squeeze it is how the labels become unreadable rather than how they fit. */
.fmap__fig { overflow-x: auto; overflow-y: hidden; }
.fmap__svg { inline-size: 100%; block-size: auto; max-inline-size: 100%; }
.fmap__n rect {
  fill: var(--color-surface);
  stroke: var(--color-border-strong); stroke-width: 1;
}
.fmap__n text { font-family: inherit; font-size: 12.5px; fill: var(--color-text-primary); }
.fmap__n.is-c rect {
  fill: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface));
  stroke: var(--color-accent); stroke-width: 1.4;
}
.fmap__n.is-c text { font-size: 14px; font-weight: 600; fill: var(--color-accent-hover); }
.fmap__l path { fill: none; stroke: var(--color-border-strong); stroke-width: 1.2; }
/* A cross-link is a claim the map is making beyond "these belong to that", so it is drawn as a
   different KIND of line rather than a darker one — dashes survive a theme that flattens
   contrast, and a colour alone would carry the whole distinction. */
.fmap__l.is-x path { stroke: var(--color-accent); stroke-dasharray: 4 3; }
.fmap__arrow { fill: var(--color-border-strong); stroke: none; }
.fmap__l.is-x .fmap__arrow { fill: var(--color-accent); }
.fmap__lt {
  font-size: 10.5px; fill: var(--color-text-muted);
  /* A halo, so a relation word crossing its own line is still legible. paint-order draws the
     stroke first and the glyph over it; without it the line runs through the middle of the word. */
  paint-order: stroke; stroke: var(--color-bg-subtle); stroke-width: 3px; stroke-linejoin: round;
}
.fmap__key {
  margin-block-start: var(--space-3);
  display: grid; gap: 4px;
  font-size: var(--text-sm); line-height: var(--leading-sm);
}
.fmap__k { display: flex; gap: 6px; align-items: baseline; }
.fmap__kn { flex: 0 0 auto; font-weight: 600; color: var(--color-text-primary); }
.fmap__kx { min-inline-size: 0; color: var(--color-text-secondary); }
/* The tool-calls panel runs its whole detail pane at 11px. A map opened in there comes down with
   it, or one step's output sets a type scale the fourteen around it do not. */
.tc-out .fmap__ttl, .tc-out .fmap__key { font-size: 11.5px; }
@media (pointer: coarse), (max-width: 560px) {
  /* Scroll rather than shrink. Below about this width the ring's labels stop being words. */
  .fmap__svg { min-inline-size: 480px; max-inline-size: none; }
}

/* ══ FCD · the Agent's flashcard step ════════════════════════════════════════
   Tokens only, no literal colour. Six themes in this file work by REDEFINING these tokens, so a
   single hard-coded hex would be the one surface in the deck that ignores five of them. */
.fcd {
  /* Its own duration, NOT --dur-base: the flip's second half is scheduled by a setTimeout in
     flip(), and --dur-base is 160ms in the classic shell but 210ms under :root[data-ui="2"].
     Reading a token there would leave the timer firing 40ms early in UI 2 — the text swapping
     while the card is still visible, which reads as a stutter rather than a turn. */
  --fcd-turn: 170ms;
  margin-block: 0 var(--space-4);
  padding: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-bg-subtle);
}
.fcd[dir="rtl"] { font-family: var(--font-arabic); }
.fcd:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fcd__top { display: flex; align-items: center; gap: var(--space-2); margin-block-end: var(--space-2); }
.fcd__title {
  flex: 1 1 auto; min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  color: var(--color-text-secondary);
}
.fcd__count {
  flex: 0 0 auto;
  font-size: var(--text-xs); font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
}
.fcd__tool {
  flex: 0 0 auto;
  padding: 3px 8px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font-size: var(--text-xs); font-weight: var(--weight-medium);
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.fcd__tool:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }
.fcd__tool[aria-pressed="true"] { color: var(--color-accent); border-color: var(--color-accent); }
.fcd__tool:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The bar measures MASTERY, never position — see render(). */
.fcd__track {
  block-size: 3px; border-radius: var(--radius-pill);
  background: var(--color-border-hairline); overflow: hidden;
}
.fcd__fill {
  block-size: 100%; inline-size: 0;
  background: var(--color-accent);
  transition: inline-size var(--dur-slow) var(--ease-out);
}

/* The due strip. Tokens only, like everything else in this deck. It WRAPS rather than shrinks:
   on a phone the sentence and its button become two rows, never one row of clipped words. */
.fcd__srs {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-2);
  margin-block-start: var(--space-2);
}
.fcd__srsT {
  flex: 1 1 auto; min-inline-size: 0;
  font-size: var(--text-xs); line-height: 1.6;
  color: var(--color-text-muted);
}
/* The deck’s own pill, borrowed from .fcd__tool and lit, because this is the one control in the
   header that proposes work rather than rearranging what is already on screen. */
.fcd__srsB {
  flex: 0 0 auto;
  padding: 3px 10px;
  border: var(--border-hairline) solid var(--color-accent);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-accent);
  font-size: var(--text-xs); font-weight: var(--weight-medium);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.fcd__srsB:hover { background: var(--color-accent); color: var(--color-on-accent); }
.fcd__srsB:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.fcd__stage { perspective: 900px; margin-block-start: var(--space-3); }
.fcd__card {
  display: block; inline-size: 100%;
  min-block-size: 132px;
  padding: var(--space-4);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-xs);
  text-align: start;
  font: inherit; color: var(--color-text-primary);
  transition: transform var(--fcd-turn) var(--ease-inout),
              opacity var(--fcd-turn) linear,
              box-shadow var(--dur-fast) var(--ease-out);
}
.fcd__card:hover { box-shadow: var(--shadow-sm); }
.fcd__card:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* Faded out at the edge-on point of the turn. Both engines leave a lit 1px sliver at exactly
   90deg, and on a bordered card that sliver reads as a flash of the wrong colour between the
   two faces. */
.fcd--turning { opacity: 0; }
.fcd__side {
  display: block; margin-block-end: 6px;
  font-size: 10.5px; font-weight: var(--weight-bold);
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--color-accent);
}
/* Arabic is cursive: letter-spacing does not space an Arabic word, it BREAKS the joins inside it.
   The tracking above is for the Latin label only. */
.fcd[dir="rtl"] .fcd__side { letter-spacing: 0; text-transform: none; }
.fcd__txt { display: block; font-size: var(--text-md); line-height: 1.7; overflow-wrap: anywhere; }
.fcd__hint { display: block; margin-block-start: var(--space-2); font-size: var(--text-xs); color: var(--color-text-muted); }
/* Per FACE, not per deck. The deck-level rule above keys off the ROOT's dir, and a
   vocabulary deck is Arabic on one side and Latin on the other — buildCardsEl stamps each
   face separately, so the Arabic stack has to follow the same stamp .agent-ck__t does. */
.fcd__txt[dir="rtl"], .fcd__hint[dir="rtl"] { font-family: var(--font-arabic); }

.fcd__acts { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-block-start: var(--space-3); }
.fcd__btn {
  flex: 1 1 auto;
  padding: 8px var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.fcd__btn:hover { border-color: var(--color-border-strong); }
.fcd__btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fcd__btn--go { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-on-accent); }
.fcd__btn--go:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); }
.fcd__btn--ok { color: var(--color-success); border-color: var(--color-success); }
.fcd__btn--no { color: var(--color-text-secondary); }

.fcd__nav { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); margin-block-start: var(--space-2); }
.fcd__step {
  padding: 4px 10px; border-radius: var(--radius-sm);
  color: var(--color-text-muted); font-size: var(--text-xs);
  transition: color var(--dur-fast) var(--ease-out);
}
.fcd__step:hover:not([disabled]) { color: var(--color-text-primary); }
.fcd__step:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.fcd__btn[disabled], .fcd__step[disabled] { opacity: .38; cursor: default; }

.fcd__end { margin-block-start: var(--space-3); text-align: center; }
.fcd__endT { font-size: var(--text-md); font-weight: var(--weight-semibold); color: var(--color-text-primary); }
.fcd__endS { margin-block-start: 4px; font-size: var(--text-sm); color: var(--color-text-secondary); }
.fcd__endB { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-block-start: var(--space-3); }

/* The UA rule for [hidden] is display:none, and an author `display` beats it. Every element here
   that the deck hides is also given a display by the rules above, so without this line hiding
   them does nothing at all. */
.fcd__stage[hidden], .fcd__acts[hidden], .fcd__nav[hidden], .fcd__end[hidden],
.fcd__hint[hidden], .fcd__btn[hidden], .fcd__srs[hidden], .fcd__srsB[hidden] { display: none; }

/* The tool-calls detail pane runs its whole output at 11px. A deck opened in there has to come
   down with it, or one step's output sets a type scale the fifteen around it do not. */
.tc-out .fcd__txt { font-size: var(--text-sm); }
.tc-out .fcd__card { min-block-size: 104px; padding: var(--space-3); }

@media (max-width: 560px) {
  /* Matching the narrow-screen inset .agent-step__out already takes. */
  .agent-ck { margin-inline: 12px 0; padding-inline: 9px; }
  /* A 32px row is a miss on a thumb; the rest of this file holds 44. */
  .agent-ck__it > label { padding: 10px 8px; }
}

/* The flip itself is cancelled in JS via motionEnabled(); these keep the incidental transitions
   still. The motion attribute is read off :root as the LEFTMOST compound, because :root IS <html> —
   put any ancestor selector in front of it and the rule can never match anything. */
@media (prefers-reduced-motion: reduce) {
  .fcd__card, .fcd__fill, .fcd__tool, .fcd__btn, .fcd__step, .agent-ck__bar i { transition: none; }
}
:root[data-motion="off"] .fcd__card,
:root[data-motion="off"] .fcd__fill,
:root[data-motion="off"] .fcd__tool,
:root[data-motion="off"] .fcd__btn,
:root[data-motion="off"] .fcd__step,
:root[data-motion="off"] .agent-ck__bar i { transition: none; }

/* ═══════════════════════════════════════════════════════════════════════════════
   FIRAS CODE — BUILD/RUN NOTIFICATION DECK  (.cw-deck)

   Ported from the 21st.dev "Toast" model (#24297): the front card whole, the ones
   behind peeking and faded, the stack fanning open to real heights on hover. The
   original said all of that in Tailwind arbitrary-value classes; with no build step
   the same maths lives in two custom properties that cwDeckLayout writes —
   --cwd-i (depth) and --cwd-oy (this card's seat once the stack is open).

   Anchored top inline-end, not the model's bottom-right: the bottom of this
   workspace is the AI composer, the status bar and .toast/.cw-undo, and a stack
   landing on any of them is a collision, not a notification. inset-inline-end
   mirrors on its own under the Arabic UI — #codeWorkspace carries dir="rtl" —
   and nothing here translates on the X axis, which is the trap that would break it.

   Motion needs no reduced-motion rule of its own: :root[data-motion="off"] * and the
   prefers-reduced-motion block above already neutralise every transition, and the
   layout is transform-driven, so it lands correctly with the transitions off.
   ═══════════════════════════════════════════════════════════════════════════════ */
.cw-deck {
  position: absolute; inset-block-start: var(--cwd-top, 62px); inset-inline-end: 16px;
  inline-size: min(330px, calc(100% - 32px));
  z-index: 15;                 /* under .cw-diff (20) and .cw-pal (40) — a modal covers this, it does not fight it */
  pointer-events: none;        /* an empty column must never swallow a click aimed at the preview */
}
.cw-deck__card {
  position: absolute; inset-block-start: 0; inset-inline: 0;
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: 9px;
  padding: 10px 12px;
  block-size: var(--cwd-fh, auto);
  background: var(--color-surface);
  border: 1px solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  transform: translateY(calc(var(--cwd-i, 0) * 8px)) scale(calc(1 - var(--cwd-i, 0) * 0.04));
  transform-origin: top center;
  z-index: calc(40 - var(--cwd-i, 0));
  transition: transform .42s var(--ease-out), block-size .28s var(--ease-out), opacity .24s var(--ease-out);
}
.cw-deck.is-open .cw-deck__card { block-size: var(--cwd-h, auto); transform: translateY(var(--cwd-oy, 0px)); }
/* The fanned stack has real space between its cards, and a pointer crossing that space would
   read as leaving the deck and snap it shut under the cursor. Each card carries the gap below it. */
.cw-deck.is-open .cw-deck__card::after {
  content: ""; position: absolute; inset-inline: 0; inset-block-start: 100%; block-size: 11px;
}
.cw-deck__card[data-behind="1"] { pointer-events: none; }
.cw-deck__card[data-behind="1"] .cw-deck__body,
.cw-deck__card[data-behind="1"] .cw-deck__ic { opacity: 0; }
.cw-deck__card.is-out { opacity: 0; transform: translateY(-14px) scale(.97); pointer-events: none; }
.cw-deck__ic {
  flex: 0 0 auto; inline-size: 18px; text-align: center;
  font: 600 11px/1.65 var(--font-mono); color: var(--color-text-muted);
}
.cw-deck__card[data-kind="ok"]  .cw-deck__ic { color: var(--color-success); }
.cw-deck__card[data-kind="err"] .cw-deck__ic { color: var(--color-error); }
.cw-deck__card[data-kind="run"] .cw-deck__ic { color: var(--color-accent); }
.cw-deck__body { flex: 1 1 auto; min-inline-size: 0; transition: opacity .25s var(--ease-out); }
.cw-deck__t {
  display: flex; align-items: center; gap: 6px;
  font: 600 12.5px/1.45 var(--font-sans); color: var(--color-text-primary);
}
/* NOT `[dir="rtl"] .cw-deck__t`: <html> is pinned LTR for the whole shell, so the card's OWN
   stamped dir is the only thing that can be matched on here. */
.cw-deck__card[dir="rtl"] .cw-deck__t,
.cw-deck__card[dir="rtl"] .cw-deck__d,
.cw-deck__card[dir="rtl"] .cw-deck__act { font-family: var(--font-arabic); }
.cw-deck__card[dir="rtl"] .cw-deck__d--code { font-family: var(--font-mono); }
.cw-deck__n {
  flex: 0 0 auto; padding: 2px 6px; border-radius: var(--radius-pill);
  background: var(--color-bg-subtle); color: var(--color-text-muted);
  font: 600 10.5px/1.3 var(--font-mono);
}
.cw-deck__d {
  margin-block-start: 2px;
  font: 400 11.5px/1.5 var(--font-sans); color: var(--color-text-muted);
  overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.cw-deck__d--code { font-family: var(--font-mono); font-size: 11px; word-break: break-word; }
.cw-deck__act {
  margin-block-start: 8px; padding: 5px 11px;
  border: 1px solid var(--color-border-hairline); border-radius: var(--radius-pill);
  background: var(--color-bg-subtle); color: var(--color-text-primary);
  font: 600 11.5px var(--font-sans); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.cw-deck__act:hover { background: var(--color-accent-soft); border-color: var(--color-accent); color: var(--color-accent); }
.cw-deck__x {
  flex: 0 0 auto; inline-size: 20px; block-size: 20px; padding: 0;
  border: 0; border-radius: 6px; background: transparent; color: var(--color-text-muted);
  font: 400 15px/1 var(--font-sans); cursor: pointer;
  opacity: 0; transition: opacity .2s var(--ease-out);
}
.cw-deck.is-open .cw-deck__x { opacity: .5; }
.cw-deck__x:hover, .cw-deck__x:focus-visible { opacity: 1; }
/* cwDeckOk() refuses to mount below 640px, but a window DRAGGED narrow after a push would leave
   the cards sitting over the pane they describe. Same breakpoint, enforced twice. */
@media (max-width: 640px) { .cw-deck { display: none; } }

/* ═══ FIRAS CODE — EMPTY STATES FOR THE CONSOLE AND THE PREVIEW ══════════════
   Ported from 21st.dev "Empty State with Marquee" (shadcnui-blocks/empty-state-04, #19377).
   Their `mask-y-from-60%` / `mask-x-from-95%` are Tailwind v4 utilities; here it is two nested
   elements, one mask each, because mask-composite is the one part of this Safari still spells
   differently — and a composite keyword it cannot parse blanks the illustration rather than
   dropping the fade.

   TWO SKINS, ONE BLOCK. .cwe--prev sits on --color-surface and is pure tokens. .cwe--con sits
   inside .cw-console, whose background is #141412 in EVERY theme because it is a terminal —
   so its text mixes toward #ffffff exactly the way the .cw-conempty sentence already did, and
   a raw --color-text-primary there would be black on black in the light theme. */
.cwe {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  max-inline-size: 340px; margin-inline: auto; padding: 18px 16px 22px;
}
/* .cw-conempty already pads 16/16/20 around whatever it holds; without this the block starts
   34px down a pane that is often only 180px tall. */
.cw-conempty .cwe { padding: 2px 0 6px; }

.cwe__art {
  inline-size: 100%; block-size: 156px; overflow: hidden; margin-block-end: 14px;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 30%, #000 62%, transparent);
          mask-image: linear-gradient(to bottom, transparent, #000 30%, #000 62%, transparent);
}
/* Both edges, matching the original's `mask-x-from-95%` — the x-axis utility fades LEFT and RIGHT,
   it is not the one-sided `mask-r-*`. Keeping it symmetric is what lets the art stay put under
   Arabic: a single-sided fade is a reading-direction decision and would need a [dir] rule to
   mirror, and this is aria-hidden scenery with no reading order to mirror it by. */
.cwe__artx {
  block-size: 100%;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
}
.cwe__lane { display: flex; flex-direction: column; animation: cwe-lane 18s linear infinite; }
/* margin-block-end on the ROW, never `gap` on the lane. The loop is translateY(-50%), and a gap
   is applied n-1 times: half the lane's height would then land mid-row and the scroll would jump
   once per cycle. A uniform 40+8 unit makes the halves identical. */
.cwe__row {
  display: flex; align-items: center; gap: 10px; flex: 0 0 auto;
  block-size: 40px; margin-block-end: 8px; padding-inline: 12px; border-radius: 10px;
  border: 1px solid transparent;
}
.cwe__ic { flex: 0 0 auto; inline-size: 16px; block-size: 16px; border-radius: 5px; }
.cwe__bar { flex: 1 1 auto; block-size: 8px; border-radius: 999px; }
/* `ms-auto` in the original, and it is not cosmetic: a short bar hits its max-inline-size before
   flex-grow has spent the row's slack, and without this the trailing circle floats in the middle
   of the row instead of closing it. */
.cwe__dot { flex: 0 0 auto; margin-inline-start: auto; inline-size: 14px; block-size: 14px; border-radius: 50%; }
/* Rows of one length read as a loading skeleton; mixed lengths read as content. The period is
   SIX over twelve rows, so the top half is width-for-width identical to the bottom half — a
   3n/4n mix looks more varied standing still and then SNAPS at the wrap, because
   translateY(-50%) drops row 7 exactly where row 1 was.
   All four stay under ~80%: the percentage resolves against the ROW, but the bar only ever gets
   the row minus the chip, the circle and two gaps, so anything above that is silently the same
   as no cap at all — four rules that visibly did nothing. */
.cwe__row:nth-child(6n+1) .cwe__bar { max-inline-size: 78%; }
.cwe__row:nth-child(6n+3) .cwe__bar { max-inline-size: 52%; }
.cwe__row:nth-child(6n+4) .cwe__bar { max-inline-size: 36%; }
.cwe__row:nth-child(6n)   .cwe__bar { max-inline-size: 64%; }
@keyframes cwe-lane { from { transform: translateY(0); } to { transform: translateY(-50%); } }

.cwe--prev .cwe__row { background: var(--color-bg-subtle); border-color: var(--color-border-hairline); }
.cwe--prev .cwe__ic  { background: color-mix(in srgb, var(--color-accent) 26%, transparent); }
.cwe--prev .cwe__bar { background: color-mix(in srgb, var(--color-text-primary) 11%, transparent); }
.cwe--prev .cwe__dot { background: color-mix(in srgb, var(--color-text-primary) 8%, transparent); }
.cwe--con .cwe__row  { background: #ffffff08; border-color: #ffffff12; }
.cwe--con .cwe__ic   { background: color-mix(in srgb, var(--color-accent) 55%, #ffffff); opacity: .38; }
.cwe--con .cwe__bar  { background: #ffffff14; }
.cwe--con .cwe__dot  { background: #ffffff10; }

.cwe__t { font: 600 14.5px/1.45 var(--font-sans); color: var(--color-text-primary); }
.cwe__d { font: 400 12.5px/1.75 var(--font-sans); color: var(--color-text-muted); margin-block-start: 6px; }
.cwe--con .cwe__t { color: color-mix(in srgb, var(--color-text-muted) 18%, #ffffff); }
.cwe--con .cwe__d { color: color-mix(in srgb, var(--color-text-muted) 46%, #ffffff); }

.cwe__acts { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-block-start: 16px; }
.cwe__btn {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  padding: 8px 14px; border-radius: 9px; font: 600 12.5px/1 var(--font-sans);
  border: 1px solid var(--color-border-hairline); background: var(--color-surface); color: var(--color-text-primary);
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.cwe__btn:hover { background: var(--color-accent-soft); border-color: color-mix(in srgb, var(--color-accent) 32%, transparent); }
.cwe__btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* --color-on-accent, never #fff, on BOTH skins — including the one sitting on the terminal, whose
   ground is irrelevant here because the ink is read against the BUTTON's own fill. #fff is right
   in exactly one of the six themes. --color-accent is a light mint (#57AE9C), a pale blue
   (#5AA9E6) or amber (#D9A05B) in the other five, and white on those measures 2.29–2.64:1 —
   this label is 12.5px/600, so that is nowhere near AA. Each theme already ships the ink its
   accent was drawn to carry (#1F1E1D · #000 · #0A0E19 · #101012 · #1B1713): 5.18–9.15:1.
   The hover is the paired token for the same reason. The mixes that were here (86% toward #000,
   80% toward #ffffff) each move the fill the ONE direction white needs and the dark inks do not:
   measured, they take the hover state to 1.84–2.15:1. */
.cwe__btn--primary { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-on-accent); }
.cwe__btn--primary:hover { background: var(--color-accent-hover); border-color: transparent; }
.cwe--con .cwe__btn { background: #ffffff0d; border-color: #ffffff1f; color: #ffffffcc; }
.cwe--con .cwe__btn:hover { background: #ffffff1a; border-color: #ffffff2e; }
.cwe--con .cwe__btn--primary { background: var(--color-accent); border-color: transparent; color: var(--color-on-accent); }
.cwe--con .cwe__btn--primary:hover { background: var(--color-accent-hover); }
.cwe__btn-ico { display: inline-flex; inline-size: 14px; block-size: 14px; }
.cwe__btn-ico svg { inline-size: 100%; block-size: 100%; }

/* The overlay lives between the chrome and the stage as a normal flex child, so it inherits the
   pane's height instead of guessing at a chrome offset that wraps to two rows under 640px. */
.cwe-prev {
  flex: 1; min-block-size: 0; display: flex; align-items: center; justify-content: center;
  overflow: auto; background: var(--color-surface);
}
.cw-prevwrap.is-cwe .cw-prevstage,
.cw-prevwrap.is-cwe > .cw-preview { display: none; }

/* The `font:` shorthand above resets font-family, so the Arabic face has to be re-stated on the
   elements that carry prose. Descendant selectors: the dir lives on .cwe-prev / .cw-conempty. */
[dir="rtl"] .cwe__t,
[dir="rtl"] .cwe__d,
[dir="rtl"] .cwe__btn { font-family: var(--font-arabic); }

@media (max-width: 640px) { .cwe { padding: 12px 12px 16px; } }
@media (pointer: coarse) { .cwe__btn { min-block-size: 38px; padding: 10px 15px; } }
/* Scenery, and only scenery: it must never be the reason someone cannot read the sentence
   under it. :root is <html>, so the motion attribute is the LEFTMOST compound or it matches
   nothing. */
@media (prefers-reduced-motion: reduce) { .cwe__lane { animation: none; } }
:root[data-motion="off"] .cwe__lane { animation: none; }

/* ═══ FIRAS CODE — THE SHORTCUT SHEET ("?"), 21st.dev @tom_ui/kbd MODEL ═══════
   The card is deliberately the palette's sibling — same overlay, same radius, same 1px
   hairline rhythm — because they are the same 21st.dev family and the IDE should not look
   like it grew two unrelated dialogs.

   THE CAP is the port. The original stacks six shadows to mould a key: a gloss line along the
   top, a darker lip along the bottom, a soft inner shade under the gloss, a hairline ring and
   a drop, and it hard-codes rgba(0,0,0,…) for light and rgba(255,255,255,…) for dark. We have
   SIX themes, not two, so every layer that is really "a bit of the foreground" is mixed off
   --color-text-primary instead and comes out dark-on-light and light-on-dark by itself. Only
   the gloss is a genuine white highlight and cannot be derived that way, so it is the one
   value flipped per theme below — a seventh theme that forgets that line gets the light gloss,
   which is a cosmetic miss and nothing worse. */
.cw-kbd { position: absolute; inset: 0; z-index: 41; display: flex; align-items: flex-start; justify-content: center;
  padding: 10vh 18px 18px; padding-block-start: 10dvh;
  background: color-mix(in srgb, var(--color-bg) 45%, transparent); backdrop-filter: blur(4px);
  opacity: 0; transition: opacity .16s var(--ease-out); }
.cw-kbd.is-open { opacity: 1; }
.cw-kbd__card { width: min(94vw, 560px); max-height: 74vh; max-height: 74dvh; display: flex; flex-direction: column;
  background: var(--color-surface); border: 1px solid var(--color-border-strong); border-radius: 16px;
  box-shadow: 0 24px 70px rgba(0,0,0,.34); overflow: hidden;
  transform: translateY(-8px) scale(.98); transition: transform .18s var(--ease-out); }
.cw-kbd.is-open .cw-kbd__card { transform: none; }
.cw-kbd__card:focus-visible { outline: none; }

.cw-kbd__head { display: flex; align-items: flex-start; gap: 12px; padding: 15px 17px 12px;
  border-bottom: 1px solid var(--color-border-hairline); }
.cw-kbd__htx { flex: 1; min-width: 0; }
.cw-kbd__ttl { font: 600 14.5px var(--font-sans); color: var(--color-text-primary); }
.cw-kbd__sub { margin-block-start: 3px; font: 400 11.5px var(--font-sans); color: var(--color-text-muted); }
/* .cw-kbd carries a real dir, so these match in Arabic and cannot in English. */
[dir="rtl"] .cw-kbd__ttl, [dir="rtl"] .cw-kbd__sub { font-family: var(--font-arabic); }
.cw-kbd__x { flex: 0 0 auto; inline-size: 28px; block-size: 28px; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-border-hairline); border-radius: 8px; background: transparent;
  color: var(--color-text-muted); font-size: 11px; line-height: 1; cursor: pointer; }
.cw-kbd__x:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }

.cw-kbd__body { flex: 1; overflow-y: auto; padding: 4px 8px 10px; }
.cw-kbd__grp { padding: 11px 10px 4px; font: 600 10px var(--font-sans); letter-spacing: .09em;
  text-transform: uppercase; color: var(--color-text-muted); }
/* Arabic has no case to upper, and letter-spacing cuts the joins between the letters. */
[dir="rtl"] .cw-kbd__grp { font-family: var(--font-arabic); letter-spacing: 0; text-transform: none; }
.cw-kbd__row { display: flex; align-items: center; gap: 14px; padding: 6px 10px; border-radius: 9px; }
.cw-kbd__row:hover { background: var(--color-bg-subtle); }
.cw-kbd__lbl { flex: 1; min-width: 0; font: 400 13px var(--font-sans); color: var(--color-text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
[dir="rtl"] .cw-kbd__lbl { font-family: var(--font-arabic); }
/* The chord block is stamped dir="ltr" in JS; this only has to stop it shrinking. */
.cw-kbd__keys { flex: 0 0 auto; display: flex; align-items: center; gap: 5px; }
.cw-kbd__or { font: 400 11px var(--font-sans); color: var(--color-text-muted); }

.cw-kbd__cap { --cw-cap-gloss: rgba(255,255,255,.9);
  box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; gap: 2px;
  min-inline-size: 24px; block-size: 23px; padding: 0 7px; border-radius: 7px;
  background: var(--color-surface); color: var(--color-text-primary);
  font: 600 11px var(--font-mono); white-space: nowrap; user-select: none;
  transition: transform .09s var(--ease-out), box-shadow .09s var(--ease-out),
              background-color .09s var(--ease-out), color .09s var(--ease-out);
  box-shadow:
    inset 0 1px 0 var(--cw-cap-gloss),
    inset 0 -2px 0 color-mix(in srgb, var(--color-text-primary) 9%, transparent),
    inset 0 6px 9px color-mix(in srgb, var(--color-text-primary) 3%, transparent),
    0 0 0 1px color-mix(in srgb, var(--color-text-primary) 14%, transparent),
    0 2px 2px color-mix(in srgb, var(--color-text-primary) 10%, transparent); }
[data-theme="dark"] .cw-kbd__cap,
[data-theme="black"] .cw-kbd__cap,
[data-theme="midnight"] .cw-kbd__cap,
[data-theme="graphite"] .cw-kbd__cap,
[data-theme="amber"] .cw-kbd__cap { --cw-cap-gloss: rgba(255,255,255,.09); }
/* Held down: the cap travels exactly its own lip (2px), loses the drop it was floating on and
   takes the accent — the raised stack and this one are the original's two states, nothing
   more. Colour carries it as well as travel does, which is what keeps the press readable when
   motion is off and the transform lands instantly. */
.cw-kbd__cap.is-down { transform: translateY(2px);
  background: var(--color-accent-soft); color: var(--color-accent);
  box-shadow:
    inset 0 2px 4px color-mix(in srgb, var(--color-text-primary) 13%, transparent),
    0 0 0 1px var(--color-accent); }

.cw-kbd__foot { display: flex; align-items: center; gap: 8px; padding: 8px 15px;
  border-top: 1px solid var(--color-border-hairline); background: var(--color-bg-subtle);
  font: 400 11px var(--font-sans); color: var(--color-text-muted); }
[dir="rtl"] .cw-kbd__foot { font-family: var(--font-arabic); }
.cw-kbd__sp { flex: 1; }
/* The count is a number: it stays LTR and mono in Arabic like every other figure in the IDE. */
.cw-kbd__n { font: 600 11px var(--font-mono); color: var(--color-text-secondary); }
.cw-kbd__cap--sm { block-size: 19px; min-inline-size: 20px; padding: 0 5px; font-size: 10px; }

/* Never opens below 640px — cwKbdAvailable() returns false on a phone — so this is only the
   band between a phone and a laptop, where the IDE panes are already stacked. */
@media (max-width: 860px) {
  .cw-kbd { padding: 7vh 12px 12px; padding-block-start: 7dvh; }
  .cw-kbd__card { width: min(96vw, 520px); }
}

/* The travel goes; the accent stays, so a held chord is still visibly held. */
@media (prefers-reduced-motion: reduce) {
  .cw-kbd, .cw-kbd__card, .cw-kbd__cap { transition: none; }
}
:root[data-motion="off"] .cw-kbd,
:root[data-motion="off"] .cw-kbd__card,
:root[data-motion="off"] .cw-kbd__cap { transition: none; }

/* ── Firas Code — the build console ──────────────────────────────────────────
   Ported from the 21st.dev AI Task List (educalvolpz/ai-task-list, demo 23793): the box mark
   whose stroke carries the state, a check that draws itself, ONE level of indented children,
   finished rows going quiet, and a right-aligned note in tabular figures. The React original
   drives two of those with framer-motion; there is no React here and both are one keyframe.
   Its greens and reds were hardcoded oklch — these are the accent and error tokens, so the
   console themes with the other six themes instead of fighting them.

   No font-family is declared on the row text. The Arabic face arrives by INHERITANCE from the
   board, which is the only shape that lets .cw-task__name--path keep its monospace: a
   [dir="rtl"] .cw-task__name rule would score (0,2,0) against the path's (0,1,0) and no
   ordering could undo it — the exact bug the rail this replaces had to be repaired for. */
.cw-task { margin-block-start: 8px; display: flex; flex-direction: column; }
.cw-task[dir="rtl"] { font-family: var(--font-arabic); }
.cw-task__row {
  position: relative;
  display: flex; align-items: flex-start; gap: 8px;
  padding-block: 3px;
  min-inline-size: 0;
  transition: opacity .25s var(--ease-inout), transform .25s var(--ease-inout);
}
.cw-task__row--sub { padding-inline-start: 20px; }   /* the model's single level of nesting */
.cw-task__box {
  flex: 0 0 auto;
  inline-size: 14px; block-size: 14px;
  margin-block-start: 2px;
  color: var(--color-text-muted);
}
.cw-task__box svg { inline-size: 100%; block-size: 100%; display: block; }
.cw-task__body { flex: 1 1 auto; min-inline-size: 0; }
.cw-task__name {
  display: block;
  font-weight: var(--weight-semibold); font-size: 12px; line-height: 1.45;
  color: var(--color-text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cw-task__sub {
  display: block;
  font-size: 11px; line-height: 1.5;
  color: var(--color-text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A path is code: monospace, LTR, and isolated so surrounding Arabic cannot reorder it. */
.cw-task__name--path, .cw-task__sub--path {
  font-family: var(--font-mono); font-size: 11px;
  direction: ltr; unicode-bidi: isolate;
}
.cw-task__note {
  flex: 0 0 auto;
  font-family: var(--font-mono); font-size: 10.5px; line-height: 1.75;
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
  direction: ltr; unicode-bidi: isolate;
}
.cw-task__sub[hidden], .cw-task__note[hidden] { display: none; }

.cw-task__row[data-state="wait"] { opacity: .62; }
.cw-task__row[data-state="wait"] .cw-task__name { color: var(--color-text-muted); }
/* Finished rows settle a pixel and lose some contrast: they go quiet so whatever is running is
   the only thing asking for attention. It is also what keeps a thirty-row build readable. */
.cw-task__row[data-state="done"] { opacity: .66; transform: translateY(1px); }
.cw-task__row[data-state="done"] .cw-task__box,
.cw-task__row[data-state="run"] .cw-task__box { color: var(--color-accent); }
.cw-task__row[data-state="fail"] .cw-task__box,
.cw-task__row[data-state="fail"] .cw-task__name { color: var(--color-error); }
.cw-task__row[data-state="stop"] { opacity: .82; }
.cw-task__row[data-state="stop"] .cw-task__box { color: var(--color-text-muted); }

/* ONE moving thing on the card: a light travelling under the row the engine is on. The model
   puts it on every running row; files are written in two parallel lanes here, so that would
   have been two lights at once — and which step is live is the one question this answers. */
.cw-task__row.is-live::after {
  content: "";
  position: absolute; inset-inline: 0; inset-block-end: 0;
  block-size: 1px;
  background-image: linear-gradient(90deg, transparent 0%, var(--color-accent) 50%, transparent 100%);
  background-size: 55% 100%; background-repeat: no-repeat;
  opacity: .6;
  animation: cwTaskSweep 1.4s var(--ease-inout) infinite;
  pointer-events: none;
}
.cw-task[dir="rtl"] .cw-task__row.is-live::after { animation-direction: reverse; }
@keyframes cwTaskSweep { from { background-position-x: -10%; } to { background-position-x: 110%; } }
/* The tick draws itself. `both` holds the finished offset, so a row patched without a state
   change never re-runs it — and a row that never changes state is never remounted either. */
.cw-task__draw { animation: cwTaskDraw 220ms cubic-bezier(0.23, 1, 0.32, 1) both; }
@keyframes cwTaskDraw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@media (prefers-reduced-motion: reduce) {
  .cw-task__row { transition: none; }
  .cw-task__row[data-state="done"] { transform: none; }
  .cw-task__row.is-live::after { animation: none; opacity: .38; }
  .cw-task__draw { animation: none; stroke-dashoffset: 0; }
}
:root[data-motion="off"] .cw-task__row { transition: none; }
:root[data-motion="off"] .cw-task__row.is-live::after { animation: none; opacity: .38; }
:root[data-motion="off"] .cw-task__draw { animation: none; stroke-dashoffset: 0; }


/* The in-flight turn in Firas Code's chat tab. Deliberately the same bubble as a finished one,
   only dimmed and breathing — a different shape would read as a different kind of message. */
.cw-thread__bubble--live{opacity:.72;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.cw-thread__since{font-size:11px;opacity:.7;font-variant-numeric:tabular-nums}
.cw-thread__turn--live .cw-thread__bubble{animation:cwLivePulse 1.8s ease-in-out infinite}
@keyframes cwLivePulse{0%,100%{opacity:.62}50%{opacity:.9}}
@media (prefers-reduced-motion: reduce){
  .cw-thread__turn--live .cw-thread__bubble{animation:none;opacity:.75}
}

/* ══ AGENT PROCEDURE — the `steps` kind ══════════════════════════════════════
   Divs carrying list roles rather than an <ol>, for the reason the timeline block above records:
   this lands inside .md, where `.md ol` sets its own marker and inline padding and would print a
   second number beside the disc the block draws itself. */
.fsp { margin-block: 0 var(--space-4); }
.fsp[dir="rtl"] { font-family: var(--font-arabic); }

.fsp__need {
  margin-block-end: var(--space-3);
  padding: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-bg-subtle);
}
.fsp__h {
  font-size: 11.5px;
  font-weight: var(--weight-bold);
  letter-spacing: .01em;
  color: var(--color-accent);
}
.fsp__needs { display: flex; flex-wrap: wrap; gap: 6px; margin-block-start: 7px; }
.fsp__need-i {
  padding: 3px 9px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--color-text-primary);
}

.fsp__s { display: flex; align-items: flex-start; gap: 10px; padding-block-end: 12px; }
.fsp__s:last-child { padding-block-end: 0; }
/* tabular-nums so the discs of steps 9 and 10 are the same width — a numbered column whose
   numerals shift sideways reads as a list that lost its alignment. */
.fsp__n {
  flex: 0 0 auto;
  display: grid; place-items: center;
  inline-size: 22px; block-size: 22px;
  margin-block-start: 1px;
  border-radius: 50%;
  background: var(--color-accent-soft);
  color: var(--color-accent);
  font-size: 11.5px;
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
}
.fsp__c { flex: 1 1 auto; min-inline-size: 0; }
.fsp__t { font-size: 14px; font-weight: var(--weight-semibold); line-height: 1.5; color: var(--color-text-primary); }
.fsp__x { margin-block-start: 3px; font-size: 13px; line-height: 1.75; color: var(--color-text-secondary); }

/* The one line in the block that is not an instruction, so it is the one thing given a ground of
   its own: a reader skimming for "what must I not do" finds it without reading the rest. The rule
   is border-INLINE-start, so it lands on the reading edge in both directions with no [dir] rule. */
.fsp__w {
  display: flex; align-items: flex-start; flex-wrap: wrap; gap: 8px;
  margin-block-start: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border-inline-start: 3px solid var(--color-error);
  background: color-mix(in srgb, var(--color-error) 10%, transparent);
  font-size: 13px; line-height: 1.7; color: var(--color-text-primary);
}
.fsp__w-ic { flex: 0 0 auto; display: grid; place-items: center; margin-block-start: 1px; color: var(--color-error); }
.fsp__w-ic svg { inline-size: 16px; block-size: 16px; }
.fsp__w-l { flex: 0 0 auto; color: var(--color-error); font-weight: var(--weight-bold); }
.fsp__w > span:last-child { flex: 1 1 140px; min-inline-size: 0; }

/* The tool-calls detail pane runs its whole output at 11px. A procedure opened in there has to
   come down with it, or one step's output sets a type scale the fifteen around it do not. */
.tc-out .fsp__t { font-size: 12.5px; }
.tc-out .fsp__x, .tc-out .fsp__w { font-size: 11.5px; }
.tc-out .fsp__need-i { font-size: 11.5px; }

/* ══ FOCUS READING ═══════════════════════════════════════════════════════════
   The shell out of the way and one answer on the screen. Everything here keys off `is-focusread`
   on <body> (put there by focusReadEnter) and `is-focusread-on` on the one turn being read (put
   there by focusReadApply, and re-stamped after every thread rebuild).

   The exit control is deliberately OUTSIDE the breakpoint below: it is the way back, and if a
   window is dragged narrow before the matchMedia guard fires it must still be sitting there. */
.focusread-bar {
  position: fixed;
  inset-block-start: 14px;
  inset-inline-end: 18px;
  z-index: var(--z-dropdown);
}
.focusread-x {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 12px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  box-shadow: var(--shadow-md);
  color: var(--color-text-secondary);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  cursor: pointer;
}
.focusread-x:hover { color: var(--color-text-primary); }
.focusread-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.focusread-x kbd {
  padding: 1px 5px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: 4px;
  background: var(--color-bg-subtle);
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--color-text-secondary);
}

@media (min-width: 768.02px) {
  /* Every column at once, and the GRID with them. A display:none child does NOT give its track
     back: hiding the rail alone would auto-place the thread into the rail's 76px and leave the
     reader a column the width of an icon. */
  body.is-focusread .rail,
  body.is-focusread .sidebar,
  body.is-focusread .inspector,
  body.is-focusread .topbar,
  body.is-focusread .ttoc,
  body.is-focusread .pinmsg-bar,
  body.is-focusread .composer-wrap,
  /* THE SAME LIST AGAIN, UNDER THE UI 2.0 PREFIX, and it is not belt-and-braces. `:root[data-ui="2"]
     .rail` and `:root[data-ui="2"] .inspector` are both (0,3,0) and a plain `body.is-focusread
     .rail` is only (0,2,1) — b beats c, so the short list alone left BOTH of them standing. With
     .app already collapsed to one track that was worse than not collapsing it: measured in the
     browser at 1440px, the rail was auto-placed as a ROW and became a 1440x28 band across the top,
     the thread started 28px lower and ran 28px past the bottom of the screen, and the inspector
     landed at y=928 — off the fold entirely. Same reason the .app rules below are written twice. */
  :root[data-ui="2"] body.is-focusread .rail,
  :root[data-ui="2"] body.is-focusread .sidebar,
  :root[data-ui="2"] body.is-focusread .inspector,
  :root[data-ui="2"] body.is-focusread .topbar,
  :root[data-ui="2"] body.is-focusread .ttoc,
  :root[data-ui="2"] body.is-focusread .pinmsg-bar,
  :root[data-ui="2"] body.is-focusread .composer-wrap { display: none; }

  /* Specificity, not source order: the UI 2.0 rules that set this grid carry :root[data-ui="2"]
     plus two classes, and a plain `body.x .app` loses to them however late it is written. */
  body.is-focusread .app,
  :root[data-ui="2"] body.is-focusread .app,
  :root[data-ui="2"] body.is-focusread .app.has-inspector { grid-template-columns: 1fr; }

  /* Only the answer being read. Never inferred with :has() or nth-child — the mark is put on by
     the same function that decides what is focused, so the two can never disagree. */
  body.is-focusread .chat-inner > .turn:not(.is-focusread-on) { display: none; }
  /* And the turn's own chrome. With exactly one answer on the screen, a line naming who wrote it
     and a row of six buttons under it answer questions nobody has while reading. */
  body.is-focusread .is-focusread-on .msg-ai__head,
  body.is-focusread .is-focusread-on .msg-actions { display: none; }

  /* The composer is gone, so the room reserved for it is a screen of empty scroll under the last
     line, and the top bar's height is a gap over the first one. */
  body.is-focusread .chat-scroll { padding-block-end: var(--space-12); }
  /* A little wider than the standard measure: with nothing beside it the column can afford the
     extra characters, and 744px centred in a 1600px window reads as a receipt. */
  body.is-focusread .chat-inner { max-inline-size: 820px; padding-block-start: var(--space-10); }
}

/* ══ LISTENING TO THE WHOLE THREAD ═══════════════════════════════════════════
   The player for the reading queue (readQueueStart). Fixed and centred at the TOP, and that is
   the whole of the reasoning: it has to sit still for ten minutes, the bottom of the screen is
   already the composer's, and the toast lane is bottom-centre — a listener gets the "voice quota
   spent" toast mid-reading, and it must not arrive underneath the control that produced it.

   NOT `[dir="rtl"] .rq-bar`. The bar is appended to <body> and <html> is pinned dir="ltr" for the
   whole shell, so an ancestor-direction selector can never match it; rqBarBuild stamps the real
   direction on the element and everything here is written in logical properties so the row
   mirrors with it. */
.rq-bar {
  position: fixed;
  inset-block-start: calc(var(--topbar-h) + 12px);
  inset-inline: 0; margin-inline: auto;
  inline-size: max-content; max-inline-size: 92vw;
  z-index: var(--z-dropdown);
  display: flex; align-items: center; gap: var(--space-2);
  padding-block: 5px; padding-inline: var(--space-2) var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  box-shadow: var(--shadow-lg);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}
.rq-nav, .rq-play, .rq-x {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 28px; block-size: 28px;
  border: 0; border-radius: var(--radius-pill);
  background: transparent; color: inherit;
  font-family: inherit; font-size: 15px; line-height: 1;
  cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.rq-nav:hover, .rq-play:hover, .rq-x:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.rq-nav:focus-visible, .rq-play:focus-visible, .rq-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* An arrow with nothing behind it stays in the row rather than disappearing from it: the three
   transport buttons must not change places when the queue reaches either end. */
.rq-nav:disabled { opacity: 0.32; cursor: default; background: transparent; }
.rq-play { color: var(--color-accent); }
.rq-play svg { inline-size: 16px; block-size: 16px; }
.rq-x svg { inline-size: 14px; block-size: 14px; }
/* Who is speaking, capped: a name is one or two words in both languages, and this row must not
   grow a line because one of them was translated long. */
.rq-who {
  max-inline-size: 10ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--color-text-primary); font-weight: var(--weight-medium);
}
.rq-pos {
  font-family: var(--font-mono); font-size: 11.5px;
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;   /* the count must not twitch as it walks past 9 */
}

/* THE TURN BEING READ. A tint and a halo of the same colour, never an inline-start rule: the
   thread runs one way in Arabic and the other in English, and an edge marker would change which
   edge it lived on with the answer's own direction. The scroll-margin is what makes
   `scrollIntoView({ block: "start" })` stop the first line BELOW the topbar and the player
   instead of behind them. */
.turn.is-readq-on {
  scroll-margin-block-start: calc(var(--topbar-h) + 56px);
  border-radius: var(--radius-lg);
  background: var(--color-bg-subtle);
  box-shadow: 0 0 0 8px var(--color-bg-subtle);
  transition: background var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .rq-nav, .rq-play, .rq-x { transition: none; }
  .turn.is-readq-on { transition: none; }
}

/* ----------------------------------------------------------------------------
   CHAPTERS INSIDE ONE ANSWER
   The strip is sticky against #chatScroll, but its containing block is
   `.msg-ai__body` — which is exactly what keeps it to its own answer: a sticky
   element cannot travel past the end of the box it sits in, so it arrives with
   the answer and leaves with it.
---------------------------------------------------------------------------- */
.chp-bar {
  position: sticky; inset-block-start: 0;
  /* Below the pinned-messages strip (z-index 4). That one is a child of the
     scroller itself and has the stronger claim on the top edge when a thread
     has both. */
  z-index: 3;
  display: flex; align-items: center; gap: var(--space-2);
  margin-block-end: var(--space-5);
  padding-block: 7px;
  /* OPAQUE, not a blur. backdrop-filter would make this element a containing
     block and trap its own stacking context — the trap the sheets layer already
     paid for once, and the pinned strip documents two sections down. */
  background: var(--color-bg);
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
  /* A drag that starts on the strip must not pull a chapter title into a
     select-to-ask quote — the chip would offer to quote the interface back to
     Firas as its own earlier words. */
  user-select: none;
}
.chp-bar__t {
  flex: 1 1 auto; min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: var(--weight-medium);
  color: var(--color-text-primary);
}
.chp-bar__t[dir="rtl"] { font-family: var(--font-arabic); }
/* tabular-nums so the counter does not jog sideways as the chapter changes. */
.chp-bar__n {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  unicode-bidi: isolate;
  color: var(--color-text-muted);
}
.chp-bar__nav {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 26px; block-size: 26px;
  padding: 0; border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
}
.chp-bar__nav:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
/* The chevrons point the way the READER travels, and that reverses with the
   answer's direction. Mirrored off the strip's own dir rather than the shell's,
   because the shell is pinned LTR and an Arabic answer can sit inside an
   English session. */
[dir="rtl"] .chp-bar__nav svg { transform: scaleX(-1); }

/* The chapter break itself: a rule above the heading, never a box around the
   section. The headings are still direct children of `.md` and must stay that
   way — the measure and rhythm rules that style this answer are all
   `.msg-ai__body > .md > …`. */
.md > .chp-h {
  border-block-start: var(--border-hairline) solid var(--color-border-hairline);
  padding-block-start: var(--space-5);
}
/* No rule above the first chapter: it would land a hairline directly under the
   strip's own border and read as a double line. */
.md > .chp-h:first-child { border-block-start: none; padding-block-start: 0; }

/* An answer that is still arriving has no chapters yet — the headings the strip
   indexes are still being written, and a strip renaming itself every paint is
   noise on the one turn the reader is already watching. Same gate `.qreply`
   uses, for the same reason. */
.msg-ai.is-generating .chp-bar { display: none; }
/* A phone shows about a third of the lines a desktop does, so a permanently
   parked strip would be a standing tax on the screen for a landmark that is
   never more than a flick away. The chapter rules stay — those cost nothing. */
@media (max-width: 768px) { .chp-bar { display: none; } }

/* ----------------------------------------------------------------------------
   A DERIVATION, DRAWN  (the Agent's `formula` step)
   Rows of expression + what changed + why. The rail of step numbers is what
   makes it a derivation rather than a stack of equations: it is the thing the
   reader points at when they say which step lost them.
---------------------------------------------------------------------------- */
.fml { margin: var(--space-5) 0; display: flex; flex-direction: column; gap: var(--space-4); }
.fml__r { display: flex; gap: var(--space-3); align-items: flex-start; }
.fml__n {
  flex: 0 0 auto;
  min-inline-size: 22px;
  padding-block-start: 3px;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-text-muted);
  text-align: center;
  /* A numeral standing alone reorders against an RTL base; the element carries
     dir="ltr" in the DOM and this keeps it isolated from the row around it. */
  unicode-bidi: isolate;
}
.fml__c { flex: 1 1 auto; min-inline-size: 0; }
/* The equation gets its OWN scroller. A long derivation line is exactly the
   content that overruns a narrow column, and the page itself must never scroll
   sideways because of it — the same treatment `.md .katex-display` already has. */
.fml__m {
  overflow-x: auto; overflow-y: hidden;
  padding-block: 2px;
  unicode-bidi: isolate;
}
.fml__w {
  margin-block-start: 5px;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}
.fml__y {
  margin-block-start: 3px;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
[dir="rtl"] .fml__w, [dir="rtl"] .fml__y { font-family: var(--font-arabic); line-height: 1.85; }


/* ===============================================================================
   CONTINUE WHERE YOU LEFT OFF  .  THE AGENT'S PRE-MORTEM CARD
   ===============================================================================

   Both are built out of the existing token scale only, so the six themes and the UI 2.0 skin
   (which redefines the radius and duration tokens) carry them without a second rule. */

/* -- the resume strip, at the top of the sidebar -- */
.rsm__head { padding-block-start: var(--space-2); }
.rsm__row {
  display: flex; flex-direction: column; gap: 3px;
  inline-size: 100%;
  padding: var(--space-2) var(--space-3);
  margin-block-end: 2px;
  border-radius: var(--radius-sm);
  text-align: start;
  color: var(--color-text-secondary);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.rsm__row:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.rsm__row:focus-visible {
  outline: none;
  background: var(--color-bg-subtle);
  box-shadow: 0 0 0 2px var(--color-accent-ring);
}
.rsm__top { display: flex; align-items: baseline; gap: var(--space-2); min-inline-size: 0; }
.rsm__title {
  flex: 1 1 auto; min-inline-size: 0;
  font-size: var(--text-xs); line-height: var(--leading-xs);
  font-weight: var(--weight-medium);
  color: var(--color-text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rsm__who {
  flex: 0 0 auto;
  font-size: 10px; line-height: var(--leading-xs);
  color: var(--color-text-muted);
}
/* Two lines, never more. This is a POINTER back into a conversation, not the conversation, and
   three of them have to stay above the list they are a shortcut into. */
.rsm__last {
  font-size: var(--text-xs); line-height: 17px;
  color: var(--color-text-muted);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}
/* THE PHONE CASE LIVES HERE, NOT IN JS. renderHistory does not run on resize, so a width read
   at paint time would leave the strip up after the rotation that turned the sidebar into a
   drawer — and a drawer you opened on purpose should give you the whole list, not a shortcut
   into three of it. 768px is where .sidebar becomes that drawer. */
@media (max-width: 768px) {
  .rsm__head, .rsm__row { display: none; }
}

/* -- the pre-mortem card -- */
.frk { display: flex; flex-direction: column; gap: var(--space-2); margin-block: var(--space-4); }
/* The likelihood is the row's LEADING EDGE rather than a tint behind it: a red panel reads as an
   error the reader has to go and fix, and every row here is a thing that has not happened. */
.frk__r {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-strong);
  border-inline-start-width: 3px;
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.frk__r.is-high { border-inline-start-color: var(--color-error); }
.frk__r.is-medium { border-inline-start-color: var(--color-accent); }
.frk__r.is-low { border-inline-start-color: var(--color-text-muted); }
.frk__lvl {
  flex: 0 0 auto;
  padding-block-start: 2px;
  font-size: var(--text-xs); line-height: var(--leading-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-text-muted);
}
/* CAPS AND TRACKING ONLY ON THE LATIN SIDE. Arabic letters join, and letter-spacing pulls the
   joins apart into a row of loose glyphs — "مرتفع" spaced out is not emphasis, it is broken
   type. buildRisksEl stamps a real dir on .frk, which is the only reason this can be asked. */
.frk[dir="ltr"] .frk__lvl { text-transform: uppercase; letter-spacing: var(--tracking-wide); }
.frk__r.is-high .frk__lvl { color: var(--color-error); }
.frk__c { flex: 1 1 auto; min-inline-size: 0; }
.frk__t {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-primary);
}
.frk__x { margin-block-start: 3px; font-size: var(--text-sm); color: var(--color-text-secondary); }
.frk__s {
  margin-block-start: var(--space-2);
  font-size: var(--text-xs); line-height: var(--leading-sm);
  color: var(--color-text-muted);
}
.frk__sl {
  margin-inline-end: 6px;
  font-weight: var(--weight-semibold);
  color: var(--color-text-secondary);
}
/* Narrow: the likelihood word stops being a column and becomes a first line of its own, so the
   risk itself keeps the full width instead of wrapping after three words. */
@media (max-width: 560px) {
  .frk__r { flex-direction: column; gap: var(--space-1); }
  .frk__lvl { padding-block-start: 0; }
}

/* ══ SCR · the conversation's scratchpad ═══════════════════════════════════════
   Docked to the right edge of .main — physically, see the note on the dock below — and given its
   own width, which the COLUMN then makes
   room for: .chat-inner and .composer are both `margin-inline: auto` inside boxes this pads, so
   one declaration re-centres the reading column in what is left. An overlay was the other option
   and it is the wrong one — a pad opened in order to keep reading must not sit on the sentence
   being read.

   Below 1024px it is not drawn at all. app.js never builds it there; the media query at the end
   of this block is the backstop for the frame between a drag-resize and the next sync. The
   reading column plus a 320px pad needs a real desktop, and on a phone there is no gutter to
   dock to. */
.main { --scratch-w: min(320px, 30vw); }
.scr {
  box-sizing: border-box;
  position: absolute;
  /* Measured by scratchSync off the real .topbar, which grows when the tier strip wraps. The
     62px is the value for the one frame before that measurement lands. */
  top: var(--scr-top, 62px);
  bottom: 0;
  /* PHYSICAL right, and the border below is a physical border-left, for the reason .ttoc gives
     one screen down: the inset properties resolve against the ELEMENT'S OWN direction, and this
     element stamps dir="rtl" the moment the interface is Arabic. Measured, not assumed — with
     dir="rtl" on the pad, `inset-inline-end: 0` computes to `left: 0` and `border-inline-start`
     computes to border-RIGHT, so the Arabic build docked the pad on the far side of .main, on
     top of the very sentence it was opened to keep readable, while the column gave up its room
     on the empty side. The shell is pinned LTR and this pad has exactly one home edge, so it is
     named outright. */
  right: 0;
  inline-size: var(--scratch-w);
  /* Over the composer and over the question rail (4), under every menu (--z-dropdown, 300) and
     under the topbar's own band (--z-sticky, 100). */
  z-index: 90;
  display: flex; flex-direction: column;
  gap: 8px;
  padding: 12px 14px max(14px, env(safe-area-inset-bottom));
  border-left: var(--border-hairline) solid var(--color-border);
  background: var(--color-surface);
}
.scr[dir="rtl"] { font-family: var(--font-arabic); }
.scr__h { display: flex; align-items: center; gap: 8px; }
.scr__t {
  flex: 1 1 auto; min-inline-size: 0;
  font: 700 10.5px var(--font-sans);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--color-text-muted);
}
/* Arabic has no case to upper and no letters to space out — tracked-out Arabic is just broken
   Arabic — so the label drops both and takes the size back instead. Same trade .jump-panel__h
   makes one screen up. */
.scr[dir="rtl"] .scr__t {
  font-family: var(--font-arabic);
  letter-spacing: 0; text-transform: none; font-size: 12px;
}
.scr__n {
  flex: 0 0 auto;
  font: 600 10.5px var(--font-mono);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
.scr__x {
  flex: 0 0 auto;
  inline-size: 24px; block-size: 24px;
  display: grid; place-items: center;
  padding: 0; border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--color-text-muted);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.scr__x:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.scr__x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.scr__x svg { inline-size: 14px; block-size: 14px; }
.scr__ta {
  flex: 1 1 auto;
  /* min-block-size:0 or the textarea's own intrinsic height wins the flex negotiation and pushes
     the footer off the bottom of the pad. */
  min-block-size: 0;
  inline-size: 100%;
  box-sizing: border-box;
  resize: none;
  padding: 10px 12px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-sm);
  background: var(--color-bg-subtle);
  color: var(--color-text-primary);
  font: 400 13.5px/1.75 var(--font-sans);
}
.scr__ta[dir="rtl"] { font-family: var(--font-arabic); }
.scr__ta::placeholder { color: var(--color-text-muted); }
.scr__ta:focus { outline: none; border-color: var(--color-border-strong); box-shadow: var(--shadow-focus); }
.scr__f { display: flex; align-items: center; gap: 8px; }
.scr__p {
  flex: 1 1 auto; min-inline-size: 0;
  font: 400 11px/1.5 var(--font-sans);
  color: var(--color-text-muted);
}
.scr[dir="rtl"] .scr__p { font-family: var(--font-arabic); }
.scr__s {
  flex: 0 0 auto;
  font: 600 10.5px var(--font-sans);
  color: var(--color-accent);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.scr[dir="rtl"] .scr__s { font-family: var(--font-arabic); }
.scr__s.is-on { opacity: 1; }
.scr__c {
  flex: 0 0 auto;
  padding: 3px 8px; border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--color-text-muted);
  font: 600 11px var(--font-sans);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.scr[dir="rtl"] .scr__c { font-family: var(--font-arabic); }
.scr__c:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.scr__c:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.scr-btn.is-on { color: var(--color-accent); }
/* The room the pad takes, given up by the two boxes that centre their own contents. Physical,
   like the dock above and for the same measured reason — these two must give up the SAME edge
   the pad is standing on, and a logical property here is one stray dir on an ancestor away from
   padding the opposite side and leaving the pad on top of the text. */
.main.is-scratch-open .chat-scroll { padding-right: var(--scratch-w); }
.main.is-scratch-open .composer-wrap { padding-right: calc(var(--space-6) + var(--scratch-w)); }
/* The question rail stands in this same gutter, and it is positioned in PHYSICAL px on purpose —
   <html> is pinned dir="ltr" and .ttoc says so in its own comment. So it moves by the pad's
   width, in the same physical property, rather than through a logical inset that would flip
   under the first Arabic block to stamp a dir on an ancestor. Moved and not hidden: taking the
   reader's map away at the exact moment they are writing notes about it is the opposite of what
   this is for. */
.main.is-scratch-open .ttoc { right: calc(var(--scratch-w) + 10px); }
/* UI 2.0 owns this edge with its inspector, and applyUi2 does NOT re-run renderAll — it rebuilds
   the rail, the inspector and the history list and stops. So the switch flipped with a pad open
   left that pad standing under the inspector until the next render. Decided in CSS rather than by
   adding a call into applyUi2: the guard then holds for every future entry point too, and zeroing
   the width in the same breath hands the column its room back instead of leaving it padded around
   a panel that is no longer drawn. */
:root[data-ui="2"] .scr, :root[data-ui="2"] .scr-btn { display: none; }
:root[data-ui="2"] .main { --scratch-w: 0px; }
/* Same zeroing on a narrow window: app.js tears the pad down on the matchMedia crossing, but a
   browser with no matchMedia never gets that callback, and the class would then sit there holding
   a 320px gutter open on a phone with nothing in it. */
@media (max-width: 1023px) {
  .scr, .scr-btn { display: none; }
  .main { --scratch-w: 0px; }
}
@media (prefers-reduced-motion: reduce) {
  .scr__s, .scr__x, .scr__c { transition: none; }
}

/* ══ FSR · the Agent's sources step ════════════════════════════════════════════
   A bibliography answers "where did this come from"; it cannot answer "what did THIS one give
   you", which is the question a reader has when they are deciding whether to believe a sentence.
   So the claim sits beside its source, set off by a rule on its start edge — the shape a
   pull-quote uses, because that is what it is: the one thing this link was used to say.

   Same rail decision as .ftl. The number is a flex child BESIDE the text, never an absolute
   inset: this block is stamped dir="rtl" for an Arabic mission, and an inset-inline-start marker
   plus a translate is the one pairing that lands on the wrong edge there. Flex order follows dir
   for free. */
.fsr {
  margin-block: 0 var(--space-4);
  display: flex; flex-direction: column; gap: 13px;
}
.fsr[dir="rtl"] { font-family: var(--font-arabic); }
.fsr__e { display: flex; align-items: flex-start; gap: 10px; }
.fsr__n {
  flex: 0 0 auto;
  inline-size: 20px; block-size: 20px;
  margin-block-start: 1px;
  display: grid; place-items: center;
  border-radius: 6px;
  background: var(--color-accent-soft);
  font: 600 10.5px var(--font-mono);
  color: var(--color-accent);
  font-variant-numeric: tabular-nums;
}
.fsr__c { flex: 1 1 auto; min-inline-size: 0; }
.fsr__t {
  display: inline-block;
  font-size: 13.5px; font-weight: var(--weight-semibold); line-height: 1.45;
  color: var(--color-text-primary);
  text-decoration: none;
  border-block-end: var(--border-hairline) solid transparent;
}
.fsr__t:hover { color: var(--color-accent); border-block-end-color: currentColor; }
.fsr__t:focus-visible { outline: none; border-radius: 4px; box-shadow: var(--shadow-focus); }
/* overflow-wrap:anywhere, because a long path has no spaces in it and one URL is enough to push
   the whole card into a horizontal scroll inside the thread column. */
.fsr__u {
  display: block;
  margin-block-start: 1px;
  font: 400 11px var(--font-mono);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}
.fsr__k {
  margin-block-start: 6px;
  padding-inline-start: 9px;
  border-inline-start: 2px solid var(--color-border);
}
.fsr__l {
  display: block;
  font: 700 9.5px var(--font-sans);
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--color-text-muted);
}
.fsr[dir="rtl"] .fsr__l {
  font-family: var(--font-arabic);
  letter-spacing: 0; text-transform: none; font-size: 11px;
}
.fsr__x {
  font-size: 12.5px; line-height: 1.7;
  color: var(--color-text-secondary);
}
/* The tool-calls panel runs its whole detail pane at 11px. A source list opened in there comes
   down with it, exactly as the timeline does, or one step sets a type scale the fourteen around
   it do not. */
.tc-out .fsr__t { font-size: 12.5px; }
.tc-out .fsr__x { font-size: 11.5px; }
.tc-out .fsr__u { font-size: 10.5px; }


/* ═══════════════════════════════════════════════════════════════════════════════
   CTG · COLOUR TAGS ON CONVERSATION ROWS  (.chat-item__tag / __tagbtn / __sw)
   ═══════════════════════════════════════════════════════════════════════════════
   The hues are the ONE thing in this feature that cannot come from a theme token — the tag's
   whole meaning IS its colour, so a red that turns green under the parchment theme has stopped
   being a tag. They arrive inline from CTG_COLORS, which is also what paints the swatches and
   the filter chips, so the three can never drift apart. Everything AROUND them is tokens.

   Logical properties throughout, so nothing here needs an RTL mirror: the row frame stays LTR
   in both languages (chatItemEl explains why) and only the text inside it carries a direction. */
.chat-item__tag {
  flex: 0 0 auto;
  inline-size: 3px; block-size: 15px;
  border-radius: var(--radius-pill);
}
.chat-item__tagbtn.is-on { opacity: 1; }
/* The row is a fixed 36px — 40 under UI 2.0 — and centres its children on one line, so opening
   the swatches has to let it grow. `:root` is carried on the front ONLY to out-weigh
   `:root[data-ui="2"] .chat-item`, which sets that height: a bare class selector loses to it
   and the swatch row is clipped to a sliver. :root always matches, so UI 1 gets it too. */
:root .chat-item.is-tagging {
  block-size: auto;
  flex-wrap: wrap;
  padding-block: 5px;
  background: var(--color-bg-subtle);
}
.chat-item__tags {
  flex: 1 0 100%;
  display: flex; align-items: center; gap: 6px;
  padding-block-start: 7px;
}
.chat-item__sw {
  inline-size: 20px; block-size: 20px; flex: 0 0 auto;
  border-radius: 50%;
  border: var(--border-hairline) solid color-mix(in srgb, var(--color-line) 40%, transparent);
  transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.chat-item__sw:hover { transform: scale(1.12); }
.chat-item__sw:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* The ring is drawn in the SURFACE colour first and the text colour second, so the marker reads
   on a red swatch and on a violet one alike — a single dark ring disappears into half the
   palette, which is the state the user is trying to see. */
.chat-item__sw.is-on {
  box-shadow: 0 0 0 2px var(--color-bg-subtle), 0 0 0 3.5px var(--color-text-primary);
}
/* "No colour" is a hollow crossed ring, never a seventh hue: a grey circle in a row of colours
   reads as a colour you cannot name rather than as the way out of all of them. */
.chat-item__sw--off {
  display: grid; place-items: center;
  background: transparent; color: var(--color-text-muted);
}
.chat-item__sw--off svg { inline-size: 12px; block-size: 12px; }

/* THE FILTER BAR. Never rendered until a conversation actually carries a colour — see ctgBar. */
.hist-tagbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  /* The group-label class brings the padding and the type; these two undo the parts of it that
     are wrong beside a row of controls. Arabic is cursive — letter-spacing does not space an
     Arabic word, it breaks the joins inside it. */
  text-transform: none; letter-spacing: 0;
}
/* UI 2.0 gives .history__group-label `justify-content: space-between` so a date heading can push
   its count to the far end. Borrowing that class for a row of controls inherits it too, and it
   spreads the six chips evenly across the whole sidebar instead of grouping them beside their
   label. Its selector is (0,3,0), so a bare class loses; matched here and won on source order,
   with the 8px gap that rule also sets pulled back to the 6px the chips are drawn for. */
:root .hist-tagbar,
:root[data-ui="2"] .hist-tagbar { justify-content: flex-start; gap: 6px; }
.hist-tagbar__lbl { margin-inline-end: 2px; }
.hist-tagbar__all {
  padding: 1px 8px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  color: var(--color-text-muted);
  font-size: var(--text-xs); font-weight: var(--weight-medium);
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.hist-tagbar__all:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }
.hist-tagbar__all.is-on { color: var(--color-accent); border-color: var(--color-accent); }
.hist-tagbar__c {
  inline-size: 15px; block-size: 15px; flex: 0 0 auto;
  border-radius: 50%;
  border: var(--border-hairline) solid color-mix(in srgb, var(--color-line) 40%, transparent);
  transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.hist-tagbar__c:hover { transform: scale(1.14); }
.hist-tagbar__c:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.hist-tagbar__c.is-on { box-shadow: 0 0 0 2px var(--color-bg), 0 0 0 3.5px var(--color-text-primary); }

@media (max-width: 768px) {
  /* A 20px circle is a miss on a thumb; the rest of this file holds 44 where it can, and the
     swatch row is the one control here that a finger has to hit exactly. */
  .chat-item__sw { inline-size: 26px; block-size: 26px; }
  .chat-item__sw--off svg { inline-size: 14px; block-size: 14px; }
}

/* ═══════════════════════════════════════════════════════════════════════════════
   QZ · the Agent's quiz step
   ═══════════════════════════════════════════════════════════════════════════════
   Tokens only, no literal colour — six themes in this file work by REDEFINING these tokens, so
   a single hard-coded hex would be the one surface in the quiz that ignores five of them. */
.qz {
  margin-block: 0 var(--space-4);
  padding: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-bg-subtle);
}
.qz[dir="rtl"] { font-family: var(--font-arabic); }
.qz__top { display: flex; align-items: center; gap: var(--space-2); margin-block-end: var(--space-2); }
.qz__title {
  flex: 1 1 auto; min-inline-size: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  color: var(--color-text-secondary);
}
.qz__count {
  flex: 0 0 auto;
  font-size: var(--text-xs); font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
}
.qz__tool {
  flex: 0 0 auto;
  padding: 3px 8px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font-size: var(--text-xs); font-weight: var(--weight-medium);
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.qz__tool:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }
.qz__tool:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.qz__track {
  block-size: 3px; border-radius: var(--radius-pill);
  background: var(--color-border-hairline); overflow: hidden;
}
.qz__fill {
  block-size: 100%; inline-size: 0;
  background: var(--color-accent);
  transition: inline-size var(--dur-slow) var(--ease-out), background var(--dur-base) var(--ease-out);
}
/* Once the sheet is marked the bar stops reporting how much is filled in and reports the SCORE,
   so it changes what it is made of too — an accent bar at 40% and a success bar at 40% are not
   the same claim. */
.qz.is-marked .qz__fill { background: var(--color-success); }

.qz__list { list-style: none; margin: var(--space-3) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.qz__q {
  padding: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
/* The verdict is carried on the border, not on a fill: a whole card washed red is a scolding,
   and half of them will be red on a first attempt at anything worth testing. */
.qz__q.is-right { border-color: color-mix(in srgb, var(--color-success) 55%, var(--color-border-hairline)); }
.qz__q.is-wrong { border-color: color-mix(in srgb, var(--color-error) 55%, var(--color-border-hairline)); }
.qz__head { display: flex; align-items: baseline; gap: var(--space-2); }
.qz__n {
  flex: 0 0 auto;
  font-size: var(--text-xs); font-variant-numeric: tabular-nums;
  font-weight: var(--weight-bold); color: var(--color-accent);
}
.qz__ask {
  flex: 1 1 auto; min-inline-size: 0;
  font-size: var(--text-sm); line-height: 1.65;
  color: var(--color-text-primary); overflow-wrap: anywhere;
}
/* Per QUESTION and per CHOICE, not once for the quiz. A vocabulary or chemistry test is half
   Latin inside an Arabic mission — the same stamp .agent-ck__t and .fcd__txt carry. */
.qz__ask[dir="rtl"], .qz__ot[dir="rtl"], .qz__key[dir="rtl"], .qz__why[dir="rtl"] { font-family: var(--font-arabic); }

.qz__opts { display: flex; flex-direction: column; gap: 2px; margin-block-start: var(--space-2); }
/* The whole row is the label, so a thumb landing anywhere on the line picks that choice. */
.qz__opt {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 7px 8px;
  border: var(--border-hairline) solid transparent;
  border-radius: 8px;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.qz__opt:hover { background: color-mix(in srgb, var(--color-accent) 8%, transparent); }
.qz:not(.is-marked) .qz__opt:has(.qz__in:checked) { border-color: var(--color-accent); }
.qz__in {
  flex: 0 0 auto; margin: 2px 0 0;
  inline-size: 15px; block-size: 15px;
  accent-color: var(--color-accent); cursor: pointer;
}
.qz__in:disabled { cursor: default; }
.qz__ob {
  flex: 0 0 auto;
  min-inline-size: 1.1em;
  font-size: var(--text-xs); font-weight: var(--weight-bold);
  color: var(--color-text-muted);
}
.qz__ot { flex: 1 1 auto; min-inline-size: 0; font-size: 13px; line-height: 1.55; color: var(--color-text-primary); overflow-wrap: anywhere; }
.qz__mine {
  flex: 0 0 auto;
  padding: 1px 6px; border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-error) 16%, transparent);
  color: var(--color-error);
  font-size: 10.5px; font-weight: var(--weight-medium);
}
.qz__opt.is-key { background: color-mix(in srgb, var(--color-success) 12%, transparent); border-color: color-mix(in srgb, var(--color-success) 45%, transparent); }
.qz__opt.is-key .qz__ob { color: var(--color-success); }
.qz__opt.is-bad .qz__ob { color: var(--color-error); }
/* The marked sheet still hovers under the pointer without this, which offers an interaction
   that no longer exists — every radio on it is disabled. */
.qz.is-marked .qz__opt { cursor: default; }
.qz.is-marked .qz__opt:hover { background: none; }

.qz__ex { margin-block-start: var(--space-2); padding-block-start: var(--space-2); border-block-start: var(--border-hairline) solid var(--color-border-hairline); }
.qz__verdict { font-size: var(--text-xs); font-weight: var(--weight-semibold); color: var(--color-text-muted); }
.qz__q.is-right .qz__verdict { color: var(--color-success); }
.qz__q.is-wrong .qz__verdict { color: var(--color-error); }
.qz__key, .qz__why { margin-block-start: 5px; font-size: 12.5px; line-height: 1.6; color: var(--color-text-secondary); overflow-wrap: anywhere; }

.qz__foot { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); margin-block-start: var(--space-3); }
.qz__note { font-size: var(--text-xs); color: var(--color-text-muted); }
.qz__btn {
  padding: 8px var(--space-4);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.qz__btn:hover { border-color: var(--color-border-strong); }
.qz__btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.qz__btn--go { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-on-accent); }
/* Disabled and still legible. The button is waiting for the reader to finish the sheet, and a
   control faded to 40% reads as one that is broken rather than one that is waiting. */
.qz__btn:disabled { opacity: .55; cursor: default; }
.qz__btn--go:disabled:hover { border-color: var(--color-accent); }

.qz__end { margin-block-start: var(--space-3); text-align: center; }
.qz__endT { font-size: var(--text-xs); font-weight: var(--weight-semibold); letter-spacing: .06em; text-transform: uppercase; color: var(--color-accent); }
/* Arabic is cursive: the tracking above is for the Latin label only. */
.qz[dir="rtl"] .qz__endT { letter-spacing: 0; text-transform: none; }
.qz__endS { margin-block-start: 4px; font-size: var(--text-md); color: var(--color-text-primary); }
.qz__endB { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2); margin-block-start: var(--space-3); }

/* The UA rule for [hidden] is display:none, and an author `display` beats it. Every element
   here that the quiz hides is also given a display by the rules above, so without this line
   hiding them does nothing at all — and the answer key would be on screen from the start,
   which is the one failure this card cannot have. */
.qz__ex[hidden], .qz__end[hidden], .qz__foot[hidden], .qz__mine[hidden],
.qz__key[hidden], .qz__why[hidden], .qz__note[hidden], .qz__tool[hidden] { display: none; }

/* The tool-calls detail pane runs its whole output at 11px. A quiz opened in there has to come
   down with it, or one step's output sets a type scale the fifteen around it do not. */
.tc-out .qz__ask, .tc-out .qz__ot { font-size: 11.5px; }
.tc-out .qz__q { padding: var(--space-2); }

@media (max-width: 560px) {
  /* Matching the narrow-screen inset .agent-step__out already takes. */
  .qz { padding: var(--space-2); }
  .qz__opt { padding: 10px 8px; }
  .qz__btn { flex: 1 1 auto; }
}

@media (prefers-reduced-motion: reduce) {
  .qz__fill, .qz__opt, .qz__btn, .qz__tool, .chat-item__sw, .hist-tagbar__c { transition: none; }
}
/* The motion attribute is read off :root as the LEFTMOST compound, because :root IS <html> —
   put any ancestor selector in front of it and the rule can never match anything. */
:root[data-motion="off"] .qz__fill,
:root[data-motion="off"] .qz__opt,
:root[data-motion="off"] .qz__btn,
:root[data-motion="off"] .qz__tool,
:root[data-motion="off"] .chat-item__sw,
:root[data-motion="off"] .hist-tagbar__c { transition: none; }


/* ═══ THE ATTACHED PASSAGE PILL ═════════════════════════════════════════════════
   Sits above the message box like an attachment, because that is what it is. */
.quote-tray{display:flex;flex-wrap:wrap;gap:6px;padding:0 2px 6px}
.quote-pill{
  display:inline-flex;align-items:center;gap:7px;max-width:100%;
  padding:5px 8px 5px 10px;border-radius:9px;
  background:var(--color-bg-subtle);border:1px solid var(--color-border-hairline);
  color:var(--color-text-secondary);font-size:12.5px;line-height:1.4;
}
/* The arrow is the whole signal that this is a reply to something, so it never shrinks away
   when the passage is long. */
.quote-pill__ic{flex:0 0 auto;display:grid;place-items:center;color:var(--color-accent);opacity:.9}
.quote-pill__name{
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  color:var(--color-text-primary);
}
.quote-pill__x{
  flex:0 0 auto;width:16px;height:16px;line-height:16px;text-align:center;
  border:none;background:transparent;border-radius:5px;cursor:pointer;
  color:var(--color-text-muted);font-size:10px;padding:0;
  transition:background .12s ease,color .12s ease;
}
.quote-pill__x:hover{background:var(--color-border-hairline);color:var(--color-text-primary)}
.quote-pill__x:focus-visible{outline:2px solid var(--color-accent-ring);outline-offset:1px}

/* ── A STACK OF THEM ──────────────────────────────────────────────────────────
   The tray already wraps, so a stack needs two things it did not: a ceiling on how much of the
   screen it may take before the message box does, and a way out that is not six separate ✕.
   7rem is the three rows a full stack of six actually occupies at this composer's width — the
   overflow behind it is the safety valve for a narrower one, not the normal case. */
.quote-tray{max-block-size:7rem;overflow-y:auto;align-items:center}
/* The pill's own padding was physical (`5px 8px 5px 10px`), which was correct while nothing
   above it was ever RTL. The tray now takes the interface language, so the wider side has to
   be the side the icon is on in BOTH directions — identical to the old values under LTR. */
.quote-pill{min-inline-size:0;padding-block:5px;padding-inline:10px 8px}
/* A ceiling per pill as well as on the tray: one 64-character passage beside five others would
   otherwise take the whole first row and push the rest onto a line of their own. NEVER on a
   lone pill — that is the one-slot fallback a phone and UI 2.0 get, where there is no row to
   share and capping it would show LESS of the passage than shipped, on the narrow screen that
   can least afford it. `:only-child` is exact rather than approximate here: the remove-all
   button is appended past one pill only, so a single pill is always the tray's only child. */
.quote-pill:not(:only-child) .quote-pill__name{max-inline-size:26ch}
.quote-tray__all{
  flex:0 0 auto;padding:5px 8px;border-radius:8px;
  border:1px solid transparent;background:transparent;cursor:pointer;
  color:var(--color-text-muted);font-family:inherit;font-size:12px;line-height:1.4;
  transition:background .12s ease,color .12s ease;
}
.quote-tray__all:hover{background:var(--color-bg-subtle);color:var(--color-text-primary)}
.quote-tray__all:focus-visible{outline:2px solid var(--color-accent-ring);outline-offset:1px}
/* Only the button, never the tray: the tray holds pills whose text carries its own dir, and an
   Arabic face set up there would land on an English passage sitting inside an Arabic stack. */
.quote-tray[dir="rtl"] .quote-tray__all{font-family:var(--font-arabic,var(--font-sans))}
@media (pointer: coarse){ .quote-tray__all{padding-block:8px} }
@media (prefers-reduced-motion: reduce){ .quote-tray__all{transition:none} }
/* The motion attribute is read off :root as the LEFTMOST compound, because :root IS <html> —
   put any ancestor selector in front of it and the rule can never match anything. */
:root[data-motion="off"] .quote-tray__all{transition:none}


/* ══ READ IT BIGGER ══════════════════════════════════════════
   One answer, alone, on a page of its own. Built by bigTextOpen, which clones the rendered
   .msg-ai__body into .bigtext__page — so every .md rule in this stylesheet already applies and
   the only thing left to say here is how big and how far apart.

   OPAQUE, ON THE THEME'S OWN BACKGROUND. Not a dimmed backdrop over the thread: a scrim says
   "you are looking at a thing on top of your page", and this IS the page while it is open.

   THE SIZING RULES ARE WRITTEN OFF THE ID, and that is specificity, not laziness. Reading
   comfort ships `:root[data-reading] .msg-ai__body[dir="rtl"]` at (0,4,0); a class-scoped copy
   here would lose to it for anyone who has ever touched that panel, and this page would open at
   the thread's size. #firasBigText is (1,0,0) and settles it in both directions. */
.bigtext {
  position: fixed; inset: 0;
  /* Above the settings/annotation sheets (1400) and everything on the token scale, and below
     the attached-image viewer at 9999 — which is the one surface that may legitimately cover
     this one. */
  z-index: 1420;
  display: flex; flex-direction: column;
  background: var(--color-bg);
  animation: bigtextIn var(--dur-fast, .15s) var(--ease-out, ease-out);
}
.bigtext:focus { outline: none; }        /* it is focused to place the reader, not to be pointed at */

.bigtext__bar {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--space-2);
  padding: 10px 14px;
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-surface);
}
.bigtext__sp { flex: 1 1 auto; }
.bigtext__b,
.bigtext__x {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 12px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  cursor: pointer;
}
.bigtext__b:hover:not([disabled]),
.bigtext__x:hover { color: var(--color-text-primary); }
.bigtext__b:focus-visible,
.bigtext__x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.bigtext__b { min-inline-size: 44px; justify-content: center; font-weight: var(--weight-semibold); }
/* Dimmed rather than removed at the ends: a control that vanishes makes the row jump under the
   cursor that was aiming at it. */
.bigtext__b[disabled] { opacity: .38; cursor: default; }
.bigtext__x kbd {
  padding: 1px 5px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: 4px;
  background: var(--color-bg-subtle);
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--color-text-secondary);
}

.bigtext__scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  /* The thread is still behind this page. Without containment, scrolling past the last line
     would carry on scrolling the conversation underneath it. */
  overscroll-behavior: contain;
  padding: var(--space-8) var(--space-5) var(--space-12);
}
/* A shade wider than the 820px Focus settles on, and nowhere near the window: at the top of the
   step table a full-width line is a head-turn per line, which is the opposite of the errand. */
.bigtext__page { max-inline-size: 860px; margin-inline: auto; }

#firasBigText .msg-ai__body {
  font-size: calc(var(--text-lg) * var(--bigtext-z, 1.45));
  /* The leading is scaled WITH the size and then opened a little further: the whole point of a
     bigger line is the space around it, and --leading-lg alone would give back a wall. */
  line-height: calc(var(--leading-lg) * var(--bigtext-z, 1.45) * 1.06);
}
/* RESTATED FOR ARABIC, and this line is why the rules are on the id. `.msg-ai__body[dir="rtl"]`
   pins Arabic leading to a flat unitless 1.9, and diacritics plus stacked letterforms need MORE
   room as the type grows, not the same ratio. */
#firasBigText .msg-ai__body[dir="rtl"] { line-height: 2; }

/* Headings, tables and code carry absolute tokens of their own — inheriting the body size does
   not reach them, so each is scaled by the same factor and the page keeps its proportions. */
#firasBigText .md h1,
#firasBigText .md h2 { font-size: calc(var(--text-2xl) * var(--bigtext-z, 1.45)); line-height: 1.3; }
#firasBigText .md h3 { font-size: calc(var(--text-xl) * var(--bigtext-z, 1.45)); line-height: 1.35; }
#firasBigText .md table,
#firasBigText .md :not(pre) > code,
#firasBigText .code-block pre,
#firasBigText .code-block code { font-size: calc(var(--text-sm) * var(--bigtext-z, 1.45)); }

@keyframes bigtextIn { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .bigtext { animation: none; } }
/* The motion attribute is read off :root as the LEFTMOST compound, because :root IS <html> —
   put any ancestor selector in front of it and the rule can never match anything. */
:root[data-motion="off"] .bigtext { animation: none; }

/* ══ ONE SENTENCE AT A TIME ══════════════════════
   The page paceOpen builds: the same cloned .msg-ai__body Bigger puts on the screen, with every
   sentence in it wrapped in a .pace__s and hidden until the reader reaches it. Every .md rule in
   this stylesheet already applies to that clone, so the only things left to say here are what a
   step that has not been reached looks like (nothing) and what the current one looks like.

   THE HIDING RULES ARE WRITTEN OFF THE ID, and that is specificity, not laziness. `display` for a
   <pre>, a <table>, an <li> or a figure is set by rules carrying their own class chains, and a
   class-scoped `display: none` here would lose to some of them and leave a code block standing in
   the middle of a page the reader has not got to yet. #firasPace is (1,1,0) and settles it. */
.pace {
  position: fixed; inset: 0;
  /* The same shelf Bigger sits on: above the settings and annotation sheets (1400) and everything
     on the token scale, below the attached-image viewer at 9999. The two can never be open at
     once — each one covers the action row the other is opened from. */
  z-index: 1420;
  display: flex; flex-direction: column;
  background: var(--color-bg);
  animation: paceIn var(--dur-fast, .15s) var(--ease-out, ease-out);
}
.pace:focus { outline: none; }        /* it is focused to place the reader, not to be pointed at */
/* THE END OF THE PAGE IS REPORTED BY A TOAST, and `--z-toast` is 500 — under this overlay's 1420,
   behind an opaque ground. "That was the last sentence" was being drawn where nobody could ever
   read it, which is the one moment this mode has to explain itself: the player stops, the play
   button goes dim, and without the line nothing says why. Lifted only while this page is up, the
   same way the archive grid lifts it, and still under the attached-image viewer at 9999. An
   engine without :has() drops the rule and is no worse off than it was. */
body:has(#firasPace) #toast { z-index: 1425; }

.pace__bar {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--space-2);
  padding: 10px 14px;
  background: var(--color-surface);
}
.pace__grp { display: inline-flex; align-items: center; gap: 4px; }
.pace__sp { flex: 1 1 auto; }
.pace__b,
.pace__x {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 12px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  cursor: pointer;
}
.pace__b { min-inline-size: 40px; justify-content: center; font-weight: var(--weight-semibold); }
.pace__b:hover:not([disabled]),
.pace__x:hover { color: var(--color-text-primary); }
.pace__b:focus-visible,
.pace__x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* Dimmed rather than removed at the ends: a control that vanishes makes the row jump under the
   cursor that was aiming at it. */
.pace__b[disabled] { opacity: .38; cursor: default; }
.pace__b--play > svg { inline-size: 13px; block-size: 13px; }
.pace__spd,
.pace__pos {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-secondary);
  min-inline-size: 46px;
  text-align: center;
}
.pace__x kbd {
  padding: 1px 5px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: 4px;
  background: var(--color-bg-subtle);
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--color-text-secondary);
}

/* How far in, at a glance. `inline-size` and the bar's own dir, not `width`: in Arabic the run
   starts at the right edge, which is the edge that reader started at. */
.pace__meter { flex: 0 0 auto; block-size: 2px; background: var(--color-border-hairline); }
.pace__meter > i {
  display: block; block-size: 100%;
  inline-size: var(--pace-p, 0%);
  background: var(--color-accent);
  transition: inline-size var(--dur-fast, .15s) linear;
}

.pace__scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  /* The thread is still behind this page. Without containment, scrolling past the last line would
     carry on scrolling the conversation underneath it. */
  overscroll-behavior: contain;
  /* The tail is deep on purpose: the LAST sentence has to be able to sit where every other one
     sat, a little above the middle, instead of pinned against the bottom of the window. */
  padding: var(--space-8) var(--space-5) 46vh;
}
/* Narrower than Bigger's 860px: this page is read one line at a time, and a short measure is the
   whole errand. */
.pace__page { max-inline-size: 720px; margin-inline: auto; }

#firasPace .is-pace-off { display: none; }
/* Quieted, not greyed. `opacity` leaves inline code, links and syntax colours exactly the colours
   they are in the thread — a `color` here would sit at (1,1,0) and overwrite every one of them. */
#firasPace .pace__s,
#firasPace .pace__blk {
  opacity: .62;
  transition: opacity var(--dur-fast, .15s) var(--ease-out, ease-out);
}
#firasPace .is-pace-now { opacity: 1; }
/* The wash marks the line without moving it: vertical padding on an inline box paints without
   entering the line height, box-decoration-break keeps the band continuous across a sentence that
   wraps, and the negative inline margin gives back the width the padding took so the words around
   it do not shift as the mark moves on. */
#firasPace .pace__s.is-pace-now {
  background: var(--color-accent-soft);
  border-radius: var(--radius-sm, 6px);
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  padding-block: 1px;
  padding-inline: 3px;
  margin-inline: -3px;
}

@keyframes paceIn { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .pace { animation: none; }
  .pace__meter > i { transition: none; }
  #firasPace .pace__s,
  #firasPace .pace__blk { transition: none; }
}
/* The motion attribute is read off :root as the LEFTMOST compound, because :root IS <html> —
   put any ancestor selector in front of it and the rule can never match anything. */
:root[data-motion="off"] .pace { animation: none; }
:root[data-motion="off"] .pace__meter > i { transition: none; }
:root[data-motion="off"] #firasPace .pace__s,
:root[data-motion="off"] #firasPace .pace__blk { transition: none; }

/* ══ COMPOSER LENGTH METER ══════════════════════════════════════════════════
   The strip lenmSync() mounts into .composer__bar once the message passes its threshold.

   IT SHRINKS BEFORE ANYTHING ELSE MOVES. Every other control on that row is flex:0 0 auto and
   floored by a min-inline-size — the row's whole width budget was measured and spent without
   this in it — so the thing arriving late has to be the thing that gives: flex:0 1 auto with
   min-inline-size:0, and a warning phrase that ellipsises rather than wrap the row and push Send
   onto a second line. */
.lenm {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  flex: 0 1 auto;
  min-inline-size: 0;
  overflow: hidden;
  white-space: nowrap;
  font-size: var(--text-xs);
  line-height: var(--leading-xs);
  color: var(--color-text-muted);
  /* A live count redraws under the reader's own hands; proportional digits would make the whole
     strip twitch sideways every time a column changed width. */
  font-variant-numeric: tabular-nums;
  user-select: none;
  transition: color var(--dur-base) var(--ease-out);
  animation: lenm-in var(--dur-base) var(--ease-out);
}
/* The separator is DRAWN, not stored: it is punctuation between two readings, never something
   anyone needs to select and never something a screen reader should have to announce. */
.lenm__t::before { content: "·"; margin-inline-end: 6px; opacity: .55; }
.lenm__w { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; }
/* The UA rule for [hidden] is display:none and any author display beats it. This span is given
   none of its own, so the restatement is belt only — and the cost of being wrong here is a
   stale warning left on screen. */
.lenm__w[hidden] { display: none; }
/* Approaching is not the same as past. The near state borrows part of the error tone so the
   change is felt before it is read; only over spends the whole colour. */
.lenm.is-near { color: color-mix(in srgb, var(--color-error) 55%, var(--color-text-secondary)); }
.lenm.is-over { color: var(--color-error); }
.lenm.is-over .lenm__w { font-weight: var(--weight-medium); }

@keyframes lenm-in { from { opacity: 0; } to { opacity: 1; } }

/* TWO LAYERS, EACH DOING THE HALF IT CAN. lenmSync() decides whether to BUILD the strip and
   re-asks on every keystroke; these two rules guarantee it is never SEEN where it does not
   belong — including in the frame right after a rotation into portrait, when nothing has been
   typed yet to trigger that re-ask. `:root[data-ui="2"]` is the LEFTMOST compound because
   :root IS <html>: any ancestor selector in front of it can never match anything. */
@media (max-width: 640px) { .lenm { display: none; } }
:root[data-ui="2"] .lenm { display: none; }

@media (prefers-reduced-motion: reduce) { .lenm { animation: none; transition: none; } }
:root[data-motion="off"] .lenm { animation: none; transition: none; }


/* ----------------------------------------------------------------------------
   TEMPORARY CHAT — the mode that keeps nothing

   The visual language is a DASHED line, everywhere and only here. It needs no new colour, so it
   survives all six themes unchanged, and it says the one thing an accent colour cannot: this is
   provisional. The signal has to last the whole conversation, which is why it is carried by the
   composer — the one element on screen at every scroll position — rather than by a banner in a
   thread that scrolls away from it.
---------------------------------------------------------------------------- */
.eph-btn svg { inline-size: 18px; block-size: 18px; }
.eph-btn.is-on {
  color: var(--color-text-primary);
  background: var(--color-bg-subtle);
  /* An inset ring rather than a border: .icon-btn is a fixed 36px box, and a real border would
     shift the glyph by a pixel the moment the mode came on. */
  box-shadow: inset 0 0 0 var(--border-hairline) var(--color-border-strong);
}

.eph-note {
  /* .composer-wrap is pointer-events:none and only its composer opts back in. This pill stays
     out: it is a label, and a label that swallows a click aimed at the thread behind it is a
     bug that takes a long time to be blamed on the label. */
  align-self: center;
  display: inline-flex; align-items: center; gap: 6px;
  max-inline-size: 100%;
  margin-block-end: var(--space-2);
  padding: 3px 10px;
  border: var(--border-hairline) dashed var(--color-border-strong);
  border-radius: var(--radius-pill);
  background: var(--color-bg);
  color: var(--color-text-secondary);
  font-size: var(--text-xs); font-weight: var(--weight-medium);
  line-height: 1.6;
}
.eph-note__ic { flex: 0 0 auto; display: inline-flex; }
.eph-note__ic svg { inline-size: 14px; block-size: 14px; }
.eph-note__tx { min-inline-size: 0; }

/* The composer keeps the promise visible for the whole conversation. The STYLE is unconditional
   so a focused temporary composer stays dashed instead of snapping back to solid the moment the
   user types into it — but the COLOUR stops at the focus ring. `body.eph-on .composer` outranks
   `.composer.is-focused`, so setting the colour on both would have quietly replaced the accent
   focus tint with a flat grey for as long as the mode was on: the one piece of feedback the
   composer gives, spent on a state that already has a dashed border and a pill above it. */
body.eph-on .composer { border-style: dashed; }
body.eph-on .composer:not(.is-focused) { border-color: var(--color-border-strong); }

/* Sharing publishes a snapshot of the conversation to the server, which is precisely the thing
   this mode promises not to do. The control is taken away rather than left to fail: a Share
   button that refuses is a worse answer than a Share button that is not there. */
body.eph-on #shareChatBtn { display: none; }

/* The motion attribute is read off :root as the LEFTMOST compound, because :root IS <html> —
   put any ancestor selector in front of it and the rule can never match anything. */
@media (prefers-reduced-motion: reduce) { .eph-btn { transition: none; } }
:root[data-motion="off"] .eph-btn { transition: none; }

/* ── FIRAS CODE · LIVE PREVIEW BADGE ─────────────────────────────────────────────────────────
   The address strip already says which pane this is; this says that what is inside it is still
   arriving. It sits in the chrome beside the address rather than over the frame, because an
   overlay would cover the page the user opened the pane to watch. Tokens only, so it themes
   with the other six themes and with data-ui="2"; flex:0 0 auto so the phone chrome (which is
   already nowrap + overflow-x:auto) scrolls it instead of crushing the buttons beside it. */
.cw-prevlive{display:inline-flex;align-items:center;gap:6px;flex:0 0 auto;white-space:nowrap;font:600 10px var(--font-mono);letter-spacing:.02em;color:var(--color-accent);background:color-mix(in srgb,var(--color-accent) 12%,transparent);border:1px solid color-mix(in srgb,var(--color-accent) 30%,transparent);border-radius:var(--radius-pill);padding:3px 9px}
.cw-prevlive__dot{width:6px;height:6px;flex:0 0 auto;border-radius:50%;background:var(--color-accent);animation:cwPrevLivePulse 1.4s ease-in-out infinite}
@keyframes cwPrevLivePulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.35;transform:scale(.65)}}
/* A pulsing dot next to a pane that is already repainting itself is exactly the pairing this
   query exists to stop. The badge stays; only the motion goes. */
@media (prefers-reduced-motion:reduce){.cw-prevlive__dot{animation:none}}


/* ══ FIRAS CODE · ATTACHMENTS ════════════════════════════════════════════════
   The tray reuses .attach-thumb and .attach-file wholesale — same 56px cell, same chip, same
   remove button — because it is the same tray on a second screen. Only the shelf it sits on and
   the two controls that fill it are new.

   THE SHELF IS A SIBLING OF .cw-ai, NOT A CHILD. .cw-ai is one of the fixed-height fences in the
   .cw flex column, and it is explicitly `flex-wrap:nowrap` so the mobile panes keep their places;
   a wrapping row of thumbnails inside it would fight both of those. */
.cw-attach {
  display: flex; flex-wrap: wrap; gap: 8px; flex: 0 0 auto;
  padding: 10px 12px 0;
  background: var(--color-surface);
}
/* `hidden` alone loses to `.cw-attach{display:flex}` — same specificity, later rule wins — so the
   attribute has to be restated at a weight that can actually win. */
.cw-attach[hidden] { display: none; }
.cw-attach:empty { display: none; }
.cw-home__card .cw-attach { padding: 4px 0 0; background: transparent; }

.cw-ai__clip {
  flex: 0 0 auto; align-self: flex-end;
  inline-size: 40px; block-size: 40px;
  border-radius: var(--radius-pill);
  display: inline-grid; place-items: center;
  border: 1px solid var(--color-border-hairline);
  background: transparent; color: var(--color-text-muted);
  transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}
.cw-ai__clip:hover {
  background: var(--color-bg-subtle);
  color: var(--color-text-primary);
  border-color: var(--color-border-strong);
}
.cw-ai__clip svg { inline-size: 18px; block-size: 18px; }

/* The home screen's control carries a label: nothing else on that card explains what a paperclip
   would attach to, and the card has the width to say it. */
.cw-attach__btn {
  align-self: flex-start;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 12px; border-radius: var(--radius-pill);
  border: 1px dashed var(--color-border-strong);
  background: transparent; color: var(--color-text-muted);
  font: 500 12.5px var(--font-sans);
  transition: color var(--dur-fast), border-color var(--dur-fast);
}
[dir="rtl"] .cw-attach__btn { font-family: var(--font-arabic); }
.cw-attach__btn:hover { color: var(--color-text-primary); border-color: var(--color-accent); }
.cw-attach__btn svg { inline-size: 15px; block-size: 15px; }

/* THE DROP VEIL. .product-code #codeWorkspace is position:relative, so inset:0 covers the whole
   workspace and nothing above it — the topbar and the product switcher stay reachable, and the
   veil cannot appear over Firas AI or Firas Brain because #codeWorkspace is display:none there. */
.cw-drop {
  position: absolute; inset: 0; z-index: 40;
  display: grid; place-items: center;
  pointer-events: none;                      /* the drop is handled on the workspace itself */
  background: color-mix(in srgb, var(--color-bg) 78%, transparent);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
}
.cw-drop[hidden] { display: none; }
.cw-drop__in {
  padding: 18px 26px;
  border-radius: var(--radius-lg);
  border: 2px dashed var(--color-accent);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font: 600 15px var(--font-sans);
}
[dir="rtl"] .cw-drop__in { font-family: var(--font-arabic); }

/* ON A PHONE THE TRAY IS PART OF ONE PANE, NOT OF ALL FOUR. .cw-ai is display:none below 860px
   until .cw[data-mob="ai"] turns it on, and a shelf that ignored that would hang a row of
   thumbnails above the file tree, the editor and the preview alike. It follows the bar it belongs
   to — and the :not() pair restates `hidden`/empty at a weight that survives the extra class.
   860px, NOT 640: that is the one breakpoint the single-pane IDE is built on — .cw-mobnav,
   .cw[data-mob] and the .cw-ai hide/show all switch there. A shelf that waited for 640 was
   loose across the whole tablet range, which is where this was actually visible.
   Also: a 40px square stretched across a column reads as a second Run button, so it is pinned. */
@media (max-width: 860px) {
  .cw .cw-attach { display: none; }
  .cw[data-mob="ai"] .cw-attach:not([hidden]):not(:empty) { display: flex; }
  .cw[data-mob="ai"] .cw-ai__clip { align-self: flex-start; }
}
/* UI 2.0 floats the command bar on a translucent ground; the shelf above it follows rather than
   painting an opaque strip across the seam. */
:root[data-ui="2"] .cw-attach { background: transparent; }
@media (prefers-reduced-motion: reduce) {
  .cw-ai__clip, .cw-attach__btn { transition: none; }
}
:root[data-motion="off"] .cw-ai__clip,
:root[data-motion="off"] .cw-attach__btn { transition: none; }


/* ═══ PER-FILE CHANGE COUNTS ════════════════════════════════════════════════════
   The 21st.dev Edit Tool's row header, on our tokens: the count sits at the far end of the
   summary so eight rows can be scanned down one column. margin-inline-start:auto rather than a
   float, so it lands on the correct end under RTL without a second rule. */
.cw-diff__stat{
  margin-inline-start:auto; display:inline-flex; gap:8px; flex:0 0 auto;
  font:600 11px/1 var(--font-mono, ui-monospace, monospace);
  font-variant-numeric:tabular-nums; unicode-bidi:isolate;
}
.cw-diff__stat--add{color:var(--color-success,#3f9d6b)}
.cw-diff__stat--del{color:var(--color-error,var(--color-danger,#c0392b))}
/* The summary is a flex row already; this only guarantees the count cannot be squeezed out by a
   long path, which is the one thing that would make it useless. */
.cw-diff__file > summary{display:flex; align-items:center; gap:8px}
.cw-diff__file > summary code{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}


/* === FIRAS CODE — THE PATH ABOVE THE EDITOR ===================================
   The 21st.dev "Flexnative Breadcrumb" (felipemenezes098) on our tokens. Its list is muted and
   its current page is at full strength, and that single contrast IS the component — so it is
   the one thing reproduced exactly, with --color-text-muted for the trail and
   --color-text-primary for the leaf, which is what carries it through all six themes without a
   per-theme rule. Everything else here is geometry. */
/* z-index 7 is MEASURED, not chosen. position+z-index here opens a stacking context, and the
   folded-levels menu below is trapped inside it however high its own z-index goes — so this
   number, not that one, is what the menu is really painted at. CodeMirror 5.65.16's scrollbars
   (.CodeMirror-vscrollbar/-hscrollbar/-scrollbar-filler) sit at 6 and are NOT inside
   .CodeMirror-scroll's own context, so at 3 the menu dropped behind the editor's scrollbar the
   moment .cw-ed was dragged narrow. 7 clears them and still passes under .cw-ol (15), .cw-fr
   (16), .cw-diff (20) and .cw-snap (22) — the outline, find, the AI's change review and the
   version sheet all stay on top, which is the order the find-box note next door already set. */
.cw-crumb {
  position: relative; z-index: 7;
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 4px;
  min-block-size: 28px;
  padding: 3px 6px;
  background: var(--color-surface);
  border-block-end: 1px solid var(--color-border-hairline);
}
/* The row SCROLLS instead of being measured — the note above cwCrumbRender says why. The bar
   itself must never scroll, or the copy button would ride away with the trail. */
.cw-crumb__list {
  flex: 1 1 auto; min-inline-size: 0;
  margin: 0; padding: 0; list-style: none;
  display: flex; align-items: center; gap: 1px;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
}
.cw-crumb__list::-webkit-scrollbar { block-size: 0; }
.cw-crumb__item { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 2px; }
.cw-crumb__sep {
  flex: 0 0 auto; display: inline-flex; align-items: center;
  color: var(--color-text-muted); opacity: .5;
}
.cw-crumb__seg, .cw-crumb__more, .cw-crumb__root {
  flex: 0 0 auto;
  display: inline-flex; align-items: center;
  max-inline-size: 150px;
  padding: 2px 6px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-muted);
  font: 500 11.5px/1.5 var(--font-mono);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.cw-crumb__seg:hover, .cw-crumb__more:hover, .cw-crumb__root:hover {
  background: var(--color-bg-subtle); color: var(--color-text-primary);
}
.cw-crumb__more svg, .cw-crumb__root svg { inline-size: 13px; block-size: 13px; }
/* THE CONTRAST THE REFERENCE IS BUILT ON: the trail is muted, the file is not. */
.cw-crumb__leaf {
  flex: 0 0 auto; min-inline-size: 0;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 4px;
  color: var(--color-text-primary);
  font: 600 11.5px/1.5 var(--font-mono);
}
.cw-crumb__leafn { max-inline-size: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cw-crumb__copy {
  flex: 0 0 auto;
  /* 24x24, the WCAG 2.5.8 floor. Measured at 20x20; the breadcrumb row is 28px, so this grows
     the HIT AREA without moving anything. The glyph below stays 12px — the target got bigger,
     the icon did not. */
  inline-size: 24px; block-size: 24px;
  display: grid; place-items: center;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent; color: var(--color-text-muted); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.cw-crumb__copy svg { inline-size: 12px; block-size: 12px; }
.cw-crumb__copy:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.cw-crumb__copy.is-done { color: var(--color-success, #3f9d6b); }
.cw-crumb__seg:focus-visible, .cw-crumb__more:focus-visible, .cw-crumb__root:focus-visible,
.cw-crumb__copy:focus-visible, .cw-crumb__mi:focus-visible {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}
/* The folded-away levels. A sibling of its button rather than a portal: .cw-ed clips nothing,
   so an absolute box is the whole of what Radix's portal was doing for the reference. */
.cw-crumb__menu {
  position: absolute;
  inset-block-start: calc(100% + 4px);
  inset-inline-start: 8px;
  z-index: 30;
  min-inline-size: 160px; max-inline-size: 280px;
  padding: 4px;
  display: flex; flex-direction: column; gap: 2px;
  border: 1px solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-md);
}
.cw-crumb__menu[hidden] { display: none; }
.cw-crumb__mi {
  inline-size: 100%;
  padding: 5px 8px; text-align: start;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-secondary);
  font: 500 11.5px/1.4 var(--font-mono);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer;
}
.cw-crumb__mi:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
/* The editor's dark ground is a set of literals, not tokens (the syntax colours next door are
   the same), so the bar that sits on top of it is matched to those literals by hand. */
[data-theme="dark"] .cw-crumb { background: #242422; border-block-end-color: #3a3a37; }
[data-theme="dark"] .cw-crumb__seg:hover,
[data-theme="dark"] .cw-crumb__more:hover,
[data-theme="dark"] .cw-crumb__root:hover,
[data-theme="dark"] .cw-crumb__copy:hover { background: #1e1e1c; color: #e8e6df; }
[data-theme="dark"] .cw-crumb__menu { background: #262624; border-color: #3a3a37; }
/* The JS takes the row down on both crossings; these two are the guarantee that nothing shows
   in the frame between a resize and the media listener, and they lead with the ROOT attribute
   because :root IS <html> and nothing can be its ancestor. */
:root[data-ui="2"] .cw-crumb { display: none; }
@media (max-width: 860px) {
  .cw-crumb { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .cw-crumb__seg, .cw-crumb__more, .cw-crumb__root, .cw-crumb__copy { transition: none; }
}
:root[data-motion="off"] .cw-crumb__seg,
:root[data-motion="off"] .cw-crumb__more,
:root[data-motion="off"] .cw-crumb__root,
:root[data-motion="off"] .cw-crumb__copy { transition: none; }


/* ═══════════ FIRAS CODE — DOCUMENT OVERVIEW RAIL (the minimap) ═══════════
   The 21st.dev "Chapter Scrubber" model (@ruixen.ui, demo 19825) on our tokens. The rise toward
   the pointer is not an animation here — it is arithmetic. --r is the raised-cosine bump the rAF
   loop in cwMapWave writes onto the handful of ticks inside the radius, and all three of the
   original's useTransforms live in the three calc()s below: length, offset and opacity all read
   the same number. Nothing here transitions, so the reduced-motion rules have nothing to switch
   off — cwMapReduced drops the easing in JS instead, where the movement actually is.

   PHYSICAL right in both languages, and that is deliberate. #codeWorkspace carries dir="rtl"
   under the Arabic UI and the IDE mirrors with it, but .cw-ed .CodeMirror stays direction:ltr —
   the code's vertical axis and the scrollbar beside it never move. See the note over cwMapMount. */
.cw-ed[data-cwmap="on"] { --cwmap-w: 30px; }
/* Space is RESERVED, not taken: the rail sits beside the code rather than over CodeMirror's own
   scrollbar, so dragging that scrollbar still works. cwRefreshEditor re-measures after the swap. */
.cw-ed[data-cwmap="on"] .CodeMirror,
.cw-ed[data-cwmap="on"] > .cw-ed__ta { margin-right: var(--cwmap-w); }
/* The find box and the outline float over the editor at inset-inline-end:10px. Pull them clear of
   the rail rather than let either cover the search marks it is the reason for — the outline only
   in the LTR shell, because inset-inline-end already seats it on the far side under Arabic. */
.cw-ed[data-cwmap="on"] > .cw-fr { right: calc(10px + var(--cwmap-w)); }
#codeWorkspace[dir="ltr"] .cw-ed[data-cwmap="on"] > .cw-ol { right: calc(10px + var(--cwmap-w)); }

.cwmap {
  position: absolute; right: 0; z-index: 17;
  box-sizing: border-box;
  inline-size: var(--cwmap-w, 30px);
  padding: 0 4px;
  --pk: 21;                      /* px a tick reaches at the crest — CW_MAP_IW in app.js, and exactly this box's content width */
  color: var(--color-text-primary);
  /* Translucent rather than a token background: .cw-ed paints its own near-black in the dark
     themes and its own paper in the light ones, and the rail has to sit on whichever it finds. */
  background: color-mix(in srgb, var(--color-text-primary) 4%, transparent);
  border-inline-start: 1px solid color-mix(in srgb, var(--color-text-primary) 10%, transparent);
  cursor: pointer;
  touch-action: none;            /* a drag down this rail scrubs the document, it does not scroll the page */
}
.cwmap[hidden] { display: none; }
.cwmap:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }

.cwmap__body { position: relative; pointer-events: none; }
.cwmap__t {
  display: block; block-size: 2px; margin-block-end: 1px; border-radius: 1px;
  background: currentColor;
  margin-inline-start: calc(var(--x, 0) * (1 - var(--r, 0)) * 1px);
  inline-size: calc((var(--w, 0) + var(--r, 0) * (var(--pk) - var(--w, 0))) * 1px);
  opacity: calc(var(--o, 0) + var(--r, 0) * (1 - var(--o, 0)));
}
/* A blank stretch keeps its pitch and draws nothing, at the crest as much as at rest — the gaps
   between functions are half of what makes the column recognisable as THIS file. */
.cwmap__t.is-blank { --pk: 0; opacity: 0; }

.cwmap__marks, .cwmap__view, .cwmap__caret { position: absolute; pointer-events: none; }
.cwmap__marks { inset: 0; }
.cwmap__m { position: absolute; inset-inline: 0; block-size: 2px; border-radius: 1px; }
.cwmap__m.is-hit { background: var(--color-accent); }
/* Errors are stubs on the edge rather than full-width bars: a hit is a place in the text, an
   error is a flag in the margin, and two full-width bars in one colour family would blur. */
.cwmap__m.is-err {
  inset-inline: auto; inset-inline-start: 0;
  inline-size: 6px; block-size: 3px; border-radius: 0 2px 2px 0;
  background: var(--color-error, #B3261E);
}
.cwmap__view {
  inset-inline: 1px; z-index: 1; border-radius: 3px;
  background: color-mix(in srgb, var(--color-text-primary) 11%, transparent);
  outline: 1px solid color-mix(in srgb, var(--color-text-primary) 16%, transparent);
}
.cwmap__caret { inset-inline: 2px; block-size: 2px; border-radius: 2px; background: var(--color-accent); z-index: 2; }

.cwmap__card {
  position: absolute; inset-inline-end: calc(100% + 10px); z-index: 3;
  inline-size: min(300px, 38vw);
  opacity: 0; pointer-events: none;
  padding: 8px 11px;
  background: var(--color-surface);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border-strong, var(--color-border-hairline));
  border-radius: var(--cwv-radius-panel, var(--radius-md, 10px));
  box-shadow: var(--cwv-shadow-float, var(--shadow-lg));
}
.cwmap__cln {
  display: flex; gap: 6px; align-items: baseline; direction: ltr; unicode-bidi: isolate;
  font: 600 10.5px var(--font-sans); color: var(--color-text-muted); letter-spacing: .02em;
}
.cwmap__cn { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.cwmap__ctx {
  margin-block-start: 3px;
  font: 12px/1.5 var(--font-mono); color: var(--color-text-secondary);
  white-space: pre; overflow: hidden; text-overflow: ellipsis;
}
.cwmap__ctx[hidden] { display: none; }

/* ≤860px the workspace shows ONE pane at a time and the code pane IS the screen. A rail there
   would spend a tenth of it describing what a flick of the thumb already does — and the second
   interface has no room for a third axis either. cwMapPossible refuses both in JS; these two
   rules are the belt to that pair of braces, for a window dragged across the line mid-session. */
@media (max-width: 860px) {
  .cwmap { display: none !important; }
  .cw-ed[data-cwmap="on"] .CodeMirror,
  .cw-ed[data-cwmap="on"] > .cw-ed__ta { margin-right: 0; }
}
/* The second interface, same belt — and the margin has to come back with the rail. applyUi2 only
   flips an attribute on <html>: it never re-renders the workspace, so a project already open when
   the switch is thrown keeps its data-cwmap="on" and would have been left with a 30px empty
   gutter down the code where the hidden rail used to stand. */
:root[data-ui="2"] .cwmap { display: none !important; }
:root[data-ui="2"] .cw-ed[data-cwmap="on"] .CodeMirror,
:root[data-ui="2"] .cw-ed[data-cwmap="on"] > .cw-ed__ta { margin-right: 0; }

/* ══ FIRAS CODE — RUN STATUS STRIP (under the preview) ═════════════════════════════
   Ported model: 21st.dev "Status" (#25395, diceui). Its cva variant map is one custom property
   here: --rb carries the state's tone and everything downstream reads it, which is the only
   reason five states cost five declarations instead of five rulesets. The tones themselves are
   OURS — the original hardcodes green-500/orange-500 with a dark: twin, and this app has six
   themes, so each one is derived from a token that every theme already redefines. */
.cw-runbar{
  flex:0 0 auto;display:flex;align-items:center;gap:8px;box-sizing:border-box;
  min-height:34px;padding:6px 10px;
  background:var(--color-bg-subtle);border-top:1px solid var(--color-border-hairline);
  /* Same answer .cw-prevchrome gives at its own breakpoint: a narrow pane scrolls this row
     rather than wrapping it into a second band that would eat the preview's height. */
  overflow-x:auto;scrollbar-width:none;
  --rb:var(--color-text-muted);
}
.cw-runbar::-webkit-scrollbar{display:none}
.cw-runbar[data-state="run"]{--rb:var(--color-accent)}
.cw-runbar[data-state="ok"]{--rb:var(--color-success)}
/* No --color-warn exists, and inventing an amber would freeze one theme's idea of caution into
   all six. Softening the theme's OWN error tone toward its body text is a caution that follows
   the palette wherever it goes. */
.cw-runbar[data-state="warn"]{--rb:color-mix(in srgb,var(--color-error) 55%,var(--color-text-secondary))}
.cw-runbar[data-state="fail"]{--rb:var(--color-error)}

.cw-runbar__pill{
  flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;
  padding:3px 10px;border-radius:var(--radius-pill);
  border:1px solid color-mix(in srgb,var(--rb) 26%,transparent);
  background:color-mix(in srgb,var(--rb) 12%,transparent);
  color:var(--rb);font:600 11px var(--font-sans);
  transition:color var(--dur-slow) var(--ease-out),background-color var(--dur-slow) var(--ease-out),border-color var(--dur-slow) var(--ease-out);
}
/* The indicator, one for one with StatusIndicator: a ring and a core, both taking the pill's
   colour through currentColor instead of Tailwind reaching down with bg-inherit. */
.cw-runbar__dot{position:relative;flex:0 0 auto;width:8px;height:8px;border-radius:50%}
.cw-runbar__dot::before,.cw-runbar__dot::after{content:"";position:absolute;border-radius:50%;background:currentColor}
.cw-runbar__dot::before{inset:0;opacity:.32}
.cw-runbar__dot::after{inset:1.5px}
.cw-runbar[data-state="run"] .cw-runbar__dot::before{animation:cwRunbarPing 1.15s cubic-bezier(0,0,.2,1) infinite}
@keyframes cwRunbarPing{0%{transform:scale(1);opacity:.5}75%,100%{transform:scale(2.2);opacity:0}}

.cw-runbar__ms{
  flex:0 0 auto;padding:2px 8px;border-radius:var(--radius-pill);
  background:var(--color-surface);border:1px solid var(--color-border-hairline);
  color:var(--color-text-muted);font:600 10.5px var(--font-mono);
  direction:ltr;unicode-bidi:isolate;   /* a duration is a number — see .cw-status__seg */
}
.cw-runbar__m{
  flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;white-space:nowrap;
  padding:2px 8px;border:1px solid transparent;border-radius:var(--radius-pill);
  background:transparent;color:var(--color-text-secondary);
  font:600 11px var(--font-sans);cursor:pointer;
  transition:background-color var(--dur-slow) var(--ease-out),border-color var(--dur-slow) var(--ease-out),color var(--dur-slow) var(--ease-out);
}
.cw-runbar__m:hover{background:var(--color-surface);border-color:var(--color-border-hairline)}
.cw-runbar__m.is-zero{color:var(--color-text-muted);opacity:.6}
.cw-runbar__m--err:not(.is-zero){color:var(--color-error)}
.cw-runbar__m--warn:not(.is-zero){color:color-mix(in srgb,var(--color-error) 55%,var(--color-text-secondary))}
.cw-runbar__n{font-family:var(--font-mono);font-weight:700;direction:ltr;unicode-bidi:isolate}
.cw-runbar__sp{flex:1 1 auto;min-width:0}
.cw-runbar__go{
  flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;
  padding:3px 11px;border-radius:var(--radius-pill);
  border:1px solid var(--color-border-hairline);background:var(--color-surface);
  color:var(--color-text-primary);font:600 11px var(--font-sans);cursor:pointer;
  transition:border-color var(--dur-slow) var(--ease-out),color var(--dur-slow) var(--ease-out);
}
.cw-runbar__go:hover{border-color:var(--color-accent);color:var(--color-accent)}
.cw-runbar__go-i{display:inline-block;font-size:12px;line-height:1;transition:transform var(--dur-slow) var(--ease-out)}
.cw-runbar__go:hover .cw-runbar__go-i{transform:rotate(180deg)}

/* WHERE IT DOES NOT BELONG.
   · the empty state: .is-cwe folds the stage away to say "add an index.html", and a verdict on
     a preview that does not exist is noise stacked on the fix;
   · the second interface, whose whole argument is fewer readouts;
   · the phone, where ≤860px gives one pane the entire screen and the preview needs all of it.
   The root attribute comes FIRST because :root IS <html> — `body :root[…]` can never match. */
.cw-prevwrap.is-cwe .cw-runbar{display:none}
:root[data-ui="2"] .cw-runbar{display:none}
@media (max-width:860px){.cw-runbar{display:none}}
@media (prefers-reduced-motion:reduce){
  .cw-runbar[data-state="run"] .cw-runbar__dot::before{animation:none;opacity:.45}
  .cw-runbar__pill,.cw-runbar__m,.cw-runbar__go,.cw-runbar__go-i{transition:none}
}

/* ═══ FIRAS CODE — FOCUS MODE (ported from 21st.dev's Morphing Dialog, ibelick) ═════════
   One control hides .cw-tree and .cw-right and hands .cw-ed the whole row. Everything is bounded
   at 861px for exactly the reason the draggable seams are: below it .cw-main is a flex column
   showing ONE pane at a time behind the mobile nav, so there is nothing left to hide and the
   control would be a button that removes nothing.

   :root:not([data-ui="2"]) leads every rule and both halves of that are deliberate. The attribute
   is read off :root as the LEFTMOST compound because :root IS <html> — an ancestor in front of it
   makes the rule unmatchable, which is the trap the motion notes elsewhere in this file describe.
   And the extra weight is the point: .cw-main[data-split] is the layout this has to out-rank, and
   its own note warns that source order is not a thing to bet a layout on. A rule that merely came
   later would be one appended block away from losing that argument — and losing it means the
   saved column widths painting themselves back over a single-column grid.
   ════════════════════════════════════════════════════════════════════════════ */
.cw-zen__btn { display: none; }

@media (min-width: 860.02px) {
  :root:not([data-ui="2"]) .cw-zen__btn {
    display: inline-flex; align-items: center; gap: 7px; flex: 0 0 auto;
    block-size: 30px; padding-inline: 9px;
    border-radius: var(--radius-pill); border: 1px solid var(--color-border-hairline);
    background: transparent; color: var(--color-text-muted);
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
  }
  :root:not([data-ui="2"]) .cw-zen__btn:hover {
    background: var(--color-bg-subtle); color: var(--color-text-primary);
  }
  /* Pressed is a STATE, not a hover, so it reads off aria-pressed rather than a class nobody else
     can see. The accent-soft ground is the same one .cw-dock__more wears when its menu is open,
     which is the only other "this is currently on" button on this bar. */
  :root:not([data-ui="2"]) .cw-zen__btn[aria-pressed="true"] {
    background: var(--color-accent-soft); color: var(--color-accent);
    border-color: color-mix(in srgb, var(--color-accent) 40%, var(--color-border-hairline));
  }
  :root:not([data-ui="2"]) .cw-zen__btn:focus-visible {
    outline: none; box-shadow: var(--shadow-focus);
  }
  .cw-zen__ico { display: inline-flex; }
  .cw-zen__ico > svg { inline-size: 15px; block-size: 15px; }
  /* The key name, shown only while the mode is on. currentColor for the border so it inherits the
     pressed accent instead of needing a second token that would drift from it in one theme. */
  .cw-zen__k {
    font: 600 9.5px/1 var(--font-mono, ui-monospace, monospace);
    letter-spacing: .04em;
    padding: 3px 5px;
    border: 1px solid currentColor; border-radius: var(--radius-sm, 6px);
    opacity: .66;
  }
  .cw-zen__k[hidden] { display: none; }

  /* The mode itself. */
  :root:not([data-ui="2"]) .cw-main[data-zen="1"] { grid-template-columns: minmax(0, 1fr); }
  :root:not([data-ui="2"]) .cw-main[data-zen="1"] > .cw-tree,
  :root:not([data-ui="2"]) .cw-main[data-zen="1"] > .cw-right,
  :root:not([data-ui="2"]) .cw-main[data-zen="1"] > .cw-split { display: none; }
  /* .cw-ed is placed BY HAND at 1 / 2 for the three-column grid. With one track that cell does
     not exist, and the browser would open an implicit second column to hold the editor — leaving
     the row it was given empty and the code hanging off the side. The find and outline panels
     float inside .cw-ed and so come along untouched; the border was drawn against .cw-right, and
     with nothing beside it any more it is a hairline down the edge of the workspace. */
  :root:not([data-ui="2"]) .cw-main[data-zen="1"] > .cw-ed {
    grid-area: 1 / 1; border-inline-end: 0;
  }
}

/* The motion attribute is read off :root as the LEFTMOST compound — :root IS <html>, so any
   ancestor in front of it makes the rule unmatchable. */
@media (prefers-reduced-motion: reduce) { .cw-zen__btn { transition: none; } }
:root[data-motion="off"] .cw-zen__btn { transition: none; }


/* ═══════════════════════════════════════════════════════════════════════════
   THE CHECKLIST THE READER OWNS  (.tdo)  — see tdoDecorate in app.js
   ══════════════════════════════════════════════════════════════════════════ */

/* A BULLET SAYS NOTHING A BOX DOES NOT, so on an unordered list the box simply takes the marker's
   place: the prose does not shift by a pixel. A NUMBER does say something, because the surrounding
   prose refers to "step 7", so an ordered list keeps its marker and the box is given its own
   column after it.
   :has, for the same reason the ordered rule below has it and NOT as a flourish. Three places
   clone a rendered answer and strip the boxes back out, and one of them — the big-text overlay
   — is INSIDE the app and still reading this stylesheet. Unconditional, that copy loses its
   bullets AND its boxes, and a list of steps arrives in reading mode as a run of bare lines.
   Tied to a box that is actually standing there, the stripped copy is laid out as if none of
   this had ever run. Where :has is missing the bullet simply stays beside the box. */
.md > ul.tdo:has(> li > .tdo-in) { list-style: none; }
.md > .tdo > li.tdo-i { position: relative; }
/* inset-inline-start, not left: autoDirBlocks stamps a real dir on every li, so an Arabic step
   inside an English answer puts its own box on the right without a second rule here.
   The offset is PX because the gutter it has to stay inside is px: `.md ul` sets a flat 24px
   padding-inline-start, while the reading-size control scales the TYPE around it — an em
   offset would be right at the default and would walk the box clean out of the gutter and
   over the margin at the largest setting, which is where a reader most needs the list tidy. */
.md > .tdo > li.tdo-i > .tdo-in {
  position: absolute; inset-inline-start: -22px; inset-block-start: .34em; margin: 0;
  inline-size: 15px; block-size: 15px;
  accent-color: var(--color-accent); cursor: pointer;
  opacity: .4; transition: opacity .16s var(--ease-out);
}
/* :has, so the ordered list's extra column exists only while a box is standing in it. Three
   places clone a rendered answer and strip the boxes back out — both exports and the big-text
   overlay — and an unconditional padding would leave those copies with a gap after every
   number. Where :has is missing the box falls back beside the marker: cramped, never overlapping. */
.md > ol.tdo > li.tdo-i:has(> .tdo-in) { padding-inline-start: 1.6em; }
.md > ol.tdo > li.tdo-i:has(> .tdo-in) > .tdo-in { inset-inline-start: 0; }
/* Quiet until it is wanted. An answer can hold four lists, and forty boxes at full strength turn
   prose into a form; at .4 they read as a margin mark until a pointer, a keyboard or a tick
   brings one forward. A thumb has no hover to offer, so touch gets them closer to legible. */
.md > .tdo > li.tdo-i:hover > .tdo-in,
.md > .tdo > li.tdo-i > .tdo-in:focus-visible,
.md > .tdo > li.tdo-i > .tdo-in:checked { opacity: 1; }
@media (hover: none) { .md > .tdo > li.tdo-i > .tdo-in { opacity: .7; } }
.md > .tdo > li.tdo-i.is-on { color: var(--color-text-muted); }
.md > .tdo > li.tdo-i.is-on.tdo-flat {
  text-decoration: line-through; text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, var(--color-text-muted) 55%, transparent);
}

/* The progress strip, hidden by JS until the first tick. */
.tdo-head { display: flex; align-items: center; gap: 7px; margin-block: 2px 6px; }
.tdo-head[hidden] { display: none; }   /* an author `display` beats the UA rule for [hidden] */
.tdo-head__n { font: var(--weight-bold) 11.5px var(--font-sans); color: var(--color-accent); font-variant-numeric: tabular-nums; }
.tdo-head__l { font: var(--weight-semibold) 11.5px var(--font-sans); color: var(--color-text-muted); }
/* The sans stack carries no Arabic face, so an Arabic label has to be stamped the same way
   .agent-ck__lbl is. The strip's own dir is the only thing that knows which language it is in. */
.tdo-head[dir="rtl"] .tdo-head__l, .tdo-head[dir="rtl"] .tdo-head__r { font-family: var(--font-arabic); }
.tdo-head__bar {
  flex: 0 1 84px; block-size: 3px; overflow: hidden;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-text-muted) 20%, transparent);
}
/* inline-size rather than width, so an Arabic checklist fills from the right like its text. */
.tdo-head__bar i { display: block; block-size: 100%; inline-size: 0; border-radius: var(--radius-pill); background: var(--color-accent); transition: inline-size .35s var(--ease-out); }
.tdo-head__r {
  margin-inline-start: auto; padding: 3px 10px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill); background: none;
  color: var(--color-text-muted); font: var(--weight-semibold) 11px var(--font-sans); cursor: pointer;
}
.tdo-head__r:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); }
.tdo-head__r:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

@media (max-width: 560px) {
  /* A 15px box is a miss on a thumb, and the phone is where a checklist is actually carried. */
  .md > .tdo > li.tdo-i > .tdo-in { inline-size: 18px; block-size: 18px; inset-block-start: .3em; }
  .md > ol.tdo > li.tdo-i:has(> .tdo-in) { padding-inline-start: 1.75em; }
}

/* The motion attribute is read off :root as the LEFTMOST compound — :root IS <html>, so any
   ancestor in front of it makes the rule unmatchable. */
@media (prefers-reduced-motion: reduce) { .tdo-head__bar i, .md > .tdo > li.tdo-i > .tdo-in { transition: none; } }
:root[data-motion="off"] .tdo-head__bar i,
:root[data-motion="off"] .md > .tdo > li.tdo-i > .tdo-in { transition: none; }


/* ══ FIND INSIDE THIS CONVERSATION ═══════════════════════════════════
   A bar over the thread, not a panel beside it: the answer being searched has to stay readable
   while the search is running, which is the whole difference between this and the sidebar. */
.cfind {
  position: absolute;
  inset-block-start: var(--cfind-top, 66px);
  /* `inset-inline: 0; margin-inline: auto` and NOT `inset-inline-start: 50%` + translate: under
     the fixed-LTR shell the second idiom resolves its start edge to the right in an RTL context
     and lands the box half a width off centre. Section E of this file documents that trap
     against .scroll-bottom, and this is the same containing block. */
  inset-inline: 0;
  margin-inline: auto;
  /* The 96px is the two gutters, not padding: `.ttoc` stands absolutely in this same box at
     `right: 10px` and 26px wide, and a bar allowed to run to the edge would sit across the top
     of that rail on a narrow column. 48px a side clears it with room, and above roughly a 560px
     column the 460px cap is what applies anyway. */
  inline-size: min(460px, calc(100% - 96px));
  /* One above the topbar's own layer. The bar is placed BELOW that bar's box so the two never
     overlap; this only keeps it clear of the sticky headers inside the thread, and deliberately
     stays under dropdowns, the drawer and toasts. */
  z-index: calc(var(--z-sticky) + 1);
  display: flex; align-items: center; gap: 4px;
  padding: 5px 8px;
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-lg);
  opacity: 0; transform: translateY(-8px);
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.cfind.is-open { opacity: 1; transform: none; }
/* SELF, not descendant: the dir is stamped on this very element by cfLabel(), and everything
   above it in the tree is pinned dir="ltr" forever. */
.cfind[dir="rtl"] { font-family: var(--font-arabic); }

.cfind__ic {
  flex: 0 0 auto; display: grid; place-items: center;
  inline-size: 22px; block-size: 22px;
  color: var(--color-text-muted);
}
.cfind__ic svg { inline-size: 16px; block-size: 16px; }

.cfind__q {
  flex: 1 1 auto; min-inline-size: 0;
  padding: 5px 4px;
  border: 0; background: transparent;
  color: var(--color-text-primary);
  font: 400 13px var(--font-sans);
}
.cfind__q:focus { outline: none; }
.cfind__q::placeholder { color: var(--color-text-muted); }
/* The `font` shorthand above resets font-family, so the bar's own [dir="rtl"] rule cannot reach
   into it — the Arabic placeholder would come out in the Latin stack. The same explicit
   override .cw-fr__sc and .cw-fr__go already carry, for the same reason. */
.cfind[dir="rtl"] .cfind__q { font-family: var(--font-arabic); }

.cfind__n {
  flex: 0 0 auto; min-inline-size: 48px; text-align: center;
  font: 600 11px var(--font-mono);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
  unicode-bidi: isolate;
}
.cfind__n:empty { min-inline-size: 0; }
.cfind__n.is-none { color: var(--color-text-secondary); }

.cfind__b {
  flex: 0 0 auto; inline-size: 26px; block-size: 26px;
  display: grid; place-items: center;
  padding: 0; border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--color-text-muted);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.cfind__b:hover { background: var(--color-accent-soft); color: var(--color-accent); }
.cfind__b:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.cfind__b svg { inline-size: 15px; block-size: 15px; }

/* THE MARKS. Nothing in here may change the text's metrics — no padding, no font-weight, no
   letter-spacing. A find bar that reflowed the paragraph on every keystroke would move the very
   line it had just found, and inside a code block it would push every column out of alignment.
   Background and colour only; `color: inherit` so a highlighted token in a code block keeps the
   colour highlight.js gave it. */
mark.cfind__hit {
  background: var(--color-accent-soft);
  color: inherit;
  border-radius: var(--radius-xs);
}
mark.cfind__hit.is-cur {
  background: var(--color-accent);
  color: var(--color-on-accent);
}

@media (prefers-reduced-motion: reduce) { .cfind, .cfind__b { transition: none; } }
/* Read off :root as the leftmost compound — :root IS <html>, so an ancestor in front of it makes
   the rule unmatchable. */
:root[data-motion="off"] .cfind,
:root[data-motion="off"] .cfind__b { transition: none; }


/* ============================================================================
   MISSION WATCH - a running mission, summarised in the corner
   ============================================================================
   Read at a glance and never the thing on screen: one narrow column, a head that survives
   folding, at most two running steps, three finished ones and a clock. */
.mwatch {
  position: fixed;
  /* PHYSICAL `right`, deliberately, against this file's logical-property habit. <html> is pinned
     dir="ltr" for the whole shell and this element stamps its OWN dir so its text flows the right
     way round - which is exactly what would send a LOGICAL inset to the opposite corner in
     Arabic. The corner is the same corner in both languages, so it is written as one.
     `inset-block-end` needs no such care: the block axis does not turn with direction. */
  right: var(--space-5);
  /* --mwatch-lift is the composer's own measured height, written by missionWatchRender, and 0
     wherever there is no composer. Without it this corner IS the composer's corner on every
     window narrower than ~1700px, which is all of them. */
  inset-block-end: calc(var(--mwatch-lift, 0px) + var(--space-8) + var(--safe-bottom));
  /* Under the toast, over the thread. Every modal in this file sits at 1200 and above, so a
     dialog covers this instead of fighting it. */
  z-index: calc(var(--z-toast) - 1);
  inline-size: min(320px, calc(100vw - 2 * var(--space-5)));
  display: flex; flex-direction: column; gap: 9px;
  padding: 11px 12px 12px;
  border: var(--border-hairline) solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-lg), 0 2px 8px rgba(0, 0, 0, .07);
  font-family: var(--font-sans);
  color: var(--color-text-primary);
  animation: mwatchIn var(--dur-slow) var(--ease-out);
}
@keyframes mwatchIn { from { opacity: 0; transform: translateY(10px); } }
/* SELF, not descendant. The dir is stamped on this very element and every ancestor of it is
   pinned dir="ltr" forever, so `[dir="rtl"] .mwatch` has nothing to match. */
.mwatch[dir="rtl"] { font-family: var(--font-arabic); }
.mwatch.is-fold { gap: 0; }
.mwatch.is-fold .mwatch__body { display: none; }

.mwatch__head { display: flex; align-items: center; gap: 8px; min-inline-size: 0; }
/* The same 6px pulse the sidebar's activity dot wears - one visual sentence for "still running",
   said in both places. Reusing its keyframes, not a second copy of them. */
.mwatch__dot {
  flex: 0 0 auto;
  inline-size: 6px; block-size: 6px; border-radius: 50%;
  background: var(--color-accent);
  animation: chat-live-pulse 1.9s var(--ease-inout) infinite;
}
.mwatch.is-over .mwatch__dot { animation: none; opacity: .45; }

.mwatch__ttl {
  flex: 1 1 auto; min-inline-size: 0;
  padding: 0; border: 0; background: none; cursor: pointer;
  text-align: start;
  /* Longhand rather than the `font` shorthand: the family has to keep INHERITING so the per-dir
     rules below can reach it, and the shorthand would reset it to the initial family. */
  font-weight: var(--weight-semibold); font-size: 12.5px; line-height: 1.4;
  color: var(--color-text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mwatch__ttl:hover { color: var(--color-accent); }
.mwatch__ttl:focus-visible { outline: none; border-radius: var(--radius-xs); box-shadow: var(--shadow-focus); }

.mwatch__t {
  flex: 0 0 auto;
  font: 600 11px/1 var(--font-mono);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
.mwatch__t[hidden] { display: none; }

.mwatch__b {
  flex: 0 0 auto;
  inline-size: 20px; block-size: 20px;
  display: grid; place-items: center;
  padding: 0; border: 0; border-radius: var(--radius-xs);
  background: none; color: var(--color-text-muted);
  font-size: 11px; line-height: 1; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.mwatch__b:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.mwatch__b:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.mwatch__body { display: flex; flex-direction: column; gap: 9px; }

.mwatch__now, .mwatch__last { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.mwatch__now { gap: 5px; }
.mwatch__last { gap: 4px; }
.mwatch__nowrow, .mwatch__row { display: flex; align-items: baseline; gap: 7px; min-inline-size: 0; }
.mwatch__spark {
  flex: 0 0 auto;
  inline-size: 5px; block-size: 5px; border-radius: 50%;
  background: var(--color-accent);
  transform: translateY(-1px);
  animation: agentPulse 1.6s ease-in-out infinite;
}
.mwatch.is-over .mwatch__spark { animation: none; background: var(--color-text-muted); }
.mwatch__nowt {
  flex: 1 1 auto; min-inline-size: 0;
  font-size: 12px; line-height: 1.45;
  color: var(--color-text-primary);
  /* Two lines, then a clamp: a step title can be a sentence, and a panel that grows with it
     stops being a glance. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

.mwatch__meta { display: flex; align-items: center; gap: 8px; }
.mwatch__bar {
  flex: 1 1 auto; block-size: 3px;
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
  overflow: hidden;
}
.mwatch__fill {
  display: block; block-size: 100%;
  background: var(--color-accent); border-radius: inherit;
  transition: width var(--dur-slow) var(--ease-out);
}
.mwatch__n {
  flex: 0 0 auto;
  font: 700 10.5px/1 var(--font-mono);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

.mwatch__h { font-size: 10px; font-weight: 700; color: var(--color-text-muted); }
/* Tracking and uppercasing are LATIN typography. Letter-spacing breaks the joins in an Arabic
   word, and there is no case to raise - so the label gets neither when the panel is Arabic. */
.mwatch[dir="ltr"] .mwatch__h { text-transform: uppercase; letter-spacing: var(--tracking-wide); }
.mwatch__ok { flex: 0 0 auto; font-size: 10px; line-height: 1; color: var(--color-accent); }
.mwatch__rowt {
  flex: 1 1 auto; min-inline-size: 0;
  font-size: 11.5px; line-height: 1.45;
  color: var(--color-text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* A mission can be Arabic while the step it just finished names an English file, so the family
   is chosen per ELEMENT off the dir app.js stamps on it, not once for the panel. */
.mwatch__ttl[dir="rtl"], .mwatch__nowt[dir="rtl"], .mwatch__rowt[dir="rtl"] { font-family: var(--font-arabic); }
.mwatch__ttl[dir="ltr"], .mwatch__nowt[dir="ltr"], .mwatch__rowt[dir="ltr"] { font-family: var(--font-sans); }

/* The stylesheet's own half of the phone exclusion. missionWatchFits() re-tests on the panel's
   own tick, which is a second late; a window dragged narrow should lose this immediately, and a
   panel sitting on the composer for that second is exactly what the exclusion is for. */
@media (max-width: 640px) { .mwatch { display: none; } }

@media (prefers-reduced-motion: reduce) {
  .mwatch { animation: none; }
  .mwatch__dot, .mwatch__spark { animation: none; }
  .mwatch__fill, .mwatch__b { transition: none; }
}
/* The motion attribute is read off :root as the LEFTMOST compound - :root IS <html>, so any
   ancestor in front of it makes the rule unmatchable. */
:root[data-motion="off"] .mwatch { animation: none; }
:root[data-motion="off"] .mwatch__dot,
:root[data-motion="off"] .mwatch__spark { animation: none; }
:root[data-motion="off"] .mwatch__fill,
:root[data-motion="off"] .mwatch__b { transition: none; }


/* ===============================================================================
   THE PINNED STRIP
   ===============================================================================
   The pinned group, lifted out of the scroll. Built out of the existing token scale only, so
   the six themes carry it without a second rule anywhere. */

/* Sticky against .history, which is the sidebar's scroller (flex: 1 / overflow-y: auto). The
   negative inline margin is that scroller's own trick repeated: without it this ground stops
   short of the padding and the rows underneath slide past through a gap at each edge. */
.pcs {
  position: sticky;
  inset-block-start: 0;
  z-index: 2;
  margin-inline: calc(var(--space-1) * -1);
  padding-inline: var(--space-1);
  padding-block-end: var(--space-1);
  background: var(--color-sidebar-bg);
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
}
/* The label already flexes and carries the accent — this only trims the lead-in, which the
   group heading sizes for a gap between two groups and this one sits at the very top of. */
.pcs__head { padding-block-start: var(--space-2); }
.pcs__lb { min-inline-size: 0; }
.pcs__n {
  margin-inline-start: auto;
  font-weight: var(--weight-semibold);
  color: var(--color-text-muted);
}
/* A dozen pins is a sidebar with no list left under it, so the strip scrolls inside itself past
   five rows and a half — the half row IS the scroll affordance, and an exact five would read
   as the end of the pins rather than the top of them. The vh term keeps this honest on a short
   laptop, where 196px is most of the sidebar; the px term caps it on a tall monitor, where a
   strip that grew with the screen would stop being a strip. overscroll-behavior keeps the
   scroll from chaining into the history behind it — reaching the last pin should not start
   moving everything else. Hidden across, not auto: .history keeps its rows clear of its own
   edge with 4px of padding, this box does not, and `.chat-item:hover` translates 2px — enough
   for a horizontal scrollbar to flash in and out of a 196px strip on every row the pointer
   crosses. Two axes named in one declaration so the phone rule below can undo both at once. */
.pcs__rows {
  max-block-size: min(196px, 26vh);
  overflow: hidden auto;
  overscroll-behavior: contain;
}
/* THE PHONE CASE LIVES HERE, NOT IN JS, for the reason the resume strip's does: renderHistory
   does not run on resize, so a width read at paint time would leave a sticky block behind after
   the rotation that turned the sidebar into a drawer. It stops sticking and gives back its full
   height instead of disappearing — a drawer opened on purpose should hold every pinned
   conversation, and these rows exist nowhere else in the list. 768px is where .sidebar becomes
   that drawer. */
@media (max-width: 768px) {
  .pcs { position: static; }
  /* `overflow` and not `overflow-y`: leaving the across axis hidden would keep this a scroll
     container, which is the one thing the drawer must not be. */
  .pcs__rows { max-block-size: none; overflow: visible; }
}

/* ═══ THE COMMAND PALETTE (Ctrl/Cmd+K) ════════════════════════════════════════════════════════
   Deliberately the same card as .cw-pal, which people meet first inside Firas Code: same
   proportions, same header, same row, so the two do not read as two different features. It
   differs in exactly one structural way — this one is position:fixed against the viewport,
   because it is appended to <body> rather than to a workspace that has been made
   position:relative.

   1430 sits above Settings (1200) and the dev modal (1400), and below the image lightbox (9999)
   and the intro curtain (4000), which are the only two things that should ever cover it. */
.gpal{position:fixed;inset:0;z-index:1430;display:flex;align-items:flex-start;justify-content:center;padding:12vh 18px 18px;background:color-mix(in srgb,var(--color-bg) 45%,transparent);backdrop-filter:blur(4px);opacity:0;transition:opacity .16s var(--ease-out)}
.gpal.is-open{opacity:1}
.gpal__card{width:min(94vw,620px);max-height:66vh;display:flex;flex-direction:column;background:var(--color-surface);border:1px solid var(--color-border-strong);border-radius:16px;box-shadow:0 24px 70px rgba(0,0,0,.34);overflow:hidden;transform:translateY(-8px) scale(.98);transition:transform .18s var(--ease-out)}
.gpal.is-open .gpal__card{transform:none}
.gpal__head{display:flex;align-items:center;gap:10px;padding:0 16px;border-bottom:1px solid var(--color-border-hairline)}
/* The ring belongs to the row, not to the bare field: an outline on the input alone drew a
   rectangle across the middle of the header. Same fix .cw-pal__head already carries. */
.gpal__head:focus-within{box-shadow:inset 0 0 0 2px var(--color-accent-ring)}
.gpal__mag{flex:0 0 auto;display:flex;color:var(--color-text-muted)}
.gpal__mag svg{width:15px;height:15px}
.gpal__in{flex:1;min-width:0;border:none;padding:16px 0;background:transparent;color:var(--color-text-primary);font:500 15px var(--font-sans)}
.gpal__in:focus,.gpal__in:focus-visible{outline:none}
.gpal__in::placeholder{color:var(--color-text-muted)}
.gpal[dir="rtl"] .gpal__in{font-family:var(--font-arabic)}
.gpal__kbd{flex:0 0 auto;font:600 10.5px var(--font-mono);color:var(--color-text-muted);background:var(--color-bg-subtle);border:1px solid var(--color-border-hairline);border-radius:6px;padding:3px 6px}
.gpal__list{flex:1;overflow-y:auto;overscroll-behavior:contain;padding:6px}
.gpal__grp{padding:9px 12px 3px;font:600 10px var(--font-sans);letter-spacing:.09em;text-transform:uppercase;color:var(--color-text-muted)}
/* Arabic has no uppercase and no tracking to give: both are undone rather than inherited. */
.gpal[dir="rtl"] .gpal__grp{font-family:var(--font-arabic);letter-spacing:0;text-transform:none}
.gpal__row{display:flex;align-items:center;gap:12px;padding:9px 12px;border-radius:10px;cursor:pointer;color:var(--color-text-primary)}
.gpal__row:hover{background:var(--color-bg-subtle)}
.gpal__row.is-sel,.gpal__row.is-sel:hover{background:var(--color-accent-soft);box-shadow:inset 2px 0 0 var(--color-accent)}
.gpal[dir="rtl"] .gpal__row.is-sel,.gpal[dir="rtl"] .gpal__row.is-sel:hover{box-shadow:inset -2px 0 0 var(--color-accent)}
.gpal__ic{flex:0 0 auto;width:22px;text-align:center;font-size:14px;color:var(--color-accent);opacity:.9}
/* The theme rows wear the theme instead of naming it. `i` is a swatch chip here, not italics. */
.gpal__sw{flex:0 0 auto;width:22px;display:flex;align-items:center;justify-content:center;gap:2px}
.gpal__sw i{width:5px;height:14px;border-radius:2px;border:1px solid var(--color-border-hairline)}
.gpal__txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.gpal__lbl{min-width:0;font:500 13.5px var(--font-sans);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Per ELEMENT, not per card: a conversation titled in English, or a product name, sits inside an
   RTL card and must keep the Latin face — the label already carries its own resolved dir. */
.gpal__lbl[dir="rtl"]{font-family:var(--font-arabic)}
/* The highlight inside a label is a <mark class="cw-pal__hit">, because cwPalMark is the function
   drawing it. That rule is unscoped and already global, so there is deliberately no .gpal__hit
   here — a second class for the same ink is how the two palettes start to look different. */
.gpal__sub{font:400 11.5px var(--font-sans);color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.gpal__sub[dir="rtl"]{font-family:var(--font-arabic)}
.gpal__empty{padding:26px 14px;text-align:center}
.gpal__zt{font:500 13.5px var(--font-sans);color:var(--color-text-secondary)}
.gpal__zs{margin-top:4px;font:400 12px var(--font-sans);color:var(--color-text-muted)}
.gpal__zt[dir="rtl"],.gpal__zs[dir="rtl"]{font-family:var(--font-arabic)}
.gpal__foot{display:flex;gap:16px;padding:9px 16px;border-top:1px solid var(--color-border-hairline);background:var(--color-bg-subtle);font:400 11px var(--font-sans);color:var(--color-text-muted)}
.gpal[dir="rtl"] .gpal__foot{font-family:var(--font-arabic)}
.gpal__foot b{color:var(--color-text-secondary);font-weight:700}
/* gpalLive() already refuses to open below 640px. This is the belt to that brace: a window
   resized while the card is up must not leave a 620px dialog wider than the screen. */
@media (max-width:640px){.gpal{display:none}}
@media (prefers-reduced-motion:reduce){.gpal,.gpal__card{transition:none}}


/* ═══════════════════════════════════════════════════════════════════════════
   THE ARITHMETIC, CHECKED HERE  (.mck)  — see mckDecorate in app.js
   ══════════════════════════════════════════════════════════════════════════ */

/* A caution, not an alarm: the answer around it is almost certainly still worth reading, and
   only one or two lines of it are in question. So it takes the theme's OWN error tone at a
   fraction of its strength — the same move .cw-runbar[data-state="warn"] makes, and for the
   same reason: no amber is invented here that one theme would wear and the other five would not.
   border-inline-start AFTER the shorthand, so the marked edge follows the strip's own dir and
   stands on the right of an Arabic answer without a second rule. */
.md .mck {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-block: var(--space-5) 0;
  padding: var(--space-3);
  border: var(--border-hairline) solid color-mix(in srgb, var(--color-error) 26%, transparent);
  border-inline-start: var(--border-strong) solid var(--color-error);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-error) 6%, transparent);
}
.md .mck__ic {
  flex: 0 0 auto;
  inline-size: 15px;
  block-size: 15px;
  margin-block-start: 1px;
  color: var(--color-error);
}
.md .mck__ic svg { display: block; inline-size: 100%; block-size: 100%; }
/* min-inline-size, or a long equality would push the whole strip wider than the answer. */
.md .mck__b { flex: 1 1 auto; min-inline-size: 0; }
.md .mck__t { margin: 0; font: var(--weight-semibold) 12.5px var(--font-sans); color: var(--color-error); }
.md .mck__n { margin: 3px 0 0; font: var(--weight-regular) 12px var(--font-sans); color: var(--color-text-secondary); }
/* The sans stack carries no Arabic face, so an Arabic line has to be stamped the way
   .tdo-head__l is. The strip's own dir is the only thing here that knows the language. */
.md .mck[dir="rtl"] .mck__t,
.md .mck[dir="rtl"] .mck__n,
.md .mck[dir="rtl"] .mck__got { font-family: var(--font-arabic); }
/* These rows are a set, not prose: `.md ul` would give them a bullet and a 24px gutter. */
.md .mck__rows { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: grid; gap: 5px; }
.md .mck__row { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }
/* Wins over `.md :not(pre) > code`, which supplies the mono face, the tint and the isolated
   LTR run this chip is built on — only the size is ours. */
.md .mck__eq { font-size: 12px; }
.md .mck__got { font: var(--weight-semibold) 11.5px var(--font-sans); color: var(--color-text-secondary); }
.md .mck__got b { font-weight: var(--weight-bold); color: var(--color-text-primary); font-variant-numeric: tabular-nums; }

/* The phone is where an answer is actually checked against a homework sheet, and where a wide
   equality would otherwise force the whole strip to scroll. */
@media (max-width: 560px) {
  .md .mck { padding: var(--space-2); gap: 6px; }
  .md .mck__eq { font-size: 11.5px; }
  .md .mck__n { font-size: 11.5px; }
}


/* ── THE OUTBOX ────────────────────────────────────────────────────────────
   The strip that says a message never went out. It sits above the composer, beside where the
   temporary-chat note goes, because the answer it is waiting for would have appeared at the
   bottom of the thread and that is where the reader is already looking.

   pointer-events:auto IS NOT OPTIONAL. .composer-wrap is pointer-events:none and only .composer
   opts back in; .eph-note stays out on purpose because it is a label. This one carries a BUTTON,
   and a button that cannot be pressed is worse than no button at all.

   Tokens only, so it follows all six themes and data-ui="2" without knowing either exists. */
.outbox-bar {
  pointer-events: auto;
  align-self: center;
  display: inline-flex; align-items: center; gap: 8px;
  inline-size: auto; max-inline-size: min(100%, var(--column-w));
  margin-block-end: var(--space-2);
  padding-block: 4px;
  /* Logical, because the pill flips with the language it is written in. */
  padding-inline: 12px 5px;
  border: var(--border-hairline) solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  font-size: var(--text-xs); font-weight: var(--weight-medium);
  line-height: 1.55;
  box-shadow: var(--shadow-composer);
}
/* Red while nothing can be done, the identity green once something can — the sentence changes
   at the same moment, so the colour is a second reading of the same fact and not a new one. */
.outbox-bar__ic { flex: 0 0 auto; display: inline-flex; color: var(--color-error); }
.outbox-bar.is-ready .outbox-bar__ic { color: var(--color-accent); }
.outbox-bar__ic svg { inline-size: 14px; block-size: 14px; }
/* min-inline-size:0 so a long Arabic sentence wraps inside the pill instead of pushing the
   button off the end of a 360px phone. */
.outbox-bar__tx { min-inline-size: 0; }
.outbox-bar__go {
  flex: 0 0 auto;
  border: 0;
  border-radius: var(--radius-pill);
  padding: 4px 11px;
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-family: inherit; font-size: inherit; font-weight: var(--weight-semibold);
  line-height: 1.4;
  cursor: pointer;
  /* A finger target, not a mouse one: this is read on a phone far more often than on a laptop. */
  min-block-size: 28px;
}
.outbox-bar__go:hover { background: var(--color-accent-hover); }
.outbox-bar__go:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }

/* ══ RELATED CONVERSATIONS ═════════════════════════════════════════
   The foot of a conversation: the three others closest to it. Built by syncRelated().

   A DIRECT CHILD OF THE SCROLLER, not of .chat-inner — renderThread reconciles #chatThread down
   to exactly one node per message and trims whatever is left over, so this block cannot live
   inside the reading column and has to re-state its measure instead of inheriting it. The
   numbers below are .chat-inner's own, to the pixel, at every breakpoint it changes them.
---------------------------------------------------------------------------- */
.relconv {
  max-inline-size: var(--column-w);
  margin-inline: auto;
  padding: var(--space-6) var(--space-6) var(--space-8);
}
.relconv__head {
  display: flex; align-items: center; gap: 6px;
  padding-block-end: var(--space-2);
  padding-inline-start: var(--space-3);   /* line the words up with the row titles under them */
  font-size: var(--text-xs); line-height: var(--leading-xs);
  font-weight: var(--weight-medium);
  color: var(--color-text-muted);
  /* The hairline is what makes this a FOOTNOTE rather than another turn. Above the heading, so
     the block reads as attached to the thread it ends and not as a card of its own. */
  border-block-start: var(--border-hairline) solid var(--color-border-hairline);
  padding-block-start: var(--space-4);
}
.relconv__ic { flex: 0 0 auto; display: inline-flex; color: var(--color-accent); }
.relconv__ic svg { inline-size: 14px; block-size: 14px; }

.relconv__list { display: flex; flex-direction: column; gap: 2px; }

/* The row shape is the message-search hit's, because it answers the same question in the same
   two lines: what it is on top, why you are looking at it underneath. */
.relconv__row {
  display: flex; flex-direction: column; gap: 3px;
  inline-size: 100%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  text-align: start;
  color: var(--color-text-secondary);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.relconv__row:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.relconv__row:focus-visible {
  outline: none;
  background: var(--color-bg-subtle);
  box-shadow: 0 0 0 2px var(--color-accent-ring);
}
.relconv__top { display: flex; align-items: baseline; gap: var(--space-2); min-inline-size: 0; }
.relconv__title {
  flex: 1 1 auto; min-inline-size: 0;
  font-size: var(--text-sm); line-height: var(--leading-sm);
  font-weight: var(--weight-medium);
  color: var(--color-text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.relconv__when {
  flex: 0 0 auto;
  font-size: 10px; line-height: var(--leading-xs);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
}
.relconv__words {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  min-inline-size: 0;
}
.relconv__lbl {
  font-size: 10px; line-height: var(--leading-xs);
  color: var(--color-text-muted);
}
/* Tinted, never boxed — the same choice the search snippet's mark made: a hard outline at this
   size reads as an error state, and the accent already means "this is the thing you asked for"
   everywhere else in the product. */
.relconv__w {
  max-inline-size: 180px;
  padding: 0 6px;
  border-radius: var(--radius-pill);
  background: var(--color-accent-soft);
  color: var(--color-text-secondary);
  font-size: 10px; line-height: 16px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* .chat-inner narrows its gutters here (768px and again at 640px); this is a sibling of that
   column, so it has to follow or the foot would hang 8px wider than the thread above it. */
@media (max-width: 768px) {
  .relconv { padding-inline: var(--space-4); }
}

/* Focus reading shows ONE answer and hides every other turn, the topbar, the pinned strip and
   the composer. A list of other conversations is the furthest thing from that. Written as its
   own rule rather than added to the list up in the focus-read block: this selector is (0,2,1)
   and the only other rule that touches .relconv is (0,1,0), so it wins on both specificity and
   source order without editing a list three other features share. The media query mirrors that
   block's — below 769px focus reading hides nothing, so neither does this. */
@media (min-width: 768.02px) {
  body.is-focusread .relconv,
  :root[data-ui="2"] body.is-focusread .relconv { display: none; }
}

@media (prefers-reduced-motion: reduce) { .relconv__row { transition: none; } }
/* Read off :root as the leftmost compound — :root IS <html>, so an ancestor in front of it makes
   the rule unmatchable. */
:root[data-motion="off"] .relconv__row { transition: none; }


/* ══ FPF · the Agent's proof step ══════════════════════════════════════════════
   The same numbered rail .fml uses, because a student reads a derivation and a proof the same
   way and giving them two layouts teaches them nothing. What is different is the RULE line: it
   is set at the weight of an ordinary sentence rather than as a footnote, because in a proof it
   is not an aside — it is the thing being taught. The claim gets a box of its own so that the
   statement under examination is visible from anywhere in a long proof.

   Rows separated by a hairline rather than by whitespace: a proof is read by pointing at the
   line that lost you, and a rule is what makes a line pointable-at. */
.fpf { margin-block: 0 var(--space-4); }
.fpf[dir="rtl"] { font-family: var(--font-arabic); }
.fpf__k {
  display: block; margin-block-end: 5px;
  font-size: 10.5px; font-weight: var(--weight-bold); letter-spacing: .06em; text-transform: uppercase;
  color: var(--color-text-muted);
}
/* letter-spacing is the one property that takes an Arabic word apart: the letters are JOINED and
   spacing them breaks every join into a separate glyph. Cleared for the whole RTL card, not just
   this label, so nothing added here inherits it by accident. */
.fpf[dir="rtl"] .fpf__k { letter-spacing: 0; text-transform: none; }
.fpf__claim {
  margin-block-end: 10px; padding: 11px 13px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-bg-subtle);
}
.fpf__ct { margin: 0; font-size: 14px; font-weight: var(--weight-semibold); line-height: 1.55; color: var(--color-text-primary); }
.fpf__g { margin-block-end: 10px; }
.fpf__gi { font-size: 12.5px; line-height: 1.7; color: var(--color-text-secondary); }
/* The argument itself, held apart from the claim and the givens above it. The rows carry their
   own separators, so all this wrapper owes them is the gap. */
.fpf__l { margin-block-start: 4px; }
.fpf__r { display: flex; align-items: flex-start; gap: 10px; padding-block: 8px; }
.fpf__r + .fpf__r { border-block-start: 1px solid var(--color-border-hairline); }
.fpf__n {
  flex: 0 0 auto; min-inline-size: 20px;
  font-size: 11.5px; font-weight: var(--weight-bold); line-height: 2;
  color: var(--color-text-muted);
  text-align: center; font-variant-numeric: tabular-nums;
  /* A numeral standing alone reorders against an RTL base; the element carries dir="ltr" in the
     DOM and this keeps it isolated from the row around it. Same pairing .fml__n uses. */
  unicode-bidi: isolate;
}
.fpf__c { flex: 1 1 auto; min-inline-size: 0; }
/* The expression gets its OWN scroller. A long line of a proof is exactly the content that
   overruns a narrow column, and the page itself must never scroll sideways because of it — the
   treatment `.md .katex-display` and `.fml__m` already have. */
.fpf__m { overflow-x: auto; overflow-y: hidden; padding-block: 2px; unicode-bidi: isolate; }
.fpf__s { font-size: 13px; line-height: 1.7; color: var(--color-text-primary); }
.fpf__w { margin-block-start: 3px; font-size: 12px; line-height: 1.7; color: var(--color-text-secondary); }
.fpf__f {
  display: inline-block; margin-inline-start: 6px;
  padding: 0 7px; border-radius: var(--radius-pill);
  background: var(--color-accent-soft); color: var(--color-accent);
  font-size: 10.5px; line-height: 17px; font-variant-numeric: tabular-nums;
  unicode-bidi: isolate;
}
.fpf__q {
  display: flex; align-items: baseline; gap: 8px;
  margin-block-start: 10px; padding-block-start: 9px;
  border-block-start: var(--border-hairline) solid var(--color-border-hairline);
  font-size: 12.5px; line-height: 1.7; color: var(--color-text-secondary);
}
.fpf__qm { font-size: 13px; color: var(--color-accent); }
/* The tool-calls panel runs its whole detail pane at 11px. A proof opened in there has to come
   down with it, or one step's output sets a type scale the fourteen around it do not. */
.tc-out .fpf__ct { font-size: 12.5px; }
.tc-out .fpf__s { font-size: 11.5px; }
.tc-out .fpf__w, .tc-out .fpf__gi { font-size: 11px; }

/* ══ FDS · the Agent's dataset step ════════════════════════════════════════════
   Built on .fbg's frame rather than a second table style, because it is the same object: a
   scrolling grid with a bar over it. Two things are its own. The bar leads with the illustrative
   mark instead of a total — the first thing a reader must know about these figures is what they
   are — and there is a real <figcaption> under the table carrying the provenance, which is the
   half of this kind that survives into the exported file. */
.fds {
  margin-block: 0 var(--space-4);
  border: 1px solid var(--color-border-hairline); border-radius: 10px;
  background: var(--color-bg-subtle); overflow: hidden;
}
.fds[dir="rtl"] { font-family: var(--font-arabic); }
.fds__bar {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 10px; border-bottom: 1px solid var(--color-border-hairline);
}
.fds__tag {
  padding: 2px 9px; border-radius: var(--radius-pill);
  background: var(--color-accent-soft); color: var(--color-accent);
  font: 700 10.5px var(--font-sans);
}
.fds[dir="rtl"] .fds__tag { font-family: var(--font-arabic); }
/* Pushed to the far end so the tag and the button never trade places between the two
   directions: margin-inline-start is resolved against the card's own dir. */
.fds__dims { margin-inline-start: auto; font: 600 11px var(--font-sans); color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.fds__copy {
  flex: 0 0 auto; padding: 4px 12px;
  border: 1px solid var(--color-border-hairline); border-radius: 999px; cursor: pointer;
  background: var(--color-surface, transparent); color: var(--color-text-secondary);
  font: 600 11px var(--font-sans);
  transition: color var(--dur-fast, .15s) var(--ease-out, ease-out), border-color var(--dur-fast, .15s) var(--ease-out, ease-out);
}
.fds__copy:hover { color: var(--color-accent); border-color: color-mix(in srgb, var(--color-accent) 45%, transparent); }
.fds__copy[dir="rtl"] { font-family: var(--font-arabic); }
.fds__scroll { max-block-size: 420px; overflow: auto; }
.fds table { display: table; inline-size: 100%; border-collapse: collapse; margin: 0; overflow: visible; font: 12.5px/1.55 var(--font-sans); }
.fds th, .fds td {
  border: 0; border-bottom: 1px solid var(--color-border-hairline);
  padding: 6px 11px; text-align: start;
  overflow-wrap: break-word; word-break: normal; hyphens: none;
}
.fds th {
  position: sticky; inset-block-start: 0; z-index: 1;
  background: var(--color-bg-subtle); color: var(--color-text-primary);
  font-weight: 700; white-space: nowrap;
  box-shadow: inset 0 -1px 0 var(--color-border-hairline);
}
.fds td { color: var(--color-text-secondary); }
.fds tbody tr:last-child td { border-bottom: 0; }
.fds th[dir="rtl"], .fds td[dir="rtl"] { font-family: var(--font-arabic); }
/* text-align:right and not `end`: a figure column is right-aligned in BOTH directions, which is
   what makes the digits line up under each other for comparison. */
.fds th.fds__n, .fds td.fds__n { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.fds__src {
  padding: 7px 11px 9px;
  border-top: 1px solid var(--color-border-hairline);
  font-size: 11.5px; line-height: 1.65; color: var(--color-text-muted);
}
.fds__k { font-weight: var(--weight-semibold); color: var(--color-text-secondary); margin-inline-end: 6px; }
.tc-out .fds table { font-size: 11px; }
.tc-out .fds__src { font-size: 10.5px; }

/* ══ FCR · the Agent's critique step ═══════════════════════════════════════════
   Two columns and then a band, the same skeleton .fdc uses, and for the same reason: the shape
   is what forces the commit. Side by side, a weakness cannot be buried under three compliments,
   and the band at the foot has room for exactly one change.

   The columns wrap at 200px rather than collapsing at a breakpoint — this block renders in the
   thread, in a 340px detail pane and in a shared page, and a media query keyed to the VIEWPORT
   gets the narrow ones wrong in all three. */
.fcr {
  margin-block: 0 var(--space-4);
  padding: 14px 16px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-bg-subtle);
}
.fcr[dir="rtl"] { font-family: var(--font-arabic); }
.fcr__k {
  display: block; margin-block-end: 6px;
  font-size: 10.5px; font-weight: var(--weight-bold); letter-spacing: .06em; text-transform: uppercase;
  color: var(--color-text-muted);
}
.fcr[dir="rtl"] .fcr__k { letter-spacing: 0; text-transform: none; }
.fcr__cols { display: flex; flex-wrap: wrap; gap: 14px 22px; margin-block-end: 13px; }
.fcr__col { flex: 1 1 200px; min-inline-size: 0; }
.fcr__l { display: flex; flex-direction: column; gap: 8px; }
.fcr__i { display: flex; align-items: flex-start; gap: 8px; }
/* A fixed box so the marks stay in one column however long the points run. */
.fcr__m { flex: 0 0 auto; inline-size: 14px; block-size: 14px; margin-block-start: 3px; }
.fcr__m svg { display: block; inline-size: 100%; block-size: 100%; }
.fcr__col.is-works .fcr__m { color: var(--color-success); }
.fcr__col.is-weak .fcr__m { color: var(--color-text-muted); }
.fcr__c { flex: 1 1 auto; min-inline-size: 0; }
.fcr__p { font-size: 12.5px; font-weight: var(--weight-semibold); line-height: 1.55; color: var(--color-text-primary); }
.fcr__w { margin-block-start: 2px; font-size: 12px; line-height: 1.65; color: var(--color-text-secondary); }
.fcr__band { padding-block-start: 12px; border-block-start: var(--border-hairline) solid var(--color-border-hairline); }
.fcr__badge {
  display: inline-block; margin-block-end: 5px;
  padding: 2px 9px; border-radius: var(--radius-pill);
  background: var(--color-accent-soft); color: var(--color-accent);
  font-size: 10.5px; font-weight: var(--weight-bold);
}
.fcr__ct { margin: 0; font-size: 14.5px; font-weight: var(--weight-bold); line-height: 1.45; color: var(--color-text-primary); }
.fcr__why { margin: 5px 0 0; font-size: 12.5px; line-height: 1.7; color: var(--color-text-secondary); }
.tc-out .fcr { padding: 11px 12px; }
.tc-out .fcr__ct { font-size: 13px; }
.tc-out .fcr__p, .tc-out .fcr__w, .tc-out .fcr__why { font-size: 11.5px; }

/* ══ FSC · the Agent's schedule step ═══════════════════════════════════════════
   The rail .ftl draws, because a plan and a chronology look alike and should — what separates
   them is what hangs off each row. The dependency chip and the slip line are the whole
   difference, so the slip line keeps the full width under the row rather than sitting in a
   column that would clip it on a phone.

   Every inset is logical and the rail is a flex child, never absolutely positioned: this block
   is stamped dir="rtl" for an Arabic plan, flex order follows dir for free, and an absolute
   inset would land on the wrong edge there. */
.fsc { margin-block: 0 var(--space-4); }
.fsc[dir="rtl"] { font-family: var(--font-arabic); }
.fsc__e { display: flex; align-items: stretch; gap: 12px; }
.fsc__rail { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; inline-size: 10px; }
.fsc__dot {
  flex: 0 0 auto; inline-size: 9px; block-size: 9px; margin-block-start: 6px;
  border-radius: 50%; background: var(--color-accent);
}
.fsc__line { flex: 1 1 auto; inline-size: 2px; margin-block: 4px; border-radius: 1px; background: var(--color-border-hairline); }
.fsc__c { flex: 1 1 auto; min-inline-size: 0; padding-block-end: var(--space-4); }
.fsc__d {
  display: block;
  font-size: 11px; font-weight: var(--weight-bold); letter-spacing: .04em;
  color: var(--color-accent);
  /* A date standing alone reorders against an RTL base; the element carries its own dir in the
     DOM and this keeps it isolated from the row around it. */
  unicode-bidi: isolate;
}
.fsc[dir="rtl"] .fsc__d { letter-spacing: 0; }
.fsc__t { margin-block-start: 2px; font-size: 13.5px; font-weight: var(--weight-semibold); line-height: 1.5; color: var(--color-text-primary); }
.fsc__meta { display: flex; flex-wrap: wrap; gap: 5px 7px; margin-block-start: 5px; }
.fsc__chip {
  padding: 1px 8px; border-radius: var(--radius-pill);
  border: 1px solid var(--color-border-hairline);
  font-size: 11px; line-height: 17px; color: var(--color-text-secondary);
}
.fsc__ck { margin-inline-end: 5px; font-weight: var(--weight-semibold); color: var(--color-text-muted); }
.fsc__s { margin-block-start: 5px; font-size: 11.5px; line-height: 1.65; color: var(--color-text-muted); }
.fsc__sl { margin-inline-end: 6px; font-weight: var(--weight-semibold); color: var(--color-text-secondary); }
.tc-out .fsc__t { font-size: 12px; }
.tc-out .fsc__s, .tc-out .fsc__chip { font-size: 10.5px; }


/* ══ THE WHOLE CONVERSATION, IN THE OTHER LANGUAGE ═══════════════════════════
   Built by ctrxOpen: every turn of the open conversation as plain text, translated, on a page of
   its own. The thread it was read out of is still behind this one, unchanged.

   OPAQUE, ON THE THEME'S OWN BACKGROUND, exactly as Read It Bigger is: a dimmed scrim would say
   "you are looking at a thing on top of your page", and this IS the page while it is open. It
   reuses that page's keyframe too — one fade already exists here and says precisely this.

   NOTHING IN THIS BLOCK DECIDES A DIRECTION. Every line of text on the page carries a real `dir`
   put there by ctrxPaint from the text itself, and every box below is written in logical
   properties (inline-start, margin-inline, padding-inline) so it follows that dir rather than
   arguing with it. A `left`/`right` anywhere in here would be a rule that is correct for half
   the conversation. */
.ctrx {
  position: fixed; inset: 0;
  /* Above the settings and annotation sheets (1400) and above the enlarged answer at 1420 —
     which is the surface this one would ever be opened over — and below the attached-image
     viewer at 9999, the one thing that may legitimately cover it. */
  z-index: 1425;
  display: flex; flex-direction: column;
  background: var(--color-bg);
  animation: bigtextIn var(--dur-fast, .15s) var(--ease-out, ease-out);
}
.ctrx:focus { outline: none; }         /* it is focused to place the reader, not to be pointed at */
/* THE ONE FAILURE THIS PAGE REPORTS IS REPORTED BY A TOAST, and `--z-toast` is 500 — under this
   overlay's 1425, behind an opaque ground. "Translation failed, showing the original" was being
   drawn where nobody could ever read it, which is the one moment this page has to explain itself:
   every paragraph comes back unchanged and without the line nothing says why. Lifted only while
   this page is up, exactly the way the paced-read overlay lifts it two blocks down, and still
   under the attached-image viewer at 9999 and the palette at 1430. An engine without :has() drops
   the rule and is no worse off than it was. */
body:has(#firasChatTr) #toast { z-index: 1426; }

.ctrx__bar {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--space-2);
  padding: 10px 14px;
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-surface);
}
.ctrx__title {
  min-inline-size: 0; max-inline-size: 34ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--font-sans); font-size: var(--text-sm);
  font-weight: var(--weight-semibold); color: var(--color-text-primary);
}
.ctrx__sp { flex: 1 1 auto; }

.ctrx__seg {
  display: inline-flex; gap: 2px; padding: 2px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
}
.ctrx__lang {
  padding: 4px 11px; border: 0;
  border-radius: var(--radius-pill);
  background: transparent; color: var(--color-text-secondary);
  font-family: var(--font-sans); font-size: var(--text-sm);
  cursor: pointer;
}
.ctrx__lang:hover { color: var(--color-text-primary); }
.ctrx__lang:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* aria-pressed IS the state, so it is what the rule reads — a class kept alongside it would be a
   second source of truth for one fact, and the one a screen reader cannot see. */
.ctrx__lang[aria-pressed="true"] {
  background: var(--color-surface); color: var(--color-text-primary);
  font-weight: var(--weight-semibold);
}

.ctrx__note {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-sans); font-size: var(--text-xs);
  color: var(--color-text-secondary);
}
/* An inline-flex box ignores the UA's own [hidden] rule, so the one thing that turns this line
   off has to be said again here. */
.ctrx__note[hidden] { display: none; }
.ctrx__note-n { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.ctrx__x {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 12px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-surface); color: var(--color-text-secondary);
  font-family: var(--font-sans); font-size: var(--text-sm);
  cursor: pointer;
}
.ctrx__x:hover { color: var(--color-text-primary); }
.ctrx__x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.ctrx__x kbd {
  padding: 1px 5px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: 4px;
  background: var(--color-bg-subtle);
  font-family: var(--font-mono); font-size: 10.5px;
  color: var(--color-text-secondary);
}

.ctrx__scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  /* The thread is still behind this page. Without containment, scrolling past the last turn
     would carry on scrolling the conversation underneath it. */
  overscroll-behavior: contain;
  padding: var(--space-8) var(--space-5) var(--space-12);
}
/* Narrower than the enlarged answer's 860px: this is a transcript of forty turns rather than one
   piece of prose, and a shorter line is what makes the speaker changes findable down the page. */
.ctrx__page { max-inline-size: 800px; margin-inline: auto; }

.ctrx__turn { margin-block-end: var(--space-7); }
.ctrx__turn:last-child { margin-block-end: 0; }

.ctrx__lbl {
  display: flex; align-items: baseline; gap: 8px;
  margin-block-end: 6px;
  font-family: var(--font-sans); font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-accent);
  /* No letter-spacing, deliberately: tracking on Arabic pulls a joined word apart, and a label
     that changes shape with the language is a label that was designed for one of them. */
  letter-spacing: 0;
}
.ctrx__n { color: var(--color-text-secondary); font-variant-numeric: tabular-nums; }

.ctrx__p {
  white-space: pre-wrap;
  /* A pasted URL or a long token has no space to break at, and the page has no horizontal
     scrollbar to escape into. */
  overflow-wrap: anywhere;
  font-family: var(--font-sans); font-size: var(--text-base);
  line-height: var(--leading-lg); color: var(--color-text-primary);
  margin-block-end: var(--space-3);
  /* A gutter every part reserves, and a rule already sitting in it, transparent. The mark below
     then only has to change that rule's COLOUR — if it had to add a border, every marked
     paragraph would shift sideways the moment its translation failed, and a page that reflows
     while it loads is a page that cannot be read while it loads. Logical longhands rather than
     the `padding` shorthand throughout: the shorthand is physical, and mixing the two here is
     how a rule ends up correct for one direction and silently wrong for the other. */
  padding-inline: 12px 0;
  border-inline-start: 2px solid transparent;
}
.ctrx__p:last-child { margin-block-end: 0; }
/* The question reads as a quoted panel rather than as prose, so a reader scanning the page can
   see where each exchange starts without reading it — the same move the printed transcript makes.
   inline-start, so the bar lands on the side that turn's own text starts on. */
.ctrx__turn--you .ctrx__p {
  padding-inline: 14px; padding-block: 10px;
  border-inline-start-color: var(--color-accent);
  border-radius: var(--radius-md);
  background: var(--color-bg-subtle);
}
/* A passage the translator could not reach — refused, offline, or past the request budget. It is
   showing its own language, which is the honest thing to show; this says so quietly rather than
   leaving the reader to wonder why one paragraph did not change. */
.ctrx__p.is-untranslated { border-inline-start-color: var(--color-border-hairline); border-inline-start-style: dashed; }
/* A question already wears a bar, and that bar is what says "this is the question". Only its LINE
   goes dashed; the colour stays. (0,3,0) against the (0,2,0) above, so it wins on specificity
   rather than on where it happens to sit in the file. */
.ctrx__turn--you .ctrx__p.is-untranslated { border-inline-start-color: var(--color-accent); }

/* Code is never translated and never mirrored: it reaches this page byte for byte. */
.ctrx__pre {
  margin: 0 0 var(--space-3);
  padding: 12px 14px;
  overflow-x: auto;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-bg-subtle);
  font-family: var(--font-mono); font-size: var(--text-sm);
  line-height: 1.55; color: var(--color-text-primary);
  white-space: pre;
}
.ctrx__pre:last-child { margin-block-end: 0; }

@media (max-width: 768px) {
  .ctrx__scroll { padding-inline: var(--space-4); }
  .ctrx__title { max-inline-size: 14ch; }
}

@media (prefers-reduced-motion: reduce) { .ctrx { animation: none; } }
/* Read off :root as the leftmost compound — :root IS <html>, so an ancestor in front of it makes
   the rule unmatchable. */
:root[data-motion="off"] .ctrx { animation: none; }

/* ═══ MG · THE MERGE SEAM  (.mg-seam)  and the sixth control in the bulk bar ═══════════
   A merged conversation is two transcripts end to end. The seam is the line that says where the
   second one began — a hairline with the other conversation’s name in it, quiet enough to read
   past and present enough that the question under it is not read as a non-sequitur.

   It lives INSIDE the first turn of the second transcript rather than between two turns, because
   renderThread reconciles #chatThread strictly one child per message and trims everything past
   that count. That is what the two .msg-user rules below are for: .msg-ai is a block and takes
   the seam as an ordinary first child, while .msg-user is a flex row ending in a bubble. */
.mg-seam {
  display: flex; align-items: center; gap: var(--space-3);
  margin-block-end: var(--space-5);
  font-family: var(--font-sans);
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-muted);
}
.mg-seam::before, .mg-seam::after {
  content: ""; flex: 1 1 auto;
  block-size: var(--border-hairline);
  background: var(--color-border-hairline);
}
/* The name can be long and can be in either script; it wraps rather than pushing the rules off
   the column, and the element itself carries the dir — the shell is pinned LTR. */
.mg-seam__tx { flex: 0 1 auto; min-inline-size: 0; text-align: center; overflow-wrap: anywhere; }
/* .msg-user is `display: flex; justify-content: flex-end` with the bubble taking up to 85% of the
   column, so a seam placed inside it would be crushed against the edge BESIDE the question
   instead of standing over it. Wrapping is switched on only for the turns that carry one, so no
   other question’s layout is touched — the same containment .msg-user > .pinmsg-row uses. */
.msg-user.has-mgseam { flex-wrap: wrap; }
.msg-user.has-mgseam > .mg-seam { flex: 0 0 100%; order: -1; }

/* Six controls where there were five. On a 360px phone that row is 6 × 40px and the drawer is
   narrower than that, so it is allowed to take a second line rather than push the last button
   out of reach. Its own rule, after theirs, so the declaration five other actions share is left
   exactly as it was. */
.hist-bs__acts { flex-wrap: wrap; }
/* Merge takes exactly two. At every other count it dims but still answers — the same rule as
   .hist-bs__acts.is-off above it: nothing here is disabled, because the press is what earns the
   sentence explaining why. */
.hist-bs__mg.is-dim { opacity: .4; }


/* ── Firas Code — the shortcut sheet's filter row and its zero state ──────────────────
   Sits INSIDE .cw-kbd__htx, under the title and the hint, so the head's own flex row (text
   block + close button) never learns that a third control arrived and the ✕ stays where it
   has always been. Every colour here is a token that the six themes already redefine — nothing
   is declared for the first time inside a media query, so a theme cannot land on a box with no
   background. */
.cw-kbd__find { margin-block-start: 9px; display: flex; align-items: center; gap: 7px;
  padding-inline: 9px; block-size: 30px; border-radius: 9px;
  background: var(--color-bg-subtle); border: 1px solid var(--color-border-hairline);
  transition: border-color .14s var(--ease-out), background-color .14s var(--ease-out); }
/* focus-within, not :focus on the input: the ring belongs to the box the reader sees, and the
   input itself is borderless. This is the visible focus for a control reached by Tab. */
.cw-kbd__find:focus-within { border-color: var(--color-accent); background: var(--color-surface); }
.cw-kbd__fico { flex: 0 0 auto; display: flex; align-items: center; color: var(--color-text-muted); }
.cw-kbd__fico svg { inline-size: 13px; block-size: 13px; }
.cw-kbd__q { flex: 1; min-inline-size: 0; padding: 0; border: 0; background: none; outline: none;
  font: 400 12.5px var(--font-sans); color: var(--color-text-primary); }
[dir="rtl"] .cw-kbd__q { font-family: var(--font-arabic); }
.cw-kbd__q::placeholder { color: var(--color-text-muted); opacity: 1; }
.cw-kbd__zero { padding: 30px 14px 34px; text-align: center;
  font: 400 12.5px var(--font-sans); color: var(--color-text-muted); }
[dir="rtl"] .cw-kbd__zero { font-family: var(--font-arabic); }
/* .cw-kbd__row is display:flex and .cw-kbd__grp is display:block with padding, and BOTH beat the
   user-agent [hidden]{display:none} on specificity. Without these two the filter would hide
   nothing at all. */
.cw-kbd__row[hidden], .cw-kbd__grp[hidden], .cw-kbd__zero[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .cw-kbd__find { transition: none; }
}
:root[data-motion="off"] .cw-kbd__find { transition: none; }

/* ═══════════ FIRAS CODE — THE PROBLEMS PANEL ═══════════
   A fourth pane in .cw-right, laid out like the console it sits beside but built out of the
   product's own tokens rather than the console's hardcoded terminal palette — these are the
   app's messages about your files, not your program's stdout.

   COLOUR IS DEFINED HERE, NOT INSIDE A MEDIA QUERY. --color-error already exists in all six
   theme scopes; there is no --color-warn in this stylesheet at all (it is REFERENCED twice and
   defined zero times, which is a separate bug and not this panel's to fix), so --cwp-warn is
   declared for each of the six themes below. Six declarations, one per theme block, so nothing
   here depends on prefers-color-scheme to have a colour at all. */
:root { --cwp-warn: #9a6a00; --cwp-ok: #2f7d52; }
[data-theme="dark"]     { --cwp-warn: #e3b341; --cwp-ok: #6fc48b; }
[data-theme="black"]    { --cwp-warn: #e3b341; --cwp-ok: #6fc48b; }
[data-theme="midnight"] { --cwp-warn: #e0b45c; --cwp-ok: #6fc48b; }
[data-theme="graphite"] { --cwp-warn: #ddb45f; --cwp-ok: #77c191; }
[data-theme="amber"]    { --cwp-warn: #e8b552; --cwp-ok: #7cc596; }

.cw-prob {
  flex: 1; min-block-size: 0;
  display: flex; flex-direction: column;
  background: var(--color-surface);
  overflow: hidden;
}
.cw-prob[hidden] { display: none; }

.cw-prob__bar {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px;
  border-block-end: 1px solid var(--color-border-hairline);
  flex: 0 0 auto;
}
.cw-prob__sum { display: flex; align-items: center; gap: 6px; flex: 1; min-inline-size: 0; flex-wrap: wrap; }
.cw-prob__chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
  font: 600 11.5px var(--font-sans);
  white-space: nowrap;
}
[dir="rtl"] .cw-prob__chip { font-family: var(--font-arabic); }
.cw-prob__chip.is-err   { background: color-mix(in srgb, var(--color-error) 14%, transparent); color: var(--color-error); }
.cw-prob__chip.is-warn  { background: color-mix(in srgb, var(--cwp-warn) 16%, transparent); color: var(--cwp-warn); }
.cw-prob__chip.is-clean { background: color-mix(in srgb, var(--cwp-ok) 14%, transparent); color: var(--cwp-ok); }
.cw-prob__chip.is-idle,
.cw-prob__chip.is-busy  { background: var(--color-bg-subtle); color: var(--color-text-muted); font-weight: 500; }

.cw-prob__act {
  flex: 0 0 auto;
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border-strong);
  background: var(--color-bg-subtle);
  color: var(--color-text-primary);
  font: 600 12px var(--font-sans);
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .cw-prob__act { font-family: var(--font-arabic); }
.cw-prob__act:hover:not(:disabled) { background: var(--color-accent-soft); color: var(--color-accent); border-color: color-mix(in srgb, var(--color-accent) 40%, transparent); }
.cw-prob__act:disabled { opacity: .55; }
.cw-prob__act--ghost { border-color: transparent; background: transparent; color: var(--color-text-muted); font-weight: 500; }
.cw-prob__act--ghost:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); border-color: transparent; }
.cw-prob__act[hidden] { display: none; }

.cw-prob__list { flex: 1; min-block-size: 0; overflow-y: auto; padding: 6px 0 10px; }

.cw-prob__ghead {
  inline-size: 100%;
  display: flex; align-items: center; gap: 8px;
  padding: 6px 12px;
  background: transparent;
  color: var(--color-text-secondary);
  font: 600 11.5px var(--font-mono);
  text-align: start;
  position: sticky; inset-block-start: 0; z-index: 2;
  backdrop-filter: none;
}
.cw-prob__ghead::before {
  /* The sticky head needs its own ground or the rows scroll visibly through the text. */
  content: ""; position: absolute; inset: 0; z-index: -1; background: var(--color-surface);
}
.cw-prob__ghead:hover { color: var(--color-text-primary); }
.cw-prob__caret {
  inline-size: 12px; block-size: 12px; flex: 0 0 auto;
  border-inline-start: 1.6px solid currentColor; border-block-start: 1.6px solid currentColor;
  /* A rotated square, so the arrow is drawn rather than typed — a glyph caret picks up the
     Arabic font stack and stops looking like the tree's. */
  transform: rotate(225deg); margin-inline-start: 3px;
  inline-size: 7px; block-size: 7px;
  transition: transform var(--dur-fast) var(--ease-out);
}
.cw-prob__grp.is-shut .cw-prob__caret { transform: rotate(-45deg); }
[dir="rtl"] .cw-prob__grp.is-shut .cw-prob__caret { transform: rotate(135deg); }
.cw-prob__gname { flex: 1; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cw-prob__gcount {
  flex: 0 0 auto;
  min-inline-size: 18px; text-align: center;
  padding: 1px 6px; border-radius: var(--radius-pill);
  background: var(--color-bg-subtle); color: var(--color-text-muted);
  font: 600 10.5px var(--font-mono);
}

.cw-prob__row {
  inline-size: 100%;
  display: flex; align-items: flex-start; gap: 8px;
  padding: 6px 12px 6px 26px;
  padding-inline: 26px 12px;
  background: transparent;
  text-align: start;
  border-block-end: 1px solid color-mix(in srgb, var(--color-border-hairline) 60%, transparent);
  transition: background var(--dur-fast) var(--ease-out);
}
.cw-prob__row:hover { background: var(--color-bg-subtle); }
.cw-prob__row.is-dead { cursor: default; }
.cw-prob__dot {
  flex: 0 0 auto; inline-size: 7px; block-size: 7px; border-radius: 50%;
  margin-block-start: 5px; background: var(--color-error);
}
.cw-prob__row.is-warn .cw-prob__dot { background: var(--cwp-warn); }
.cw-prob__src {
  flex: 0 0 auto; margin-block-start: 1px;
  padding: 1px 6px; border-radius: var(--radius-xs, 4px);
  background: var(--color-bg-subtle); color: var(--color-text-muted);
  font: 600 10px var(--font-sans); text-transform: none; white-space: nowrap;
}
[dir="rtl"] .cw-prob__src { font-family: var(--font-arabic); }
.cw-prob__msg {
  flex: 1; min-inline-size: 0;
  color: var(--color-text-primary);
  font: 400 12px/1.55 var(--font-sans);
  /* The message may be an English linter sentence, a browser throw, or a path. Let each row
     resolve its own base direction from its own first strong character. */
  unicode-bidi: plaintext; text-align: start;
  overflow-wrap: anywhere;
}
.cw-prob__row.is-warn .cw-prob__msg { color: var(--color-text-secondary); }
.cw-prob__loc {
  flex: 0 0 auto; margin-block-start: 1px;
  color: var(--color-text-muted);
  font: 500 11px var(--font-mono);
  white-space: nowrap;
}

.cw-prob__empty { padding: 34px 24px; text-align: center; }
.cw-prob__emptyT { margin: 0 0 6px; color: var(--color-text-primary); font: 600 14px var(--font-sans); }
.cw-prob__emptyD { margin: 0 auto; max-inline-size: 42ch; color: var(--color-text-muted); font: 400 12.5px/1.65 var(--font-sans); }
[dir="rtl"] .cw-prob__emptyT, [dir="rtl"] .cw-prob__emptyD { font-family: var(--font-arabic); }

.cw-prob__hint {
  flex: 0 0 auto;
  padding: 6px 12px;
  border-block-start: 1px solid var(--color-border-hairline);
  color: var(--color-text-muted);
  font: 500 11px var(--font-sans);
  text-align: start;
}
[dir="rtl"] .cw-prob__hint { font-family: var(--font-arabic); }

/* The count on the tab. Three states, three looks, because "not checked yet" and "checked,
   clean" are different claims and a lone integer can only make one of them. */
.cw-prob__badge {
  display: inline-block;
  min-inline-size: 16px;
  margin-inline-start: 6px;
  padding: 0 5px;
  border-radius: var(--radius-pill);
  font: 700 10px/16px var(--font-mono);
  text-align: center;
  vertical-align: 1px;
}
.cw-prob__badge.is-idle  { background: var(--color-bg-subtle); color: var(--color-text-muted); }
.cw-prob__badge.is-clean { background: color-mix(in srgb, var(--cwp-ok) 18%, transparent); color: var(--cwp-ok); }
.cw-prob__badge.is-hot   { background: var(--color-error); color: #fff; }
.cw-tab--prob { display: inline-flex; align-items: center; }

/* Both switches, always both: the media query serves the OS setting and the attribute serves
   the app's own Reduced-motion toggle, and neither one covers the other. */
:root[data-motion="off"] .cw-prob__caret,
:root[data-motion="off"] .cw-prob__row,
:root[data-motion="off"] .cw-prob__act { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .cw-prob__caret, .cw-prob__row, .cw-prob__act { transition: none; }
}

/* Focus has to be visible on all six themes and must not be clipped by the scrolling list. */
.cw-prob__row:focus-visible,
.cw-prob__ghead:focus-visible,
.cw-prob__act:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
  border-radius: 4px;
}


/* ═══ FIRAS CODE — LOCAL HISTORY TIMELINE (cwx_timeline) ═══
   The version-history panel's flat list became a spine: one dot per save point, day headings, and
   a node that opens onto the per-file counts for that point.

   The add/remove colours are TOKENS on .cw-snap itself, never only inside a media query, and they
   are mixed toward --color-text-primary instead of being hard-coded twice. One definition then
   reads dark green on the three light themes and bright green on the three dark ones, because
   text-primary is what actually differs between them — which is also why this needs no per-theme
   block for light / dark / midnight / graphite / black / amber.

   The old .cw-snap__row rules above are deliberately left in place: they are dead once this
   lands, but other patches in flight may still be anchored on them. */
.cw-snap{
  --cwtl-add:color-mix(in srgb,#2ecc71 62%,var(--color-text-primary));
  --cwtl-del:color-mix(in srgb,#e74c3c 62%,var(--color-text-primary));
  --cwtl-line:color-mix(in srgb,var(--color-border-strong) 55%,transparent);
}
.cw-snap__list{gap:0}
.cwtl__day{position:sticky;top:0;z-index:2;margin:12px 0 5px;padding:3px 0;background:var(--color-surface);font:700 10.5px var(--font-sans);letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted)}
.cwtl__day:first-child{margin-top:2px}
[dir="rtl"] .cwtl__day{font-family:var(--font-arabic);letter-spacing:0;text-transform:none;font-size:11.5px}

.cwtl__node{display:flex;align-items:stretch;gap:10px}
.cwtl__rail{flex:0 0 14px;position:relative;display:flex;justify-content:center}
.cwtl__rail::before{content:"";position:absolute;top:0;bottom:0;width:2px;background:var(--cwtl-line)}
.cwtl__node:last-child .cwtl__rail::before{bottom:auto;height:20px}
.cwtl__dot{position:relative;align-self:flex-start;margin-top:13px;width:8px;height:8px;border-radius:50%}
/* Two dots, two meanings, both written down: a hollow ring is a point the workspace took on its
   own, a filled accent dot is one the reader asked for. Stating the auto case explicitly rather
   than letting it fall through to the base rule is what stops the modifier from being decoration. */
.cwtl__dot--auto{background:var(--color-surface);box-shadow:0 0 0 2px var(--color-border-strong)}
.cwtl__dot--manual{background:var(--color-accent);box-shadow:0 0 0 2px var(--color-accent),0 0 0 5px var(--color-accent-soft)}

.cwtl__box{flex:1;min-width:0;margin-bottom:8px;border:1px solid var(--color-border-hairline);border-radius:10px;background:var(--color-bg-subtle);transition:border-color .15s var(--ease-out)}
.cwtl__box:hover,.cwtl__node.is-open .cwtl__box{border-color:var(--color-border-strong)}
.cwtl__node.is-fresh .cwtl__box{animation:cwSnapFlash 1.1s var(--ease-out) both}

.cwtl__head{display:flex;align-items:center;gap:8px;width:100%;padding:9px 11px;border:0;border-radius:10px;background:transparent;text-align:start;cursor:pointer;color:var(--color-text-primary)}
[dir="rtl"] .cwtl__head{font-family:var(--font-arabic)}
.cwtl__chev{flex:0 0 auto;font-size:9px;line-height:1;color:var(--color-text-muted);transition:transform .18s var(--ease-out)}
[dir="rtl"] .cwtl__chev{transform:rotate(180deg)}
.cwtl__chev.is-open,[dir="rtl"] .cwtl__chev.is-open{transform:rotate(90deg)}
.cwtl__when{flex:0 0 auto;font:600 11px var(--font-mono);color:var(--color-text-secondary)}
.cwtl__kind{flex:0 0 auto;font:600 9.5px var(--font-sans);letter-spacing:.04em;text-transform:uppercase;padding:2px 7px;border-radius:var(--radius-pill)}
[dir="rtl"] .cwtl__kind{font-family:var(--font-arabic);letter-spacing:0;text-transform:none}
.cwtl__kind--manual{background:color-mix(in srgb,var(--color-accent) 16%,transparent);color:var(--color-accent)}
.cwtl__kind--auto{background:var(--color-bg);color:var(--color-text-muted);border:1px solid var(--color-border-hairline)}
.cwtl__sum{flex:1;min-width:0;font:500 11.5px var(--font-sans);color:var(--color-text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
[dir="rtl"] .cwtl__sum{font-family:var(--font-arabic)}
.cwtl__lab{flex:0 0 auto;max-width:34%;padding:2px 7px;border-radius:var(--radius-pill);background:var(--color-accent-soft);color:var(--color-accent);font:500 10.5px var(--font-sans);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
[dir="rtl"] .cwtl__lab{font-family:var(--font-arabic)}
.cwtl__ago{flex:0 0 auto;font:400 10.5px var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .cwtl__ago{font-family:var(--font-arabic)}

.cwtl__files{display:flex;flex-direction:column;gap:1px;margin:0 11px;padding:5px 0 7px;border-top:1px solid var(--color-border-hairline)}
.cwtl__f{display:flex;align-items:center;gap:8px;padding:2px 0}
.cwtl__fb{flex:0 0 auto;min-width:54px;text-align:center;font:600 9px var(--font-sans);letter-spacing:.03em;padding:2px 5px;border-radius:5px;background:transparent;color:var(--color-text-muted)}
[dir="rtl"] .cwtl__fb{font-family:var(--font-arabic);letter-spacing:0;font-size:10px}
.cwtl__fb--new{background:color-mix(in srgb,var(--cwtl-add) 15%,transparent);color:var(--cwtl-add)}
.cwtl__fb--del{background:color-mix(in srgb,var(--cwtl-del) 15%,transparent);color:var(--cwtl-del)}
.cwtl__fb--edit{background:var(--color-accent-soft);color:var(--color-accent)}
/* The base point has no verdict to give per file, so its badge is an empty cell that holds the
   column open — the paths stay on the same left edge as every other node's. */
.cwtl__fb--base{background:transparent}
.cwtl__fp{flex:1;min-width:0;font:500 11px var(--font-mono);color:var(--color-text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cwtl__fc{flex:0 0 auto;font:600 10px var(--font-mono);color:var(--color-text-muted)}
.cwtl__fc .add{color:var(--cwtl-add)}
.cwtl__fc .del{color:var(--cwtl-del)}
.cwtl__fc--n{font:400 10.5px var(--font-sans)}
[dir="rtl"] .cwtl__fc--n{font-family:var(--font-arabic)}
.cwtl__fmore{padding:4px 0 0;font:600 10px var(--font-mono);color:var(--color-text-muted)}

.cwtl__acts{display:flex;align-items:center;flex-wrap:wrap;gap:6px;padding:0 11px 9px}
.cwtl__act{padding:5px 10px;border:1px solid var(--color-border-hairline);border-radius:var(--radius-pill);background:transparent;color:var(--color-text-secondary);font:600 10.5px var(--font-sans);line-height:1.35;cursor:pointer;transition:border-color .15s var(--ease-out),color .15s var(--ease-out),background .15s var(--ease-out)}
[dir="rtl"] .cwtl__act{font-family:var(--font-arabic)}
.cwtl__act:hover{border-color:var(--color-accent);color:var(--color-accent)}
.cwtl__act.is-on{background:var(--color-accent);border-color:var(--color-accent);color:var(--color-on-accent)}
.cwtl__act--go{border-color:var(--color-accent);color:var(--color-accent)}
.cwtl__act--go:hover{background:var(--color-accent);color:var(--color-on-accent)}
.cwtl__act--x{margin-inline-start:auto}
.cwtl__act--x:hover{border-color:var(--cwtl-del);color:var(--cwtl-del);background:transparent}

.cwtl__foot{padding:10px 12px 12px;border-top:1px solid var(--color-border-hairline);background:var(--color-bg-subtle)}
.cwtl__meter{height:4px;margin-bottom:8px;border-radius:var(--radius-pill);background:var(--color-bg);overflow:hidden}
.cwtl__meterfill{display:block;height:100%;border-radius:var(--radius-pill);background:var(--color-accent);transition:width .3s var(--ease-out)}
.cwtl__keep{margin:0;font:400 10.5px/1.7 var(--font-sans);color:var(--color-text-muted)}
[dir="rtl"] .cwtl__keep{font-family:var(--font-arabic);font-size:11.5px}
.cwtl__used{margin-top:6px;font:600 10px var(--font-mono);color:var(--color-text-secondary);text-align:end}

/* The head is a real button, so it gets the shell's own focus treatment rather than a bespoke
   ring, and nothing on the node clips it — .cwtl__box carries no overflow:hidden for that reason. */
.cwtl__head:focus-visible,.cwtl__act:focus-visible{outline:none;box-shadow:var(--shadow-focus)}

.cw--v3 .cwtl__box{border-radius:var(--radius-md);transition:border-color var(--cwv-motion),transform var(--cwv-motion),box-shadow var(--cwv-motion)}
.cw--v3 .cwtl__box:hover{transform:translateY(-1px);box-shadow:0 4px 12px color-mix(in srgb,var(--color-text-primary) 6%,transparent)}

/* data-ui="2" is the stripped-back shell. The spine and the budget bar are the decoration here;
   the verdicts, the counts and the retention sentence are the substance, so only the decoration
   goes. A version history that disappeared in simple mode would be the wrong trade. */
:root[data-ui="2"] .cwtl__rail,
:root[data-ui="2"] .cwtl__meter{display:none}

/* Both switches, always: the media query serves the OS setting and :root[data-motion] serves the
   app's own toggle, and :root IS <html>, so it has to be the leftmost compound. */
:root[data-motion="off"] .cwtl__chev,
:root[data-motion="off"] .cwtl__box,
:root[data-motion="off"] .cwtl__act,
:root[data-motion="off"] .cwtl__meterfill{transition:none}
:root[data-motion="off"] .cwtl__node.is-fresh .cwtl__box{animation:none}
:root[data-motion="off"] .cw--v3 .cwtl__box:hover{transform:none}
@media (prefers-reduced-motion: reduce){
  .cwtl__chev,.cwtl__box,.cwtl__act,.cwtl__meterfill{transition:none}
  .cwtl__node.is-fresh .cwtl__box{animation:none}
  .cw--v3 .cwtl__box:hover{transform:none}
}

@media (max-width:640px){
  .cwtl__ago{display:none}
  .cwtl__head{flex-wrap:wrap;gap:6px;padding:9px 10px}
  .cwtl__sum{flex:1 0 100%;white-space:normal}
  .cwtl__node{gap:7px}
  .cwtl__rail{flex-basis:10px}
  .cwtl__act{padding:6px 9px}
  .cwtl__fb{min-width:0}
}


/* ═══ FIRAS CODE — EDITOR PREFERENCES ═══
   Two halves. The sheet, and the handful of rules the sheet's settings actually act through.
   Every colour is a token, so all six themes are covered without a single per-theme rule; the
   thumb and the switch knob move on inset-inline-start, never translateX, because both groups
   carry dir="rtl" under the Arabic UI inside a shell that is LTR for good. */
.cw-prefsbtn { gap: 6px; }
/* Mounted only above 640px, and hidden again if the window is dragged down to a phone while the
   workspace stays mounted — cwDockFit measures with offsetWidth and a display:none button is 0. */
@media (max-width: 640px) { .cw-prefsbtn { display: none; } }

.cw-prefs {
  position: absolute; inset: 0; z-index: 24;
  display: flex; align-items: center; justify-content: center; padding: 18px;
  background: color-mix(in srgb, var(--color-bg) 58%, transparent);
  backdrop-filter: blur(3px);
}
.cw-prefs__card {
  width: min(96vw, 540px); max-height: 88%;
  display: flex; flex-direction: column;
  border: 1px solid var(--color-border-strong); border-radius: 16px;
  background: var(--color-surface); box-shadow: 0 18px 60px rgba(0, 0, 0, .25);
  overflow: hidden; animation: cwPrefsIn .26s var(--ease-out) both;
}
@keyframes cwPrefsIn { from { opacity: 0; transform: translateY(10px) scale(.985); } to { opacity: 1; transform: none; } }
.cw-prefs__head { display: flex; align-items: flex-start; gap: 10px; padding: 14px 16px 12px; border-bottom: 1px solid var(--color-border-hairline); }
.cw-prefs__ttl { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; text-align: start; }
.cw-prefs__ttl b { font: 600 14.5px var(--font-sans); color: var(--color-text-primary); }
.cw-prefs__ttl i { font: 400 11.5px/1.5 var(--font-sans); font-style: normal; color: var(--color-text-muted); }
[dir="rtl"] .cw-prefs__ttl b, [dir="rtl"] .cw-prefs__ttl i { font-family: var(--font-arabic); }
.cw-prefs__x { flex: 0 0 auto; width: 28px; height: 28px; display: grid; place-items: center; border: none; border-radius: 9px; background: var(--color-bg-subtle); color: var(--color-text-secondary); cursor: pointer; transition: .15s; }
.cw-prefs__x svg { width: 13px; height: 13px; }
.cw-prefs__x:hover { background: var(--color-accent-soft); color: var(--color-accent); }
.cw-prefs__body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px 16px 8px; }
.cw-prefs__row { display: flex; align-items: center; gap: 16px; padding: 11px 0; border-bottom: 1px solid var(--color-border-hairline); }
.cw-prefs__row:last-child { border-bottom: none; }
.cw-prefs__lbl { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; text-align: start; }
.cw-prefs__lbl b { font: 600 12.5px var(--font-sans); color: var(--color-text-primary); }
.cw-prefs__lbl i { font: 400 11px/1.45 var(--font-sans); font-style: normal; color: var(--color-text-muted); }
[dir="rtl"] .cw-prefs__lbl b, [dir="rtl"] .cw-prefs__lbl i { font-family: var(--font-arabic); }

/* The ported segmented control. --n is how many options there are and --i which one is on; the
   thumb's whole geometry is those two numbers, so a repaint is one custom property, not a
   measurement. Equal tracks come from grid-auto-columns, which is what makes the arithmetic
   below true rather than approximately true. */
.cw-prefs__seg {
  position: relative; flex: 0 0 auto;
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  padding: 3px; min-width: 156px;
  border: 1px solid var(--color-border-hairline); border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
}
.cw-prefs__thumb {
  position: absolute; top: 3px; bottom: 3px;
  width: calc((100% - 6px) / var(--n, 1));
  inset-inline-start: calc(var(--i, 0) * (100% - 6px) / var(--n, 1) + 3px);
  border: 1px solid var(--color-border-strong); border-radius: var(--radius-pill);
  background: var(--color-surface); box-shadow: 0 1px 3px rgba(0, 0, 0, .10);
  transition: inset-inline-start .24s var(--ease-out);
}
.cw-prefs__opt {
  position: relative; z-index: 1;
  padding: 6px 10px; border: none; border-radius: var(--radius-pill); background: transparent;
  color: var(--color-text-muted); font: 600 11.5px var(--font-sans);
  white-space: nowrap; cursor: pointer; transition: color .18s;
}
[dir="rtl"] .cw-prefs__opt { font-family: var(--font-arabic); }
.cw-prefs__opt:hover, .cw-prefs__opt.is-on { color: var(--color-text-primary); }

.cw-prefs__sw { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 9px; padding: 2px; border: none; background: transparent; cursor: pointer; }
.cw-prefs__swt { position: relative; width: 38px; height: 22px; border-radius: 999px; border: 1px solid var(--color-border-strong); background: var(--color-bg-subtle); transition: background .2s, border-color .2s; }
.cw-prefs__swk { position: absolute; top: 2px; inset-inline-start: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--color-text-muted); transition: inset-inline-start .22s var(--ease-out), background .2s; }
.cw-prefs__sw.is-on .cw-prefs__swt { background: var(--color-accent); border-color: var(--color-accent); }
.cw-prefs__sw.is-on .cw-prefs__swk { inset-inline-start: 18px; background: #fff; }
.cw-prefs__swv { min-width: 44px; text-align: start; font: 600 11px var(--font-sans); color: var(--color-text-muted); }
[dir="rtl"] .cw-prefs__swv { font-family: var(--font-arabic); }
.cw-prefs__sw.is-on .cw-prefs__swv { color: var(--color-accent); }

.cw-prefs__step { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 2px; padding: 3px; border: 1px solid var(--color-border-hairline); border-radius: var(--radius-pill); background: var(--color-bg-subtle); }
.cw-prefs__stepb { width: 26px; height: 24px; display: grid; place-items: center; border: none; border-radius: 999px; background: transparent; color: var(--color-text-secondary); font: 600 15px var(--font-mono); cursor: pointer; transition: .15s; }
.cw-prefs__stepb:hover { background: var(--color-surface); color: var(--color-accent); }
.cw-prefs__num { min-width: 42px; text-align: center; font: 600 12.5px var(--font-mono); font-variant-numeric: tabular-nums; color: var(--color-text-primary); }

.cw-prefs__foot { display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--color-border-hairline); }
.cw-prefs__reset { margin-inline-end: auto; padding: 7px 13px; border: 1px solid var(--color-border-strong); border-radius: var(--radius-pill); background: transparent; color: var(--color-text-muted); font: 500 11.5px var(--font-sans); cursor: pointer; transition: .15s; }
.cw-prefs__reset:hover { border-color: var(--color-accent); color: var(--color-accent); }
.cw-prefs__done { padding: 7px 18px; border: none; border-radius: var(--radius-pill); background: var(--color-accent); color: #fff; font: 600 11.5px var(--font-sans); cursor: pointer; transition: .15s; }
.cw-prefs__done:hover { filter: brightness(1.08); }
[dir="rtl"] .cw-prefs__reset, [dir="rtl"] .cw-prefs__done { font-family: var(--font-arabic); }

/* One ring, on the thing the eye is on. The switch puts it on its track rather than the whole
   button, because the button is a track plus a word and a ring around both reads as a mistake. */
.cw-prefs__opt:focus-visible,
.cw-prefs__stepb:focus-visible,
.cw-prefs__x:focus-visible,
.cw-prefs__reset:focus-visible,
.cw-prefs__done:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.cw-prefs__sw:focus-visible { outline: none; }
.cw-prefs__sw:focus-visible .cw-prefs__swt { box-shadow: var(--shadow-focus); }

/* Both switches, as always: the media query serves the OS setting and the attribute serves the
   app's own Reduced toggle. :root IS <html>, so it has to be the leftmost compound. */
:root[data-motion="off"] .cw-prefs__card,
:root[data-motion="off"] .cw-prefs__thumb,
:root[data-motion="off"] .cw-prefs__swk,
:root[data-motion="off"] .cw-prefs__opt { animation: none; transition: none; }
@media (prefers-reduced-motion: reduce) {
  .cw-prefs__card, .cw-prefs__thumb, .cw-prefs__swk, .cw-prefs__opt { animation: none; transition: none; }
}

/* ── what the settings actually act through ─────────────────────────────────
   Appended at the end of the stylesheet on purpose: every .cw-ed .CodeMirror rule these have to
   beat is further up, and an attribute selector plus the class already outranks them. */
.cw-ed[data-cwp-fs] .CodeMirror,
.cw-ed[data-cwp-fs] .cw-ed__ta { font-size: var(--cwp-fs); }
/* The fallback textarea has no CodeMirror to hold tabSize/lineWrapping for it. */
.cw-ed .cw-ed__ta { -moz-tab-size: var(--cwp-tab, 2); tab-size: var(--cwp-tab, 2); }
.cw-ed[data-cwp-wrap="on"] .cw-ed__ta { white-space: pre-wrap; overflow-wrap: anywhere; }
.cw-ed[data-cwp-wrap="off"] .cw-ed__ta { white-space: pre; overflow-x: auto; }

/* CodeMirror's cursor is an absolutely positioned div containing one nbsp, with width:0 and a
   left border. width:auto is therefore exactly one character cell in a monospace face, which is
   what makes a block and an underline possible without an addon or a keymap. */
.cw-ed[data-cwp-cur="block"] .CodeMirror-cursor { width: auto; border: 0; background: var(--color-accent); opacity: .34; }
.cw-ed[data-cwp-cur="under"] .CodeMirror-cursor { width: auto; border: 0; border-bottom: 2px solid var(--color-accent); }

/* One dot per column, drawn as a repeating background rather than a glyph, because the character
   underneath is a real space and replacing it would change what gets copied. 1ch is the cell in
   a monospace face, so the dots land under the spaces and not between them. */
.cw-ed[data-cwp-ws="all"] .CodeMirror .cm-cwws-sp {
  background-image: radial-gradient(circle at 50% 54%, currentColor .95px, transparent 1.05px);
  background-size: 1ch 100%; background-repeat: repeat-x; opacity: .40;
}
/* Tabs need no overlay: CodeMirror wraps every one of them in .cm-tab already. */
.cw-ed[data-cwp-ws="all"] .CodeMirror .cm-tab {
  background: linear-gradient(to right, currentColor, currentColor) no-repeat center / calc(100% - 4px) 1px;
  opacity: .34;
}
.cw-ed[data-cwp-ws="trail"] .CodeMirror .cm-cwws-tr { background: color-mix(in srgb, #c0392b 30%, transparent); border-radius: 2px; }


/* ══════ FIRAS CODE — OVERVIEW RAIL: THE THUMB, AND THE MATCH YOU ARE ON ══════
   Three additions to the rail defined above, and nothing else moves.

   (1) THE VIEWPORT BOX IS A THUMB. cwMapMount now remembers how far down the box you grabbed it
   and carries it rather than teleporting it under your finger. These rules are the only thing
   that says so on screen. The box itself stays pointer-events:none and the cursor is set on the
   RAIL: one pointer capture on one element is far easier to reason about than a second target
   that would have to hand the drag back mid-gesture.

   (2) THE ACTIVE FIND MATCH. .cwmap__m bars are deduped per 3px row on purpose, so "current"
   could never be a state of one — forty hits in one band are one bar. It is its own node, and it
   is ringed in the surface colour rather than merely brighter, because on the accent-coloured
   band it would otherwise sit in it reads as nothing at all.

   (3) All physical-free: logical inset-* like the rest of the rail, which is itself pinned
   physically right in both languages because .cw-ed .CodeMirror never mirrors. See the note over
   .cwmap. Nothing here is defined only inside a media query, so all six themes get it. */
.cwmap.is-onview { cursor: grab; }
.cwmap.is-grab { cursor: grabbing; }
.cwmap__view {
  transition: background-color var(--dur-fast, .14s) var(--ease-out, ease),
              outline-color var(--dur-fast, .14s) var(--ease-out, ease);
}
.cwmap.is-onview .cwmap__view,
.cwmap.is-grab .cwmap__view {
  background: color-mix(in srgb, var(--color-text-primary) 18%, transparent);
  outline-color: color-mix(in srgb, var(--color-text-primary) 32%, transparent);
}
/* The grip appears only while the box is worth grabbing. currentColor, so it is the rail's own
   text colour in every theme rather than a grey that is invisible in two of them. */
.cwmap__view::after {
  content: "";
  position: absolute;
  inset-inline: 5px;
  inset-block-start: 50%;
  block-size: 1px;
  margin-block-start: -1px;
  border-radius: 1px;
  background: currentColor;
  opacity: 0;
  transition: opacity var(--dur-fast, .14s) var(--ease-out, ease);
}
.cwmap.is-onview .cwmap__view::after,
.cwmap.is-grab .cwmap__view::after { opacity: .3; }

/* Taller and ringed rather than recoloured: the hit bars are already the accent, so the only way
   to say "this one" without inventing a seventh colour is to give it room and a halo. */
.cwmap__cur {
  position: absolute;
  inset-inline: 0;
  z-index: 3;
  block-size: 4px;
  margin-block-start: -1px;
  border-radius: 2px;
  pointer-events: none;
  background: var(--color-accent);
  box-shadow: 0 0 0 1px var(--color-surface),
              0 0 0 2px color-mix(in srgb, var(--color-accent) 50%, transparent);
}
.cwmap__cur[hidden] { display: none; }

/* Both, as everywhere in this file: the media query serves the OS setting and the attribute
   serves the app's own Settings panel. :root IS <html>, so it must be the leftmost compound. */
@media (prefers-reduced-motion: reduce) {
  .cwmap__view, .cwmap__view::after { transition: none; }
}
:root[data-motion="off"] .cwmap__view,
:root[data-motion="off"] .cwmap__view::after { transition: none; }

/* == FIRAS CODE -- MULTIPLE CURSORS ==================================================
   Two things have to be visible for multi-cursor editing to be usable rather than alarming:
   which carets are live, and how many there are. The count is a status segment built in
   cwMcSeg; this is the caret half.

   SPECIFICITY IS DELIBERATE. ensureCodeMirror injects CodeMirror's own stylesheet with
   document.head.appendChild AT RUNTIME, so it lands AFTER this file and wins every tie.
   Each rule below is one compound heavier than the rule it is answering, and the tokens sit
   on bare :root so all six themes get them without a media query owning a colour. */
:root{
  --cw-mc-caret:var(--color-accent);
  --cw-mc-tint:color-mix(in srgb,var(--color-accent) 22%,transparent);
}
.CodeMirror.cw-mc-on .CodeMirror-cursor{border-color:var(--cw-mc-caret);border-width:2.4px}
.CodeMirror.cw-mc-on .CodeMirror-selected,
.CodeMirror.cw-mc-on.CodeMirror-focused .CodeMirror-selected{background:var(--cw-mc-tint)}
.cw-status__mc{color:var(--color-accent)}
.cw-status__mc .cw-status__ico{opacity:.95;color:var(--color-accent)}
.cw-status__mc:hover{background:color-mix(in srgb,var(--color-accent) 16%,transparent);color:var(--color-accent)}
.cw-status__mc:hover .cw-status__ico{color:var(--color-accent)}

/* ═══════ FIRAS CODE — indent guides + bracket pairs (cwig) ═══════════════════
   The editor is warm PAPER in five of the six themes and charcoal in "dark" — the
   .cw-ed block above splits exactly there and nowhere else, so these tokens split
   there too. Defined on .cw-ed itself and never only inside a media query: a colour
   that exists only under a preference is a colour half the app cannot reach. */
.cw-ed{
  --cwig-line:color-mix(in srgb,#3f3e38 15%,transparent);
  --cwig-on:color-mix(in srgb,#A8562E 72%,transparent);
  --cwig-hit:color-mix(in srgb,#A8562E 16%,transparent);
  --cwig-ring:color-mix(in srgb,#A8562E 46%,transparent);
  --cwig-b0:#B3512F;--cwig-b1:#5B6ABF;--cwig-b2:#23786A;
  --cwig-b3:#9C6B1F;--cwig-b4:#7C3AED;--cwig-b5:#A03D6B;
}
[data-theme="dark"] .cw-ed{
  --cwig-line:color-mix(in srgb,#E4E1D6 17%,transparent);
  --cwig-on:color-mix(in srgb,#E39A72 82%,transparent);
  --cwig-hit:color-mix(in srgb,#E39A72 20%,transparent);
  --cwig-ring:color-mix(in srgb,#E39A72 55%,transparent);
  --cwig-b0:#E08B66;--cwig-b1:#9BA8E8;--cwig-b2:#6FB8AB;
  --cwig-b3:#E0B35E;--cwig-b4:#C4A7F5;--cwig-b5:#E58AB4;
}
/* Inside .CodeMirror-sizer, which is what CodeMirror's own "local" coordinates are
   measured against. z-index 1 puts the rails above the selection layer and below
   pre.CodeMirror-line, which CodeMirror ships at 2 — so a guide can never sit on top
   of a glyph. No overflow:hidden: a sizer that has not been measured yet would clip
   every bar to nothing. */
.cw-ed .cwig{position:absolute;left:0;top:0;right:0;bottom:0;z-index:1;pointer-events:none}
/* PHYSICAL left/width. .cw-ed .CodeMirror is direction:ltr in Arabic as well as in
   English, so the code's axis never mirrors and a logical inset would be wrong there. */
.cw-ed .cwig__g{
  position:absolute;width:1px;
  background:var(--cwig-line);
  transition:background var(--dur-fast,.14s) ease;
}
.cw-ed .cwig__g[hidden]{display:none}
.cw-ed .cwig__g--on{background:var(--cwig-on);box-shadow:0 0 0 .5px var(--cwig-on)}
/* Three classes deep so these win over .cw-ed .cm-bracket, which CodeMirror puts on
   the same span. No font-weight: a synthesised bold can widen a glyph in a fallback
   monospace, and the caret would drift a pixel every time a pair lit up. */
.cw-ed .CodeMirror .cwig-b--d0{color:var(--cwig-b0)}
.cw-ed .CodeMirror .cwig-b--d1{color:var(--cwig-b1)}
.cw-ed .CodeMirror .cwig-b--d2{color:var(--cwig-b2)}
.cw-ed .CodeMirror .cwig-b--d3{color:var(--cwig-b3)}
.cw-ed .CodeMirror .cwig-b--d4{color:var(--cwig-b4)}
.cw-ed .CodeMirror .cwig-b--d5{color:var(--cwig-b5)}
.cw-ed .CodeMirror .cwig-b--on{
  background:var(--cwig-hit);
  border-radius:2px;
  box-shadow:0 0 0 1px var(--cwig-ring);
}
/* The switch, wearing the status bar's own clothes. */
.cw-status__seg.cwig-btn.is-on .cw-status__ico{opacity:1;color:var(--color-accent)}
.cw-status__seg.cwig-btn:not(.is-on) .cw-status__ico{opacity:.4}
.cw-status__seg.cwig-btn:not(.is-on) .cw-status__v{opacity:.55;text-decoration:line-through}
.cw-status__seg.cwig-btn:focus-visible{outline:2px solid var(--color-accent);outline-offset:1px}
/* Degrade to NOTHING rather than to something half-placed: the mount already refuses
   these two, and this covers a viewport that crossed the phone breakpoint while the
   workspace was open. */
:root[data-ui="2"] .cw-ed .cwig{display:none}
@media (max-width:640px){.cw-ed .cwig{display:none}}
/* Both switches: the OS preference and the app's own Reduced setting. */
@media (prefers-reduced-motion: reduce){.cw-ed .cwig__g{transition:none}}
:root[data-motion="off"] .cw-ed .cwig__g{transition:none}


/* ═══ FIRAS CODE — QUICK FIXES ═══════════════════════════════════════════════════════
   Three surfaces on the same host .cw-ed already gives find (.cw-fr, 16) and the outline
   (.cw-ol, 15): a bulb in a CodeMirror gutter, an inline card that is a real line widget
   inside the document flow, and a panel docked to the bottom INLINE-START corner — the one
   corner of that pane nothing else claims, so all three can be open at once.

   z-index 15 puts the panel in the same band as the outline and keeps it under .cw-diff (20)
   and .cw-snap (22): a change review and the version sheet are modal, and a list of repairs
   must never float over either.

   Every colour is a token, so the six themes carry it with no per-theme rule, and every offset
   is logical (inset-inline-*, padding-inline, text-align:start) so the panel docks to the
   correct edge under Arabic without a mirrored copy. Numbers, paths, code and tag names all
   declare dir="ltr" in the markup — the shell is LTR for good, and a bare count inside an
   Arabic sentence is the one shape that comes out reordered. */

/* the gutter bulb — a column CodeMirror only draws because cwQfWireCm added it at runtime */
.CodeMirror .CodeMirror-gutter.cw-qf-gut { width: 16px; }
.cw-qf-bulb {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 16px; block-size: 100%;
  color: var(--color-warn); cursor: pointer; opacity: .9;
}
.cw-qf-bulb:hover { opacity: 1; color: var(--color-accent); }
.cw-qf-bulb svg { display: block; }

/* the count on the editor's bottom inline-start corner; hidden at zero, because a reading of
   0 problems is furniture — the same rule the status bar's own counter follows */
.cw-qf-chip {
  position: absolute; inset-inline-start: 10px; inset-block-end: 10px; z-index: 14;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 9px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
  color: var(--color-text-muted);
  font: 600 11px var(--font-sans);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.cw-qf-chip[hidden] { display: none; }
.cw-qf-chip.is-fixable { color: var(--color-warn); border-color: color-mix(in srgb, var(--color-warn) 45%, var(--color-border-strong)); }
.cw-qf-chip:hover { color: var(--color-accent); border-color: var(--color-accent); }
.cw-qf-chip:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
.cw-qf-chip__ico { display: inline-flex; }
.cw-qf-chip__n { font-variant-numeric: tabular-nums; unicode-bidi: isolate; }

/* ── the inline card, ported from 21st.dev's Edit Tool (@serafimcloud, 12385) ──────────
   A proposed edit drawn as a diff: header, a −/+ body, an approval footer. Here it is a
   CodeMirror line widget rather than a floating popover, which means it can never be
   mis-positioned, never covers the line it is talking about, and scrolls with the code. */
.cw-qf-card {
  margin: 4px 0 8px;
  margin-inline-start: 8px;
  border: 1px solid var(--color-border-strong);
  border-inline-start: 3px solid var(--color-warn);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  max-inline-size: 640px;
}
.cw-qf-card--err { border-inline-start-color: var(--color-danger); }
.cw-qf-card__head {
  display: flex; align-items: center; gap: 7px;
  padding-block: 7px; padding-inline: 10px;
  background: var(--color-bg-subtle);
  border-block-end: 1px solid var(--color-border-hairline);
  font: 600 11.5px var(--font-sans); color: var(--color-text-primary);
}
[dir="rtl"] .cw-qf-card__head { font-family: var(--font-arabic); }
.cw-qf-card__ico { flex: 0 0 auto; display: inline-flex; color: var(--color-warn); }
.cw-qf-card--err .cw-qf-card__ico { color: var(--color-danger); }
.cw-qf-card__ln-no {
  flex: 0 0 auto; margin-inline-start: auto;
  padding: 1px 6px; border-radius: var(--radius-pill);
  background: var(--color-surface-sunken, var(--color-bg-subtle));
  font: 600 10.5px var(--font-mono); color: var(--color-text-muted);
  font-variant-numeric: tabular-nums; unicode-bidi: isolate;
}
.cw-qf__msg { flex: 1 1 auto; min-inline-size: 0; text-align: start; line-height: 1.45; }
.cw-qf__tok {
  padding: 0 4px; border-radius: var(--radius-xs);
  background: var(--color-accent-soft); color: var(--color-accent);
  font: 600 10.5px var(--font-mono); unicode-bidi: isolate;
}
.cw-qf-card__diff { padding: 6px 8px; background: var(--color-surface); }
.cw-qf-card__ln {
  display: flex; align-items: flex-start; gap: 7px;
  padding: 2px 6px; border-radius: var(--radius-xs);
  font: 400 11.5px/1.55 var(--font-mono);
  white-space: pre; overflow-x: auto; scrollbar-width: thin;
}
.cw-qf-card__ln--del { background: color-mix(in srgb, var(--color-danger) 11%, transparent); color: var(--color-text-secondary); }
.cw-qf-card__ln--add { background: color-mix(in srgb, var(--color-success) 13%, transparent); color: var(--color-text-primary); }
.cw-qf-card__sig { flex: 0 0 auto; opacity: .75; }
.cw-qf-card__ln--del .cw-qf-card__sig { color: var(--color-danger); }
.cw-qf-card__ln--add .cw-qf-card__sig { color: var(--color-success); }
.cw-qf-card__code { flex: 1 1 auto; min-inline-size: 0; }
.cw-qf-card__foot {
  display: flex; align-items: center; gap: 8px;
  padding-block: 7px; padding-inline: 10px;
  border-block-start: 1px solid var(--color-border-hairline);
  background: var(--color-bg-subtle);
}
.cw-qf-card__go, .cw-qf-card__skip, .cw-qf__fix, .cw-qf__all {
  border-radius: var(--radius-sm); cursor: pointer;
  font: 600 11px var(--font-sans);
  padding: 5px 11px;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .cw-qf-card__go, [dir="rtl"] .cw-qf-card__skip,
[dir="rtl"] .cw-qf__fix, [dir="rtl"] .cw-qf__all { font-family: var(--font-arabic); }
.cw-qf-card__go, .cw-qf__all {
  border: 1px solid transparent;
  background: var(--color-accent); color: var(--color-on-accent);
}
.cw-qf-card__go:hover, .cw-qf__all:hover { background: var(--color-accent-hover, var(--color-accent-deep)); }
.cw-qf-card__skip, .cw-qf__fix {
  border: 1px solid var(--color-border-strong);
  background: transparent; color: var(--color-text-muted);
}
.cw-qf-card__skip:hover, .cw-qf__fix:hover { color: var(--color-accent); border-color: var(--color-accent); background: var(--color-accent-soft); }
.cw-qf-card__go:focus-visible, .cw-qf-card__skip:focus-visible,
.cw-qf__fix:focus-visible, .cw-qf__all:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
.cw-qf-card__skip { margin-inline-start: auto; }
.cw-qf-card__nofix, .cw-qf__nofix {
  font: 500 10.5px var(--font-sans); color: var(--color-text-muted);
  padding: 3px 7px; border-radius: var(--radius-pill);
  background: var(--color-bg-subtle); border: 1px dashed var(--color-border-hairline);
  white-space: nowrap;
}
[dir="rtl"] .cw-qf-card__nofix, [dir="rtl"] .cw-qf__nofix { font-family: var(--font-arabic); }

/* ── the project-wide list ─────────────────────────────────────────────────────────── */
.cw-qf {
  position: absolute; inset-inline-start: 10px; inset-block-end: 10px; z-index: 15;
  inline-size: min(380px, 68%);
  display: flex; flex-direction: column;
  max-block-size: min(66%, 460px);
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--cwv-radius-panel, var(--radius-lg));
  box-shadow: var(--cwv-shadow-float, var(--shadow-lg));
  overflow: hidden;
  opacity: 0; transform: translateY(8px);
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.cw-qf.is-open { opacity: 1; transform: none; }
.cw-qf__head {
  display: flex; align-items: center; gap: 7px;
  padding-block: 9px; padding-inline: 12px 8px;
  border-block-end: 1px solid var(--color-border-hairline); background: var(--color-bg-subtle);
}
.cw-qf__ico { display: inline-flex; color: var(--color-warn); }
.cw-qf__ttl { flex: 1 1 auto; font: 650 11.5px var(--font-sans); color: var(--color-text-primary); text-align: start; }
[dir="rtl"] .cw-qf__ttl { font-family: var(--font-arabic); }
.cw-qf__n {
  flex: 0 0 auto; padding: 1px 7px; border-radius: var(--radius-pill);
  background: var(--color-accent-soft); color: var(--color-accent);
  font: 700 10.5px var(--font-mono); font-variant-numeric: tabular-nums; unicode-bidi: isolate;
}
.cw-qf__n:empty { display: none; }
.cw-qf__x {
  flex: 0 0 auto; inline-size: 24px; block-size: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  border: none; background: transparent; border-radius: var(--radius-sm);
  color: var(--color-text-muted); cursor: pointer; font-size: 12px;
}
.cw-qf__x:hover { background: var(--color-accent-soft); color: var(--color-accent); }
.cw-qf__x:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: -2px; }
.cw-qf__out { flex: 1 1 auto; min-block-size: 0; overflow-y: auto; padding: 5px; }
.cw-qf__empty { padding: 22px 14px; text-align: center; font: 400 12px/1.6 var(--font-sans); color: var(--color-text-muted); }
[dir="rtl"] .cw-qf__empty { font-family: var(--font-arabic); }
.cw-qf__file {
  padding: 7px 8px 3px;
  font: 600 10.5px var(--font-mono); color: var(--color-text-tertiary, var(--color-text-muted));
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  unicode-bidi: isolate; text-align: start;
}
.cw-qf__row {
  display: flex; align-items: center; gap: 6px;
  padding: 2px 4px 2px 2px; border-radius: var(--radius-sm);
}
.cw-qf__row:hover { background: var(--color-bg-subtle); }
.cw-qf__jump {
  flex: 1 1 auto; min-inline-size: 0;
  display: flex; align-items: center; gap: 7px;
  padding: 6px 8px; border: none; background: transparent; border-radius: var(--radius-sm);
  font: 500 11.5px var(--font-sans); color: var(--color-text-secondary);
  cursor: pointer; text-align: start;
}
[dir="rtl"] .cw-qf__jump { font-family: var(--font-arabic); }
.cw-qf__jump:hover { color: var(--color-text-primary); }
.cw-qf__jump:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: -2px; }
.cw-qf__row--err .cw-qf__jump { border-inline-start: 2px solid var(--color-danger); padding-inline-start: 7px; }
.cw-qf__row--warn .cw-qf__jump { border-inline-start: 2px solid var(--color-warn); padding-inline-start: 7px; }
.cw-qf__ln {
  flex: 0 0 auto; font: 500 10.5px var(--font-mono); color: var(--color-text-muted);
  font-variant-numeric: tabular-nums; unicode-bidi: isolate;
}
.cw-qf__fix { flex: 0 0 auto; }
.cw-qf__nofix { flex: 0 0 auto; }
.cw-qf__foot {
  display: flex; align-items: center; gap: 10px;
  padding-block: 8px; padding-inline: 10px;
  border-block-start: 1px solid var(--color-border-hairline); background: var(--color-bg-subtle);
}
.cw-qf__all[hidden] { display: none; }
.cw-qf__alln { font-variant-numeric: tabular-nums; unicode-bidi: isolate; opacity: .85; }
.cw-qf__hint {
  flex: 1 1 auto; min-inline-size: 0;
  font: 400 10.5px var(--font-sans); color: var(--color-text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: end;
}
[dir="rtl"] .cw-qf__hint { font-family: var(--font-arabic); }

/* Both switches, by hand: the global reduced-motion block and the app's own data-motion="off"
   already out-specify a bare class rule, and naming these anyway is what keeps that true if
   either list is ever narrowed. Nothing here MOVES to report — the card's meaning is in the
   −/+ rows, not in how it arrives — so removing the transition removes nothing. */
@media (prefers-reduced-motion: reduce) {
  .cw-qf, .cw-qf-chip, .cw-qf-card__go, .cw-qf-card__skip, .cw-qf__fix, .cw-qf__all { transition: none; }
}
:root[data-motion="off"] .cw-qf,
:root[data-motion="off"] .cw-qf-chip,
:root[data-motion="off"] .cw-qf-card__go,
:root[data-motion="off"] .cw-qf-card__skip,
:root[data-motion="off"] .cw-qf__fix,
:root[data-motion="off"] .cw-qf__all { transition: none; }

/* The phone never gets here — cwQfFits stands the whole feature down at ≤860px, where .cw-ed
   is the entire screen and three pieces of chrome would be bought with rows of code. These
   two rules exist so a window DRAGGED across that line while the panel is open does not leave
   it stranded mid-screen before the next scan takes it away. */
@media (max-width: 860px) {
  .cw-qf, .cw-qf-chip { display: none; }
}


/* ═══ FIRAS CODE — REORGANISING THE TREE (cwx_filetree_dnd) ═════════════════════
   Every colour here is a token, defined at :root by the theme layer, so the six themes carry it
   without a single hard-coded hex. Nothing below lives only inside a media query. */

/* A row picked up but not yet let go: still legible, visibly lifted off the list. */
.cw-tree__list.is-cwdragging .cw-file.is-msel,
.cw-tree__list.is-cwdragging .cw-folder.is-mgrab { opacity: .45; }
.cw-tree__list.is-cwdragging { cursor: grabbing; }

/* THE SELECTION. Distinct from .is-active, which means "this file is open in the editor" — a
   file can be both, so the two marks have to be readable at once: active keeps the accent wash
   and its inline-start bar, selected adds a hairline ring and a weight it does not have. */
.cw-file.is-msel {
  background: color-mix(in srgb, var(--color-accent) 7%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-accent) 38%, transparent);
  color: var(--color-text-primary);
}
.cw-file.is-msel .cw-file__p { font-weight: 600; }
.cw-file.is-active.is-msel { background: color-mix(in srgb, var(--color-accent) 15%, transparent); }

/* GRABBED from the keyboard — a dashed outline, because the row is in transit and a solid one
   would read as another kind of selection. */
.cw-file.is-mgrab, .cw-folder.is-mgrab {
  outline: 1.5px dashed color-mix(in srgb, var(--color-accent) 70%, transparent);
  outline-offset: -2px;
  opacity: .8;
}

/* WHERE IT WILL LAND. The folder heading, the file row and the empty list all answer the same
   way so the hand never has to learn three signals. */
.cw-folder.is-cwdrop, .cw-file.is-cwdrop {
  background: color-mix(in srgb, var(--color-accent) 18%, transparent);
  box-shadow: inset 0 0 0 1.5px var(--color-accent);
  color: var(--color-text-primary);
}
.cw-tree__list.is-cwdrop {
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--color-accent) 55%, transparent);
  border-radius: var(--radius-md);
}

/* THE TWO ZONES. Buttons first and drop targets second: "put these in a new folder" has to work
   from the keyboard, and a bare rectangle you can only aim at with a mouse does not. Hidden
   until there is a selection or a drag, so at rest the rail is exactly the list it was. */
.cw-dndzone {
  display: none;
  gap: 6px;
  align-items: center;
  padding: 8px 8px 10px;
  border-top: 1px solid var(--color-border-hairline);
  background: var(--color-surface);
  position: sticky;
  inset-block-end: 0;
}
.cw-dndzone.is-on { display: flex; }
.cw-dndzone__n {
  flex: 0 0 auto;
  min-inline-size: 20px;
  text-align: center;
  font: 600 10px var(--font-mono);
  color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 13%, transparent);
  border-radius: 999px;
  padding: 2px 6px;
}
.cw-dndzone__n:empty { display: none; }
.cw-dndzone__b {
  flex: 1 1 auto;
  min-inline-size: 0;
  padding: 5px 8px;
  border: 1px dashed var(--color-border-hairline);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-secondary);
  font: 600 10.5px var(--font-sans);
  text-align: center;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.cw-dndzone__b:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.cw-dndzone__b:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.cw-dndzone__b.is-cwdrop {
  border-style: solid;
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 16%, transparent);
  color: var(--color-accent);
}

/* Both switches, never one: the media query serves the OS setting and the attribute serves the
   app's own Reduced control. :root is <html>, so it must be the leftmost compound. */
:root[data-motion="off"] .cw-dndzone__b,
:root[data-motion="off"] .cw-file.is-msel,
:root[data-motion="off"] .cw-folder.is-cwdrop,
:root[data-motion="off"] .cw-file.is-cwdrop { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .cw-dndzone__b, .cw-file.is-msel, .cw-folder.is-cwdrop, .cw-file.is-cwdrop { transition: none; }
}

/* Windows High Contrast throws every custom colour away, including the drop highlight — which
   is the one thing on screen saying where the file is about to go. */
@media (forced-colors: active) {
  .cw-folder.is-cwdrop, .cw-file.is-cwdrop, .cw-dndzone__b.is-cwdrop { outline: 2px solid Highlight; outline-offset: -2px; }
  .cw-file.is-msel { outline: 1px solid Highlight; outline-offset: -2px; }
}

/* ══ FIRAS CODE — RUN & TASK BAR (above the three right-hand panes) ════════════════
   Same trick .cw-runbar plays with --rb: one custom property carries the state's tone and
   everything downstream reads it, so five states cost five declarations instead of five
   rulesets. The tones are the theme's own tokens — six themes redefine them and this follows.
   Deliberately NOT hidden by .cw-prevwrap.is-cwe: a Python-only project is exactly the case
   where the run strip disappears and this bar has to stay. */
.cw-tbar{
  flex:0 0 auto;box-sizing:border-box;position:relative;
  background:var(--color-bg-subtle);border-bottom:1px solid var(--color-border-hairline);
  --tb:var(--color-text-muted);
}
.cw-tbar[data-state="run"]{--tb:var(--color-accent)}
.cw-tbar[data-state="ok"]{--tb:var(--color-success)}
/* No --color-warn exists; softening the theme's OWN error tone toward its body text is a caution
   that follows the palette. Copied from .cw-runbar[data-state="warn"] on purpose. */
.cw-tbar[data-state="warn"]{--tb:color-mix(in srgb,var(--color-error) 55%,var(--color-text-secondary))}
.cw-tbar[data-state="fail"]{--tb:var(--color-error)}
/* No project open at all is not an empty bar; it is no bar. */
.cw-tbar.is-bare{display:none}

/* No overflow on the row: an overflow ancestor clips :focus-visible, and the global focus style
   in this sheet is a box-shadow. Everything that can shrink does, so nothing needs to scroll. */
.cw-tbar__row{display:flex;align-items:center;gap:8px;min-height:34px;padding:5px 10px}
.cw-tbar__go{
  flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;white-space:nowrap;
  padding:4px 12px;border-radius:var(--radius-pill);
  border:1px solid color-mix(in srgb,var(--color-accent) 30%,transparent);
  background:var(--color-accent-soft);color:var(--color-accent);
  font:600 11.5px var(--font-sans);cursor:pointer;
  transition:border-color var(--dur-slow) var(--ease-out),opacity var(--dur-slow) var(--ease-out);
}
.cw-tbar__go:hover:not(:disabled){border-color:var(--color-accent)}
.cw-tbar__go:disabled{opacity:.45;cursor:default}
.cw-tbar__go-i{font-size:9px;line-height:1}
.cw-tbar__pick{
  flex:0 1 auto;min-width:0;display:inline-flex;align-items:center;gap:6px;
  padding:4px 9px;border-radius:8px;border:1px solid var(--color-border-hairline);
  background:var(--color-surface);color:var(--color-text-primary);
  font:500 11.5px var(--font-sans);cursor:pointer;
  transition:border-color var(--dur-slow) var(--ease-out);
}
.cw-tbar__pick:hover:not(:disabled){border-color:var(--color-accent)}
.cw-tbar__pick:disabled{opacity:.55;cursor:default}
/* cwLangLabel returns "HTML" / "Python" in both languages, so letter-spacing here can never
   land on Arabic and break its joins. */
.cw-tbar__kind,.cw-tbar__optkind{
  flex:0 0 auto;font:700 9.5px var(--font-mono);letter-spacing:.04em;
  text-transform:uppercase;color:var(--color-text-muted);
}
/* No `direction` here: the node carries dir="ltr" for a path and dir="auto" for the empty-state
   sentence, and an author `direction` would overrule both. */
.cw-tbar__name{
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-family:var(--font-mono);font-size:11px;unicode-bidi:isolate;
}
.cw-tbar__chev{flex:0 0 auto;font-size:9px;color:var(--color-text-muted);transition:transform var(--dur-slow) var(--ease-out)}
.cw-tbar[data-open="1"] .cw-tbar__chev{transform:rotate(180deg)}

.cw-tbar__res{
  flex:0 1 auto;min-width:0;overflow:hidden;display:inline-flex;align-items:center;gap:6px;
  white-space:nowrap;color:var(--tb);font:600 11px var(--font-sans);
}
/* The indicator: a ring and a core, both taking the row's colour through currentColor. */
.cw-tbar__dot,.cw-tbar__optdot{position:relative;flex:0 0 auto;width:7px;height:7px;border-radius:50%;color:var(--tb)}
.cw-tbar__dot::before,.cw-tbar__dot::after,
.cw-tbar__optdot::before,.cw-tbar__optdot::after{content:"";position:absolute;border-radius:50%;background:currentColor}
.cw-tbar__dot::before,.cw-tbar__optdot::before{inset:0;opacity:.3}
.cw-tbar__dot::after,.cw-tbar__optdot::after{inset:1.5px}
.cw-tbar[data-state="run"] .cw-tbar__dot::before,
.cw-tbar__opt[data-v="run"] .cw-tbar__optdot::before{animation:cwTbarPing 1.15s cubic-bezier(0,0,.2,1) infinite}
@keyframes cwTbarPing{0%{transform:scale(1);opacity:.5}75%,100%{transform:scale(2.2);opacity:0}}
.cw-tbar__verdict{overflow:hidden;text-overflow:ellipsis}
.cw-tbar__ms{flex:0 0 auto;font:600 10.5px var(--font-mono);color:var(--color-text-muted);direction:ltr;unicode-bidi:isolate}

.cw-tbar__tail{
  flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  text-align:start;padding:3px 7px;border-radius:6px;border:1px solid transparent;
  background:transparent;color:var(--color-text-muted);
  font:11px/1.4 var(--font-mono);cursor:pointer;unicode-bidi:plaintext;
  transition:background-color var(--dur-slow) var(--ease-out),border-color var(--dur-slow) var(--ease-out);
}
.cw-tbar__tail:disabled{cursor:default}
.cw-tbar__tail:hover:not(:disabled){background:var(--color-surface);border-color:var(--color-border-hairline)}
.cw-tbar__tail[data-lv="error"]{color:var(--color-error)}
.cw-tbar__tail[data-lv="warn"]{color:color-mix(in srgb,var(--color-error) 55%,var(--color-text-secondary))}

/* The list is a disclosure, not a popover: it pushes the panes down instead of floating over
   them, which is why nothing here needs positioning, a z-index or a scroll listener. */
.cw-tbar__list{
  max-height:min(40vh,260px);overflow-y:auto;padding:5px 6px 7px;
  border-top:1px solid var(--color-border-hairline);
  display:flex;flex-direction:column;gap:2px;
}
.cw-tbar__opt{
  display:grid;grid-template-columns:auto auto minmax(0,1fr) auto;align-items:center;gap:8px;
  width:100%;text-align:start;padding:5px 8px;border-radius:8px;
  border:1px solid transparent;background:transparent;color:var(--color-text-primary);
  cursor:pointer;--tb:var(--color-text-muted);
}
.cw-tbar__opt[data-v="ok"]{--tb:var(--color-success)}
.cw-tbar__opt[data-v="warn"]{--tb:color-mix(in srgb,var(--color-error) 55%,var(--color-text-secondary))}
.cw-tbar__opt[data-v="fail"]{--tb:var(--color-error)}
.cw-tbar__opt[data-v="run"]{--tb:var(--color-accent)}
.cw-tbar__opt:hover{background:var(--color-bg-subtle)}
.cw-tbar__opt.is-active{
  border-color:color-mix(in srgb,var(--color-accent) 35%,transparent);
  background:var(--color-accent-soft);
}
.cw-tbar__optname{
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font:12px var(--font-mono);direction:ltr;unicode-bidi:isolate;
}
.cw-tbar__optmeta{display:inline-flex;align-items:center;gap:8px;white-space:nowrap;font:600 10.5px var(--font-sans);color:var(--tb)}
.cw-tbar__optms{font-family:var(--font-mono);color:var(--color-text-muted);direction:ltr;unicode-bidi:isolate}
.cw-tbar__optn{display:inline-flex;align-items:baseline;gap:4px;font-style:normal}
.cw-tbar__optn i{font-style:normal}
.cw-tbar__optn b{font-family:var(--font-mono);font-weight:700;direction:ltr;unicode-bidi:isolate}
.cw-tbar__optn--err{color:var(--color-error)}
.cw-tbar__optn--warn{color:color-mix(in srgb,var(--color-error) 55%,var(--color-text-secondary))}
.cw-tbar__none{margin:0;padding:8px 10px;color:var(--color-text-muted);font:12px/1.6 var(--font-sans)}

/* The bar carries its own dir, so the Arabic face has to be asked for on the bar itself —
   a `[dir="rtl"] .cw-tbar` descendant selector could never match it. */
.cw-tbar[dir="rtl"] .cw-tbar__go-t,
.cw-tbar[dir="rtl"] .cw-tbar__verdict,
.cw-tbar[dir="rtl"] .cw-tbar__optv,
.cw-tbar[dir="rtl"] .cw-tbar__optn i,
.cw-tbar[dir="rtl"] .cw-tbar__none{font-family:var(--font-arabic)}

/* The sheet's global :focus-visible is a box-shadow, and .cw-tbar__list clips on its scroll
   axis, so the rows take an INSET ring that cannot be cut off. */
.cw-tbar__go:focus-visible,.cw-tbar__pick:focus-visible,.cw-tbar__tail:focus-visible{
  outline:2px solid var(--color-accent-deep,var(--color-accent));outline-offset:2px;
}
.cw-tbar__opt:focus-visible{
  outline:2px solid var(--color-accent-deep,var(--color-accent));outline-offset:-2px;
}

/* WHERE IT DOES NOT BELONG: the second interface, whose argument is fewer readouts, and the
   phone, where <=860px gives one pane the whole screen. The root attribute comes FIRST because
   :root IS <html> and `body :root[...]` can never match. */
:root[data-ui="2"] .cw-tbar{display:none}
@media (max-width:860px){.cw-tbar{display:none}}
/* Both switches: the OS setting and the app's own Reduced toggle. The ping is removed and the
   state it was carrying is re-expressed as a steady tint, never simply deleted. */
:root[data-motion="off"] .cw-tbar[data-state="run"] .cw-tbar__dot::before,
:root[data-motion="off"] .cw-tbar__opt[data-v="run"] .cw-tbar__optdot::before{animation:none;opacity:.45}
:root[data-motion="off"] .cw-tbar__go,
:root[data-motion="off"] .cw-tbar__pick,
:root[data-motion="off"] .cw-tbar__chev,
:root[data-motion="off"] .cw-tbar__tail{transition:none}
@media (prefers-reduced-motion:reduce){
  .cw-tbar[data-state="run"] .cw-tbar__dot::before,
  .cw-tbar__opt[data-v="run"] .cw-tbar__optdot::before{animation:none;opacity:.45}
  .cw-tbar__go,.cw-tbar__pick,.cw-tbar__chev,.cw-tbar__tail{transition:none}
}


/* ═══════════ FIRAS CODE — THE CHANGE GUTTER ═══════════════════════════════════
   A CodeMirror gutter column registered at runtime by cwGutMount, plus the card that opens under
   a mark. .cw-ed .CodeMirror is direction:ltr in BOTH languages, so this column and its marks are
   physically left of the code in both — a mark that must line up with a line of code cannot swap
   sides with the UI. The card inside it is chrome, not code, so it carries the UI's direction and
   only its <pre> and its line range are pinned to LTR.

   TOKENS, NOT RAW HEX AT THE POINT OF USE, and never defined only inside a media query. The
   editor ships two skins — the warm light one and the charcoal [data-theme="dark"] one — and all
   six themes land on one of the two, so two token blocks cover every theme. */
.cw-ed{
  --cwg-add:#2E7D5B; --cwg-chg:#B07A22; --cwg-del:#C0392B;
  --cwg-card:#FFFDF6; --cwg-card-bd:#E4DFCD; --cwg-card-tx:#4A483F; --cwg-card-mut:#8F8A7A;
  --cwg-pre-bg:#F6F4EC; --cwg-btn:#EFEBDD; --cwg-btn-h:#E6E0CD; --cwg-ring:#A8562E;
}
[data-theme="dark"] .cw-ed{
  --cwg-add:#4BA784; --cwg-chg:#E0B35E; --cwg-del:#E06A60;
  --cwg-card:#26261F; --cwg-card-bd:#3B3A33; --cwg-card-tx:#E4E1D6; --cwg-card-mut:#918D80;
  --cwg-pre-bg:#1B1B18; --cwg-btn:#33322B; --cwg-btn-h:#3E3D34; --cwg-ring:#E39A72;
}
.cw-ed .CodeMirror-gutter.cwg{width:9px}
.cw-ed .CodeMirror-gutter-elt > .cwg-i{
  display:block;position:relative;height:100%;font:inherit;line-height:inherit;
  cursor:pointer;-webkit-user-select:none;user-select:none;
}
.cw-ed .cwg-i::before{
  content:"";position:absolute;top:1px;bottom:1px;left:2px;width:3px;border-radius:2px;
  transition:width .12s ease,left .12s ease;
}
.cw-ed .cwg-i--add::before{background:var(--cwg-add)}
.cw-ed .cwg-i--chg::before{background:var(--cwg-chg)}
.cw-ed .cwg-i--add:hover::before,.cw-ed .cwg-i--chg:hover::before{width:5px;left:1px}
/* A deletion owns no line, so it is drawn as a wedge ON THE BOUNDARY the lines were cut out of:
   the top edge of the line that now stands there, or the bottom edge when they came off the end. */
.cw-ed .cwg-i--del::before{
  top:-3px;bottom:auto;left:1px;width:0;height:0;background:none;border-radius:0;
  border-left:6px solid var(--cwg-del);border-top:4px solid transparent;border-bottom:4px solid transparent;
}
.cw-ed .cwg-i--del.cwg-i--end::before{top:auto;bottom:-3px}
.cw-ed .cwg-i--del:hover::before{border-left-width:8px}

/* the card. CodeMirror sizes ITS OWN wrapper for a noHScroll widget with
       style.width = wrapperWidth - gutterTotalWidth;  style.paddingLeft = gutterTotalWidth
   which is content-box arithmetic — and the bare `* { box-sizing: border-box }` near the top of
   this stylesheet puts every element in the document into border-box, so the card came out one
   whole gutter narrower than the code it sits over. Restoring the box model CodeMirror
   actually assumes — but ONLY over this card. That parenthetical used to read "the only
   addLineWidget call in app.js" and it is no longer
   true: cwQfCardOpen draws the quick-fix card as a line widget too, and it hangs off the same
   wrapper class. A bare .CodeMirror-linewidget rule here would silently re-size a surface
   this patch has no business touching. :has() names the one wrapper CodeMirror built around
   this card's own node; this stylesheet already leans on :has() in dozens of rules, and where
   it is unsupported the whole rule drops and the card is a gutter-width narrow, never broken.
   That universal rule carries no specificity at all, so this wins without !important. */
.cw-ed .CodeMirror-linewidget:has(> .cwg-peekw){box-sizing:content-box}
.cw-ed .cwg-peekw{max-width:100%}
.cw-ed .cwg-peek{
  margin:3px 8px 6px 0;border:1px solid var(--cwg-card-bd);border-radius:9px;overflow:hidden;
  background:var(--cwg-card);color:var(--cwg-card-tx);
  font:12px/1.5 var(--font-sans,system-ui,sans-serif);box-shadow:var(--shadow-sm);
  animation:cwgPeekIn .16s ease-out both;
}
.cw-ed .cwg-peek__bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:6px 9px;border-bottom:1px solid var(--cwg-card-bd)}
.cw-ed .cwg-peek__dot{width:8px;height:8px;border-radius:3px;flex:0 0 auto}
.cw-ed .cwg-peek__dot--add{background:var(--cwg-add)}
.cw-ed .cwg-peek__dot--chg{background:var(--cwg-chg)}
.cw-ed .cwg-peek__dot--del{background:var(--cwg-del)}
.cw-ed .cwg-peek__kind{font-weight:600}
.cw-ed .cwg-peek__meta{color:var(--cwg-card-mut);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cw-ed .cwg-peek__rng{font:11.5px/1 var(--font-mono);color:var(--cwg-card-mut);white-space:nowrap}
.cw-ed .cwg-peek__sp{flex:1 1 auto;min-width:0}
.cw-ed .cwg-peek__b{
  border:1px solid var(--cwg-card-bd);background:var(--cwg-btn);color:var(--cwg-card-tx);
  border-radius:6px;padding:3px 9px;font:inherit;font-size:11.5px;cursor:pointer;white-space:nowrap;
}
.cw-ed .cwg-peek__b:hover:not(:disabled){background:var(--cwg-btn-h)}
.cw-ed .cwg-peek__b:disabled{opacity:.45;cursor:default}
.cw-ed .cwg-peek__b--go{background:transparent;border-color:var(--cwg-del);color:var(--cwg-del);font-weight:600}
.cw-ed .cwg-peek__b--go:hover{background:color-mix(in srgb,var(--cwg-del) 13%,transparent)}
.cw-ed .cwg-peek__x{
  border:none;background:transparent;color:var(--cwg-card-mut);cursor:pointer;
  width:22px;height:22px;border-radius:6px;font-size:14px;line-height:1;flex:0 0 auto;
}
.cw-ed .cwg-peek__x:hover{background:var(--cwg-btn-h);color:var(--cwg-card-tx)}
.cw-ed .cwg-peek__b:focus-visible,.cw-ed .cwg-peek__x:focus-visible{outline:2px solid var(--cwg-ring);outline-offset:2px}
/* the previous text. Its own scroller in both axes, so a long line scrolls INSIDE the card and
   never widens the editor under it. */
.cw-ed .cwg-peek__pre{
  margin:0;padding:8px 10px;background:var(--cwg-pre-bg);color:var(--cwg-card-tx);
  font:12.5px/1.55 var(--font-mono);white-space:pre;direction:ltr;text-align:left;
  max-height:220px;overflow:auto;
}
.cw-ed .cwg-peek__note{padding:7px 10px;color:var(--cwg-card-mut);font-size:11.5px}

@keyframes cwgPeekIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
/* Both switches, always: the media query serves the OS setting and the attribute serves the
   app's own Settings toggle. :root IS <html>, so it must be the leftmost compound here. */
:root[data-motion="off"] .cw-ed .cwg-peek{animation:none}
:root[data-motion="off"] .cw-ed .cwg-i::before{transition:none}
@media (prefers-reduced-motion: reduce){
  .cw-ed .cwg-peek{animation:none}
  .cw-ed .cwg-i::before{transition:none}
}
/* A desktop window dragged under a phone width: the column and the card stand down rather than
   spend a margin on a screen where the code pane is the whole product. A workspace that MOUNTS
   at that width never builds either — cwGutPossible refuses it. */
@media (max-width: 640px){
  .cw-ed .CodeMirror-gutter.cwg{display:none}
  .cw-ed .cwg-peekw{display:none}
}
@media (forced-colors: active){
  .cw-ed .cwg-i--add::before,.cw-ed .cwg-i--chg::before{background:CanvasText}
  .cw-ed .cwg-i--del::before{border-left-color:CanvasText}
  .cw-ed .cwg-peek{border-color:CanvasText}
}


/* ═════ FIRAS CODE — STATUS BAR: THE THREE NEW READINGS AND THEIR PANEL ═════════════════
   Appended, not woven: the segment rules above are a settled block and an append cannot land in
   the wrong media query or unbalance a brace that is not mine. Every colour here is a token or a
   color-mix of one — including the alarm, which uses --color-error rather than the raw #c0392b
   the older error counter hard-codes. --color-error is defined once per theme, all six of them,
   so this stays legible in black, midnight, graphite and amber instead of only in light. */

/* The encoding segment is the bar's only non-fatal alarm. Red ONLY while a U+FFFD is actually in
   the buffer; the rest of the time it is another grey reading and says nothing. */
.cw-status__enc.is-bad{color:var(--color-error)}
.cw-status__enc.is-bad .cw-status__ico{opacity:.95;color:var(--color-error)}
.cw-status__enc.is-bad:hover{background:color-mix(in srgb,var(--color-error) 14%,transparent);color:var(--color-error)}
.cw-status__enc.is-bad:hover .cw-status__ico{color:var(--color-error)}

/* A pressed segment keeps the accent for as long as its panel is open, so the strip says which
   reading the floating box belongs to. */
.cw-status__seg[aria-expanded="true"]{background:var(--color-accent-soft);color:var(--color-text-primary)}
.cw-status__seg[aria-expanded="true"] .cw-status__ico{opacity:1;color:var(--color-accent)}

/* WIDTH BUDGET. The bar scrolls rather than wraps, and three more segments is real width.
   Encoding goes first because it is the least often needed of the three; the line total goes
   next. Below 860 .cw-status is display:none already, so this only has to cover the band
   between that and a comfortable desktop. */
@media (max-width:1120px){.cw-status__enc{display:none}}
@media (max-width:960px){.cw-status__lines{display:none}}

/* ── THE PANEL ── anchored inside .cw, which is position:relative for good (see the BULLETPROOF
   IDE LAYOUT block far above). Physical left/bottom, both set from script: this shell is fixed
   LTR and the panel's CONTENTS carry the direction. */
.cw-spop{
  position:absolute;z-index:44;min-width:212px;max-width:min(320px,calc(100vw - 32px));
  padding:11px 12px 10px;border-radius:13px;
  border:1px solid var(--color-border-hairline);background:var(--color-surface);
  box-shadow:0 14px 38px color-mix(in srgb,var(--color-text-primary) 15%,transparent),
             0 2px 8px color-mix(in srgb,var(--color-text-primary) 8%,transparent);
  font:500 12px var(--font-sans);color:var(--color-text-primary);
  animation:cwSPopIn .16s cubic-bezier(.2,.8,.3,1)
}
.cw-spop[dir="rtl"]{font-family:var(--font-arabic)}
@keyframes cwSPopIn{from{opacity:0;transform:translateY(4px) scale(.985)}to{opacity:1;transform:none}}

/* The arrow, from the anchored-popover model: a rotated square with only its two OUTWARD edges
   bordered, so it reads as a continuation of the panel's own hairline rather than a diamond
   sitting on top of it. Script sets its left; the negative margin pulls it back by half. */
.cw-spop__arw{
  position:absolute;bottom:-5px;width:10px;height:10px;margin-left:-5px;
  background:var(--color-surface);
  border-right:1px solid var(--color-border-hairline);
  border-bottom:1px solid var(--color-border-hairline);
  transform:rotate(45deg);border-bottom-right-radius:2px
}

.cw-spop__hd{display:flex;align-items:center;gap:8px;margin-bottom:8px}
.cw-spop__ttl{
  flex:1;min-width:0;font:600 12px var(--font-sans);color:var(--color-text-primary);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap
}
.cw-spop[dir="rtl"] .cw-spop__ttl{font-family:var(--font-arabic)}
.cw-spop__x{
  appearance:none;-webkit-appearance:none;flex:none;width:22px;height:22px;margin:0;padding:0;
  border:0;border-radius:7px;background:transparent;color:var(--color-text-muted);
  font-size:12px;line-height:1;cursor:pointer;
  transition:background var(--dur-fast,.14s) ease,color var(--dur-fast,.14s) ease
}
.cw-spop__x:hover{background:var(--color-accent-soft);color:var(--color-text-primary)}
.cw-spop__x:focus-visible{outline:2px solid var(--color-accent);outline-offset:1px}

/* A tall file can produce a tall panel; the BODY scrolls, never the frame, so the arrow and the
   action row stay where they were put. */
.cw-spop__bd{max-height:min(46vh,300px);overflow-y:auto;overscroll-behavior:contain}

/* THE TWO-COLUMN GRID. The label takes the panel's direction and the value never does — that
   split is the whole reason this is a grid and not a sentence. */
.cw-spop__row{display:flex;align-items:baseline;gap:10px;padding:3px 0}
.cw-spop__row + .cw-spop__row{border-top:1px solid color-mix(in srgb,var(--color-border-hairline) 60%,transparent)}
.cw-spop__k{flex:1;min-width:0;color:var(--color-text-secondary);font-size:11.5px}
.cw-spop__v{
  flex:none;direction:ltr;unicode-bidi:isolate;
  font:600 11.5px var(--font-mono);color:var(--color-text-primary);
  font-variant-numeric:tabular-nums
}
.cw-spop__row.is-warn .cw-spop__k,.cw-spop__row.is-warn .cw-spop__v{color:var(--color-error)}

.cw-spop__note{
  margin:8px 0 0;padding-top:7px;border-top:1px solid var(--color-border-hairline);
  font:400 11px var(--font-sans);line-height:1.55;color:var(--color-text-muted)
}
.cw-spop[dir="rtl"] .cw-spop__note{font-family:var(--font-arabic)}

.cw-spop__in{
  display:block;width:100%;box-sizing:border-box;margin-bottom:7px;padding:7px 9px;
  border:1px solid var(--color-border-hairline);border-radius:9px;
  background:var(--color-bg-subtle);color:var(--color-text-primary);
  font:600 12.5px var(--font-mono);direction:ltr;text-align:left;
  transition:border-color var(--dur-fast,.14s) ease,box-shadow var(--dur-fast,.14s) ease
}
.cw-spop__in:focus{
  outline:none;border-color:var(--color-accent);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--color-accent) 18%,transparent)
}
/* A rejected coordinate says so in colour and then forgets about it. No movement: a shake here
   would be the one piece of motion on this surface that a reduced-motion reader could not opt
   out of by simply not pressing anything. */
.cw-spop__in.is-bad{
  border-color:var(--color-error);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--color-error) 16%,transparent)
}

.cw-spop__acts{display:flex;flex-wrap:wrap;gap:6px;margin-top:9px}
.cw-spop__b{
  appearance:none;-webkit-appearance:none;flex:1 1 auto;margin:0;padding:6px 10px;
  border:1px solid var(--color-border-hairline);border-radius:9px;
  background:var(--color-bg-subtle);color:var(--color-text-primary);
  font:600 11.5px var(--font-sans);line-height:1.3;cursor:pointer;white-space:nowrap;
  transition:background var(--dur-fast,.14s) ease,border-color var(--dur-fast,.14s) ease,color var(--dur-fast,.14s) ease
}
.cw-spop[dir="rtl"] .cw-spop__b{font-family:var(--font-arabic)}
.cw-spop__b:hover{background:var(--color-accent-soft);border-color:color-mix(in srgb,var(--color-accent) 45%,transparent)}
.cw-spop__b:focus-visible{outline:2px solid var(--color-accent);outline-offset:1px}
.cw-spop__b.is-primary{
  background:color-mix(in srgb,var(--color-accent) 15%,transparent);
  border-color:color-mix(in srgb,var(--color-accent) 42%,transparent);color:var(--color-accent)
}
.cw-spop__b.is-primary:hover{background:color-mix(in srgb,var(--color-accent) 24%,transparent)}
.cw-spop__b.is-warn{color:var(--color-error);border-color:color-mix(in srgb,var(--color-error) 40%,transparent)}
.cw-spop__b.is-warn:hover{background:color-mix(in srgb,var(--color-error) 12%,transparent)}

/* BOTH forms, always. The media query serves the OS setting; the attribute serves this app's own
   settings panel — and :root IS <html>, so it has to be the leftmost compound or the rule can
   never match anything. */
:root[data-motion="off"] .cw-spop{animation:none}
:root[data-motion="off"] .cw-spop__x,
:root[data-motion="off"] .cw-spop__b,
:root[data-motion="off"] .cw-spop__in{transition:none}
@media (prefers-reduced-motion: reduce){
  .cw-spop{animation:none}
  .cw-spop__x,.cw-spop__b,.cw-spop__in{transition:none}
}


/* ============================================================================
   THE DOWNLOAD MENU ON A PHONE OR A TABLET — FIRAS_DL_MOBILE
   exportMenuFit() (app.js) measures the visual viewport when the list opens and decides
   which side it hangs from; these are the two states it can put it in, plus the finger-sized
   row and the width cap that keep an eleven-item list inside a 320px screen.

   Specificity is deliberate: `.export-menu__list--flip` is (0,1,0) and appears after
   `.export-menu__list`, so it wins in LTR; the RTL half has to be `[dir="rtl"] .…--flip`
   (0,2,0) because the existing `[dir="rtl"] .export-menu__list` rule is already (0,2,0) and
   a single class could never outrank it.
   ========================================================================== */
.export-menu__list--below {
  inset-block-end: auto;
  inset-block-start: calc(100% + 6px);
}
.export-menu__list--flip { inset-inline-start: auto; inset-inline-end: 0; }
[dir="rtl"] .export-menu__list--flip { inset-inline-start: 0; inset-inline-end: auto; }

@media (pointer: coarse), (max-width: 640px) {
  .export-menu__list {
    /* Never wider than the screen it opened on — the flip above can only rescue a list that
       fits somewhere; this is what makes sure one always does. */
    max-inline-size: min(280px, calc(100vw - 28px));
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    /* When the list is the thing nearest the bottom edge, its last row must not land under
       the home indicator, where the system swipe eats the tap. */
    padding-block-end: max(var(--space-1), env(safe-area-inset-bottom, 0px));
  }
  .export-menu__item {
    /* A fingertip is ~44px and cannot see under itself. These rows are in a vertical list
       with nothing beside them, so the whole target can grow without stealing a neighbour's
       taps the way widening a chip in the actions row would. */
    min-block-size: 44px;
    /* `nowrap` is right on a desktop dropdown that may be as wide as it likes. Under the
       width cap above it is what would push "PDF — بالهوية المحفوظة" off the end of the
       screen instead of onto a second line. */
    white-space: normal;
    line-height: 1.35;
  }
}


/* ----------------------------------------------------------------------------
   MATH KATEX COULD NOT PARSE  (see mathHealErrors in app.js)

   Two shapes reach the DOM as TEXT rather than as a KaTeX box. `.katex-error` is a whole failed
   expression, which KaTeX paints #cc0000 and gives no direction of its own. `.math-fallback` is
   what mathHealErrors puts there instead: the same maths as readable Unicode.

   A text run needs BOTH halves of the bidi declaration — `direction` on an inline box does
   nothing while `unicode-bidi` is `normal`. Measured in Chrome, one Latin run inside an Arabic
   paragraph, `direction:ltr` alone vs. paired with `unicode-bidi:isolate`:

       (a+b)/2         ->  2/)a+b(           vs  (a+b)/2
       ∫₀¹ x dx = 1/2  ->  x dx = 1/2 ₀¹∫    vs  ∫₀¹ x dx = 1/2
       5 ± 3           ->  3 ± 5             vs  5 ± 3

   The last is not ugly, it is WRONG: the operands swapped. (KaTeX's own output needs none of
   this — its `.base` is an inline-block and therefore already bidi-atomic; isolating it
   measured as a no-op.) And neither shape may be red: a colour the reader cannot act on is
   noise. `color: inherit` is !important because KaTeX writes its red as an INLINE style.
---------------------------------------------------------------------------- */
.katex.math-fallback,
.katex-error {
  direction: ltr;
  unicode-bidi: isolate;
  white-space: normal;
  font-family: "Cambria Math", "Segoe UI Symbol", "Noto Sans Math", KaTeX_Main, "Times New Roman", serif;
  color: inherit !important;
  -webkit-text-fill-color: currentColor !important;
}
.katex-display.math-fallback-display {
  display: block;
  direction: ltr;
  unicode-bidi: isolate;
  text-align: center;
  margin-block: var(--space-4, 1em);
  overflow-x: auto;
  overflow-y: hidden;
  break-inside: avoid;
  page-break-inside: avoid;
}

/* The share-card photograph. html2canvas ignores the `clip: rect(1px,1px,1px,1px)` KaTeX uses
   to hide its MathML mirror, so the raw source is painted next to the rendered equation —
   exportCss already forces this for the PDF and this capture root was the one that was missed.
   Scoped to #firasImgCardRoot, which lives off-screen for the length of one shot and is then
   removed, so no reader anywhere loses the accessible mirror. */
#firasImgCardRoot .katex-mathml { display: none !important; }


/* ═══════════════════════════════════════════════════════════════════════════════
   FX_MOBILE_CHAT — THE CHAT SURFACE ON PHONES AND TABLETS

   Measured in a browser at 320/360/375/414/744/768/769/800/834/1024/1180 CSS px with every
   `(pointer: coarse)` block in this file force-applied, which is the only way a desktop reports
   what an iPad reports. Appended last on purpose: each rule below is at the same specificity as
   the one it supersedes, so it wins on source order alone and cannot reach into a surface that
   states its own case at a higher one.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* ── A. THE TOPBAR CANNOT PUSH THE DOCUMENT SIDEWAYS ─────────────────────────────
   At 769px the row needed 631px in a 501px box and the document grew to 899px. The reader could
   not even scroll to what had been pushed out: the viewport's overflow comes from
   `body{overflow:hidden}`, so Share and the bell were off-screen and unreachable, not merely
   off to the right. Closing the 768/769 fencepost above removes the 130px case; this removes
   the rest of the class. `.tier-switch` is the one control here that CAN give way without losing
   anything — every segment stays reachable by scrolling the strip, which is what the strip
   already does below 365px — so it is the one told it may. Everything else keeps its size. */
.topbar { min-inline-size: 0; }
.topbar__spacer { min-inline-size: 0; }
.topbar .tier-switch {
  min-inline-size: 0; max-inline-size: 100%;
  /* overflow-y is stated because a single `overflow-x: auto` computes the other axis to `auto`
     as well; naming it `hidden` keeps the segments' focus rings from opening a second scrollbar.
     Nothing hangs out of this box — the tier-pin menu is a SIBLING of the strip, not a child. */
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none; -ms-overflow-style: none;
}
.topbar .tier-switch::-webkit-scrollbar { display: none; }

/* The band where a 268px sidebar is still pinned open beside a main column narrow enough for the
   row to be tight. The label already goes below 768px; this is the same trade one breakpoint up,
   and only while the sidebar is actually taking the space — collapse it and the words come back.
   min-inline-size keeps the icon-only form a 44px target instead of the 32px it measured at. */
@media (min-width: 768.02px) and (max-width: 900px) {
  body:not(.sidebar-collapsed) .topbar__newchat-label { display: none; }
  body:not(.sidebar-collapsed) .topbar__newchat {
    padding-inline: var(--space-2); min-inline-size: 44px; justify-content: center;
  }
}

/* ── B. THE ON-SCREEN KEYBOARD, IN A BROWSER ─────────────────────────────────────
   The native-shell section further up rides the keyboard 1:1 — but only under Capacitor, because
   every rule there gates on html.native-ios / html.native-android. In Safari on an iPhone or
   Chrome on an Android tablet the composer is absolute at the bottom of a 100dvh column and the
   layout viewport does not shrink for a keyboard, so it sat behind it.
   `--kbw` and `.kbw-open` are published by installWebKeyboardInset() in app.js from the real
   visualViewport inset. `:not(.native-shell)` is the whole safety story: the two paths drive the
   same element and must never both be live. The transform exists ONLY while the class is on, so
   the composer is not a containing block for its own popovers the other 99% of the time. */
html.kbw-open:not(.native-shell) .composer-wrap {
  transform: translateY(calc(-1 * var(--kbw, 0px)));
  transition: transform var(--dur-base) var(--ease-out);
  /* The home-indicator gap is under the keyboard now; keeping it would float the card. */
  padding-block-end: var(--space-3);
}
/* The reserve --composer-room measures is the composer's own height; the keyboard is the other
   wall. Without this the last answer is complete and hidden behind a composer that just moved. */
html.kbw-open:not(.native-shell) .chat-scroll {
  padding-bottom: calc(var(--composer-room, 150px) + var(--kbw, 0px));
}
@media (prefers-reduced-motion: reduce) {
  html.kbw-open:not(.native-shell) .composer-wrap { transition: none; }
}

/* ── C. SAFE AREAS REACH THE READING COLUMN ──────────────────────────────────────
   Section C1 gave the topbar, the composer and the sidebar their insets and stopped there, so in
   LANDSCAPE on a notched phone the answer text itself still ran under the cutout — the one
   surface in the app whose whole job is to be read. Physical left/right and restated per
   breakpoint, for the two reasons C1 gives: an inset is physical, and the originals compact the
   padding as the viewport narrows. */
.chat-inner {
  padding-left:  max(var(--space-6), var(--safe-left));
  padding-right: max(var(--space-6), var(--safe-right));
}
.welcome {
  padding-left:  max(var(--space-6), var(--safe-left));
  padding-right: max(var(--space-6), var(--safe-right));
}
@media (max-width: 768px) {
  .chat-inner {
    padding-left:  max(var(--space-4), var(--safe-left));
    padding-right: max(var(--space-4), var(--safe-right));
  }
  .welcome {
    padding-left:  max(var(--space-4), var(--safe-left));
    padding-right: max(var(--space-4), var(--safe-right));
  }
}

/* ── D. WHAT A FINGER STILL COULD NOT HIT ────────────────────────────────────────
   Everything here measured under 44px with every coarse-pointer block in this file already
   applied, so none of it is a target some earlier bump already covers. */
@media (pointer: coarse), (max-width: 560px) {
  /* The six composer controls were unified at 40px — one row, one size, which is the right
     instinct — but 40 is under the number every other bumped control in this file lands on, and
     this is the row that sends. 44 for all six keeps the uniformity and clears the bar. Measured
     at 280/320/360/375/414: no overflow, and the row wraps no earlier than it did at 40px. */
  .composer__bar .composer__attach,
  .composer__bar .tools-menu__trigger,
  .composer__bar .composer__mic,
  .composer__bar .composer__call,
  .composer__bar .composer__send {
    inline-size: 44px; block-size: 44px; min-inline-size: 44px;
  }
  .composer__bar .mode-select__trigger { block-size: 44px; min-block-size: 44px; }

  /* 26x26, sitting in the actions row under every answer between nine 44px siblings — the odd
     one out visually and the only one you had to aim at. A slug was the wrong tool: .code-block
     clips, and in the question's gutter the row is the layout. Real size, so the row is uniform. */
  .pinmsg-b { inline-size: 44px; block-size: 44px; }

  /* The tier pin does not exist below 640px, which means it exists ONLY on the widths nobody
     opens on a touch screen — and it was 32px there. */
  .tpin__btn { block-size: 44px; min-block-size: 44px; }

  /* Copy / Preview / Save on every code block. The head's hard 36px height capped all three at
     34, and below 560px the save control loses its word and measured 26x22 — the smallest thing
     on the chat surface. The head grows; nothing inside it moves horizontally. */
  .code-block__head { block-size: 48px; min-block-size: 48px; }
  .code-block__head .code-block__copy {
    min-inline-size: 44px; min-block-size: 44px; justify-content: center;
  }

  /* The second interface fades its action row to .55 and only restores it on :hover, which a
     touch screen never delivers — so the row under every answer stayed half-there for good. */
  :root[data-ui="2"] .msg-actions { opacity: 1; }
}

/* ═══════════════ PHONES AND TABLETS: AGENT, CODE, BRAIN — FX_MOBILE_PRODUCTS ═══════════════
   Appended last on purpose: every rule below has to beat an earlier one at equal specificity.
   Measured in a real browser at 375, 414, 768, 834 and 1024 against the real workspaces — the
   numbers quoted in each comment are what the DOM reported, not what the stylesheet implies.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. The centred wordmark became a three-row grid between 601px and 768px ─────────────────
   `.topbar .topbar__mobile-only { display: grid }` was written for the two icon BUTTONS that
   carry that class. The centred wordmark carries it too, and it is a span holding THREE things
   — <span>Firas</span>, a non-breaking space, <span>Code</span> — so grid laid them out as
   three rows. Measured at 768 (iPad portrait): x360, width 49px (the width of "Firas" alone,
   because the two words had been stacked), top -8px, height 72px. Absolutely positioned and
   centred in a 56px bar, that shears the product name off against the top of the window and
   drops the other half onto the toolbar underneath it.
   Bounded at BOTH ends. Below 601px an earlier rule hides this wordmark outright, and an
   unbounded `display: block` here would out-order it and put a 20px serif wordmark back into a
   phone topbar that already has no room. */
@media (min-width: 601px) and (max-width: 768px) {
  .topbar .topbar__wordmark-center { display: block; }
}

/* ── 2. Firas Code and Firas Brain were measuring the window, not the room they had ──────────
   The sidebar stops being a drawer at 769px and becomes a pinned 268px column. Every
   breakpoint in the IDE ladder — 640 / 860 / 900 / 1100 — is written against the VIEWPORT, so
   from 769px upward each one fires about 268px early, and the gap is exactly the width of a
   conversation list the workspace never asked for.

   On an iPad at 1024 the result was measurable: `.cw-main` took the 901–1100 desktop band and
   laid three columns of 168 / 314 / 273 into 756px of room; `.cw` reported scrollWidth 833
   against clientWidth 756, and `.cw-right` scrollWidth 350 against width 274 — the preview
   pane's own tab strip was being cut off inside it. At 834 the IDE ran the PHONE layout, one
   pane behind the bottom nav, inside 566px.

   So these two products take the whole window on a tablet and hand the sidebar back as the
   drawer it already is below 769px. Nothing new: same #drawerOpen, same openDrawer/closeDrawer,
   same scrim, same Escape key, same focus trap. Firas AI and Firas Agent are untouched — a chat
   thread reads perfectly well beside its own history, which is why the sidebar is pinned at all.

   1128 = 860 + 268. Above it the IDE has the 861px its own ladder is written for and the
   sidebar stays where it is. After: at 1024 `.cw-main` is 1024 wide with columns 168 / 458 /
   398 and nothing clips; at 834 the single-pane IDE has the full 834.

   Appended, so at equal specificity these out-order `body.sidebar-collapsed .sidebar`: a
   workspace entered with the sidebar already collapsed gets a real drawer rather than the 0px
   one that rule leaves behind. */
@media (min-width: 769px) and (max-width: 1128px) {
  body.product-code .app,
  body.product-brain .app { grid-template-columns: 1fr; }
  body.product-code .sidebar,
  body.product-brain .sidebar {
    position: fixed; inset-block: 0; inset-inline-start: 0; z-index: var(--z-drawer);
    inline-size: min(288px, 88vw); min-inline-size: 0;
    padding: 0; overflow: hidden;
    transform: translateX(-100%);
    transition: transform var(--dur-base) var(--ease-out);
    box-shadow: var(--shadow-lg);
    pointer-events: auto;
  }
  [dir="rtl"] body.product-code .sidebar,
  [dir="rtl"] body.product-brain .sidebar { transform: translateX(100%); }
  body.product-code .sidebar.is-open,
  body.product-brain .sidebar.is-open { transform: translateX(0); }
  body.product-code .sidebar .sidebar__close,
  body.product-brain .sidebar .sidebar__close { display: grid; }
  /* #drawerOpen by its id, NOT by `.topbar__mobile-only`: the centred wordmark wears that class
     too, and item 1 above is the entire story of what happens when it is treated as a button. */
  body.product-code .topbar #drawerOpen,
  body.product-brain .topbar #drawerOpen { display: grid; }
  /* One sidebar, one control. The desktop collapse toggle drives a column that is not there any
     more, and the desktop lockup would be a second wordmark beside the drawer's own. */
  body.product-code .topbar__desktop-only,
  body.product-brain .topbar__desktop-only { display: none; }
  body.product-code .topbar__desktop-mark,
  body.product-brain .topbar__desktop-mark { display: none; }
}

/* ── 3. The project name was the only thing in the IDE toolbar that could shrink ─────────────
   Below 861px `.cw-bar` is a horizontal scroller (≤640px) or a wrapping row (≤860px), and the
   dock's strip is `flex: 0 0 auto` by design — it holds its max-content width so cwDockFit has
   two different numbers to compare. That left `.cw-bar__name` as the one flexible item in the
   row and it was squeezed to exactly ZERO: measured at 414 and again at 375, scrollWidth 103
   into clientWidth 0. The project being edited had no name on screen at all.
   The `max-width` already there (40vw / 34vw / 26vw) is a ceiling with nothing under it; this
   is the floor. The bar scrolls, so the width this costs is width the row can afford — at 375
   the name comes back at 98px, ellipsised, and the bar's scroll range grows by the same 98. */
@media (max-width: 860px) {
  .cw-bar__name { flex: 0 0 auto; }
}

/* ── 4. Firas Brain on a screen that is short rather than narrow ─────────────────────────────
   Below 900px the library stacks and the sources rail becomes a horizontal strip above the
   answer, taking its height as `flex: 0 0 auto`. That is right on a phone held upright and
   wrong the moment the phone is turned. Measured at 812×375: the answer got 90px with the
   strip collapsed and 32px with it open — a scroll region shorter than one line of the text
   inside it.
   Nothing here moves a pane. It trims the chrome that is charging full price for a caption:
   after, the same answer has 122px collapsed and 60px open. `.fb-up__status` is the static list
   of accepted file formats — the one line in that footer that tells you nothing you need while
   you are reading. Bounded at 900px as well as by height, because above it the rail is a
   VERTICAL column whose list must keep growing to the foot. */
@media (max-width: 900px) and (max-height: 560px) {
  .fb-bar { padding-block: 6px; }
  .fb-rail__head { padding-block: 7px 5px; }
  .fb-rail__foot { padding-block: 6px; }
  .fb-up__status { display: none; }
  .fb-thread { padding-block: var(--space-3); }
  .fb-rail__list { max-block-size: 34vh; }
}

/* ── 5. The Agent's live preview was a fixed 420px on every screen ───────────────────────────
   Half of a phone held upright, and more than the whole window turned on its side. Capped
   against the viewport rather than replaced: on any window taller than ~914px — which is every
   desktop this has ever been designed on — 420px is still exactly what it gets. dvh restated
   after vh, as everywhere else in this file. */
.agent-live-preview iframe { height: min(420px, 46vh); height: min(420px, 46dvh); }
/* ═══════════════ end FX_MOBILE_PRODUCTS ════════════════════════════════════════════════════ */



/* ══════════════════════════════════════════════════════════════════════════════════════════
   DIFFICULTY LADDER - the composer pill and its menu.
   Shaped after .mode-select, which sits immediately before it in the same row: the two must
   read as one control strip, and a pill of a different height gives that row two baselines.
   Its own class names rather than a reuse of .mode-select's, so a change to the mode pill can
   never silently restyle this one.
   ══════════════════════════════════════════════════════════════════════════════════════════ */
.dfx-select { position: relative; display: inline-flex; flex: 0 0 auto; }
.dfx-select__trigger {
  display: inline-flex; align-items: center; gap: 6px;
  block-size: 34px; min-block-size: 34px;
  padding-inline: var(--space-3);
  border-radius: var(--radius-pill);
  border: var(--border-hairline) solid var(--color-border-hairline);
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
  font-family: var(--font-sans);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
[dir="rtl"] .dfx-select__trigger { font-family: var(--font-arabic); }
.dfx-select__trigger .dfx-ico { display: inline-flex; }
.dfx-select__trigger .dfx-ico svg { inline-size: 15px; block-size: 15px; }
.dfx-select__trigger .dfx-n {
  display: inline-flex; align-items: center; justify-content: center;
  min-inline-size: 18px; block-size: 18px;
  padding-inline: 4px;
  border-radius: var(--radius-pill);
  background: var(--color-accent-soft);
  color: var(--color-accent);
  font-size: var(--text-xs); font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}
.dfx-select__trigger:hover { color: var(--color-text-primary); border-color: var(--color-border-strong); background: var(--color-surface); }
.dfx-select.is-open .dfx-select__trigger,
.dfx-select__trigger[aria-expanded="true"] {
  color: var(--color-accent); border-color: var(--color-accent); background: var(--color-accent-soft);
}
.dfx-select__caret { display: inline-flex; }
.dfx-select__caret svg { inline-size: 13px; block-size: 13px; transition: transform var(--dur-fast) var(--ease-out); }
.dfx-select.is-open .dfx-select__caret svg { transform: rotate(180deg); }

/* Seven rungs is taller than the mode menu, so it scrolls inside itself rather than growing
   past the top of a short viewport. */
.dfx-select__menu {
  position: absolute; inset-block-end: calc(100% + 8px); inset-inline-start: 0;
  z-index: var(--z-dropdown);
  inline-size: 288px; max-inline-size: 84vw;
  max-block-size: min(58vh, 420px); overflow-y: auto; overscroll-behavior: contain;
  padding: var(--space-1);
  background: var(--color-surface);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 2px;
  animation: msg-in var(--dur-fast) var(--ease-out);
}
.dfx-select__item {
  display: flex; align-items: flex-start; gap: var(--space-3);
  inline-size: 100%;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  text-align: start;
  color: var(--color-text-primary);
  transition: background var(--dur-fast) var(--ease-out);
}
.dfx-select__item:hover, .dfx-select__item:focus-visible { background: var(--color-bg-subtle); outline: none; }
.dfx-select__item.is-active { background: var(--color-accent-soft); }
.dfx-select__rung {
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
  inline-size: 22px; block-size: 22px;
  border-radius: var(--radius-pill);
  border: var(--border-hairline) solid var(--color-border-hairline);
  color: var(--color-text-secondary);
  font-size: var(--text-xs); font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}
.dfx-select__item.is-active .dfx-select__rung { border-color: var(--color-accent); color: var(--color-accent); }
.dfx-select__text { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; flex: 1 1 auto; }
.dfx-select__label { font-family: var(--font-sans); font-size: var(--text-sm); font-weight: var(--weight-semibold); }
[dir="rtl"] .dfx-select__label { font-family: var(--font-arabic); }
.dfx-select__item.is-active .dfx-select__label { color: var(--color-accent); }
.dfx-select__hint { font-size: var(--text-xs); line-height: var(--leading-xs); color: var(--color-text-muted); }
.dfx-select__check { display: inline-flex; flex: 0 0 auto; color: var(--color-accent); margin-block-start: 2px; }
.dfx-select__check svg { inline-size: 16px; block-size: 16px; }
.dfx-select__check:empty { inline-size: 16px; }

/* The composer strip flattens its controls to ghosts; this pill is one of them. */
.composer__bar .dfx-select__trigger {
  border-color: transparent;
  background: transparent;
  color: var(--color-text-secondary);
  block-size: 34px; min-block-size: 34px;
}
.composer__bar .dfx-select__trigger:hover {
  background: var(--color-bg-subtle);
  border-color: transparent;
  color: var(--color-text-primary);
}
.composer__bar .dfx-select.is-open .dfx-select__trigger,
.composer__bar .dfx-select__trigger[aria-expanded="true"] {
  color: var(--color-accent); background: var(--color-accent-soft); border-color: transparent;
}

/* Phones: 40px to match the rest of the row, and the rung NAME gives way to the number, which
   is the part that still says where you are when there is no room for a word. */
@media (pointer: coarse), (max-width: 560px) {
  .composer__bar .dfx-select__trigger { block-size: 40px; min-block-size: 40px; padding-inline: var(--space-2); gap: 4px; }
  .dfx-select__trigger .dfx-name { display: none; }
  .dfx-select__menu { inline-size: min(88vw, 288px); }
}

/* The ladder belongs to Firas AI's chat. Agent, Code and Brain compose different things and
   their composer must not grow a control that governs none of them. */
body.product-agent .dfx-select,
body.product-code .dfx-select,
body.product-brain .dfx-select { display: none; }

/* :root IS <html> - an ancestor selector in front of it can never match. Both blocks, because
   one serves the OS setting and the other the app's own motion switch. */
:root[data-motion="off"] .dfx-select__caret svg,
:root[data-motion="off"] .dfx-select__item,
:root[data-motion="off"] .dfx-select__trigger { transition: none; }
:root[data-motion="off"] .dfx-select__menu { animation: none; }
@media (prefers-reduced-motion: reduce) {
  .dfx-select__caret svg, .dfx-select__item, .dfx-select__trigger { transition: none; }
  .dfx-select__menu { animation: none; }
}


/* ==========================================================================================
   DFXASK - the difficulty chooser above the composer.
   Built out of .qreply's vocabulary, which is this app's existing answer to "a row of choices":
   quiet outlined chips on a wrapping flex strip, tinting only on hover and focus, so the offer
   never competes with the composer it sits on top of. Its own class names rather than a reuse
   of .qreply's, so a change to the follow-up chips can never silently restyle this.

   NOTHING HERE CAN PUSH THE PAGE SIDEWAYS. Every chip is min-inline-size:0 with max-inline-size
   :100% (a flex item's automatic minimum is its content width, which is what makes a long hint
   widen the column otherwise), the hint wraps mid-word if it has to, and the strip scrolls
   inside itself rather than growing past the top of a short viewport - the same clamp
   .dfx-select__menu uses for the same seven rows.
   ========================================================================================== */
.dfxask {
  display: flex; flex-direction: column; gap: var(--space-2);
  inline-size: 100%; min-inline-size: 0;
  padding-block: var(--space-2) var(--space-3);
  padding-inline: 2px;
  margin-block-end: var(--space-2);
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
}
/* The PANEL takes the interface language, so its face is selected from its own dir - the shell
   above it is permanently LTR and carries none. */
.dfxask[dir="rtl"] { font-family: var(--font-arabic, var(--font-sans)); }
.dfxask__head { display: flex; align-items: center; gap: var(--space-2); min-inline-size: 0; }
.dfxask__ico { display: inline-flex; flex: 0 0 auto; color: var(--color-accent); }
.dfxask__ico svg { inline-size: 15px; block-size: 15px; }
.dfxask__title {
  min-inline-size: 0;
  font-size: var(--text-sm); line-height: var(--leading-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-primary);
  overflow-wrap: anywhere;
}
.dfxask__row {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  min-inline-size: 0;
  max-block-size: min(46vh, 340px);
  overflow-y: auto; overscroll-behavior: contain;
}
.dfxask__chip {
  display: inline-flex; align-items: flex-start; gap: var(--space-2);
  flex: 1 1 190px;
  max-inline-size: 100%; min-inline-size: 0;
  padding-block: 8px; padding-inline: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  background: transparent;
  color: var(--color-text-secondary);
  font-family: inherit;
  text-align: start;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.dfxask__n {
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
  min-inline-size: 20px; block-size: 20px; padding-inline: 4px;
  border-radius: var(--radius-pill);
  background: var(--color-accent-soft); color: var(--color-accent);
  font-size: var(--text-xs); font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}
.dfxask__tx { display: flex; flex-direction: column; gap: 1px; min-inline-size: 0; flex: 1 1 auto; }
.dfxask__nm {
  font-size: var(--text-sm); line-height: var(--leading-sm);
  font-weight: var(--weight-semibold); color: var(--color-text-primary);
}
.dfxask__hint {
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}
/* The rung a Skip would land on, marked so the escape hatch and the chip it corresponds to
   are visibly the same choice. */
.dfxask__chip.is-default { border-color: var(--color-border-strong); }
.dfxask__chip:hover { background: var(--color-accent-soft); border-color: var(--color-accent-ring); }
.dfxask__chip:hover .dfxask__nm { color: var(--color-accent); }
.dfxask__chip:active { transform: scale(0.99); }
.dfxask__chip:focus-visible { outline: 2px solid var(--color-accent-deep); outline-offset: 2px; }
.dfxask__foot {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-2); min-inline-size: 0;
}
.dfxask__note {
  flex: 1 1 170px; min-inline-size: 0; margin: 0;
  font-size: var(--text-xs); line-height: var(--leading-xs);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}
.dfxask__skip {
  display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  max-inline-size: 100%;
  padding-block: 6px; padding-inline: var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-text-secondary);
  font-family: inherit;
  font-size: var(--text-sm); line-height: var(--leading-sm);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.dfxask__skiptx { min-inline-size: 0; overflow-wrap: anywhere; }
.dfxask__skip:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.dfxask__skip:focus-visible { outline: 2px solid var(--color-accent-deep); outline-offset: 2px; }

/* A phone gets one chip per row - the hint is the half that makes a rung choosable, and two
   columns of it at 375px is two words per line. The strip is capped shorter there because the
   composer it grows out of is already sitting on the keyboard. */
@media (max-width: 560px), (pointer: coarse) {
  .dfxask__row { max-block-size: 40vh; }
  .dfxask__chip { flex: 1 1 100%; padding-block: 10px; }
  .dfxask__skip { padding-block: 8px; }
}

/* Degrades to nothing where the rest of this chrome does. :root IS <html>, so an ancestor
   selector in front of it could never match.

   UI 2.0 USED TO BE ON THIS LIST. The owner asked for the chooser back: picking the level
   is the whole point of asking for problems, and a shell that answers it on your behalf is
   not simpler, it is a decision taken from you. Agent, Code and Brain still hide it - they
   do not generate problem sets. */
body.product-agent .dfxask,
body.product-code .dfxask,
body.product-brain .dfxask { display: none; }

:root[data-motion="off"] .dfxask__chip,
:root[data-motion="off"] .dfxask__skip { transition: none; }
:root[data-motion="off"] .dfxask__chip:active { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .dfxask__chip, .dfxask__skip { transition: none; }
  .dfxask__chip:active { transform: none; }
}



/* ── SONGS ─────────────────────────────────────────────────────────
   THE REAL TOKEN NAMES. The first version of this block used --accent and --line, which this
   project does not define; they resolved to nothing and the whole player rendered transparent on a
   grey rectangle. The names here are the ones the stylesheet actually declares, so all six themes
   carry the card instead of one of them accidentally working. */
.music-card {
  margin: 10px 0;
  border: 1px solid var(--color-border);
  border-radius: 14px;
  background: var(--color-surface);
  overflow: hidden;
}
.music-card__head {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px;
  border-block-end: 1px solid var(--color-border);
  background: var(--color-bg-subtle);
}
.music-card__ico { font-size: 15px; color: var(--color-accent); }
.music-card__cap {
  font-size: 13px; font-weight: 600; color: var(--color-text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-inline-size: 0;
}
.music-card__body { padding: 12px; }
.music-card__work { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; font-size: 13px; color: var(--color-text-muted); }
.music-card__spin {
  inline-size: 14px; block-size: 14px; border-radius: 50%;
  border: 2px solid var(--color-border); border-top-color: var(--color-accent);
  animation: music-spin .8s linear infinite;
}
@keyframes music-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .music-card__spin { animation-duration: 2.4s; } }
.music-card__row { display: flex; gap: 8px; flex-wrap: wrap; margin-block-start: 10px; }
.music-card__btn {
  font: inherit; font-size: 12px; padding: 6px 12px; border-radius: 999px;
  border: 1px solid var(--color-border); background: transparent;
  color: var(--color-text-secondary); cursor: pointer; text-decoration: none;
}
.music-card__btn:hover { background: var(--color-accent-soft); color: var(--color-text-primary); }
.music-card__btn:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
.music-card__msg { margin: 0 0 10px; font-size: 13px; color: var(--color-text-muted); }
.music-card.is-failed { border-color: var(--color-border-strong); }
:root[data-ui="2"] .music-card { border-radius: 0; }

/* THE PLAYER. The native <audio> keeps decoding, buffering, seeking and Range; its chrome goes,
   because a white browser bar is the brightest thing on a dark card and no theme can reach it. */
.music-player__audio { display: none; }
.music-player {
  display: flex; align-items: center; gap: 12px;
  padding: 12px; border-radius: 12px;
  background: var(--color-bg-subtle);
  border: 1px solid var(--color-border);
}
.music-player__play {
  flex: none; inline-size: 44px; block-size: 44px; border-radius: 50%;
  border: 0; cursor: pointer; display: grid; place-items: center;
  background: var(--color-accent); color: var(--color-on-accent);
  transition: transform .12s ease, background .12s ease;
}
.music-player__play:hover { background: var(--color-accent-hover); transform: scale(1.05); }
.music-player__play:active { transform: scale(.96); }
.music-player__play:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 3px; }
.music-player__play svg { inline-size: 20px; block-size: 20px; fill: currentColor; }
.music-player__mid { flex: 1; min-inline-size: 0; display: grid; gap: 6px; }

.music-player__seek {
  inline-size: 100%; margin: 0; block-size: 6px; cursor: pointer; direction: ltr;
  -webkit-appearance: none; appearance: none; border-radius: 999px;
  /* NEUTRAL BEHIND, ACCENT IN FRONT. Both halves used to be the same green at different alphas,
     so the track read as two tints of one colour and the boundary — the only thing a progress bar
     is for — was a step between near-identical shades. Green now means one thing here: played. */
  background: linear-gradient(to right,
    var(--color-accent) 0%, var(--color-accent) calc(var(--p, 0) * 1%),
    var(--color-border) calc(var(--p, 0) * 1%), var(--color-border) 100%);
}
.music-player__seek::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  inline-size: 13px; block-size: 13px; border-radius: 50%;
  background: var(--color-accent); border: 2px solid var(--color-border); cursor: pointer;
}
.music-player__seek::-moz-range-thumb {
  inline-size: 13px; block-size: 13px; border-radius: 50%;
  background: var(--color-accent); border: 2px solid var(--color-border); cursor: pointer;
}
.music-player__seek:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 4px; }

/* FORCED LTR. `space-between` in an RTL container put elapsed on the right and total on the left,
   backwards from every player anyone has used. A pair of clock values reads left-to-right in any
   language. */
.music-player__times {
  display: flex; justify-content: space-between; direction: ltr;
  font-size: 11px; color: var(--color-text-muted); font-variant-numeric: tabular-nums;
}
.music-player.is-broken { opacity: .55; }


/* ═══ فِراس كومبيوتر — the live agent panel ═══════════════════════════════════
   Built on .agent-card's own vocabulary so it reads as part of the product rather than a widget
   bolted on. The running step is the one thing the eye should find, so it is the only thing that
   moves; finished steps dim rather than vanish, so the reader can see distance travelled. */
.fc { margin-block: 10px 4px; }
.fc__hd { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.fc__name { font-weight: var(--weight-medium); font-size: 13.5px; color: var(--color-text-primary); }
.fc__clock { margin-inline-start: auto; font-size: 12px; font-variant-numeric: tabular-nums;
  color: var(--color-text-tertiary, var(--color-text-secondary)); }
.fc__spark { inline-size: 8px; block-size: 8px; border-radius: 50%; flex: 0 0 auto;
  background: var(--color-accent); animation: fc-pulse 1.4s ease-in-out infinite; }
.fc.is-done .fc__spark { animation: none; opacity: .45; }
@keyframes fc-pulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.72); } }

.fc__ttl { display: flex; align-items: baseline; gap: 8px; margin: 12px 0 6px; font-size: 11.5px;
  color: var(--color-accent); }
.fc__ttl b { margin-inline-start: auto; font-variant-numeric: tabular-nums;
  color: var(--color-text-tertiary, var(--color-text-secondary)); font-weight: var(--weight-medium); }

.fc__steps, .fc__tl { list-style: none; margin: 0; padding: 0; }
.fc__st { display: flex; align-items: flex-start; gap: 9px; padding: 4px 2px; font-size: 13.5px;
  color: var(--color-text-secondary); transition: color .25s ease, opacity .25s ease; }
.fc__mk { inline-size: 14px; block-size: 14px; border-radius: 4px; flex: 0 0 auto; margin-block-start: 3px;
  border: 1.5px solid var(--color-border-strong, var(--color-border)); transition: all .25s ease; }
.fc__st.is-done { opacity: .55; }
.fc__st.is-done .fc__mk { background: var(--color-accent); border-color: var(--color-accent); }
.fc__st.is-run { color: var(--color-text-primary); font-weight: var(--weight-medium); }
.fc__st.is-run .fc__mk { border-color: var(--color-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 22%, transparent);
  animation: fc-pulse 1.4s ease-in-out infinite; }

.fc__tool { display: flex; align-items: baseline; gap: 8px; padding: 3px 2px; font-size: 12.5px;
  color: var(--color-text-secondary); animation: fc-in .28s ease both; }
.fc__tool code { font-size: 11.5px; padding: 1px 6px; border-radius: 5px; flex: 0 0 auto;
  background: color-mix(in srgb, var(--color-accent) 12%, transparent); color: var(--color-accent); }
.fc__tool em { font-style: normal; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--color-text-tertiary, var(--color-text-secondary)); }
@keyframes fc-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

.fc__grid { display: flex; flex-wrap: wrap; gap: 8px; }
.fc__file { display: inline-flex; align-items: center; padding: 6px 10px; border-radius: 9px;
  font-size: 12.5px; text-decoration: none; animation: fc-in .3s ease both;
  border: 1px solid var(--color-border); color: var(--color-text-secondary); }
.fc__file.is-img { padding: 0; overflow: hidden; }
.fc__file.is-img img { display: block; inline-size: 108px; block-size: 76px; object-fit: cover; }
.fc__say { margin-top: 12px; padding-top: 10px; font-size: 13.5px; line-height: 1.75;
  border-top: 1px solid var(--color-border-hairline, var(--color-border));
  color: var(--color-text-secondary); }

/* ── Agent workspace 2.0 ────────────────────────────────────────────────────
   The live run is a working surface, not another chat bubble. It keeps the existing Firas palette
   and type, then adds hierarchy around the data Manus already returns: now → progress → plan →
   execution log → artifacts. No new dependency and no layout outside this card is touched. */
.fc.agent-card {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: 0 0 16px;
  border: 1px solid color-mix(in srgb, var(--color-accent) 30%, var(--color-border));
  border-radius: 18px;
  background:
    radial-gradient(680px 180px at 12% -20%, color-mix(in srgb, var(--color-accent) 17%, transparent), transparent 72%),
    color-mix(in srgb, var(--color-bg-elevated, var(--color-surface)) 92%, transparent);
  box-shadow: 0 1px 2px rgba(0,0,0,.12), 0 18px 50px rgba(0,0,0,.16);
}
.fc.agent-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  block-size: 2px;
  z-index: 2;
  background: linear-gradient(90deg, transparent, var(--color-accent), #5b8def, transparent);
  opacity: .85;
}
.fc__hd {
  min-block-size: 54px;
  margin: 0;
  padding: 13px 16px;
  border-bottom: 1px solid color-mix(in srgb, var(--color-border) 78%, transparent);
  background: color-mix(in srgb, var(--color-bg-subtle) 72%, transparent);
  backdrop-filter: blur(14px);
}
.fc__name { font-size: 14px; letter-spacing: -.01em; }
.fc__status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-inline-start: 5px;
  padding: 4px 9px;
  border: 1px solid color-mix(in srgb, var(--color-accent) 32%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--color-accent) 9%, transparent);
  color: var(--color-accent);
  font-size: 10.5px;
  font-weight: var(--weight-medium);
  white-space: nowrap;
}
.fc__status i {
  inline-size: 5px;
  block-size: 5px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 4px color-mix(in srgb, currentColor 14%, transparent);
  animation: fc-pulse 1.4s ease-in-out infinite;
}
.fc.is-done .fc__status { color: var(--color-text-secondary); border-color: var(--color-border); background: var(--color-bg-subtle); }
.fc.is-done .fc__status i { animation: none; box-shadow: none; }
.fc__clock { padding-inline-start: 8px; }

.fc__focus {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  align-items: center;
  gap: 11px;
  margin: 14px 16px 10px;
  padding: 12px 13px;
  border: 1px solid color-mix(in srgb, var(--color-accent) 24%, var(--color-border));
  border-radius: 13px;
  background: linear-gradient(105deg, color-mix(in srgb, var(--color-accent) 10%, transparent), color-mix(in srgb, #5b8def 5%, transparent));
}
.fc__focus-ic {
  display: grid;
  place-items: center;
  inline-size: 34px;
  block-size: 34px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--color-accent) 16%, var(--color-bg-subtle));
  color: var(--color-accent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-accent) 22%, transparent);
  animation: fc-focus 2.2s ease-in-out infinite;
}
.fc__focus div { min-inline-size: 0; }
.fc__focus small { display: block; margin-bottom: 2px; font-size: 10.5px; color: var(--color-text-muted); }
.fc__focus strong {
  display: block;
  overflow: hidden;
  color: var(--color-text-primary);
  font-size: 13px;
  font-weight: var(--weight-medium);
  line-height: 1.55;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@keyframes fc-focus { 50% { transform: translateY(-1px); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-accent) 34%, transparent), 0 0 18px color-mix(in srgb, var(--color-accent) 12%, transparent); } }

.fc__meter { margin: 0 16px 12px; }
.fc__meter-copy { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; color: var(--color-text-muted); font-size: 10.5px; }
.fc__meter-copy b { margin-inline-start: auto; color: var(--color-text-secondary); font-weight: var(--weight-medium); font-variant-numeric: tabular-nums; }
.fc__meter-track { block-size: 4px; overflow: hidden; border-radius: 999px; background: color-mix(in srgb, var(--color-border) 70%, transparent); }
.fc__meter-track i { display: block; inline-size: 0; block-size: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--color-accent), #5b8def); box-shadow: 0 0 12px color-mix(in srgb, var(--color-accent) 35%, transparent); transition: inline-size .5s var(--ease-out, ease-out); }

.fc__plan, .fc__tools, .fc__files, .fc__say, .fc__activity { margin-inline: 16px; }
.fc__plan .fc__ttl, .fc__tools .fc__ttl, .fc__files .fc__ttl { margin-top: 14px; }
.fc__st { position: relative; padding-block: 5px; }
.fc__st.is-run { border-radius: 8px; background: linear-gradient(90deg, color-mix(in srgb, var(--color-accent) 8%, transparent), transparent); }
.fc__st.is-run .fc__mk { margin-inline-start: 3px; }

/* Manus-like task disclosures, kept inside the Firas visual system. Each row is both status and
   doorway: opening it reveals the assistant messages, tool calls and plan updates that belonged to
   that exact task. Native <details> keeps mouse, touch and keyboard behaviour aligned. */
.fc__tasks { display: grid; gap: 8px; }
.fc__task {
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--color-border) 88%, transparent);
  border-radius: 12px;
  background: color-mix(in srgb, var(--color-bg-subtle) 62%, transparent);
  transition: border-color .2s ease, background .2s ease, box-shadow .2s ease;
}
.fc__task[open] {
  border-color: color-mix(in srgb, var(--color-accent) 27%, var(--color-border));
  background: color-mix(in srgb, var(--color-bg-subtle) 78%, transparent);
  box-shadow: 0 7px 22px rgba(0,0,0,.08);
}
.fc__task.is-run {
  border-color: color-mix(in srgb, var(--color-accent) 42%, var(--color-border));
  background: linear-gradient(105deg, color-mix(in srgb, var(--color-accent) 8%, transparent), color-mix(in srgb, var(--color-bg-subtle) 76%, transparent));
}
.fc__task > summary {
  display: grid;
  grid-template-columns: 22px minmax(0,1fr) auto 16px;
  align-items: center;
  gap: 9px;
  min-block-size: 48px;
  padding: 10px 11px;
  cursor: pointer;
  list-style: none;
  color: var(--color-text-secondary);
}
.fc__task > summary::-webkit-details-marker { display: none; }
.fc__task > summary:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: -3px; border-radius: 10px; }
.fc__task-mark {
  display: grid;
  place-items: center;
  inline-size: 19px;
  block-size: 19px;
  border: 1.5px solid var(--color-border-strong, var(--color-border));
  border-radius: 6px;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
}
.fc__task.is-done .fc__task-mark { border-color: var(--color-accent); background: var(--color-accent); }
.fc__task.is-run .fc__task-mark {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-accent) 13%, transparent);
  animation: fc-pulse 1.4s ease-in-out infinite;
}
.fc__task-copy {
  min-inline-size: 0;
  overflow: hidden;
  color: var(--color-text-primary);
  font-size: 13px;
  font-weight: var(--weight-medium);
  line-height: 1.5;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fc__task-meta { color: var(--color-text-muted); font-size: 10px; font-weight: var(--weight-medium); white-space: nowrap; }
.fc__task.is-run .fc__task-meta { color: var(--color-accent); }
.fc__task-chev { color: var(--color-text-muted); font-size: 13px; transition: transform .18s ease; }
.fc__task[open] .fc__task-chev { transform: rotate(180deg); }
.fc__task-body {
  position: relative;
  margin: 0 11px 11px;
  padding: 9px 10px;
  border-top: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);
  border-radius: 0 0 9px 9px;
  background: color-mix(in srgb, var(--color-bg-elevated, var(--color-surface)) 50%, transparent);
}
.fc__task-empty { margin: 0; padding: 4px 2px; color: var(--color-text-muted); font-size: 11.5px; line-height: 1.6; }
.fc__task-event {
  position: relative;
  display: grid;
  grid-template-columns: 24px minmax(0,1fr);
  gap: 9px;
  padding: 7px 0;
}
.fc__task-event:not(:last-child)::after {
  content: "";
  position: absolute;
  inset-block: 31px -7px;
  inset-inline-start: 11px;
  inline-size: 1px;
  background: color-mix(in srgb, var(--color-border) 76%, transparent);
}
.fc__task-event-ic {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  inline-size: 24px;
  block-size: 24px;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-bg-subtle);
  color: var(--color-text-muted);
  font-size: 10px;
}
.fc__task-event.is-tool .fc__task-event-ic { color: #7aa7ff; border-color: color-mix(in srgb, #7aa7ff 34%, var(--color-border)); }
.fc__task-event.is-plan .fc__task-event-ic { color: var(--color-accent); border-color: color-mix(in srgb, var(--color-accent) 34%, var(--color-border)); }
.fc__task-event.is-file .fc__task-event-ic { color: #c694ff; border-color: color-mix(in srgb, #c694ff 34%, var(--color-border)); }
.fc__task-event-copy { min-inline-size: 0; padding-top: 1px; }
.fc__task-event-copy strong,
.fc__task-event-copy span { display: block; overflow-wrap: anywhere; }
.fc__task-event-copy strong { color: var(--color-text-secondary); font-size: 11.5px; font-weight: var(--weight-medium); line-height: 1.5; }
.fc__task-event-copy span { margin-top: 2px; color: var(--color-text-muted); font-size: 10.75px; line-height: 1.6; }

.fc__activity {
  margin-top: 14px;
  border: 1px solid color-mix(in srgb, var(--color-border) 82%, transparent);
  border-radius: 12px;
  background: color-mix(in srgb, var(--color-bg-subtle) 68%, transparent);
}
.fc__activity > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  min-block-size: 42px;
  padding: 9px 11px;
  cursor: pointer;
  list-style: none;
  color: var(--color-text-secondary);
  font-size: 11.5px;
  font-weight: var(--weight-medium);
}
.fc__activity > summary::-webkit-details-marker { display: none; }
.fc__activity > summary:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; border-radius: 10px; }
.fc__activity-ic { display: grid; place-items: center; inline-size: 22px; block-size: 22px; border-radius: 7px; background: color-mix(in srgb, var(--color-accent) 11%, transparent); color: var(--color-accent); }
.fc__activity > summary b { margin-inline-start: auto; color: var(--color-text-muted); font-size: 10.5px; font-weight: var(--weight-medium); font-variant-numeric: tabular-nums; }
.fc__activity-chev { color: var(--color-text-muted); transition: transform .18s ease; }
.fc__activity[open] .fc__activity-chev { transform: rotate(180deg); }
.fc__feed {
  max-block-size: 260px;
  margin: 0;
  padding: 4px 11px 11px;
  overflow: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  list-style: none;
}
.fc__event {
  position: relative;
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr);
  gap: 8px;
  align-items: start;
  padding: 6px 0;
  color: var(--color-text-secondary);
  font-size: 11.75px;
  line-height: 1.6;
}
.fc__event:not(:last-child)::before { content: ""; position: absolute; inset-block: 27px -7px; inset-inline-start: 11px; inline-size: 1px; background: color-mix(in srgb, var(--color-border) 78%, transparent); }
.fc__event-ic { position: relative; z-index: 1; display: grid; place-items: center; inline-size: 24px; block-size: 24px; border: 1px solid var(--color-border); border-radius: 7px; background: var(--color-bg-subtle); color: var(--color-text-muted); font-size: 10px; }
.fc__event.is-tool .fc__event-ic { color: #7aa7ff; border-color: color-mix(in srgb, #7aa7ff 34%, var(--color-border)); }
.fc__event.is-plan .fc__event-ic { color: var(--color-accent); border-color: color-mix(in srgb, var(--color-accent) 34%, var(--color-border)); }
.fc__event.is-file .fc__event-ic { color: #c694ff; border-color: color-mix(in srgb, #c694ff 34%, var(--color-border)); }
.fc__event-t { min-inline-size: 0; overflow-wrap: anywhere; }
.fc__event.is-new { animation: fc-event-in .32s var(--ease-out, ease-out) both; }
@keyframes fc-event-in { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }

.fc__grid { margin-top: 4px; }
.fc__file { min-block-size: 38px; background: color-mix(in srgb, var(--color-bg-subtle) 84%, transparent); transition: transform .18s ease, border-color .18s ease, color .18s ease; }
.fc__file:hover { transform: translateY(-1px); border-color: color-mix(in srgb, var(--color-accent) 42%, var(--color-border)); color: var(--color-text-primary); }
.fc__say { margin-top: 14px; padding: 12px 13px 0; border-top: 1px dashed color-mix(in srgb, var(--color-border) 84%, transparent); }

@media (max-width: 560px) {
  .fc.agent-card { border-radius: 14px; }
  .fc__hd { padding-inline: 12px; }
  .fc__status { max-inline-size: 98px; overflow: hidden; text-overflow: ellipsis; }
  .fc__focus, .fc__meter, .fc__plan, .fc__tools, .fc__files, .fc__say, .fc__activity { margin-inline: 12px; }
  .fc__focus { grid-template-columns: 30px minmax(0,1fr); padding: 10px; }
  .fc__focus-ic { inline-size: 30px; block-size: 30px; }
  .fc__feed { max-block-size: 220px; }
  .fc__task > summary { grid-template-columns: 22px minmax(0,1fr) 14px; gap: 8px; }
  .fc__task-meta { display: none; }
  .fc__task-body { margin-inline: 8px; padding-inline: 8px; }
}

/* A reader who asked for less motion gets none of it — a pulsing panel is exactly what that
   preference is about. */
@media (prefers-reduced-motion: reduce) {
  .fc__spark, .fc__status i, .fc__focus-ic, .fc__st.is-run .fc__mk, .fc__task.is-run .fc__task-mark { animation: none; }
  .fc__tool, .fc__file, .fc__event { animation: none; transition: none; }
  .fc__meter-track i, .fc__activity-chev, .fc__task, .fc__task-chev { transition: none; }
}


/* ═══ CODE CARD: WRAP OR SCROLL ══════════════════════════════════════════════
   `white-space: pre` clipped every long line at the card's right edge and left dragging sideways as
   the only way to read it — on a desktop as much as a phone, because the chat column is ~860px and
   the lines were 200+ characters.

   Wrapping is not simply better: it suits long prose-ish strings and ruins indented code, whose
   shape carries the structure. So it is a control, defaulting by measured line length. */
.code-card__code.is-wrap,
.code-card__body.is-wrap pre,
.code-card__body.is-wrap code {
  white-space: pre-wrap;
  /* An unbroken 300-character token — a base64 blob, a long URL — would otherwise force the card
     wide again and undo the wrap it was just given. */
  overflow-wrap: anywhere;
  word-break: break-word;
}
.code-card__body.is-wrap { overflow-x: hidden; }

/* A real target, not an 11px word: this sits in a row of small header controls and is the one a
   thumb reaches for. */
.code-card__wrapbtn { min-block-size: 28px; }
@media (pointer: coarse) {
  .code-card__wrapbtn { min-block-size: 44px; padding-inline: 12px; }
}


/* Agent task disclosure comfort + press contract. */
/* Only the running task marker loops. The other three live indicators stay still so the card has
   one focal motion instead of four competing pulses. */
.fc.agent-card .fc__spark,
.fc.agent-card .fc__status i,
.fc.agent-card .fc__focus-ic { animation: none; }
.fc__task > summary:active {
  background: color-mix(in srgb, var(--color-accent) 8%, transparent);
}

/* Reduced motion has two switches in this product. The task still reports "working" through the
   house opacity pulse; it loses the scale movement under both the OS preference and Settings. */
@media (prefers-reduced-motion: reduce) {
  .fc__task.is-run .fc__task-mark {
    animation-name: rm-working !important;
    animation-duration: 1.5s !important;
    animation-iteration-count: infinite !important;
    transform: none !important;
  }
}
:root[data-motion="off"] .fc__task.is-run .fc__task-mark {
  animation-name: rm-working !important;
  animation-duration: 1.5s !important;
  animation-iteration-count: infinite !important;
  transform: none !important;
}


/* ═══ OPEN AGENT STREAM + DAILY CREDIT SURFACE ═══ */
/* The execution is part of the conversation, not a dashboard trapped inside another card. */
.fc.agent-card.fc--stream {
  overflow: visible;
  padding: 4px 0 18px;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
.fc.agent-card.fc--stream::before { display: none; }
.fc--stream .fc__hd {
  min-block-size: 44px;
  padding: 4px 0 10px;
  border: 0;
  background: transparent;
  backdrop-filter: none;
}
.fc--stream .fc__spark {
  inline-size: 28px;
  block-size: 28px;
  border-radius: 9px;
  background: color-mix(in srgb, var(--color-accent) 13%, var(--color-bg-subtle));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-accent) 24%, transparent);
}
.fc--stream .fc__spark::after { content: "F"; color: var(--color-accent); font-size: 12px; font-weight: 800; }
.fc--stream .fc__status { padding: 3px 8px; background: transparent; }
.fc--stream .fc__focus {
  grid-template-columns: 26px minmax(0, 1fr);
  gap: 8px;
  margin: 0 0 12px;
  padding: 4px 0 8px;
  border: 0;
  border-radius: 0;
  background: transparent;
}
.fc--stream .fc__focus-ic {
  inline-size: 26px;
  block-size: 26px;
  border: 0;
  border-radius: 8px;
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
  box-shadow: none;
}
.fc--stream .fc__focus strong { white-space: normal; text-overflow: clip; }
.fc--stream .fc__say {
  margin: 0 0 18px;
  padding: 0 34px 0 0;
  border: 0;
}
[dir="ltr"] .fc--stream .fc__say { padding: 0 0 0 34px; }
.fc--stream .fc__say:not([hidden]) { display: grid; gap: 4px; }
.fc--stream .fc__say b { color: var(--color-text-primary); font-size: 12px; font-weight: var(--weight-semibold); }
.fc--stream .fc__say p { margin: 0; color: var(--color-text-secondary); font-size: 14px; line-height: 1.85; white-space: pre-wrap; }
.fc--stream .fc__meter { margin: 0 0 14px; }
.fc--stream .fc__meter-track { block-size: 3px; }
.fc--stream .fc__plan,
.fc--stream .fc__tools,
.fc--stream .fc__files,
.fc--stream .fc__activity { margin-inline: 0; }
.fc--stream .fc__plan .fc__ttl,
.fc--stream .fc__tools .fc__ttl,
.fc--stream .fc__files .fc__ttl { margin: 0 0 7px; padding-inline: 2px; }
.fc--stream .fc__tasks { gap: 0; border-block: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent); }
.fc--stream .fc__task {
  overflow: visible;
  border: 0;
  border-block-end: 1px solid color-mix(in srgb, var(--color-border) 62%, transparent);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
.fc--stream .fc__task:last-child { border-block-end: 0; }
.fc--stream .fc__task[open],
.fc--stream .fc__task.is-run { border-color: color-mix(in srgb, var(--color-border) 62%, transparent); background: transparent; box-shadow: none; }
.fc--stream .fc__task > summary { min-block-size: 52px; padding: 10px 2px; }
.fc--stream .fc__task > summary:hover { background: color-mix(in srgb, var(--color-accent) 5%, transparent); }
.fc--stream .fc__task-mark { border-radius: 50%; }
.fc--stream .fc__task-body {
  margin: 0 0 10px 0;
  padding: 2px 0 5px 32px;
  border: 0;
  border-radius: 0;
  background: transparent;
}
[dir="rtl"] .fc--stream .fc__task-body { padding: 2px 32px 5px 0; }
.fc--stream .fc__task-event { padding: 8px 0; }
.fc--stream .fc__task-event-ic { border-radius: 50%; background: var(--color-bg-surface, var(--color-bg-subtle)); }
.fc--stream .fc__task-event.is-browser .fc__task-event-ic {
  color: #37b5ff;
  border-color: color-mix(in srgb, #37b5ff 44%, var(--color-border));
  box-shadow: 0 0 0 4px color-mix(in srgb, #37b5ff 8%, transparent);
}
.fc--stream .fc__task-event-copy strong { font-size: 12px; }
.fc--stream .fc__task-event-copy span { font-size: 11.5px; }

.fc__browser { margin: 18px 0 4px; }
.fc__browser-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.fc__browser-head > div { display: flex; align-items: center; gap: 8px; }
.fc__browser-head strong { color: var(--color-text-primary); font-size: 12.5px; }
.fc__browser-head small { color: var(--color-text-muted); font-size: 10.5px; }
.fc__browser-live { inline-size: 7px; block-size: 7px; border-radius: 50%; background: #29c778; box-shadow: 0 0 0 4px color-mix(in srgb, #29c778 13%, transparent); }
.fc__browser-window {
  overflow: hidden;
  border: 1px solid color-mix(in srgb, #37b5ff 25%, var(--color-border));
  border-radius: 14px;
  background: color-mix(in srgb, var(--color-bg-subtle) 74%, transparent);
  box-shadow: 0 10px 28px rgba(0,0,0,.09);
}
.fc__browser-chrome {
  display: grid;
  grid-template-columns: auto minmax(0,1fr) auto;
  align-items: center;
  gap: 10px;
  min-block-size: 43px;
  padding: 7px 10px;
  border-block-end: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);
  background: color-mix(in srgb, var(--color-bg-elevated, var(--color-surface)) 62%, transparent);
}
.fc__browser-chrome > span { display: flex; gap: 4px; }
.fc__browser-chrome > span i { inline-size: 7px; block-size: 7px; border-radius: 50%; background: var(--color-border-strong, var(--color-border)); }
.fc__browser-address {
  overflow: hidden;
  padding: 5px 10px;
  border: 1px solid color-mix(in srgb, var(--color-border) 76%, transparent);
  border-radius: 8px;
  color: var(--color-text-muted);
  background: color-mix(in srgb, var(--color-bg-surface, var(--color-bg-subtle)) 80%, transparent);
  font-size: 10.5px;
  text-align: start;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fc__browser-open { min-block-size: 30px; padding: 6px 8px; border-radius: 7px; color: var(--color-accent); font-size: 10.5px; text-decoration: none; white-space: nowrap; }
.fc__browser-open:hover { background: color-mix(in srgb, var(--color-accent) 9%, transparent); }
.fc__browser-open:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
.fc__browser-now { padding: 18px 18px 14px; }
.fc__browser-action { display: inline-flex; margin-bottom: 8px; padding: 4px 8px; border-radius: 999px; color: #37b5ff; background: color-mix(in srgb, #37b5ff 10%, transparent); font-size: 10px; font-weight: var(--weight-medium); }
.fc__browser-now strong { display: block; color: var(--color-text-primary); font-size: 14px; }
.fc__browser-now p { margin: 6px 0 0; color: var(--color-text-secondary); font-size: 11.5px; line-height: 1.7; overflow-wrap: anywhere; }
.fc__browser-history { margin: 0; padding: 0 18px 14px; list-style: none; }
.fc__browser-history > li { border-block-start: 1px solid color-mix(in srgb, var(--color-border) 58%, transparent); }
.fc__browser-history > li > a,
.fc__browser-history-row { display: grid; grid-template-columns: 20px minmax(0,1fr); gap: 8px; padding: 8px 0; color: inherit; text-decoration: none; }
.fc__browser-history > li > a:hover { color: var(--color-accent); }
.fc__browser-history > li > a:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 3px; border-radius: 6px; }
.fc__browser-history strong,
.fc__browser-history small { display: block; overflow-wrap: anywhere; }
.fc__browser-history strong { color: var(--color-text-secondary); font-size: 11px; }
.fc__browser-history small { margin-top: 2px; color: var(--color-text-muted); font-size: 10px; line-height: 1.5; }

.agent-credit-chip {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 7px;
  min-block-size: 38px;
  padding: 5px 10px;
  border: 1px solid color-mix(in srgb, var(--color-accent) 27%, var(--color-border));
  border-radius: 999px;
  color: var(--color-text-primary);
  background: color-mix(in srgb, var(--color-accent) 7%, var(--color-bg-surface, transparent));
  cursor: pointer;
  transition: border-color .18s ease, background .18s ease, transform .18s ease;
}
.agent-credit-chip:hover { border-color: color-mix(in srgb, var(--color-accent) 55%, var(--color-border)); background: color-mix(in srgb, var(--color-accent) 11%, transparent); transform: translateY(-1px); }
.agent-credit-chip:active { transform: translateY(0); }
.agent-credit-chip:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
.agent-credit-chip__mark { display: grid; place-items: center; inline-size: 23px; block-size: 23px; border-radius: 50%; color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 13%, transparent); font-size: 11px; }
.agent-credit-chip__label { color: var(--color-text-muted); font-size: 10.5px; }
.agent-credit-chip__value { color: var(--color-text-primary); font-size: 12px; font-variant-numeric: tabular-nums; }

.ac-dialog-layer { position: fixed; inset: 0; z-index: 10020; display: grid; place-items: start center; padding: max(72px, 10vh) 16px 24px; overflow-y: auto; background: rgba(5,8,14,.58); backdrop-filter: blur(10px); animation: ac-layer-in .18s ease both; }
.ac-dialog { inline-size: min(100%, 420px); overflow: hidden; border: 1px solid color-mix(in srgb, var(--color-accent) 27%, var(--color-border)); border-radius: 20px; color: var(--color-text-primary); background: var(--color-bg-elevated, var(--color-surface)); box-shadow: 0 28px 80px rgba(0,0,0,.34); animation: ac-dialog-in .24s var(--ease-out, ease-out) both; }
.ac-dialog > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding: 20px 20px 10px; }
.ac-dialog__eyebrow { display: block; margin-bottom: 4px; color: var(--color-accent); font-size: 10px; font-weight: var(--weight-semibold); }
.ac-dialog h2 { margin: 0; font-size: 19px; letter-spacing: -.02em; }
.ac-dialog__close { display: grid; place-items: center; inline-size: 44px; block-size: 44px; margin: -8px -8px 0 0; border: 0; border-radius: 12px; color: var(--color-text-secondary); background: transparent; font-size: 25px; cursor: pointer; }
[dir="rtl"] .ac-dialog__close { margin: -8px 0 0 -8px; }
.ac-dialog__close:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.ac-dialog__close:focus-visible,
.ac-dialog__add:focus-visible { outline: 2px solid var(--color-accent-ring); outline-offset: 2px; }
.ac-dialog__balance { display: flex; align-items: baseline; gap: 8px; padding: 10px 20px 8px; }
.ac-dialog__balance span { margin-inline-end: auto; color: var(--color-text-muted); font-size: 12px; }
.ac-dialog__balance strong { color: var(--color-text-primary); font-size: 38px; line-height: 1; letter-spacing: -.04em; }
.ac-dialog__balance small { color: var(--color-text-muted); font-size: 11px; }
.ac-dialog__bar { block-size: 6px; margin: 6px 20px 18px; overflow: hidden; border-radius: 999px; background: color-mix(in srgb, var(--color-border) 65%, transparent); }
.ac-dialog__bar i { display: block; block-size: 100%; border-radius: inherit; background: var(--color-accent); box-shadow: 0 0 16px color-mix(in srgb, var(--color-accent) 30%, transparent); transition: inline-size .4s ease; }
.ac-dialog__stats { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); margin: 0; padding: 0 20px 18px; }
.ac-dialog__stats > div { min-inline-size: 0; padding: 11px 12px; border: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent); background: color-mix(in srgb, var(--color-bg-subtle) 68%, transparent); }
.ac-dialog__stats > div:first-child { border-radius: 0 12px 12px 0; }
.ac-dialog__stats > div:last-child { border-radius: 12px 0 0 12px; }
[dir="ltr"] .ac-dialog__stats > div:first-child { border-radius: 12px 0 0 12px; }
[dir="ltr"] .ac-dialog__stats > div:last-child { border-radius: 0 12px 12px 0; }
.ac-dialog__stats dt { color: var(--color-text-muted); font-size: 9.5px; line-height: 1.45; }
.ac-dialog__stats dd { margin: 5px 0 0; color: var(--color-text-primary); font-size: 12px; font-weight: var(--weight-semibold); overflow-wrap: anywhere; }
.ac-dialog__add { display: flex; align-items: center; justify-content: center; gap: 8px; inline-size: calc(100% - 40px); min-block-size: 46px; margin: 0 20px; border: 0; border-radius: 12px; color: var(--color-on-accent, #fff); background: var(--color-accent); font-size: 13px; font-weight: var(--weight-semibold); cursor: pointer; box-shadow: 0 9px 24px color-mix(in srgb, var(--color-accent) 25%, transparent); }
.ac-dialog__add:hover { filter: brightness(1.06); }
.ac-dialog__note { margin: 0; padding: 12px 20px 18px; color: var(--color-text-muted); font-size: 10.5px; line-height: 1.6; text-align: center; }
@keyframes ac-layer-in { from { opacity: 0; } }
@keyframes ac-dialog-in { from { opacity: 0; transform: translateY(10px) scale(.985); } }

@media (max-width: 900px) {
  .agent-credit-chip { gap: 5px; padding-inline: 8px; }
  .agent-credit-chip__label { display: none; }
}

/* At tablet width the open sidebar leaves a narrow topbar. Keep the primary controls and daily
   balance visible; export actions remain available as soon as the sidebar is collapsed. */
@media (min-width: 768.02px) and (max-width: 900px) {
  body.product-agent:not(.sidebar-collapsed) #chatMdBtn,
  body.product-agent:not(.sidebar-collapsed) #chatPrintBtn,
  body.product-agent:not(.sidebar-collapsed) #chatTrBtn { display: none !important; }
}

@media (max-width: 560px) {
  .fc.agent-card.fc--stream { border-radius: 0; }
  .fc--stream .fc__hd { padding-inline: 0; }
  .fc--stream .fc__focus,
  .fc--stream .fc__meter,
  .fc--stream .fc__plan,
  .fc--stream .fc__tools,
  .fc--stream .fc__files,
  .fc--stream .fc__say,
  .fc--stream .fc__activity { margin-inline: 0; }
  .fc--stream .fc__focus { padding-inline: 0; }
  .fc--stream .fc__task > summary { grid-template-columns: 22px minmax(0,1fr) 16px; }
  .fc--stream .fc__task-body { margin-inline: 0; }
  .fc__browser-chrome { grid-template-columns: auto minmax(0,1fr); }
  .fc__browser-open { grid-column: 1 / -1; justify-self: stretch; min-block-size: 40px; text-align: center; }
  .agent-credit-chip { min-inline-size: 44px; min-block-size: 44px; justify-content: center; padding: 5px 7px; }
  .agent-credit-chip__label { display: none; }
  .agent-credit-chip__mark { inline-size: 20px; block-size: 20px; }
  .agent-credit-chip__value { font-size: 11px; }
  .ac-dialog-layer { align-items: end; padding: 12px; }
  .ac-dialog { border-radius: 20px; }
  .ac-dialog__stats { grid-template-columns: 1fr; gap: 7px; }
  .ac-dialog__stats > div,
  .ac-dialog__stats > div:first-child,
  .ac-dialog__stats > div:last-child { border-radius: 10px; }
}

@media (prefers-reduced-motion: reduce) {
  .agent-credit-chip, .ac-dialog-layer, .ac-dialog, .ac-dialog__bar i { animation: none; transition: none; }
}
:root[data-motion="off"] .agent-credit-chip,
:root[data-motion="off"] .ac-dialog-layer,
:root[data-motion="off"] .ac-dialog,
:root[data-motion="off"] .ac-dialog__bar i { animation: none; transition: none; }


/* ═══ AGENT CONVERSATION FLOW 3.0 ═══ */
/* One chronological conversation surface. Every visual decision inherits the active Firas theme. */
.agent-card.agent-card--flow,
.agent-card--flow .fc,
.fc.fc--stream {
  --fc-line: color-mix(in srgb, var(--color-border-hairline) 82%, transparent);
  --fc-hover: color-mix(in srgb, var(--color-accent) 6%, transparent);
  --fc-active-wash: color-mix(in srgb, var(--color-accent) 9%, transparent);
  --fc-active-ring: color-mix(in srgb, var(--color-accent) 36%, var(--color-border-hairline));
  --fc-active-halo: color-mix(in srgb, var(--color-accent) 18%, transparent);
}

.agent-card.agent-card--flow {
  inline-size: 100%;
  max-inline-size: 100%;
  margin: 0;
  padding: 0;
  overflow: visible;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
.agent-card.agent-card--flow::before,
.agent-card.agent-card--flow::after { display: none; }

/* UI 2's older timeline rule has greater specificity than the flow card above. Match it so the
   mission remains a conversation surface in every theme instead of regaining the boxed panel. */
:root[data-ui="2"] .agent-card.agent-card--flow,
:root[data-ui="2"] [dir="rtl"] .agent-card.agent-card--flow {
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
/* A structured mission owns its identity row in every shell and every theme. */
.msg-ai.msg-ai--agent-run > .msg-ai__head { display: none; }
:root[data-ui="2"] .msg-ai[dir="rtl"] .msg-ai__name { letter-spacing: 0; text-transform: none; }

.fc.fc--stream {
  position: relative;
  inline-size: 100%;
  max-inline-size: 100%;
  overflow: visible;
  padding-block: var(--space-2) var(--space-5);
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  color: var(--color-text-primary);
  isolation: isolate;
}

.fc__identity {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: 44px;
  padding-block: 0 var(--space-2);
}
.fc__identity-mark {
  display: grid;
  place-items: center;
  inline-size: 24px;
  block-size: 28px;
  flex: 0 0 auto;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--color-accent);
  direction: ltr;
}
.fc__identity-mark .fmark {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  color: var(--color-accent);
}
.fc__identity-name {
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
}
[lang="ar"] .fc__identity-name { font-family: var(--font-arabic-display); }
.fc__identity-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-inline-size: 0;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  white-space: nowrap;
}
.fc__identity-status i {
  inline-size: 6px;
  block-size: 6px;
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 12%, transparent);
}
.fc.is-done .fc__identity-status i { background: var(--color-success); box-shadow: none; }
.fc.is-fail .fc__identity-status i { background: var(--color-error); box-shadow: none; }
.fc__clock {
  margin-inline-start: auto;
  color: var(--color-text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}

.fc__speech,
.fc__mission {
  max-inline-size: 68ch;
  margin: var(--space-2) 0 var(--space-5);
  padding-inline-start: calc(28px + var(--space-2));
  color: var(--color-text-primary);
  font-size: var(--text-base);
  line-height: var(--leading-md);
  text-align: start;
}
.fc__speech { display: grid; gap: var(--space-3); }
.fc__speech[hidden], .fc__mission[hidden] { display: none; }
.fc__speech p, .fc__mission { margin-block-start: 0; margin-block-end: 0; white-space: pre-wrap; }

.fc__flow { border-block-start: var(--border-hairline) solid var(--fc-line); }
.fc__group,
.fc__event,
.fc__plan-step {
  position: relative;
  display: block;
  grid-template-columns: none;
  gap: 0;
  align-items: stretch;
  margin: 0;
  padding: 0;
  border: 0;
  border-block-end: var(--border-hairline) solid var(--fc-line);
  background: transparent;
  color: inherit;
  font-size: inherit;
  line-height: inherit;
}
.fc__event::before { content: none !important; display: none !important; }
.fc__group > summary,
.fc__event > summary,
.fc__plan-step > summary {
  position: relative;
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr) auto 20px;
  align-items: center;
  gap: var(--space-3);
  min-block-size: 48px;
  padding: var(--space-2) 2px;
  border-radius: var(--radius-md);
  list-style: none;
  color: var(--color-text-secondary);
  cursor: pointer;
  transition: color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
}
.fc__group > summary::-webkit-details-marker,
.fc__event > summary::-webkit-details-marker,
.fc__plan-step > summary::-webkit-details-marker { display: none; }
.fc__group > summary:hover,
.fc__event > summary:hover,
.fc__plan-step > summary:hover { background: var(--fc-hover); color: var(--color-text-primary); }
.fc__group > summary:focus-visible,
.fc__event > summary:focus-visible,
.fc__plan-step > summary:focus-visible {
  outline: 0;
  box-shadow: var(--shadow-focus);
}
.fc__row-icon {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  inline-size: 28px;
  block-size: 28px;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-bg-subtle);
  color: var(--color-text-secondary);
  transition: color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.fc__row-icon svg { inline-size: 17px; block-size: 17px; }
.fc__row-copy { min-inline-size: 0; text-align: start; }
.fc__row-copy strong,
.fc__row-copy small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fc__row-copy strong { color: var(--color-text-primary); font-size: 13.5px; line-height: var(--leading-sm); font-weight: var(--weight-semibold); }
.fc__row-copy small { margin-block-start: 1px; color: var(--color-text-muted); font-size: var(--text-xs); line-height: var(--leading-xs); }
.fc__row-meta {
  color: var(--color-text-muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.fc__chevron { display: grid; place-items: center; color: var(--color-text-muted); transition: transform var(--dur-base) var(--ease-out); }
.fc__chevron svg { inline-size: 18px; block-size: 18px; }
.fc__group[open] > summary .fc__chevron,
.fc__event[open] > summary .fc__chevron,
.fc__plan-step[open] > summary .fc__chevron { transform: rotate(180deg); }

.fc__group--plan > summary .fc__row-icon { color: var(--color-accent); border-color: var(--fc-active-ring); background: var(--color-accent-soft); }
.fc__plan-list { padding-inline-start: calc(28px + var(--space-3)); }
.fc__plan-step:last-child { border-block-end: 0; }
.fc__plan-step > summary { grid-template-columns: 24px minmax(0,1fr) auto 18px; min-block-size: 44px; gap: var(--space-2); }
.fc__plan-step .fc__row-icon { inline-size: 24px; block-size: 24px; border-radius: var(--radius-pill); }
.fc__plan-step .fc__row-icon svg { inline-size: 14px; block-size: 14px; }
.fc__plan-step.is-done .fc__row-icon { color: var(--color-success); border-color: color-mix(in srgb, var(--color-success) 42%, var(--color-border-hairline)); background: color-mix(in srgb, var(--color-success) 8%, transparent); }
.fc__plan-step.is-run .fc__row-icon { color: var(--color-accent); border-color: var(--fc-active-ring); box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 12%, transparent), 0 0 18px var(--fc-active-halo); }
.fc__plan-step.is-todo { opacity: .66; }
.fc__plan-step.is-fail .fc__row-icon { color: var(--color-error); border-color: color-mix(in srgb, var(--color-error) 42%, var(--color-border-hairline)); }
.fc__plan-step-body,
.fc__event-body {
  margin-inline-start: calc(28px + var(--space-3));
  padding: var(--space-1) 2px var(--space-3);
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  line-height: var(--leading-sm);
}
.fc__plan-step-body { margin-inline-start: calc(24px + var(--space-2)); }
.fc__plan-step-body > p,
.fc__event-body > p { margin: 0 0 var(--space-2); overflow-wrap: anywhere; }
.fc__event-body > a {
  display: inline-flex;
  align-items: center;
  min-block-size: 36px;
  color: var(--color-accent);
  font-size: var(--text-xs);
  text-decoration: none;
}
.fc__event-body > a:hover { text-decoration: underline; }
.fc__event-body > a:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.fc__detail-label { display: block; margin-block-end: var(--space-2); color: var(--color-text-muted); font-size: var(--text-xs); }
.fc__step-output { color: var(--color-text-secondary); font-size: var(--text-sm); line-height: var(--leading-base); overflow-wrap: anywhere; }
.fc__step-output > :first-child { margin-block-start: 0; }
.fc__step-output > :last-child { margin-block-end: 0; }

.fc__event.is-active {
  background: radial-gradient(circle at 14px 50%, var(--fc-active-wash), transparent 96px);
}
[dir="rtl"] .fc__event.is-active { background: radial-gradient(circle at calc(100% - 14px) 50%, var(--fc-active-wash), transparent 96px); }
.fc__event.is-active .fc__row-icon {
  color: var(--color-accent);
  border-color: var(--fc-active-ring);
  background: var(--color-accent-soft);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 13%, transparent), 0 0 18px var(--fc-active-halo);
  animation: fc-active-halo 1.6s var(--ease-inout) infinite;
}
.fc__event.is-done .fc__row-icon { color: var(--color-success); border-color: color-mix(in srgb, var(--color-success) 38%, var(--color-border-hairline)); }
.fc__event.is-fail .fc__row-icon { color: var(--color-error); border-color: color-mix(in srgb, var(--color-error) 42%, var(--color-border-hairline)); }
.fc__event.is-new { animation: fc-row-in var(--dur-slow) var(--ease-out) both; }
@keyframes fc-row-in { from { opacity: 0; transform: translateY(6px); } }
@keyframes fc-active-halo { 50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-accent) 8%, transparent), 0 0 22px var(--fc-active-halo); } }
:root[data-theme="light"] .fc__event.is-active .fc__row-icon { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 8%, transparent), 0 0 14px color-mix(in srgb, var(--color-accent) 9%, transparent); }

.fc__prose {
  max-inline-size: 68ch;
  margin: var(--space-5) 0;
  padding-inline-start: calc(28px + var(--space-3));
  color: var(--color-text-primary);
  font-size: var(--text-base);
  line-height: var(--leading-md);
  white-space: pre-wrap;
}
.fc__source-list,
.fc__file-grid {
  margin-inline-start: calc(28px + var(--space-3));
  padding: var(--space-1) 2px var(--space-3);
}
.fc__source-list { display: grid; gap: var(--space-2); }
.fc__source-list a {
  display: grid;
  gap: 2px;
  min-block-size: 44px;
  padding: var(--space-2) var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: inherit;
  text-decoration: none;
  box-shadow: var(--shadow-xs);
}
.fc__source-list a:hover { border-color: var(--fc-active-ring); }
.fc__source-list a:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.fc__source-list strong { color: var(--color-text-primary); font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--weight-medium); overflow-wrap: anywhere; }
.fc__source-list small { color: var(--color-text-muted); font-size: var(--text-xs); line-height: var(--leading-xs); overflow-wrap: anywhere; }
.fc__file-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr)); gap: var(--space-2); }
.fc__file-link {
  display: grid;
  gap: var(--space-2);
  min-block-size: 44px;
  overflow: hidden;
  padding: var(--space-2);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--text-sm);
  text-decoration: none;
  box-shadow: var(--shadow-xs);
}
.fc__file-link:hover { border-color: var(--fc-active-ring); }
.fc__file-link:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.fc__file-link img { display: block; inline-size: 100%; aspect-ratio: 16 / 9; border-radius: var(--radius-md); object-fit: cover; }

.agent-card--flow .agent-steer,
.agent-card--flow .agent-card__report,
.agent-card--flow .agent-card__resume,
.agent-card--flow .agent-card__final {
  margin-inline: 0;
}
.agent-card--flow .agent-card__report {
  margin-block-start: var(--space-5);
  padding-block: var(--space-3);
  border-block: var(--border-hairline) solid var(--fc-line);
  border-radius: 0;
  background: transparent;
}
.agent-card--flow .agent-card__final {
  margin-block-start: var(--space-6);
  padding: var(--space-5) 0 0;
  border: 0;
  border-block-start: var(--border-hairline) solid var(--fc-line);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
.agent-card--flow .agent-card__final-label {
  margin-block-end: var(--space-3);
  color: var(--color-accent);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}

.agent-card--flow .agent-card__resume {
  border-color: var(--color-border-hairline);
  background: var(--color-bg-subtle);
  color: var(--color-text-primary);
  box-shadow: none;
}
.agent-card--flow .agent-card__resume:hover { border-color: color-mix(in srgb, var(--color-accent) 40%, var(--color-border-hairline)); background: var(--color-accent-soft); }
.agent-card--flow .agent-card__fixrest,
.agent-card--flow .agent-steer__go {
  border-color: transparent;
  background: var(--color-accent);
  color: var(--color-on-accent);
  box-shadow: var(--shadow-sm);
}

.agent-credit-chip,
.ac-dialog-layer,
.ac-dialog,
.ac-dialog__bar i { transition-duration: var(--dur-base); transition-timing-function: var(--ease-out); }
.agent-credit-chip { background: color-mix(in srgb, var(--color-accent) 7%, transparent); }
.ac-dialog { border-radius: var(--radius-xl); background: var(--color-surface); }
.ac-dialog__bar i { background: var(--color-accent); }

@media (max-width: 560px) {
  .fc__identity { gap: 6px; }
  .fc__identity-status { max-inline-size: 100px; overflow: hidden; text-overflow: ellipsis; }
  .fc__speech, .fc__mission, .fc__prose { padding-inline-start: 0; }
  .fc__group > summary, .fc__event > summary { grid-template-columns: 28px minmax(0,1fr) 18px; gap: var(--space-2); }
  .fc__group > summary .fc__row-meta, .fc__event > summary .fc__row-meta { display: none; }
  .fc__plan-list, .fc__source-list, .fc__file-grid, .fc__event-body { margin-inline-start: 0; padding-inline-start: calc(28px + var(--space-2)); }
  .fc__plan-step > summary { grid-template-columns: 24px minmax(0,1fr) 18px; }
  .fc__plan-step > summary .fc__row-meta { display: none; }
  .fc__plan-step-body { margin-inline-start: 0; padding-inline-start: calc(24px + var(--space-2)); }
}

@media (prefers-reduced-motion: reduce) {
  .fc__chevron,
  .fc__group > summary,
  .fc__event > summary,
  .fc__plan-step > summary,
  .fc__row-icon { transition: none; }
  .fc__event.is-active .fc__row-icon,
  .fc__event.is-new { animation: none; }
}
:root[data-motion="off"] .fc__chevron,
:root[data-motion="off"] .fc__group > summary,
:root[data-motion="off"] .fc__event > summary,
:root[data-motion="off"] .fc__plan-step > summary,
:root[data-motion="off"] .fc__row-icon { transition: none; }
:root[data-motion="off"] .fc__event.is-active .fc__row-icon,
:root[data-motion="off"] .fc__event.is-new { animation: none; }

/* Durable Agent polish: status dots keep the active theme. Brand marks remain the original
   transparent Beam — grouping them with the dots recreated the square tile over the logo. */
.product-agent .product-switch__dot,
.msg-ai__badge--agent .dot { background: linear-gradient(135deg, var(--color-accent-hover), var(--color-accent)); }
.msg-ai__badge--agent { background: linear-gradient(100deg, var(--color-accent-soft), transparent); }
.agent-card__prog-fill,
.agent-card__resume { background: linear-gradient(100deg, var(--color-accent-hover), var(--color-accent)); }
.msg-ai--agent-run.is-thinking .msg-ai__body::before { display: none !important; }


/* ═══ AGENT GEOMETRY + ARTIFACT VIEWER ═══════════════════════════════════════
   Icons use geometric boxes instead of font baselines. The same contract serves the
   topbar balance, Agent rows and the internal artifact viewer in every Firas theme. */
:where(
  .agent-credit-chip__mark,
  .ac-dialog__close,
  .ac-dialog__add > [aria-hidden="true"],
  .fc__row-icon,
  .fc__chevron,
  .fc-artifact__close
) {
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
  padding: 0;
  line-height: 0;
}

:where(
  .agent-credit-chip__mark,
  .ac-dialog__close,
  .ac-dialog__add > [aria-hidden="true"],
  .fc__row-icon,
  .fc__chevron,
  .fc-artifact__close,
  .fc-artifact__download,
  .fc__file-button
) > svg {
  display: block;
  flex: 0 0 auto;
  margin: 0;
}

/* The current balance mark is a text star. Draw it as geometry so Arabic/Latin font
   metrics and browser zoom cannot move it inside its circle. A future inline SVG wins. */
.agent-credit-chip__mark {
  position: relative;
  font-size: 0;
}
.agent-credit-chip__mark::before {
  content: "";
  inline-size: 12px;
  block-size: 12px;
  background: currentColor;
  clip-path: polygon(50% 0, 60% 39%, 100% 50%, 60% 61%, 50% 100%, 40% 61%, 0 50%, 40% 39%);
}
.agent-credit-chip__mark:has(> svg)::before { display: none; }
.agent-credit-chip__mark > svg { inline-size: 13px; block-size: 13px; }

/* The dialog currently emits a font glyph. Its Noto Arabic line box is taller than the
   44px target, so Grid centres the line box while the visible X lands low. These two
   strokes are centred on the button's own coordinate system; a future SVG can replace them. */
.ac-dialog__close {
  position: relative;
  padding: 0;
  font-size: 0;
  line-height: 0;
}
.ac-dialog__close::before,
.ac-dialog__close::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  inline-size: 18px;
  block-size: 2px;
  border-radius: var(--radius-pill);
  background: currentColor;
  transform-origin: center;
}
.ac-dialog__close::before { transform: translate(-50%, -50%) rotate(45deg); }
.ac-dialog__close::after { transform: translate(-50%, -50%) rotate(-45deg); }
.ac-dialog__close:has(> svg)::before,
.ac-dialog__close:has(> svg)::after { display: none; }
.ac-dialog__close > svg { inline-size: 20px; block-size: 20px; }

/* The Add Credits plus follows the same deterministic geometry. */
.ac-dialog__add > [aria-hidden="true"] {
  position: relative;
  inline-size: 18px;
  block-size: 18px;
  font-size: 0;
}
.ac-dialog__add > [aria-hidden="true"]::before,
.ac-dialog__add > [aria-hidden="true"]::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  inline-size: 13px;
  block-size: 1.5px;
  border-radius: var(--radius-pill);
  background: currentColor;
  transform: translate(-50%, -50%);
}
.ac-dialog__add > [aria-hidden="true"]::after { transform: translate(-50%, -50%) rotate(90deg); }
.ac-dialog__add > [aria-hidden="true"]:has(> svg)::before,
.ac-dialog__add > [aria-hidden="true"]:has(> svg)::after { display: none; }
.ac-dialog__add > [aria-hidden="true"] > svg { inline-size: 16px; block-size: 16px; }

.fc__identity-status { line-height: var(--leading-xs); }
.fc__identity-status i { align-self: center; margin: 0; }
.fc__row-icon > svg { inline-size: 17px; block-size: 17px; }
.fc__plan-step .fc__row-icon > svg { inline-size: 14px; block-size: 14px; }
.fc__chevron { inline-size: 20px; block-size: 20px; }
.fc__chevron > svg { inline-size: 18px; block-size: 18px; }
/* Only the generic wrench drawing is bottom-heavy in its 24px viewBox. Shift the drawing,
   never the rounded-square box or row baseline. */
.fc__event.is-tool > summary .fc__row-icon > svg { transform: translateY(-1px); }

/* Generated media appears inline with the conversation and remains an affordance, not a
   decorative thumbnail. Cards use the active theme tokens and open into the viewer below. */
.fc__media {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
  gap: var(--space-3);
  margin-inline-start: calc(28px + var(--space-3));
  padding: var(--space-2) 2px var(--space-4);
}
.fc__media-card {
  position: relative;
  display: grid;
  min-inline-size: 0;
  overflow: hidden;
  padding: 0;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  color: var(--color-text-primary);
  text-align: start;
  text-decoration: none;
  box-shadow: var(--shadow-xs);
  isolation: isolate;
  cursor: pointer;
  transition: border-color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
button.fc__media-card { inline-size: 100%; }
.fc__media-card:hover {
  border-color: color-mix(in srgb, var(--color-accent) 42%, var(--color-border-hairline));
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}
.fc__media-card:active { transform: none; }
.fc__media-card:focus-visible { outline: 0; box-shadow: var(--shadow-focus), var(--shadow-sm); }
.fc__media-image {
  display: block;
  inline-size: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  background: var(--color-bg-subtle);
}
.fc__media-credit {
  position: absolute;
  inset-inline: var(--space-2);
  inset-block-end: var(--space-2);
  display: inline-flex;
  align-items: center;
  justify-self: start;
  gap: 6px;
  inline-size: fit-content;
  max-inline-size: calc(100% - var(--space-4));
  min-block-size: 28px;
  padding: 4px 9px;
  overflow: hidden;
  border: var(--border-hairline) solid color-mix(in srgb, var(--color-border-hairline) 78%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-surface) 90%, transparent);
  color: var(--color-text-secondary);
  font-size: 10.5px;
  line-height: 1.35;
  text-overflow: ellipsis;
  white-space: nowrap;
  box-shadow: var(--shadow-sm);
  -webkit-backdrop-filter: blur(10px) saturate(.9);
  backdrop-filter: blur(10px) saturate(.9);
  pointer-events: none;
}
.fc__media-credit::before {
  content: "";
  inline-size: 6px;
  block-size: 6px;
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 12%, transparent);
}

.fc__file-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-block-size: 44px;
  max-inline-size: 100%;
  padding: 8px var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  line-height: var(--leading-sm);
  text-align: start;
  text-decoration: none;
  box-shadow: var(--shadow-xs);
  cursor: pointer;
  transition: border-color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.fc__file-button:hover { border-color: color-mix(in srgb, var(--color-accent) 42%, var(--color-border-hairline)); background: var(--color-accent-soft); color: var(--color-accent); }
.fc__file-button:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.fc__file-button > svg { inline-size: 18px; block-size: 18px; }

/* Internal artifact viewer: one large, keyboard-safe surface for documents, code, sites and
   generated media. It is full-screen only on phones; desktop retains context around the modal. */
.fc-artifact-layer {
  position: fixed;
  inset: 0;
  z-index: 10030;
  display: grid;
  place-items: center;
  padding: max(var(--space-4), var(--safe-top)) max(var(--space-4), var(--safe-right)) max(var(--space-4), var(--safe-bottom)) max(var(--space-4), var(--safe-left));
  background: color-mix(in srgb, var(--color-bg) 76%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(.85);
  backdrop-filter: blur(12px) saturate(.85);
  animation: fc-artifact-layer-in var(--dur-base) ease both;
}
.fc-artifact-layer[hidden] { display: none; }
.fc-artifact {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  inline-size: min(1120px, 100%);
  block-size: min(820px, calc(100vh - max(32px, var(--safe-top)) - max(32px, var(--safe-bottom))));
  block-size: min(820px, calc(100dvh - max(32px, var(--safe-top)) - max(32px, var(--safe-bottom))));
  max-block-size: 100%;
  min-block-size: 0;
  overflow: hidden;
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: calc(var(--radius-xl) + 5px);
  background: var(--color-surface);
  color: var(--color-text-primary);
  box-shadow: var(--shadow-lg);
  animation: fc-artifact-seat var(--dur-slow) var(--ease-out) both;
}
.fc-artifact__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-block-size: 58px;
  padding: 7px 8px 7px var(--space-4);
  border-block-end: var(--border-hairline) solid var(--color-border-hairline);
  background: color-mix(in srgb, var(--color-bg-subtle) 72%, var(--color-surface));
}
[dir="rtl"] .fc-artifact__head { padding: 7px var(--space-4) 7px 8px; }
.fc-artifact__title {
  flex: 1 1 auto;
  min-inline-size: 0;
  overflow: hidden;
  color: var(--color-text-primary);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-sm);
  text-align: start;
  text-overflow: ellipsis;
  white-space: nowrap;
}
[lang="ar"] .fc-artifact__title { font-family: var(--font-arabic-display); }
.fc-artifact__actions {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-inline-start: auto;
}
.fc-artifact__close {
  position: relative;
  inline-size: 44px;
  block-size: 44px;
  min-inline-size: 44px;
  border-radius: var(--radius-lg);
  color: var(--color-text-secondary);
  background: transparent;
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}
.fc-artifact__close:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.fc-artifact__close:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.fc-artifact__close > svg { inline-size: 20px; block-size: 20px; }
.fc-artifact__body {
  position: relative;
  min-inline-size: 0;
  min-block-size: 0;
  overflow: auto;
  overscroll-behavior: contain;
  background: var(--color-bg-subtle);
  scrollbar-width: thin;
}
.fc-artifact__frame {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  min-block-size: 420px;
  border: 0;
  background: var(--color-surface);
}
.fc-artifact__pre {
  min-inline-size: 100%;
  min-block-size: 100%;
  margin: 0;
  padding: clamp(var(--space-4), 3vw, var(--space-8));
  overflow: auto;
  background: var(--color-surface);
  color: var(--color-text-primary);
  font: 400 12.5px/1.75 var(--font-mono);
  direction: ltr;
  text-align: left;
  tab-size: 2;
  white-space: pre;
}
.fc-artifact__pre--arabic {
  font-family: "Segoe UI", Tahoma, "Noto Sans Arabic", sans-serif;
  font-size: 16px;
  line-height: 1.9;
  direction: rtl;
  text-align: start;
  unicode-bidi: plaintext;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  letter-spacing: normal;
}
.fc-artifact__pre--arabic code { font: inherit; letter-spacing: normal; unicode-bidi: plaintext; }
.fc-artifact__loading {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--space-3);
  padding: var(--space-6);
  background: color-mix(in srgb, var(--color-surface) 92%, transparent);
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  line-height: var(--leading-sm);
  text-align: center;
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
}
.fc-artifact__loading[hidden] { display: none; }
.fc-artifact__loading::before {
  content: "";
  inline-size: 30px;
  block-size: 30px;
  border: 2px solid color-mix(in srgb, var(--color-accent) 22%, var(--color-border-hairline));
  border-block-start-color: var(--color-accent);
  border-radius: var(--radius-pill);
  animation: fc-artifact-spin .8s linear infinite;
}
.fc-artifact__download {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-block-size: 44px;
  padding: 7px var(--space-3);
  border: var(--border-hairline) solid var(--color-border-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-xs);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}
.fc-artifact__download:hover { border-color: color-mix(in srgb, var(--color-accent) 42%, var(--color-border-hairline)); background: var(--color-accent-soft); color: var(--color-accent); }
.fc-artifact__download:focus-visible { outline: 0; box-shadow: var(--shadow-focus); }
.fc-artifact__download > svg { inline-size: 17px; block-size: 17px; }

@keyframes fc-artifact-layer-in { from { opacity: 0; } }
@keyframes fc-artifact-seat { from { opacity: 0; transform: translateY(8px) scale(.99); } }
@keyframes fc-artifact-spin { to { transform: rotate(360deg); } }

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .fc-artifact-layer { background: color-mix(in srgb, var(--color-bg) 94%, transparent); }
  .fc__media-credit,
  .fc-artifact__loading { background: var(--color-surface); }
}

@media (max-width: 700px) {
  .fc-artifact-layer {
    place-items: stretch;
    padding: 0;
  }
  .fc-artifact {
    inline-size: 100%;
    block-size: 100vh;
    block-size: 100dvh;
    max-block-size: none;
    border: 0;
    border-radius: 0;
  }
  .fc-artifact__head {
    min-block-size: calc(56px + var(--safe-top));
    padding-block-start: calc(7px + var(--safe-top));
    padding-inline-end: max(8px, var(--safe-right));
    padding-inline-start: max(var(--space-3), var(--safe-left));
  }
  [dir="rtl"] .fc-artifact__head {
    padding-inline-end: max(var(--space-3), var(--safe-right));
    padding-inline-start: max(8px, var(--safe-left));
  }
  .fc-artifact__title { font-size: var(--text-xs); }
  .fc-artifact__frame { min-block-size: 100%; }
  .fc-artifact__pre { padding: var(--space-4); font-size: 12px; }
  .fc-artifact__pre--arabic { font-size: 16px; }
}

@media (max-width: 560px) {
  .agent-credit-chip__mark::before { inline-size: 11px; block-size: 11px; }
  .agent-credit-chip__mark > svg { inline-size: 12px; block-size: 12px; }
  .fc__media {
    grid-template-columns: minmax(0, 1fr);
    margin-inline-start: 0;
    padding-inline: 0;
  }
  .fc__media-image { aspect-ratio: 4 / 3; }
  .fc__media-credit { max-inline-size: calc(100% - var(--space-4)); }
  .fc-artifact__actions { gap: 0; }
  .fc-artifact__download { padding-inline: 10px; }
}

@media (prefers-reduced-motion: reduce) {
  .fc__media-card,
  .fc__file-button,
  .fc-artifact-layer,
  .fc-artifact,
  .fc-artifact__close,
  .fc-artifact__download { animation: none; transition: none; }
  .fc-artifact__loading::before { animation: none; border-color: var(--color-accent); }
}
:root[data-motion="off"] .fc__media-card,
:root[data-motion="off"] .fc__file-button,
:root[data-motion="off"] .fc-artifact-layer,
:root[data-motion="off"] .fc-artifact,
:root[data-motion="off"] .fc-artifact__close,
:root[data-motion="off"] .fc-artifact__download { animation: none; transition: none; }
:root[data-motion="off"] .fc-artifact__loading::before { animation: none; border-color: var(--color-accent); }

/* === FIRAS CODE V5 — PROFESSIONAL WORKSPACE === */
/* Final, opt-in source-order layer.  It consumes the active theme tokens and
   deliberately leaves the canonical F mark paths and device chassis alone. */
.cw.cw--v5,
.cw-home.cw-home--v5 {
  --cwv5-hair: color-mix(in srgb, var(--color-text-primary) 15%, var(--color-border-hairline));
  --cwv5-border: color-mix(in srgb, var(--color-text-primary) 22%, var(--color-border-strong));
  --cwv5-raised: color-mix(in srgb, var(--color-surface) 88%, var(--color-bg));
  --cwv5-sunken: color-mix(in srgb, var(--color-bg-subtle) 84%, var(--color-bg));
  --cwv5-hover: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface));
  --cwv5-selected: color-mix(in srgb, var(--color-accent) 16%, var(--color-surface));
  --cwv5-shadow: 0 14px 38px color-mix(in srgb, var(--color-text-primary) 12%, transparent);
  color: var(--color-text-primary);
  background: var(--color-bg);
  font-family: var(--font-sans);
}

/* The workspace chrome and all code/path surfaces remain physically LTR. */
.cw.cw--v5,
.cw--v5 .cw-bar,
.cw--v5 .cw-main,
.cw--v5 .cw-tree,
.cw--v5 .cw-ed,
.cw--v5 .cw-etabs,
.cw--v5 .cw-right,
.cw--v5 .cw-computer,
.cw--v5 .cw-comphead,
.cw--v5 .cw-compbody,
.cw--v5 .cw-companel,
.cw--v5 .cw-status,
.cw--v5 .cw-mobnav {
  direction: ltr;
}
.cw--v5 .cw-file__p,
.cw--v5 .cw-etab,
.cw--v5 .cw-crumb,
.cw--v5 .cw-prevaddr,
.cw--v5 .cw-compage,
.cw--v5 .CodeMirror,
.cw--v5 .cw-ed__ta {
  direction: ltr;
  text-align: left;
  unicode-bidi: isolate;
}
.cw--v5 .cw-bar__name[dir="rtl"],
.cw--v5 .cw-tab[dir="rtl"],
.cw--v5 .cw-thread__bubble[dir="rtl"],
.cw--v5 .cw-compbrand[dir="rtl"],
.cw--v5 .cw-compstate[dir="rtl"],
.cw--v5 .cw-compactivity[dir="rtl"],
.cw--v5 .cw-compnote[dir="rtl"],
.cw--v5 .cw-ai__in[dir="rtl"],
.cw--v5 .cw-comptask__in[dir="rtl"],
.cw-home--v5 :where(h1, p, label, input, textarea, button)[dir="rtl"] {
  direction: rtl;
  text-align: start;
  unicode-bidi: plaintext;
  font-family: var(--font-arabic);
}
.cw--v5 .cw-bar__logo,
.cw--v5 .cw-bar__logo svg,
.cw--v5 .cw-bar__logo .fmark,
.cw-home--v5 .cw-home__mark,
.cw-home--v5 .cw-home__mark svg,
.cw-home--v5 .cw-home__mark .fmark {
  direction: ltr;
  transform: none !important;
}

/* Quiet, dense application bar.  No decorative tile is painted behind the F. */
.cw--v5 .cw-bar {
  min-block-size: 46px;
  gap: 6px;
  padding: 6px 10px;
  border-block-end: 1px solid var(--cwv5-hair);
  background: var(--cwv5-raised);
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.cw--v5 .cw-bar__logo {
  inline-size: 28px;
  block-size: 28px;
  flex: 0 0 28px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--color-accent);
  box-shadow: none;
}
.cw--v5 .cw-bar__logo .fmark,
.cw--v5 .cw-bar__logo > svg {
  inline-size: 26px;
  block-size: 26px;
  display: block;
}
.cw--v5 .cw-bar__home,
.cw--v5 .cw-bar__btn,
.cw--v5 .cw-dock__more {
  min-block-size: 32px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text-secondary);
  box-shadow: none;
  transform: none;
  transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.cw--v5 .cw-bar__home:hover,
.cw--v5 .cw-bar__btn:hover,
.cw--v5 .cw-dock__more:hover {
  border-color: var(--cwv5-hair);
  background: var(--cwv5-hover);
  color: var(--color-text-primary);
  box-shadow: none;
  transform: none;
}
.cw--v5 .cw-bar__btn.cw-run,
.cw--v5 .cw-bar__btn--primary {
  border-color: color-mix(in srgb, var(--color-accent) 62%, var(--cwv5-border));
  background: var(--color-accent);
  color: var(--color-on-accent);
  box-shadow: none;
}
.cw--v5 .cw-bar__btn.cw-run:hover,
.cw--v5 .cw-bar__btn--primary:hover {
  border-color: var(--color-accent-hover);
  background: var(--color-accent-hover);
  color: var(--color-on-accent);
}
.cw--v5 .cw-bar__name {
  min-inline-size: 0;
  max-inline-size: 24ch;
  font-size: 13px;
  font-weight: var(--weight-semibold);
}
.cw--v5 .cw-bar__count {
  padding: 2px 7px;
  border: 1px solid var(--cwv5-hair);
  border-radius: var(--radius-sm);
  background: var(--cwv5-sunken);
  font: 600 10px/1.35 var(--font-mono);
}
.cw--v5 .cw-dock,
.cw--v5 .cw-dock__strip,
.cw--v5 .cw-dock__grp {
  gap: 4px;
  background: transparent;
  border-color: var(--cwv5-hair);
  box-shadow: none;
}

/* Desktop: project tree, then the CONVERSATION as the strongest track, then the deck.
   The third column used to be the largest, from when it held the computer pane; it holds the
   editor and preview now, and the centre of gravity of this product is the conversation. The
   proportions are the old ones with the lead moved across, and the floors travel with them. */
.cw--v5 .cw-main {
  min-block-size: 0;
  grid-template-rows: minmax(0, 1fr);
  background: var(--color-bg);
}
@media (min-width: 1181px) {
  .cw--v5 .cw-main:not([data-split]) {
    grid-template-columns: minmax(176px, 210px) minmax(430px, 1.12fr) minmax(360px, 1fr);
  }
}
@media (min-width: 861px) and (max-width: 1180px) {
  .cw--v5 .cw-main:not([data-split]) {
    grid-template-columns: minmax(154px, 184px) minmax(360px, 1.08fr) minmax(310px, .92fr);
  }
}
.cw--v5 .cw-tree,
.cw--v5 .cw-ed,
.cw--v5 .cw-right {
  min-inline-size: 0;
  min-block-size: 0;
}
.cw--v5 .cw-tree {
  display: flex;
  flex-direction: column;
  border-inline-end: 1px solid var(--cwv5-hair);
  background: var(--cwv5-sunken);
}
.cw--v5 .cw-tree__head {
  min-block-size: 36px;
  padding: 7px 9px;
  border-block-end: 1px solid var(--cwv5-hair);
  color: var(--color-text-secondary);
  font-size: 10px;
  font-weight: var(--weight-semibold);
  letter-spacing: .08em;
}
.cw--v5 .cw-tree__list {
  flex: 1 1 auto;
  min-block-size: 0;
  padding: 4px;
  overflow: auto;
}
.cw--v5 .cw-file,
.cw--v5 .cw-folder {
  min-block-size: 30px;
  margin-block: 1px;
  padding-block: 4px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-secondary);
}
.cw--v5 .cw-file:hover,
.cw--v5 .cw-folder:hover {
  border-color: var(--cwv5-hair);
  background: var(--cwv5-hover);
  color: var(--color-text-primary);
}
.cw--v5 .cw-file.is-active,
.cw--v5 .cw-file[aria-current="true"] {
  border-color: color-mix(in srgb, var(--color-accent) 42%, var(--cwv5-hair));
  background: var(--cwv5-selected);
  color: var(--color-text-primary);
}
.cw--v5 .cw-file__x,
.cw--v5 .cw-folder__add,
.cw--v5 .cw-proj__del {
  color: var(--color-text-secondary);
}

/* Editor chrome stays compact; CodeMirror owns typography and its device-independent sizing. */
.cw--v5 .cw-ed {
  border-inline-end: 1px solid var(--cwv5-hair);
  background: var(--color-bg);
}
.cw--v5 .cw-etabs {
  min-block-size: 37px;
  padding: 0 4px;
  border-block-end: 1px solid var(--cwv5-hair);
  background: var(--cwv5-raised);
}
.cw--v5 .cw-etab {
  min-block-size: 36px;
  border: 0;
  border-inline-end: 1px solid var(--cwv5-hair);
  border-radius: 0;
  background: transparent;
  color: var(--color-text-secondary);
}
.cw--v5 .cw-etab:hover {
  background: var(--cwv5-hover);
  color: var(--color-text-primary);
}
.cw--v5 .cw-etab.is-active,
.cw--v5 .cw-etab[aria-selected="true"] {
  background: var(--color-bg);
  color: var(--color-text-primary);
  box-shadow: inset 0 -2px var(--color-accent);
}
.cw--v5 .cw-etabs__copy {
  border-inline-start: 1px solid var(--cwv5-hair);
  border-radius: 0;
}
.cw--v5 .cw-crumb,
.cw--v5 .cw-status {
  background: var(--cwv5-sunken);
  border-color: var(--cwv5-hair);
}
.cw--v5 .cw-ed .CodeMirror,
.cw--v5 .cw-ed__ta {
  background: var(--color-bg);
  color: var(--color-text-primary);
}

/* Right rail and tabs. */
.cw--v5 .cw-right {
  display: flex;
  flex-direction: column;
  border-inline-start: 0;
  background: var(--cwv5-raised);
}
.cw--v5 .cw-tabs {
  min-block-size: 38px;
  gap: 1px;
  padding: 3px 6px;
  border-block-end: 1px solid var(--cwv5-hair);
  background: var(--cwv5-raised);
}
.cw--v5 .cw-tab {
  min-block-size: 30px;
  padding: 5px 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-secondary);
}
.cw--v5 .cw-tab:hover {
  background: var(--cwv5-hover);
  color: var(--color-text-primary);
}
.cw--v5 .cw-tab.is-active,
.cw--v5 .cw-tab[aria-selected="true"] {
  border-color: var(--cwv5-hair);
  background: var(--color-bg);
  color: var(--color-text-primary);
  box-shadow: inset 0 -2px var(--color-accent);
}
.cw--v5 .cw-prevwrap,
.cw--v5 .cw-console,
.cw--v5 .cw-thread {
  flex: 1 1 auto;
  min-block-size: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  background: var(--color-bg);
  box-shadow: none;
}
.cw--v5 .cw-prevchrome {
  min-block-size: 38px;
  padding: 5px 7px;
  border-block-end: 1px solid var(--cwv5-hair);
  background: var(--cwv5-sunken);
}
.cw--v5 .cw-prevbtn,
.cw--v5 .cw-prevdev,
.cw--v5 .cw-conclear {
  border-color: transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-secondary);
}
.cw--v5 .cw-prevbtn:hover,
.cw--v5 .cw-prevdev:hover,
.cw--v5 .cw-conclear:hover {
  border-color: var(--cwv5-hair);
  background: var(--cwv5-hover);
  color: var(--color-text-primary);
}

/* Virtual computer: native preview plus a concise, inspectable activity rail. */
.cw--v5 .cw-computer {
  flex: 1 1 auto;
  min-inline-size: 0;
  min-block-size: 0;
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  overflow: hidden;
  border: 1px solid var(--cwv5-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg);
  box-shadow: var(--cwv5-shadow);
}
.cw--v5 .cw-comphead {
  min-block-size: 42px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px 6px 11px;
  border-block-end: 1px solid var(--cwv5-hair);
  background: var(--cwv5-raised);
}
.cw--v5 .cw-compbrand {
  min-inline-size: 0;
  overflow: hidden;
  color: var(--color-text-primary);
  font-size: 12px;
  font-weight: var(--weight-semibold);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cw--v5 .cw-compstate {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-block-size: 24px;
  padding-inline: 8px;
  border: 1px solid var(--cwv5-hair);
  border-radius: var(--radius-pill);
  background: var(--cwv5-sunken);
  color: var(--color-text-secondary);
  font-size: 10.5px;
  white-space: nowrap;
}
.cw--v5 .cw-compstate::before {
  content: "";
  inline-size: 7px;
  block-size: 7px;
  flex: 0 0 7px;
  border-radius: 50%;
  background: var(--color-success);
}
.cw--v5 .cw-computer[data-state="busy"] .cw-compstate::before,
.cw--v5 .cw-compstate.is-busy::before {
  background: var(--color-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 14%, transparent);
}
.cw--v5 .cw-computer[data-state="error"] .cw-compstate::before,
.cw--v5 .cw-compstate.is-error::before {
  background: var(--color-error);
}
.cw--v5 .cw-compactions {
  margin-inline-start: auto;
  display: flex;
  align-items: center;
  gap: 3px;
  opacity: .72;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.cw--v5 .cw-computer:hover .cw-compactions,
.cw--v5 .cw-computer:focus-within .cw-compactions {
  opacity: 1;
}
.cw--v5 .cw-compactions :where(button, a) {
  min-inline-size: 30px;
  min-block-size: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding-inline: 8px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-secondary);
}
.cw--v5 .cw-compactions :where(button, a):hover {
  border-color: var(--cwv5-hair);
  background: var(--cwv5-hover);
  color: var(--color-text-primary);
}
.cw--v5 .cw-compage {
  inline-size: min(220px, 32vw);
  max-inline-size: 220px;
  min-block-size: 32px;
  display: block;
  padding: 4px 28px 4px 9px;
  overflow: hidden;
  border: 1px solid var(--cwv5-border);
  border-radius: var(--radius-sm);
  background: var(--cwv5-sunken);
  color: var(--color-text-primary);
  font: 11px/1.35 var(--font-mono);
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}
.cw--v5 .cw-compactivity {
  min-block-size: 28px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 10px;
  border-block-end: 1px solid var(--cwv5-hair);
  background: var(--color-bg);
  color: var(--color-text-secondary);
  font-size: 11px;
}
.cw--v5 .cw-compactivity[aria-live="polite"]:not(:empty)::before {
  content: "";
  inline-size: 6px;
  block-size: 6px;
  flex: 0 0 6px;
  border-radius: 50%;
  background: var(--color-accent);
}
.cw--v5 .cw-compbody {
  position: relative;
  min-inline-size: 0;
  min-block-size: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(250px, 30%);
  overflow: hidden;
  background: var(--color-bg);
}
.cw--v5 .cw-compbody > * {
  min-inline-size: 0;
  min-block-size: 0;
}
.cw--v5 .cw-companel {
  position: relative;
  min-inline-size: 0;
  min-block-size: 0;
  display: flex;
  overflow: hidden;
  background: var(--color-bg);
}
.cw--v5 .cw-companel > .cw-preview {
  flex: 1 1 auto;
  inline-size: 100%;
  min-inline-size: 0;
  min-block-size: 0;
  border: 0;
}
.cw--v5 .cw-computer[data-state="busy"] .cw-preview {
  pointer-events: none;
  cursor: progress;
}
.cw--v5 .cw-complog {
  min-block-size: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-inline-start: 1px solid var(--cwv5-hair);
  background: var(--cwv5-raised);
  color: var(--color-text-secondary);
  font: 11px/1.55 var(--font-mono);
}
.cw--v5 .cw-complog__items {
  flex: 1 1 auto;
  min-block-size: 72px;
  padding: 5px;
  overflow: auto;
}
.cw--v5 .cw-complog__row {
  display: grid;
  grid-template-columns: 58px minmax(0, 1fr);
  gap: 8px;
  min-block-size: 27px;
  padding: 4px 6px;
  border-block-end: 1px solid var(--cwv5-hair);
}
.cw--v5 .cw-complog__time {
  color: var(--color-text-muted);
  direction: ltr;
  text-align: left;
  unicode-bidi: isolate;
}
.cw--v5 .cw-complog__text {
  min-inline-size: 0;
  color: inherit;
  overflow-wrap: anywhere;
  text-align: start;
  unicode-bidi: plaintext;
}
.cw--v5 .cw-complog__row:hover {
  background: var(--cwv5-hover);
}
.cw--v5 .cw-complog__row[data-level="error"] {
  color: var(--color-error);
}
.cw--v5 .cw-complog__row[data-level="success"] {
  color: var(--color-success);
}
.cw--v5 .cw-comptask {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: end;
  gap: 6px;
  padding: 8px;
  border-block-start: 1px solid var(--cwv5-hair);
  background: var(--cwv5-raised);
}
.cw--v5 .cw-comptask__in {
  min-inline-size: 0;
  min-block-size: 38px;
  max-block-size: 132px;
  resize: none;
  padding: 9px 11px;
  border: 1px solid var(--cwv5-border);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  color: var(--color-text-primary);
  font: 13px/1.45 var(--font-sans);
}
.cw--v5 .cw-comptask__go,
.cw--v5 .cw-comptask__stop {
  min-block-size: 38px;
  padding-inline: 13px;
  border: 1px solid var(--cwv5-border);
  border-radius: var(--radius-md);
  font-size: 12px;
  font-weight: var(--weight-semibold);
}
.cw--v5 .cw-comptask__go {
  border-color: var(--color-accent);
  background: var(--color-accent);
  color: var(--color-on-accent);
}
.cw--v5 .cw-comptask__stop {
  background: transparent;
  color: var(--color-error);
}
.cw--v5 .cw-compnote {
  margin: 0;
  padding: 7px 10px;
  border-block-start: 1px solid var(--cwv5-hair);
  background: var(--cwv5-sunken);
  color: var(--color-text-secondary);
  font-size: 10.5px;
  line-height: 1.45;
}

/* Compact widths keep the real preview visible and place its verified activity below it. */
@media (max-width: 1180px) {
  .cw--v5 .cw-compbody {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(220px, 1fr) minmax(160px, 34%);
  }
  .cw--v5 .cw-complog {
    border-block-start: 1px solid var(--cwv5-hair);
    border-inline-start: 0;
  }
}

/* Persisted AI thread and composer. */
.cw--v5 .cw-thread__list {
  min-block-size: 0;
  padding: 12px;
  background: var(--color-bg);
}
.cw--v5 .cw-thread__turn {
  max-inline-size: min(92%, 720px);
}
.cw--v5 .cw-thread__bubble {
  border: 1px solid var(--cwv5-hair);
  border-radius: var(--radius-lg);
  background: var(--cwv5-raised);
  color: var(--color-text-primary);
  box-shadow: none;
}
.cw--v5 .cw-thread__turn--me .cw-thread__bubble {
  border-color: color-mix(in srgb, var(--color-accent) 45%, var(--cwv5-hair));
  background: var(--cwv5-selected);
}
.cw--v5 .cw-ai {
  min-block-size: 54px;
  gap: 7px;
  padding: 8px 10px;
  border-block-start: 1px solid var(--cwv5-hair);
  background: var(--cwv5-raised);
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.cw--v5 .cw-ai__in {
  min-block-size: 38px;
  max-inline-size: none;
  margin: 0;
  padding: 9px 12px;
  border: 1px solid var(--cwv5-border);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  color: var(--color-text-primary);
  box-shadow: none;
}
.cw--v5 .cw-ai__in:hover {
  border-color: var(--color-text-secondary);
}
.cw--v5 .cw-ai__in:focus {
  border-color: var(--color-accent);
  background: var(--color-bg);
  box-shadow: var(--shadow-focus);
}
.cw--v5 .cw-ai__clip,
.cw--v5 .cw-ai__go {
  min-inline-size: 38px;
  min-block-size: 38px;
  border-radius: var(--radius-md);
  box-shadow: none;
  transform: none;
}
.cw--v5 .cw-ai__go {
  padding-inline: 14px;
  background: var(--color-accent);
  color: var(--color-on-accent);
}
.cw--v5 .cw-ai__go:hover {
  background: var(--color-accent-hover);
  box-shadow: none;
  transform: none;
}
.cw--v5 .cw-status {
  min-block-size: 27px;
  gap: 4px;
  padding: 2px 8px;
  color: var(--color-text-secondary);
  font-size: 10.5px;
}
.cw--v5 .cw-status__seg,
.cw--v5 .cw-status__save {
  min-block-size: 22px;
  border-radius: var(--radius-sm);
}

/* Home: product-like project launcher, using the original F mark supplied by JS. */
.cw-home.cw-home--v5 {
  align-items: stretch;
  justify-content: flex-start;
  gap: 0;
  padding: clamp(24px, 5vh, 58px) clamp(18px, 4vw, 48px);
  overflow: auto;
  background: var(--color-bg);
  background-image: none;
}
.cw-home--v5 .cw-home__inner {
  inline-size: min(100%, 900px);
  max-inline-size: 900px;
  gap: 22px;
}
.cw-home--v5 .cw-home__hero {
  align-items: flex-start;
  gap: 8px;
  text-align: start;
}
.cw-home--v5 .cw-home__mark {
  inline-size: 48px;
  block-size: 48px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--color-accent);
  box-shadow: none;
  animation: none;
}
.cw-home--v5 .cw-home__mark .fmark,
.cw-home--v5 .cw-home__mark > svg {
  inline-size: 44px;
  block-size: 44px;
  display: block;
}
.cw-home--v5 .cw-home__hero h1 {
  margin: 0;
  color: var(--color-text-primary);
  font-size: clamp(27px, 4vw, 38px);
  font-weight: var(--weight-semibold);
  line-height: 1.12;
}
.cw-home--v5 .cw-home__hero p {
  max-inline-size: 62ch;
  margin: 0;
  color: var(--color-text-secondary);
  font-size: 14px;
}
.cw-home--v5 .cw-home__card {
  inline-size: 100%;
  max-block-size: none;
  padding: 16px;
  gap: 9px;
  overflow: visible;
  border: 1px solid var(--cwv5-border);
  border-radius: var(--radius-lg);
  background: var(--cwv5-raised);
  box-shadow: var(--cwv5-shadow);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.cw-home--v5 .cw-home__name,
.cw-home--v5 .cw-home__desc {
  inline-size: 100%;
  border: 1px solid var(--cwv5-border);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  color: var(--color-text-primary);
  box-shadow: none;
}
.cw-home--v5 .cw-home__name:focus,
.cw-home--v5 .cw-home__desc:focus {
  border-color: var(--color-accent);
  outline: 0;
  box-shadow: var(--shadow-focus);
}
.cw-home--v5 .cw-home__acts {
  justify-content: flex-end;
  gap: 7px;
  margin-block-start: 6px;
}
.cw-home--v5 .cw-home__blank,
.cw-home--v5 .cw-home__create,
.cw-home--v5 .cw-attach__btn {
  min-block-size: 40px;
  border-radius: var(--radius-md);
  box-shadow: none;
  transform: none;
}
.cw-home--v5 .cw-home__create {
  flex: 0 1 auto;
  min-inline-size: 150px;
  background: var(--color-accent);
  color: var(--color-on-accent);
}
.cw-home--v5 .cw-home__create:hover {
  background: var(--color-accent-hover);
  box-shadow: none;
  filter: none;
  transform: none;
}
.cw-home--v5 .cw-home__recent {
  padding-block-start: 2px;
}
.cw-home--v5 .cw-home__recent-h {
  margin-block-end: 8px;
  color: var(--color-text-secondary);
  font-size: 11px;
  letter-spacing: .06em;
}
.cw-home--v5 .cw-home__grid {
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 8px;
}
.cw-home--v5 .cw-proj {
  min-block-size: 64px;
  padding: 10px;
  border: 1px solid var(--cwv5-hair);
  border-radius: var(--radius-md);
  background: var(--cwv5-raised);
  box-shadow: none;
  transform: none;
}
.cw-home--v5 .cw-proj:hover {
  border-color: color-mix(in srgb, var(--color-accent) 56%, var(--cwv5-border));
  background: var(--cwv5-hover);
  box-shadow: none;
  transform: none;
}

/* Single-pane workspace.  Existing data-mob values remain the behavior contract. */
@media (max-width: 860px) {
  body.product-code #codeWorkspace {
    block-size: var(--cw-view-height, auto) !important;
    max-block-size: var(--cw-view-height, none);
    min-block-size: 0;
  }
  .cw--v5 .cw-bar {
    min-block-size: 48px;
    flex-wrap: nowrap;
    overflow-x: auto;
    padding-block-start: 6px;
    padding-left: max(8px, var(--safe-left));
    padding-right: max(8px, var(--safe-right));
    scrollbar-width: none;
  }
  .cw--v5 .cw-bar::-webkit-scrollbar { display: none; }
  .cw--v5 .cw-bar__name { max-inline-size: 30vw; }
  .cw--v5 .cw-main {
    display: flex !important;
    flex: 1 1 auto;
    flex-direction: column;
    grid-template-columns: none !important;
    grid-template-rows: none !important;
    min-block-size: 0;
  }
  .cw--v5[data-mob] .cw-tree,
  .cw--v5[data-mob] .cw-ed,
  .cw--v5[data-mob] .cw-right {
    display: none !important;
  }
  .cw--v5[data-mob="files"] .cw-tree,
  .cw--v5[data-mob="code"] .cw-ed,
  .cw--v5[data-mob="preview"] .cw-right {
    display: flex !important;
    flex: 1 1 auto;
    inline-size: 100% !important;
    min-block-size: 0;
    border: 0;
  }
  .cw--v5[data-mob="ai"] .cw-main { display: none !important; }
  .cw--v5[data-mob="ai"] .cw-ai {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 10px;
    min-block-size: 0;
    padding-left: max(14px, var(--safe-left));
    padding-right: max(14px, var(--safe-right));
    padding-block-end: max(10px, var(--safe-bottom));
  }
  .cw--v5[data-mob="ai"] .cw-ai__in {
    inline-size: 100%;
    min-block-size: 120px;
    max-block-size: 42vh;
    margin: 0;
    resize: none;
  }
  .cw--v5 .cw-mobnav {
    display: flex;
    flex: 0 0 auto;
    min-block-size: 52px;
    padding-left: max(0px, var(--safe-left));
    padding-right: max(0px, var(--safe-right));
    padding-block-end: var(--safe-bottom);
    border-block-start: 1px solid var(--cwv5-hair);
    background: var(--cwv5-raised);
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .cw--v5 .cw-mobnav__b {
    min-block-size: 48px;
    color: var(--color-text-secondary);
  }
  .cw--v5 .cw-mobnav__ico {
    inline-size: 22px;
    block-size: 22px;
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    flex: 0 0 22px;
    color: currentColor;
    font-size: 18px;
    line-height: 1;
  }
  .cw--v5 .cw-mobnav__ico > svg {
    inline-size: 20px;
    block-size: 20px;
    display: block;
  }
  .cw--v5 .cw-mobnav__label {
    display: block;
    color: currentColor;
    font-size: 10px;
    line-height: 1.2;
  }
  .cw--v5 .cw-mobnav__b.is-active {
    background: var(--cwv5-selected);
    color: var(--color-text-primary);
  }
  .cw--v5 .cw-mobnav__b.is-active::before {
    background: var(--color-accent);
    box-shadow: none;
  }
  .cw--v5 .cw-prevwrap,
  .cw--v5 .cw-console,
  .cw--v5 .cw-thread {
    min-block-size: 0;
  }
  .cw--v5 .cw-status {
    padding-left: max(8px, var(--safe-left));
    padding-right: max(8px, var(--safe-right));
  }
}

/* Phone: visual viewport height, safe edges, zoom-safe fields and full touch targets. */
@media (max-width: 560px) {
  #codeWorkspace > .cw--v5 {
    block-size: 100%;
    max-block-size: 100%;
    overflow: hidden;
  }
  .cw--v5 .cw-bar {
    padding-block-start: 7px;
  }
  .cw--v5 .cw-bar__count { display: none; }
  .cw--v5 .cw-bar__name { max-inline-size: 32vw; }
  .cw--v5 :where(.cw-ai__in, .cw-comptask__in, .cw-ed__ta),
  .cw-home--v5 :where(.cw-home__name, .cw-home__desc) {
    font-size: 16px !important;
  }
  .cw--v5 .cw-comphead {
    min-block-size: 48px;
    padding-inline: 8px;
  }
  .cw--v5 .cw-compstate {
    max-inline-size: 32vw;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .cw--v5 .cw-compbody {
    grid-template-rows: minmax(190px, 1fr) minmax(176px, 42%);
  }
  .cw--v5 .cw-comptask {
    grid-template-columns: minmax(0, 1fr) auto;
    padding-block-end: max(8px, var(--safe-bottom));
  }
  .cw--v5 .cw-comptask__in { grid-column: 1 / -1; }
  .cw--v5 .cw-comptask__go,
  .cw--v5 .cw-comptask__stop { min-block-size: 44px; }
  .cw--v5[data-mob="ai"] .cw-ai {
    padding-block-end: max(14px, var(--safe-bottom));
  }
  .cw--v5[data-mob="ai"] .cw-ai__go {
    inline-size: 100%;
    min-block-size: 48px;
  }
  .cw-home.cw-home--v5 {
    padding-block-start: max(22px, var(--safe-top));
    padding-block-end: max(22px, var(--safe-bottom));
    padding-left: max(14px, var(--safe-left));
    padding-right: max(14px, var(--safe-right));
  }
  .cw-home--v5 .cw-home__hero h1 { font-size: 28px; }
  .cw-home--v5 .cw-home__card { padding: 13px; }
  .cw-home--v5 .cw-home__acts { flex-direction: column-reverse; }
  .cw-home--v5 .cw-home__blank,
  .cw-home--v5 .cw-home__create,
  .cw-home--v5 .cw-attach__btn {
    inline-size: 100%;
    min-block-size: 48px;
  }
  .cw-home--v5 .cw-home__grid { grid-template-columns: minmax(0, 1fr); }
}

/* Short landscape viewport: retain the active pane and keyboard-safe controls. */
@media (max-width: 860px) and (max-height: 520px) {
  #codeWorkspace > .cw--v5 {
    block-size: 100%;
    max-block-size: 100%;
    overflow: hidden;
  }
  .cw--v5 .cw-bar {
    min-block-size: 44px;
    padding-block-start: 4px;
    padding-block-end: 4px;
  }
  .cw--v5 .cw-status {
    min-block-size: 22px;
    padding-block: 1px;
  }
  .cw--v5 .cw-mobnav,
  .cw--v5 .cw-mobnav__b {
    min-block-size: 44px;
  }
  .cw--v5[data-mob="ai"] .cw-ai {
    justify-content: flex-end;
    padding-block-start: 8px;
    padding-block-end: max(8px, var(--safe-bottom));
  }
  .cw--v5[data-mob="ai"] .cw-ai__in {
    min-block-size: 44px;
    max-block-size: min(30vh, 112px);
  }
  .cw--v5 .cw-comphead { min-block-size: 44px; }
  .cw--v5 .cw-compactivity { display: none; }
}

@media (max-width: 380px) {
  .cw--v5 .cw-mobnav__label { display: none; }
  .cw--v5 .cw-mobnav__ico { display: inline-flex !important; }
}

/* Touch devices retain every secondary control instead of relying on hover. */
@media (hover: none), (pointer: coarse) {
  .cw--v5 .cw-bar {
    padding-block-start: 6px;
    padding-left: max(8px, var(--safe-left));
    padding-right: max(8px, var(--safe-right));
  }
  .cw--v5 .cw-ai {
    padding-left: max(10px, var(--safe-left));
    padding-right: max(10px, var(--safe-right));
  }
  .cw--v5 .cw-mobnav {
    padding-left: max(0px, var(--safe-left));
    padding-right: max(0px, var(--safe-right));
    padding-block-end: var(--safe-bottom);
  }
  .cw--v5 :where(
    .cw-bar__home,
    .cw-bar__btn,
    .cw-tab,
    .cw-etab,
    .cw-prevbtn,
    .cw-prevdev,
    .cw-conclear,
    .cw-file__x,
    .cw-etab__x,
    .cw-ai__clip,
    .cw-ai__go,
    .cw-attach__btn,
    .cw-tree__newfolder,
    .cw-folder__add,
    .cw-etabs__copy,
    .cw-mobnav__b,
    .cw-compactions button,
    .cw-compactions a,
    .cw-compage,
    .cw-comptask__go,
    .cw-comptask__stop
  ) {
    min-inline-size: 44px;
    min-block-size: 44px;
  }
  .cw--v5 .cw-file,
  .cw--v5 .cw-folder,
  .cw-home--v5 .cw-proj {
    min-block-size: 44px;
  }
  .cw--v5 .cw-file__x,
  .cw--v5 .cw-folder__add,
  .cw--v5 .cw-proj__del,
  .cw--v5 .cw-etab__x,
  .cw--v5 .cw-compactions {
    opacity: 1 !important;
    visibility: visible !important;
  }
  .cw--v5 .cw-ai__in,
  .cw--v5 .cw-comptask__in,
  .cw-home--v5 .cw-home__name,
  .cw-home--v5 .cw-home__desc {
    font-size: 16px !important;
  }
}

/* Strong, consistent keyboard focus without disturbing CodeMirror's caret. */
.cw--v5 :where(
  .cw-bar__home,
  .cw-bar__btn,
  .cw-file,
  .cw-folder,
  .cw-etab,
  .cw-etabs__copy,
  .cw-tab,
  .cw-prevbtn,
  .cw-prevdev,
  .cw-conclear,
  .cw-attach__btn,
  .cw-ai__clip,
  .cw-ai__go,
  .cw-mobnav__b,
  .cw-compactions button,
  .cw-compactions a,
  .cw-comptask__go,
  .cw-comptask__stop
):focus-visible,
.cw-home--v5 :where(
  .cw-home__name,
  .cw-home__desc,
  .cw-home__blank,
  .cw-home__create,
  .cw-attach__btn,
  .cw-proj
):focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  box-shadow: none;
}

/* OS and in-app motion preferences are kept as behavioral twins. */
@media (prefers-reduced-motion: reduce) {
  .cw--v5 *, .cw--v5 *::before, .cw--v5 *::after,
  .cw-home--v5 *, .cw-home--v5 *::before, .cw-home--v5 *::after {
    scroll-behavior: auto !important;
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
  .cw--v5 :where(.cw-bar__home, .cw-bar__btn, .cw-ai__go, .cw-prevbtn, .cw-proj):hover,
  .cw-home--v5 :where(.cw-home__create, .cw-proj):hover {
    transform: none !important;
  }
  .cw--v5 :where(
    .cw-thread__turn--live .cw-thread__bubble,
    .cw-prevlive__dot,
    .cw-prevauto.is-on .cw-prevauto__dot,
    .cw-tab.has-errors::after,
    .cw-status__save.is-saving::before,
    .cw-compstate::before
  ) {
    animation: none !important;
  }
}
:root[data-motion="off"] .cw--v5 *,
:root[data-motion="off"] .cw--v5 *::before,
:root[data-motion="off"] .cw--v5 *::after,
:root[data-motion="off"] .cw-home--v5 *,
:root[data-motion="off"] .cw-home--v5 *::before,
:root[data-motion="off"] .cw-home--v5 *::after {
  scroll-behavior: auto !important;
  animation-duration: .01ms !important;
  transition-duration: .01ms !important;
}
:root[data-motion="off"] .cw--v5 :where(.cw-bar__home, .cw-bar__btn, .cw-ai__go, .cw-prevbtn, .cw-proj):hover,
:root[data-motion="off"] .cw-home--v5 :where(.cw-home__create, .cw-proj):hover {
  transform: none !important;
}
:root[data-motion="off"] .cw--v5 :where(
  .cw-thread__turn--live .cw-thread__bubble,
  .cw-prevlive__dot,
  .cw-prevauto.is-on .cw-prevauto__dot,
  .cw-tab.has-errors::after,
  .cw-status__save.is-saving::before,
  .cw-compstate::before
) {
  animation: none !important;
}

/* Windows High Contrast: borders and focus remain visible without shadows. */
@media (forced-colors: active) {
  .cw--v5 :where(
    .cw-bar,
    .cw-tree,
    .cw-ed,
    .cw-right,
    .cw-etabs,
    .cw-tabs,
    .cw-computer,
    .cw-comphead,
    .cw-companel,
    .cw-comptask,
    .cw-ai,
    .cw-status,
    .cw-mobnav
  ),
  .cw-home--v5 :where(.cw-home__card, .cw-proj) {
    border-color: CanvasText;
  }
  .cw--v5 :where(
    .cw-bar__home,
    .cw-bar__btn,
    .cw-file,
    .cw-folder,
    .cw-etab,
    .cw-etabs__copy,
    .cw-tab,
    .cw-prevbtn,
    .cw-prevdev,
    .cw-conclear,
    .cw-ai__clip,
    .cw-ai__go,
    .cw-mobnav__b,
    .cw-compactions button,
    .cw-compactions a,
    .cw-comptask__go,
    .cw-comptask__stop
  ):focus-visible,
  .cw-home--v5 :where(
    .cw-home__name,
    .cw-home__desc,
    .cw-home__blank,
    .cw-home__create,
    .cw-attach__btn,
    .cw-proj
  ):focus-visible {
    outline: 2px solid Highlight !important;
    outline-offset: 2px;
    box-shadow: none !important;
  }
  .cw--v5 :where(.cw-file.is-active, .cw-etab.is-active, .cw-tab.is-active, .cw-mobnav__b.is-active) {
    border: 2px solid Highlight;
    color: HighlightText;
    background: Highlight;
    forced-color-adjust: none;
  }
}


/* === FIRAS CODE USAGE — OPEN DAILY + WEEKLY UI === */
/* Separate from Agent credits by design. This is a truthful UI-only open state. */
body:not(.product-agent) #agentCreditsRow,
body:not(.product-code) #codeUsageRow {
  display: none !important;
}

.code-usage-chip {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 7px;
  min-block-size: 38px;
  padding: 5px 10px;
  border: 1px solid color-mix(in srgb, var(--color-accent) 28%, var(--color-border));
  border-radius: 999px;
  color: var(--color-text-primary);
  background: color-mix(in srgb, var(--color-accent) 7%, var(--color-bg-surface, transparent));
  cursor: pointer;
  transition: border-color var(--dur-fast, .16s) var(--ease-out, ease-out), background var(--dur-fast, .16s) var(--ease-out, ease-out), transform var(--dur-fast, .16s) var(--ease-out, ease-out);
}
.code-usage-chip:hover {
  border-color: color-mix(in srgb, var(--color-accent) 58%, var(--color-border));
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
  transform: translateY(-1px);
}
.code-usage-chip:active { transform: translateY(0); }
.code-usage-chip:focus-visible { outline: 2px solid var(--color-accent-ring, var(--color-accent)); outline-offset: 2px; }
.code-usage-chip__mark {
  display: inline-grid;
  place-items: center;
  inline-size: 23px;
  block-size: 23px;
  flex: 0 0 23px;
  border-radius: 50%;
  color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 13%, transparent);
}
.code-usage-chip__mark svg { display: block; inline-size: 13px; block-size: 13px; }
.code-usage-chip__label { color: var(--color-text-muted); font-size: 10.5px; }
.code-usage-chip__state {
  color: var(--color-accent);
  font-size: 11.5px;
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}
.code-usage-chip__state::before {
  content: "";
  display: inline-block;
  inline-size: 5px;
  block-size: 5px;
  margin-inline-end: 5px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 13%, transparent);
  vertical-align: middle;
}

.cu-dialog-layer {
  position: fixed;
  inset: 0;
  z-index: 10020;
  display: grid;
  place-items: start center;
  padding: max(72px, 10vh) 16px 24px;
  overflow-y: auto;
  background: rgba(5, 8, 14, .58);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  animation: cu-layer-in .18s ease both;
}
.cu-dialog {
  inline-size: min(100%, 440px);
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--color-accent) 28%, var(--color-border));
  border-radius: var(--radius-xl, 20px);
  color: var(--color-text-primary);
  background: var(--color-surface);
  box-shadow: 0 28px 80px rgba(0, 0, 0, .34);
  animation: cu-dialog-in .24s var(--ease-out, ease-out) both;
}
.cu-dialog__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 20px 12px;
}
.cu-dialog__eyebrow {
  display: block;
  margin-block-end: 4px;
  color: var(--color-accent);
  font-size: 10px;
  font-weight: var(--weight-semibold);
  letter-spacing: .04em;
}
.cu-dialog h2 { margin: 0; color: var(--color-text-primary); font-size: 19px; letter-spacing: -.02em; }
.cu-dialog__close {
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  flex: 0 0 44px;
  margin: -8px -8px 0 0;
  padding: 0;
  border: 0;
  border-radius: 12px;
  color: var(--color-text-secondary);
  background: transparent;
  cursor: pointer;
}
[dir="rtl"] .cu-dialog__close { margin: -8px 0 0 -8px; }
.cu-dialog__close:hover { color: var(--color-text-primary); background: var(--color-bg-subtle); }
.cu-dialog__close:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.cu-dialog__close svg { inline-size: 19px; block-size: 19px; }
.cu-dialog__status {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 9px;
  margin: 0 20px 12px;
  padding: 11px 12px;
  border: 1px solid color-mix(in srgb, var(--color-accent) 24%, var(--color-border));
  border-radius: 12px;
  background: color-mix(in srgb, var(--color-accent) 8%, var(--color-bg-subtle));
}
.cu-dialog__status-dot {
  inline-size: 8px;
  block-size: 8px;
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-accent) 14%, transparent);
}
.cu-dialog__status p { min-inline-size: 0; margin: 0; color: var(--color-text-secondary); font-size: 11.5px; line-height: 1.5; }
.cu-dialog__status strong { color: var(--color-accent); font-size: 11.5px; white-space: nowrap; }
.cu-dialog__periods { display: grid; gap: 8px; padding: 0 20px; }
.cu-period {
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr) auto;
  align-items: center;
  gap: 11px;
  min-block-size: 74px;
  padding: 12px;
  border: 1px solid var(--color-border-hairline);
  border-radius: 13px;
  background: var(--color-bg-subtle);
}
.cu-period__tag {
  display: grid;
  place-items: center;
  inline-size: 38px;
  block-size: 38px;
  border-radius: 10px;
  color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 11%, var(--color-surface));
  font: 650 10px/1 var(--font-mono);
  direction: ltr;
}
.cu-period h3 { margin: 0; color: var(--color-text-primary); font-size: 12.5px; }
.cu-period p { margin: 4px 0 0; color: var(--color-text-muted); font-size: 10.5px; line-height: 1.45; }
.cu-period__value { display: inline-flex; align-items: center; gap: 6px; color: var(--color-accent); white-space: nowrap; }
.cu-period__value > span { font: 500 20px/1 var(--font-sans); }
.cu-period__value > b { font-size: 11.5px; }
.cu-dialog__note {
  margin: 0;
  padding: 13px 20px 18px;
  color: var(--color-text-muted);
  font-size: 10.5px;
  line-height: 1.6;
  text-align: center;
}
@keyframes cu-layer-in { from { opacity: 0; } }
@keyframes cu-dialog-in { from { opacity: 0; transform: translateY(10px) scale(.985); } }

@media (max-width: 900px) {
  .code-usage-chip { min-inline-size: 44px; min-block-size: 44px; justify-content: center; padding: 5px 8px; }
  .code-usage-chip__label { display: none; }
  .code-usage-chip__mark { inline-size: 20px; block-size: 20px; flex-basis: 20px; }
}
@media (max-width: 560px) {
  .cu-dialog-layer { align-items: end; padding: 12px max(12px, env(safe-area-inset-right)) max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left)); }
  .cu-dialog { border-radius: 20px; }
  .cu-dialog__status { grid-template-columns: auto minmax(0, 1fr); }
  .cu-dialog__status strong { grid-column: 2; justify-self: start; }
}
@media (max-width: 380px) {
  .code-usage-chip { gap: 5px; }
  .code-usage-chip__state { font-size: 10.5px; }
  .cu-period { grid-template-columns: 34px minmax(0, 1fr); }
  .cu-period__tag { inline-size: 34px; block-size: 34px; }
  .cu-period__value { grid-column: 2; justify-self: start; }
}
@media (hover: none), (pointer: coarse) {
  .code-usage-chip, .cu-dialog__close { min-inline-size: 44px; min-block-size: 44px; }
}
@media (prefers-reduced-motion: reduce) {
  .code-usage-chip, .cu-dialog-layer, .cu-dialog { animation: none; transition: none; }
}
:root[data-motion="off"] .code-usage-chip,
:root[data-motion="off"] .cu-dialog-layer,
:root[data-motion="off"] .cu-dialog { animation: none; transition: none; }
@media (forced-colors: active) {
  .code-usage-chip, .cu-dialog, .cu-dialog__status, .cu-period { border-color: CanvasText; }
  .code-usage-chip:focus-visible, .cu-dialog__close:focus-visible { outline-color: Highlight !important; }
  .cu-dialog__status-dot, .code-usage-chip__state::before { background: Highlight; box-shadow: none; forced-color-adjust: none; }
}


/* === FIRAS CODE V6 — UNIFIED PROFESSIONAL WORKSPACE === */
/* v6 is the final visual owner. Deep feature behavior remains in v3/v5; this layer only
   normalizes the visible shell, editor themes, responsive panes and Computer drawer. */
#codeWorkspace { container-type: inline-size; container-name: firas-code; min-inline-size: 0; }
/* FIRAS_CODE_THEME_RESTORE_V1 — the existing workspace follows the six site themes.
   Keep the original F geometry and its two green tones; the UI accent still follows
   the chosen theme. No runtime, model, progress, or project state lives in this layer. */
:root { --firas-mark-green: #237A68; --firas-mark-green-deep: #14544A; }
:root:is([data-theme="dark"],[data-theme="black"],[data-theme="midnight"],[data-theme="graphite"],[data-theme="amber"]) {
  --firas-mark-green: #57AE9C; --firas-mark-green-deep: #2F6F62;
}
.fmark__stem, .fmark__arm--1 { fill: var(--firas-mark-green); }
.fmark__arm--2 { fill: var(--firas-mark-green-deep); }
.cw.cw--v6, .cw-home.cw-home--v6 {
  --cwv6-hair: var(--color-border-hairline);
  --cwv6-border: var(--color-border-strong);
  --cwv6-panel: var(--color-bg);
  --cwv6-raised: var(--color-surface);
  --cwv6-sunken: var(--color-bg-subtle);
  --cwv6-hover: var(--color-bg-subtle);
  --cwv6-selected: var(--color-accent-soft);
  --cwv6-editor: var(--color-bg);
  --cwv6-accent: var(--color-accent);
  --cwv6-accent-hover: var(--color-accent-hover);
  --cwv6-accent-soft: var(--color-accent-soft);
  color: var(--color-text-primary); background: var(--color-bg); font-family: var(--font-sans);
}
/* NO DIRECTION LOCK. This carried `direction: ltr !important`, which nailed the whole IDE to
   left-to-right for an Arabic reader — the tree on the wrong side, every pane in Latin order,
   every logical margin resolving the wrong way — and made the rule directly below it, which is
   the CORRECT rule, do nothing. The workspace follows the document now; only the things that
   genuinely cannot mirror are held, one line down. */
.cw.cw--v6 { block-size: 100%; min-block-size: 0; overflow: hidden; }
.cw--v6 [hidden], .cw-home--v6 [hidden] { display: none !important; }
/* WHAT CANNOT MIRROR, and the reason is the same for every one of them: this is a PATH or it is
   CODE. Under an Arabic base direction "js/engine.js" comes out reordered into something that is
   not a path, and a line of JavaScript is worse. `unicode-bidi: isolate` keeps each one out of
   the surrounding paragraph's bidi run rather than merely pointing it the other way.
   The five at the end were added when the surfaces that carry code outgrew this list: diff rows,
   a whole-file diff body, a Quick Fix code line, the minimap — which is a rendering of the file —
   and the file:line a problem points at. The console row and the terminal declare their own. */
.cw--v6 :where(.cw-file__p,.cw-etab,.cw-crumb,.cw-prevaddr,.cw-compage,.CodeMirror,.cw-ed__ta,
  .cw-dl,.cw-diff__pre,.cw-qf-card__ln,.cw-mini,.cw-prob__loc) { direction: ltr; text-align: left; unicode-bidi: isolate; }
.cw--v6 [dir="rtl"], .cw-home--v6 [dir="rtl"] { font-family: var(--font-arabic); text-align: start; }
.cw--v6 .cw-bar__logo, .cw-home--v6 .cw-home__mark { display: grid; place-items: center; flex: 0 0 auto; background: none; border: 0; box-shadow: none; animation: none; }
.cw--v6 .fmark, .cw-home--v6 .fmark { color: var(--firas-mark-green); filter: none; }
.cw--v6 .fmark__stem, .cw--v6 .fmark__arm--1, .cw-home--v6 .fmark__stem, .cw-home--v6 .fmark__arm--1 { fill: var(--firas-mark-green); }
.cw--v6 .fmark__arm--2, .cw-home--v6 .fmark__arm--2 { fill: var(--firas-mark-green-deep); }

/* A quiet start: identity, brief, controls, then the person's real projects. */
.cw-home.cw-home--v6 { min-block-size: 100%; padding: clamp(26px,5vh,64px) clamp(16px,4vw,52px) !important; background: var(--color-bg) !important; overflow-y: auto; }
.cw-home--v6 .cw-home__ambient { display: none; }
.cw-home.cw-home--v6 .cw-home__inner { inline-size: 100%; max-inline-size: 860px; margin-inline: auto; display: flex; flex-direction: column; gap: 28px; }
.cw-home.cw-home--v6 .cw-home__hero { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; inline-size: 100%; text-align: start; }
.cw-home--v6 .cw-home__badge { display: flex; align-items: center; gap: 12px; font: 600 14px/1.4 var(--font-sans); color: var(--color-text-secondary); }
.cw-home--v6 .cw-home__mark { inline-size: 32px; block-size: 40px; padding: 0; margin: 0; border-radius: 0; }
.cw-home--v6 .cw-home__mark::after { content: none; }
.cw-home--v6 .cw-home__mark .fmark { inline-size: 32px; block-size: 40px; }
.cw-home.cw-home--v6 .cw-home__title { margin: 0; font-size: clamp(28px,3.2vw,40px); line-height: 1.3; font-weight: 650; letter-spacing: -.025em; color: var(--color-text-primary); background: none; -webkit-text-fill-color: currentColor; }
.cw-home--v6 .cw-home__subtitle { max-inline-size: 650px; margin: 0; color: var(--color-text-secondary); font: 400 15px/1.8 var(--font-sans); }
[dir="rtl"] .cw-home--v6 .cw-home__title { letter-spacing: 0; }
.cw-home.cw-home--v6 .cw-home__card { inline-size: 100%; max-inline-size: none; padding: 22px; display: flex; flex-direction: column; gap: 16px; border: 1px solid var(--color-border-strong); border-radius: 16px; background: var(--color-surface); box-shadow: var(--shadow-sm); }
.cw-home--v6 .cw-home__card-head { display: grid; grid-template-columns: auto minmax(0,1fr); align-items: center; gap: 16px; }
.cw-home--v6 .cw-home__param-tag { padding: 0; border: 0; border-radius: 0; background: none; color: var(--color-text-secondary); font: 600 13px/1.5 var(--font-sans); white-space: nowrap; }
.cw-home--v6 .cw-home__prompt-wrap { min-inline-size: 0; }
.cw-home.cw-home--v6 :is(.cw-home__name,.cw-home__desc) { box-sizing: border-box; inline-size: 100%; padding: 12px 14px; margin: 0; border: 1px solid var(--color-border-strong); border-radius: 9px; background: var(--color-bg); color: var(--color-text-primary); font: 400 15px/1.75 var(--font-sans); transition: border-color 160ms ease; }
.cw-home--v6 .cw-home__name { min-block-size: 46px; }
.cw-home--v6 .cw-home__desc { min-block-size: 154px; max-block-size: 340px; resize: vertical; }
.cw-home--v6 :is(.cw-home__name,.cw-home__desc)::placeholder { color: var(--color-text-muted); opacity: 1; }
.cw-home--v6 :is(.cw-home__name,.cw-home__desc):placeholder-shown { direction: inherit; text-align: start; }
.cw-home--v6 :is(.cw-home__name,.cw-home__desc):focus { outline: 2px solid var(--color-accent); outline-offset: 2px; border-color: var(--color-accent); background: var(--color-bg); box-shadow: none; }
.cw-home--v6 .cw-home__chips { display: flex; flex-wrap: wrap; gap: 8px; }
.cw-home--v6 .cw-chip { min-block-size: 44px; padding: 8px 14px; border: 1px solid var(--color-border-hairline); border-radius: 8px; background: transparent; color: var(--color-text-secondary); font: 500 13px/1.5 var(--font-sans); cursor: pointer; transition: background 160ms ease,border-color 160ms ease; }
.cw-home--v6 .cw-chip:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); border-color: var(--color-border-strong); transform: none; }
.cw-home--v6 .cw-home__acts { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; padding-block-start: 4px; }
.cw-home--v6 .cw-home__acts-right { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
.cw-home.cw-home--v6 :is(.cw-home__create,.cw-home__blank,.cw-attach__btn) { min-block-size: 44px; display: inline-flex; justify-content: center; align-items: center; gap: 8px; padding: 10px 16px; border: 1px solid var(--color-border-strong); border-radius: 9px; background: var(--color-bg); color: var(--color-text-primary); font: 600 14px/1.5 var(--font-sans); cursor: pointer; box-shadow: none; transition: background 160ms ease; }
.cw-home.cw-home--v6 .cw-home__create { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-on-accent); }
.cw-home.cw-home--v6 .cw-home__create:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); transform: none; box-shadow: none; }
.cw-home.cw-home--v6 :is(.cw-home__blank,.cw-attach__btn):hover { background: var(--color-bg-subtle); border-color: var(--color-text-muted); }
.cw-home--v6 .cw-home__kbd { padding: 1px 5px; border: 1px solid currentColor; border-radius: 4px; background: transparent; color: inherit; font: 500 10px/1.5 var(--font-mono); opacity: .8; direction: ltr; unicode-bidi: isolate; }
.cw-home--v6 .cw-home__hint { margin: -10px 0 0; color: var(--color-text-secondary); font: 400 13px/1.8 var(--font-sans); }
.cw-home--v6 .cw-home__recent { display: flex; flex-direction: column; gap: 14px; }
.cw-home--v6 .cw-home__recent-h { display: flex; align-items: center; justify-content: space-between; color: var(--color-text-secondary); font-size: 13px; font-weight: 600; }
.cw-home--v6 .cw-home__recent-count { font-family: var(--font-mono); color: var(--color-text-muted); }
.cw-home--v6 .cw-home__grid { display: grid; grid-template-columns: repeat(auto-fill,minmax(220px,1fr)); gap: 10px; }
.cw-home.cw-home--v6 .cw-proj { min-block-size: 68px; padding: 12px 14px; border: 1px solid var(--color-border-hairline); border-radius: 10px; background: var(--color-surface); color: var(--color-text-primary); box-shadow: none; }
.cw-home--v6 .cw-proj:hover { border-color: var(--color-accent); transform: none; }
.cw-home--v6 .cw-proj__ic { color: var(--color-accent); background: var(--color-accent-soft); }
.cw-home--v6 .cw-proj__meta strong { color: var(--color-text-primary); }
.cw-home--v6 .cw-proj__meta small { color: var(--color-text-muted); }

/* Chrome, editor, and preview share the same surfaces as the surrounding site. */
/* THE CONVERSATION IS THE LARGER TRACK, and that is the whole point of this product: the
   centre of gravity is the conversation and the deck is a surface opened to do the work.
   It used to read `minmax(240px,1fr) minmax(300px,1.12fr)` — measured at 1440 the deck won
   by 72px, at 1102 by 52px — which makes it an editor with a chat panel beside it.
   The floor is 360 rather than 240 because 360 is already what the SEAM enforces
   (CW_SPLIT_MIN = [132, 360, 320]); the stylesheet and the drag were disagreeing about the
   smallest usable conversation by 120px. */
.cw--v6 .cw-main { flex: 1 1 auto; min-block-size: 0; display: grid; grid-template-columns: minmax(150px,180px) minmax(360px,1.12fr) minmax(300px,1fr); grid-template-rows: minmax(0,1fr); overflow: hidden; background: var(--color-bg); }
.cw--v6 .cw-bar { min-block-size: 58px; flex: 0 0 auto; gap: 10px; padding: 6px 12px; border-block-end: 1px solid var(--color-border-hairline); background: var(--color-bg); box-shadow: none; }
.cw--v6 .cw-bar__identity { display: flex; align-items: center; gap: 10px; min-inline-size: 0; padding: 0; background: none; border: 0; }
.cw--v6 .cw-bar__logo { inline-size: 22px; block-size: 28px; }
.cw--v6 .cw-bar__logo .fmark { inline-size: 100%; block-size: 100%; }
.cw--v6 .cw-bar__name { color: var(--color-text-primary); font-size: 14px; font-weight: 600; }
.cw--v6 .cw-bar__count { padding: 3px 7px; border: 1px solid var(--color-border-hairline); background: var(--color-bg-subtle); color: var(--color-text-secondary); border-radius: 5px; font: 500 11px/1.5 var(--font-mono); }
.cw--v6 :is(.cw-bar__home,.cw-bar__btn,.cw-tree__newfolder,.cw-conclear,.cw-ai__clip,.cw-complog-toggle,.cw-complog__close) { min-inline-size: 44px; min-block-size: 44px; block-size: auto; display: inline-flex; justify-content: center; align-items: center; gap: 6px; padding: 8px; border: 1px solid transparent; border-radius: 8px; background: transparent; color: var(--color-text-secondary); font-size: 13px; box-shadow: none; }
.cw--v6 :is(.cw-bar__home,.cw-bar__btn,.cw-tree__newfolder,.cw-conclear,.cw-ai__clip,.cw-complog-toggle,.cw-complog__close):hover { color: var(--color-text-primary); background: var(--color-bg-subtle); border-color: var(--color-border-hairline); box-shadow: none; transform: none; }
.cw--v6 .cw-bar__btn:not(.cw-run):not(.cw-improve) .cw-dock__lbl { position: absolute !important; inline-size: 1px !important; block-size: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip-path: inset(50%) !important; white-space: nowrap !important; }
.cw--v6 .cw-bar__btn.cw-run { padding-inline: 13px; background: var(--color-bg-subtle); border-color: var(--color-border-strong); color: var(--color-text-primary); }
.cw--v6 .cw-bar__btn.cw-improve { padding-inline: 13px; background: var(--color-accent); border-color: var(--color-accent); color: var(--color-on-accent); font-weight: 600; }
.cw--v6 .cw-bar__btn.cw-improve:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); color: var(--color-on-accent); }
.cw--v6 :is(.cw-tree,.cw-right,.cw-prevwrap,.cw-ed,.cw-console,.cw-console__list) { min-inline-size: 0; min-block-size: 0; overflow: hidden; background: var(--color-bg); color: var(--color-text-primary); }
.cw--v6 :is(.cw-tree,.cw-ed) { border-inline-end: 1px solid var(--color-border-hairline); }
.cw--v6 .cw-tree__head { min-block-size: 48px; padding: 3px 8px 3px 12px; background: var(--color-bg); color: var(--color-text-secondary); border-block-end: 1px solid var(--color-border-hairline); letter-spacing: 0; text-transform: none; }
.cw--v6 .cw-file { min-block-size: 40px; padding: 8px 10px; border: 1px solid transparent; border-radius: 6px; color: var(--color-text-secondary); }
.cw--v6 .cw-file:hover { background: var(--color-bg-subtle); color: var(--color-text-primary); }
.cw--v6 .cw-file.is-active { background: var(--color-accent-soft); color: var(--color-text-primary); border-color: var(--color-border-strong); border-inline-start: 2px solid var(--color-accent); }
.cw--v6 :is(.cw-etabs,.cw-crumb,.cw-tabs,.cw-prevchrome) { background: var(--color-bg); border-color: var(--color-border-hairline); box-shadow: none; }
.cw--v6 .cw-etabs { min-block-size: 44px; }
.cw--v6 :is(.cw-etab,.cw-tab) { min-block-size: 44px; padding: 9px 12px; border-radius: 0; border: 0; border-block-end: 2px solid transparent; background: transparent; color: var(--color-text-secondary); box-shadow: none; }
.cw--v6 :is(.cw-etab,.cw-tab):hover { color: var(--color-text-primary); background: var(--color-bg-subtle); }
.cw--v6 :is(.cw-etab,.cw-tab).is-active { color: var(--color-text-primary); background: var(--color-accent-soft); border-color: var(--color-accent); box-shadow: none; }
.cw--v6 .cw-tabs { padding: 0 8px; gap: 2px; min-block-size: 48px; }
.cw--v6 .cw-conclear { margin-inline-start: auto; }
.cw--v6 .cw-conclear:hover { color: var(--color-error); }
.cw--v6 .cw-ed .CodeMirror, .cw--v6 .cw-ed__ta { background: var(--color-bg) !important; color: var(--color-text-primary) !important; font: 400 13px/1.65 var(--font-mono); }
.cw--v6 .cw-ed .CodeMirror-gutters { background: var(--color-bg) !important; border-color: var(--color-border-hairline) !important; }
.cw--v6 .cw-ed .CodeMirror-linenumber { color: var(--color-text-muted) !important; }
.cw--v6 .cw-ed .CodeMirror-activeline-background { background: var(--color-bg-subtle) !important; }
.cw--v6 .cw-ed .CodeMirror-selected { background: color-mix(in srgb,var(--color-accent) 24%,var(--color-bg)) !important; }
.cw--v6 .cw-ed .CodeMirror-cursor { border-color: var(--color-text-primary) !important; }
/* Syntax roles keep contrast in every dark-family theme, including amber and midnight. */
.cw--v6 .cw-ed { --cw-syntax-tag: #A33E25; --cw-syntax-string: #25694D; --cw-syntax-keyword: #834BAD; --cw-syntax-name: #355AA8; --cw-syntax-number: #825719; }
:root:is([data-theme="dark"],[data-theme="black"],[data-theme="midnight"],[data-theme="graphite"],[data-theme="amber"]) .cw--v6 .cw-ed { --cw-syntax-tag: #E8A080; --cw-syntax-string: #9ACBA8; --cw-syntax-keyword: #C7ADEB; --cw-syntax-name: #A3BCEB; --cw-syntax-number: #DFC18A; }
.cw--v6 .cw-ed :is(.cm-tag,.cm-operator) { color: var(--cw-syntax-tag); }
.cw--v6 .cw-ed :is(.cm-string,.cm-string-2,.cm-attribute,.cm-qualifier) { color: var(--cw-syntax-string); }
.cw--v6 .cw-ed :is(.cm-keyword,.cm-builtin) { color: var(--cw-syntax-keyword); }
.cw--v6 .cw-ed :is(.cm-def,.cm-property,.cm-variable-2) { color: var(--cw-syntax-name); }
.cw--v6 .cw-ed :is(.cm-number,.cm-atom) { color: var(--cw-syntax-number); }
.cw--v6 .cw-ed :is(.cm-variable,.cm-bracket,.cm-meta) { color: var(--color-text-primary); }
.cw--v6 .cw-ed .cm-comment { color: var(--color-text-muted); }
.cw--v6 .cw-ed .cm-error { color: var(--color-error); }
.cw--v6 .cw-prevchrome { min-block-size: 48px; flex: 0 0 auto; padding: 4px 8px; gap: 6px; }
.cw--v6 .cw-prevdots { display: none; }
.cw--v6 .cw-prevaddr { color: var(--color-text-secondary); background: transparent; border: 0; }
.cw--v6 .cw-prevdev { min-block-size: 44px; padding: 8px 10px; border-radius: 6px; color: var(--color-text-secondary); }
.cw--v6 .cw-prevdev.is-active { color: var(--color-text-primary); background: var(--color-accent-soft); border-color: var(--color-accent); box-shadow: none; }
.cw--v6 .cw-prevstage { background: var(--color-bg-subtle) !important; background-image: none !important; padding: 10px !important; }
.cw--v6 .cw-devframe { box-shadow: var(--shadow-sm); }
.cw--v6 .cw-devframe[data-dev="desktop"] { border-radius: 8px; }

/* The existing Activity button owns this drawer. Closed means no empty pane or focusable controls. */
.cw--v6 .cw-computer { display: flex; flex-direction: column; flex: 1 1 auto; min-block-size: 0; overflow: hidden; border: 0; border-radius: 0; background: var(--color-bg); box-shadow: none; }
.cw--v6 .cw-comphead { display: flex; align-items: center; flex-wrap: wrap; flex: 0 0 auto; min-block-size: 52px; gap: 8px; padding: 4px 10px; border-block-end: 1px solid var(--color-border-hairline); background: var(--color-bg); }
.cw--v6 .cw-compbrand { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 600; color: var(--color-text-primary); }
.cw--v6 .cw-compbrand .fmark { inline-size: 17px; block-size: 22px; }
.cw--v6 .cw-compbrand b { font-weight: 600; }
.cw--v6 .cw-compstate { border: 0; border-radius: 5px; background: var(--color-bg-subtle); color: var(--color-text-secondary); font-size: 11px; padding: 4px 7px; }
.cw--v6 .cw-compactivity-mini { display: none; }
.cw--v6 .cw-compactions { display: flex; align-items: center; gap: 6px; margin-inline-start: auto; opacity: 1; }
.cw--v6 .cw-compage { inline-size: min(140px,30vw); min-block-size: 44px; color: var(--color-text-secondary); background: var(--color-bg); border-color: var(--color-border-strong); border-radius: 6px; }
.cw--v6 .cw-compactivity { flex: 0 0 auto; min-block-size: 28px; font-size: 11px; color: var(--color-text-muted); background: var(--color-bg); }
.cw--v6 .cw-compbody { position: relative; display: flex; flex: 1 1 auto; min-block-size: 0; overflow: hidden; }
.cw--v6 .cw-companel { display: flex; flex: 1 1 auto; min-inline-size: 0; min-block-size: 0; background: var(--color-bg-subtle); }
.cw--v6 .cw-complog { display: none; }
.cw--v6 .cw-computer.is-log-open .cw-complog { position: absolute; inset-block: 0; inset-inline-end: 0; z-index: 4; display: flex; flex-direction: column; inline-size: min(100%,400px); border-inline-start: 1px solid var(--color-border-strong); background: var(--color-surface); box-shadow: var(--shadow-md); }
.cw--v6 .cw-complog__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 4px 10px; border-block-end: 1px solid var(--color-border-hairline); color: var(--color-text-primary); font: 600 12px/1.5 var(--font-sans); }
.cw--v6 .cw-complog__items { min-block-size: 40px; }
.cw--v6 .cw-comptask { flex: 0 0 auto; padding: 10px; gap: 8px; background: var(--color-surface); }
.cw--v6 :is(.cw-comptask__go,.cw-comptask__stop,.cw-comptask__in) { min-block-size: 44px; border-radius: 8px; }
.cw--v6 .cw-compnote { flex: 0 0 auto; line-height: 1.7; padding: 8px 12px; }
.cw--v6 .cw-ai { block-size: auto; flex-wrap: wrap; padding: 10px 12px; gap: 10px; border-block-start: 1px solid var(--color-border-hairline); background: var(--color-bg); box-shadow: none; }
.cw--v6 .cw-ai__in { min-block-size: 46px; padding: 11px 14px; border: 1px solid var(--color-border-strong); border-radius: 10px; background: var(--color-surface); color: var(--color-text-primary); font: 400 14px/1.7 var(--font-sans); box-shadow: none; }
.cw--v6 .cw-ai__in::placeholder { color: var(--color-text-muted); }
.cw--v6 .cw-ai__in:focus { border-color: var(--color-accent); outline: 2px solid var(--color-accent); outline-offset: 2px; background: var(--color-surface); box-shadow: none; }
.cw--v6 .cw-ai__go { min-block-size: 44px; padding: 9px 18px; border: 1px solid var(--color-accent); border-radius: 9px; background: var(--color-accent); color: var(--color-on-accent); font: 600 14px/1.6 var(--font-sans); box-shadow: none; }
.cw--v6 .cw-ai__go:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); color: var(--color-on-accent); box-shadow: none; transform: none; }
.cw--v6 .cw-status { min-block-size: 28px; gap: 12px; padding-inline: 12px; border-block-start: 1px solid var(--color-border-hairline); background: var(--color-bg); color: var(--color-text-muted); font-size: 11px; }
.cw--v6 :is(.cw-bar__btn,.cw-ai__go,.cw-comptask__go,.cw-comptask__stop):disabled, .cw-home--v6 button:disabled { opacity: .5; cursor: default; transform: none; box-shadow: none; }
.cw--v6 :is(button,select,a):focus-visible, .cw-home--v6 :is(button,select,a):focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }

/* The model controls are mounted by the existing composer integration. */
:is(.cw-home--v6,.cw--v6) .cw-model-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; min-inline-size: 0; }
.cw--v6 .cw-ai > .cw-model-controls { flex: 1 1 100%; order: -1; }
:is(.cw-home--v6,.cw--v6) .cw-model-controls > label { display: flex; align-items: center; gap: 8px; min-inline-size: 0; color: var(--color-text-secondary); font: 500 12px/1.6 var(--font-sans); }
:is(.cw-home--v6,.cw--v6) :is(.cw-model-select,.cw-strength-select) { min-block-size: 44px; max-inline-size: 100%; padding: 8px 12px; border: 1px solid var(--color-border-strong); border-radius: 8px; background: var(--color-bg); color: var(--color-text-primary); font: 500 13px/1.5 var(--font-sans); }
:is(.cw-home--v6,.cw--v6) .cw-model-select { direction: ltr; unicode-bidi: isolate; }
:is(.cw-home--v6,.cw--v6) .cw-model-status { flex: 1 1 100%; margin: 0; color: var(--color-text-secondary); font: 400 12px/1.7 var(--font-sans); }
:is(.cw-home--v6,.cw--v6) .cw-model-status:empty { display: none; }
:is(.cw-home--v6,.cw--v6) .cw-access-inline { flex: 1 1 100%; min-inline-size: 0; padding: 12px; border: 1px solid var(--color-border-strong); border-radius: 9px; background: var(--color-bg); color: var(--color-text-primary); font: 400 13px/1.7 var(--font-sans); }
:is(.cw-home--v6,.cw--v6) :is(.cw-inbox-shortcut,.cw-access-inline button) { min-block-size: 44px; padding: 8px 12px; border: 1px solid var(--color-border-strong); border-radius: 8px; background: var(--color-surface); color: var(--color-text-primary); font: 600 13px/1.6 var(--font-sans); }
.cw--v6 .cw-thread__omnix { inline-size: 100%; min-inline-size: 0; padding: 14px; color: var(--color-text-primary); }
.cw--v6 :is(.cw-omnix-approval,.cw-omnix-files) { min-inline-size: 0; padding: 12px; border: 1px solid var(--color-border-strong); border-radius: 9px; background: var(--color-surface); font: 400 13px/1.7 var(--font-sans); }
.cw--v6 :is(.cw-omnix-approval,.cw-omnix-files) :is(button,a) { min-block-size: 44px; display: inline-flex; align-items: center; padding: 8px 12px; color: var(--color-text-primary); }
@container firas-code (max-width: 900px) { .cw--v6 .cw-main { grid-template-columns: minmax(140px,160px) minmax(220px,1fr) minmax(260px,1fr); } }
@container firas-code (max-width: 760px) {
  .cw--v6 .cw-main { display: flex; min-block-size: 0; }
  .cw--v6 :is(.cw-tree,.cw-ed,.cw-right) { display: none !important; }
  .cw--v6[data-mob="files"] .cw-tree, .cw--v6[data-mob="code"] .cw-ed, .cw--v6[data-mob="preview"] .cw-right, .cw--v6[data-mob="ai"] .cw-right { display: flex !important; inline-size: 100% !important; flex: 1; }
  .cw--v6 .cw-mobnav { display: flex; flex: 0 0 auto; }
  .cw--v6 .cw-bar__count { display: none; }
  .cw--v6 .cw-bar { gap: 6px; padding-inline: 8px; }
  .cw--v6 .cw-bar__identity { gap: 6px; }
  .cw--v6 .cw-bar__name { max-inline-size: 180px; overflow: hidden; text-overflow: ellipsis; }
}
@media (max-width: 600px) {
  .cw-home.cw-home--v6 { padding: 24px 14px !important; }
  .cw-home.cw-home--v6 .cw-home__inner { gap: 22px; }
  .cw-home.cw-home--v6 .cw-home__card { padding: 16px; gap: 14px; }
  .cw-home--v6 .cw-home__card-head { grid-template-columns: 1fr; gap: 7px; }
  .cw-home.cw-home--v6 :is(.cw-home__name,.cw-home__desc) { font-size: 16px; }
  /* Keep both creation actions inside the mobile composer. A wrapped column with
     a 100% flex basis otherwise pushes this group into a second, off-card column. */
  .cw-home--v6 .cw-home__acts { display: grid; grid-template-columns: minmax(0,1fr); align-items: stretch; }
  .cw-home--v6 .cw-home__acts-right { min-inline-size: 0; display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 10px; }
  .cw-home.cw-home--v6 .cw-home__acts :is(.cw-home__blank,.cw-home__create,.cw-attach__btn) { min-inline-size: 0; inline-size: 100%; min-block-size: 44px; margin: 0; }
  .cw-home--v6 .cw-home__kbd { display: none; }
  .cw--v6 .cw-comphead { gap: 6px; }
  .cw--v6 .cw-compactions { flex: 1 1 auto; }
  .cw--v6 .cw-compage { flex: 1; max-inline-size: none; }
  .cw--v6 .cw-ai__in { font-size: 16px; }
}
@media (max-width: 860px) {
  /* The mobile AI tab already activates the real chat pane in JS. Keep it visible
     above its composer instead of turning the entire screen into an input. */
  .cw--v6[data-mob="ai"] .cw-main { display: flex !important; flex: 1 1 auto; min-block-size: 80px; }
  .cw--v6[data-mob="ai"] .cw-right { display: flex !important; flex: 1 1 auto; inline-size: 100% !important; }
  .cw--v6[data-mob="ai"] .cw-tabs { display: none; }
  .cw--v6[data-mob="ai"] .cw-ai { display: grid; grid-template-columns: 44px minmax(0,1fr) auto; align-items: end; flex: 0 0 auto; max-block-size: 48%; overflow-y: auto; gap: 8px; padding: 10px; order: 8; }
  .cw--v6[data-mob="ai"] .cw-ai > .cw-model-controls { grid-column: 1 / -1; }
  .cw--v6[data-mob="ai"] .cw-ai__clip { align-self: end; }
  .cw--v6[data-mob="ai"] .cw-ai__in { inline-size: 100%; min-block-size: 64px; max-block-size: 132px; padding: 10px; border-radius: 9px; }
  .cw--v6[data-mob="ai"] .cw-ai__go { inline-size: auto; min-block-size: 44px; padding: 9px 12px; border-radius: 9px; }
  .cw--v6 .cw-status { order: 9; }
  .cw--v6 .cw-mobnav { order: 10; }
}
@media (prefers-reduced-motion: reduce) {
  :is(.cw--v6,.cw-home--v6) *, :is(.cw--v6,.cw-home--v6) *::before, :is(.cw--v6,.cw-home--v6) *::after { animation: none !important; transition: none !important; }
}
:root[data-motion="off"] :is(.cw--v6,.cw-home--v6) *, :root[data-motion="off"] :is(.cw--v6,.cw-home--v6) *::before, :root[data-motion="off"] :is(.cw--v6,.cw-home--v6) *::after { animation: none !important; transition: none !important; }
/* END_FIRAS_CODE_THEME_RESTORE_V1 */
/* CODE_EDIT_DURABLE_V1 */
.cw-thread__edit { display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin-block:10px; }
.cw-thread__edit > p { flex-basis:100%; margin:0 0 4px; color:var(--color-text-secondary); font-size:13px; line-height:1.7; }
.cw-thread__edit > button { min-height:44px; padding:9px 13px; border:1px solid var(--color-border-strong); border-radius:10px; background:var(--color-bg-subtle); color:var(--color-text); font:inherit; font-size:13px; cursor:pointer; }
.cw-thread__edit > button:focus-visible { outline:2px solid var(--color-accent); outline-offset:3px; }
.cw-thread__edit > button:disabled { opacity:.55; cursor:default; }
/* END_CODE_EDIT_DURABLE_V1 */


/* FIRAS_CODE_TURN_POLISH_V1 — compact recorded-task controls; no event/state changes. */
.cw--v6 .cw-thread__turn--ai:has(> .cw-thread__omnix) {
  inline-size: 100%; max-inline-size: 100%; gap: 6px;
}
.cw--v6 .cw-thread__turn--ai:has(> .cw-thread__omnix) > .cw-thread__who {
  text-transform: none; letter-spacing: 0; font-size: 11px; line-height: 1.5;
}
.cw--v6 .cw-thread__turn--ai:has(> .cw-thread__omnix) > .cw-thread__bubble {
  padding: 0; margin: 0; border: 0; border-radius: 0; background: transparent;
  box-shadow: none; color: var(--color-text-primary); font-size: 14px; font-weight: 600; line-height: 1.6;
}
.cw--v6 .cw-thread__omnix {
  display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center;
  gap: 12px 10px; padding: 4px 0 16px; margin: 0; min-inline-size: 0;
  border-block-end: 1px solid var(--color-border-hairline);
}
.cw--v6 .cw-thread__omnix > :not(.cw-omnix-status):not(.cw-omnix-actions) { grid-column: 1 / -1; min-inline-size: 0; }
[dir="rtl"] .cw--v6 .cw-thread__omnix { direction: rtl; text-align: start; }
.cw--v6 .cw-thread__omnix > .msg-ai__body { inline-size: 100%; max-inline-size: none; padding: 0; margin: 0; }
.cw--v6 .cw-omnix-status { margin: 0; color: var(--color-text-secondary); font: 500 12px/1.7 var(--font-sans); overflow-wrap: anywhere; }
.cw--v6 .cw-omnix-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 6px; margin: 0; }
.cw--v6 .cw-omnix-actions button,
.cw--v6 .cw-omnix-approval button {
  min-inline-size: 44px; min-block-size: 44px; padding: 8px 12px; margin: 0;
  border: 1px solid var(--color-border-strong); border-radius: 8px;
  background: var(--color-bg); color: var(--color-text-primary);
  font: 500 12px/1.5 var(--font-sans); cursor: pointer;
  transition: background 150ms ease,border-color 150ms ease;
}
.cw--v6 .cw-omnix-actions button:hover,
.cw--v6 .cw-omnix-approval button:hover { background: var(--color-bg-subtle); border-color: var(--color-text-muted); }
.cw--v6 .cw-omnix-actions button[data-action="cancel"] { color: var(--color-error); }
.cw--v6 .cw-omnix-actions button:disabled,
.cw--v6 .cw-omnix-approval button:disabled { opacity: .55; cursor: default; }
.cw--v6 .cw-omnix-capture { margin: 0; padding: 10px 12px; border-inline-start: 2px solid var(--color-border-strong); background: var(--color-bg-subtle); color: var(--color-text-secondary); font: 400 12px/1.75 var(--font-sans); }
.cw--v6 .cw-omnix-panel { margin: 0; padding: 0; max-inline-size: none; }
.cw--v6 .cw-omnix-output { margin: 0; overflow-wrap: anywhere; }
.cw--v6 .cw-omnix-output:empty { display: none; }
.cw--v6 .cw-omnix-output > :first-child { margin-block-start: 0; }
.cw--v6 .cw-omnix-output > :last-child { margin-block-end: 0; }
.cw--v6 .cw-omnix-approval { padding: 14px; }
.cw--v6 .cw-omnix-approval :is(h4,p,pre) { margin-block: 0 10px; }
.cw--v6 .cw-omnix-approval h4 { font-size: 14px; line-height: 1.6; }
.cw--v6 .cw-omnix-approval pre { max-inline-size: 100%; padding: 10px; border-radius: 6px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; background: var(--color-bg); color: var(--color-text-primary); font: 400 12px/1.6 var(--font-mono); }
.cw--v6 .cw-omnix-approval button[data-action="once"] { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-on-accent); }
.cw--v6 .cw-omnix-files { padding: 0; border-radius: 9px; overflow: hidden; }
.cw--v6 .cw-omnix-files summary { min-block-size: 44px; padding: 11px 13px; color: var(--color-text-secondary); font: 500 12px/1.7 var(--font-sans); cursor: pointer; }
.cw--v6 .cw-omnix-files[open] summary { border-block-end: 1px solid var(--color-border-hairline); }
.cw--v6 .cw-omnix-files > p { margin: 10px 13px; color: var(--color-text-secondary); }
.cw--v6 .cw-omnix-files > p:empty, .cw--v6 .cw-omnix-files > ul:empty { display: none; }
.cw--v6 .cw-omnix-files ul { padding: 8px 12px; margin: 0; list-style: none; }
.cw--v6 .cw-omnix-files a { display: flex; min-block-size: 44px; align-items: center; overflow-wrap: anywhere; text-decoration: underline; text-underline-offset: 3px; }
.cw--v6 :is(.cw-omnix-actions,.cw-omnix-approval,.cw-omnix-files) :is(button,a,summary):focus-visible { outline: 2px solid var(--color-accent); outline-offset: -3px; }
@media (max-width: 600px) {
  /* Give project identity its own row so a long title cannot paint behind the
     existing scrollable command dock. The dock keeps all existing controls. */
  .cw--v6 .cw-bar:has(> .cw-dock) { flex-wrap: wrap; overflow: visible; row-gap: 4px; padding-block: 4px; }
  .cw--v6 .cw-bar:has(> .cw-dock) > .cw-bar__identity { flex: 1 1 100%; min-inline-size: 0; max-inline-size: 100%; min-block-size: 40px; overflow: hidden; }
  .cw--v6 .cw-bar:has(> .cw-dock) .cw-bar__name { flex: 1 1 auto; min-inline-size: 0; max-inline-size: none; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
  .cw--v6 .cw-bar:has(> .cw-dock) > .cw-bar__sp { display: none; }
  .cw--v6 .cw-bar > .cw-dock { display: flex; flex: 1 1 100%; min-inline-size: 0; max-inline-size: 100%; overflow-x: auto; overflow-y: hidden; scrollbar-width: thin; }
  .cw--v6 .cw-bar .cw-dock__clip { display: flex; flex: 0 0 auto; overflow: visible; }
  .cw--v6 .cw-bar :is(.cw-dock__strip,.cw-dock__grp) { display: flex; flex-wrap: nowrap; }
  .cw--v6 .cw-omnix-actions { gap: 4px; }
}
@media (prefers-reduced-motion: reduce) {
  .cw--v6 .cw-omnix-actions button, .cw--v6 .cw-omnix-approval button { transition: none; }
}
/* END_FIRAS_CODE_TURN_POLISH_V1 */


/* Presenter: four cards are a balanced 2x2 matrix.  Fixed minmax rows keep every card inside
   the stage; text clamps inside its own card instead of pushing a later card below the slide. */
.dp-cards__grid { min-block-size: 0; grid-auto-rows: minmax(0, 1fr); }
.dp-cards__grid--4 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: repeat(2, minmax(0, 1fr));
  gap: clamp(8px, 1.7vw, 20px);
}
.dp-card { min-inline-size: 0; min-block-size: 0; }
.dp-card__t,
.dp-card__d { overflow-wrap: anywhere; }
.dp-cards__grid--4 .dp-card { overflow: hidden; }
.dp-cards__grid--4 .dp-card__t,
.dp-cards__grid--4 .dp-card__d {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.dp-cards__grid--4 .dp-card__t { -webkit-line-clamp: 2; }
.dp-cards__grid--4 .dp-card__d { -webkit-line-clamp: 4; }

@media (max-width: 640px) {
  .dp-cards__grid--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }
  .dp-cards__grid--4 .dp-card { padding: 9px; border-radius: 12px; gap: 3px; }
  .dp-cards__grid--4 .dp-card__ic { font-size: 20px; margin-block-end: 0; }
  .dp-cards__grid--4 .dp-card__t { font-size: clamp(12px, 3.6vw, 16px); }
  .dp-cards__grid--4 .dp-card__d {
    font-size: clamp(10.5px, 3vw, 13px);
    line-height: 1.4;
    -webkit-line-clamp: 3;
  }
}

/* FIRAS_PUBLIC_TIER_NAMES_V1: preserve the exact product names in every locale. */
.msg-ai__badge[data-tier] { text-transform: none; }


/* OMNIX_CHAT_INLINE_V1 — the normal conversation keeps its existing reading width. */
.omnix-chat-status { margin: 8px 14px 0; color: var(--color-text-secondary); font: 400 12px/1.65 var(--font-sans); overflow-wrap: anywhere; }
.omnix-chat-status[hidden], .omnix-chat-run [hidden] { display: none !important; }
.omnix-chat-run { min-inline-size: 0; display: grid; gap: 12px; }
.omnix-chat-run__status, .omnix-chat-capture { margin: 0; color: var(--color-text-secondary); font: 400 12px/1.7 var(--font-sans); overflow-wrap: anywhere; }
.omnix-chat-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.omnix-chat-run button, .omnix-chat-files summary { min-block-size: 44px; min-inline-size: 44px; padding: 9px 13px; border: 1px solid var(--color-border-strong); border-radius: 8px; background: var(--color-bg); color: var(--color-text-primary); font: 500 13px/1.6 var(--font-sans); cursor: pointer; }
.omnix-chat-run button:disabled { opacity: .55; cursor: default; }
.omnix-chat-run button:hover { background: var(--color-bg-subtle); }
.omnix-chat-panel { min-inline-size: 0; }
.omnix-chat-approval { padding: 14px; border: 1px solid var(--color-border-strong); border-radius: 10px; background: var(--color-bg-subtle); }
.omnix-chat-approval :is(h4,p,pre) { margin-block: 0 10px; }
.omnix-chat-approval pre { white-space: pre-wrap; overflow-wrap: anywhere; max-inline-size: 100%; overflow: auto; max-block-size: min(40vh, 24rem); padding: 10px; border-radius: 6px; background: var(--color-bg); color: var(--color-text-primary); font: 400 12px/1.6 var(--font-mono); }
.omnix-chat-approval button[data-action=once] { background: var(--color-accent); color: var(--color-on-accent); }
.omnix-chat-output:empty, .omnix-chat-files p:empty { display: none; }
.omnix-chat-output { min-inline-size: 0; overflow-wrap: anywhere; }
.omnix-chat-copy { justify-self: start; }
.omnix-chat-files { border: 1px solid var(--color-border-hairline); border-radius: 9px; overflow: hidden; }
.omnix-chat-files summary { border: 0; border-radius: 0; }
.omnix-chat-files ul { margin: 0; padding: 8px 14px; list-style: none; }
.omnix-chat-files p { padding: 0 14px; }
.omnix-chat-files a { display: flex; min-block-size: 44px; align-items: center; overflow-wrap: anywhere; text-decoration: underline; text-underline-offset: 3px; }
.omnix-chat-run :is(button,a,summary):focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }
.omnix-chat-model-hint { color: var(--color-text-secondary); font-size: 13px; line-height: 1.7; padding: 10px 14px; }
#omnixTierRequest.tier-switch__btn { min-inline-size: 0; min-block-size: 44px; margin: 0; flex-direction: row; }
#omnixTierRequest.tier-switch__btn .omnix-request-entry__label { display: none; }
/* END_OMNIX_CHAT_INLINE_V1 */

/* OMNIX_REQUEST_UI_V1 — scoped request controls, never an active model tier. */
.omnix-request-entry { min-block-size: 44px; display: flex; align-items: center; justify-content: space-between; gap: 10px; border: 1px solid var(--color-border); border-radius: 10px; padding: 9px 12px; background: var(--color-bg-subtle); color: var(--color-text-primary); cursor: pointer; text-align: start; }
.omnix-request-entry__name { direction: ltr; unicode-bidi: isolate; font: 650 14px var(--font-sans, sans-serif); white-space: nowrap; text-transform: none; }
.omnix-request-entry__label { color: var(--color-text-secondary); font-size: 12px; line-height: 1.5; }
.omnix-request-entry:focus-visible, .omnix-access button:focus-visible, .omnix-access textarea:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; }
.omnix-request-entry:hover { border-color: var(--color-accent); }
.omnix-request-entry--strip { margin-inline: 4px; gap: 6px; }
.omnix-request-entry--strip .omnix-request-entry__label { max-inline-size: 75px; font-size: 10px; }
.omnix-request-entry--menu { inline-size: 100%; margin-block-end: 6px; }
.omnix-code-access { display: flex; justify-content: flex-end; flex: 0 0 auto; padding: 8px 12px; }
.cw-home__inner > .omnix-code-access { padding-inline: 0; }
/* OMNIX_CLOUD_TASK_UI_V1 — uses the existing fc panel, scoped to this account task. */
.omnix-cloud .cs-sheet__card { inline-size: min(96vw, 920px); max-block-size: min(94dvh, 1000px); border-radius: 20px; }
.omnix-cloud .cs-sheet__body { display: grid; gap: 18px; padding: 0 12px 12px; min-inline-size: 0; }
.omnix-cloud .cs-sheet__x, .omnix-cloud .cs-sheet__sp { min-inline-size: 44px; min-block-size: 44px; }
.omnix-cloud__intro, .omnix-cloud__capture { margin: 0; font-size: 13px; line-height: 1.8; color: var(--color-text-secondary); }
.omnix-cloud__status { margin: 0; padding-inline-start: 12px; border-inline-start: 2px solid var(--color-accent); font-weight: 650; color: var(--color-text-primary); }
.omnix-cloud__form { display: grid; gap: 10px; min-inline-size: 0; }
.omnix-cloud label, .omnix-cloud h3 { margin: 0; font-size: 15px; font-weight: 650; color: var(--color-text-primary); }
.omnix-cloud textarea { box-sizing: border-box; inline-size: 100%; min-block-size: 110px; max-block-size: 260px; resize: vertical; border: 1px solid var(--color-border-strong); border-radius: 12px; padding: 12px; background: var(--color-bg-subtle); color: var(--color-text-primary); font: inherit; font-size: 14px; line-height: 1.7; }
.omnix-cloud__actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.omnix-cloud button:not(.cs-sheet__x), .omnix-access__cloud { min-block-size: 44px; min-inline-size: 44px; border: 1px solid var(--color-border-strong); border-radius: 10px; padding: 10px 16px; color: var(--color-text-primary); background: var(--color-bg-subtle); font: inherit; font-size: 14px; cursor: pointer; }
.omnix-cloud .omnix-cloud__send, .omnix-cloud .omnix-cloud__once { background: var(--color-accent); color: #fff; border-color: var(--color-accent); font-weight: 650; }
.omnix-cloud button:disabled, .omnix-access__cloud:disabled { opacity: .55; cursor: default; }
.omnix-cloud :is(button,textarea,a,summary):focus-visible, .omnix-access__cloud:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; }
.omnix-cloud__error, .omnix-cloud__approval { margin: 0; padding: 14px; border: 1px solid var(--color-border-strong); border-radius: 12px; background: var(--color-bg-subtle); color: var(--color-text-primary); font-size: 14px; line-height: 1.8; }
.omnix-cloud__approval { display: grid; gap: 12px; }
.omnix-cloud__approval pre { max-inline-size: 100%; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; margin: 0; padding: 12px; border-radius: 8px; background: var(--color-bg); font-size: 12px; }
.omnix-cloud__answer, .omnix-cloud__files { display: grid; gap: 12px; padding-block-start: 16px; border-block-start: 1px solid var(--color-border); min-inline-size: 0; }
.omnix-cloud__answer .md { overflow-wrap: anywhere; min-inline-size: 0; }
.omnix-cloud__files-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.omnix-cloud__files ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.omnix-cloud__files a { display: flex; align-items: center; min-block-size: 44px; padding: 8px 12px; border: 1px solid var(--color-border); border-radius: 10px; color: var(--color-text-primary); overflow-wrap: anywhere; }
.omnix-cloud__files p { margin: 0; color: var(--color-text-secondary); font-size: 13px; }
.omnix-cloud [hidden] { display: none !important; }
.omnix-cloud .fc__identity-name { direction: ltr; unicode-bidi: isolate; }
@media (max-width: 600px) { .omnix-cloud .cs-sheet__body { padding-inline: 4px; } .omnix-cloud__actions > button { flex: 1 1 auto; } }
@media (prefers-reduced-motion: reduce) { .omnix-cloud *, .omnix-cloud *::before, .omnix-cloud *::after { animation: none !important; transition: none !important; } }
/* END_OMNIX_CLOUD_TASK_UI_V1 */

.omnix-access .cs-sheet__card { inline-size: min(96vw, 540px); max-block-size: min(92dvh, 840px); border-radius: 20px; }
.omnix-access .cs-sheet__x, .omnix-access .cs-sheet__sp { min-inline-size: 44px; min-block-size: 44px; }
.omnix-access .cs-sheet__body { gap: 18px; padding: 0 10px 8px; }
.omnix-access__intro h2 { margin: 8px 0; font-size: 24px; line-height: 1.3; color: var(--color-text-primary); }
.omnix-access__intro p, .omnix-access__notice p, .omnix-access__status p { margin: 0; font-size: 14px; line-height: 1.75; color: var(--color-text-secondary); }
.omnix-access__eyebrow { color: var(--color-accent); font-size: 12px; font-weight: 650; }
.omnix-access__notice { display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; border: 1px solid var(--color-border); border-radius: 12px; background: var(--color-bg-subtle); }
.omnix-access__dot { inline-size: 7px; block-size: 7px; flex: 0 0 auto; border: 1px solid var(--color-text-secondary); border-radius: 50%; margin-block-start: 8px; }
.omnix-access__status { border-inline-start: 2px solid var(--color-accent); padding-inline-start: 12px; }
.omnix-access__status-title { display: block; font-size: 16px; color: var(--color-text-primary); margin-block-end: 4px; }
.omnix-access__date { display: block; color: var(--color-text-muted); font-size: 12px; line-height: 1.6; margin-block-start: 8px; }
.omnix-access__form { display: grid; gap: 10px; }
.omnix-access__form label, .omnix-access__saved > span { color: var(--color-text-primary); font-size: 14px; font-weight: 600; }
.omnix-access textarea { inline-size: 100%; min-block-size: 124px; resize: vertical; border: 1px solid var(--color-border-strong); border-radius: 12px; padding: 12px; background: var(--color-bg-subtle); color: var(--color-text-primary); font: inherit; font-size: 16px; line-height: 1.6; }
.omnix-access__help { display: flex; justify-content: space-between; gap: 12px; color: var(--color-text-muted); font-size: 12px; line-height: 1.5; }
.omnix-access__help [dir="ltr"] { white-space: nowrap; }
.omnix-access__submit, .omnix-access__signin, .omnix-access__refresh, .omnix-access__desktop { min-block-size: 44px; border: 1px solid var(--color-border-strong); border-radius: 10px; padding: 10px 16px; font: inherit; font-size: 14px; cursor: pointer; background: var(--color-bg-subtle); color: var(--color-text-primary); }
.omnix-access__submit { background: var(--color-accent); color: #fff; border-color: var(--color-accent); font-weight: 600; }
.omnix-access button:disabled { opacity: .5; cursor: default; }
.omnix-access__actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-end; }
.omnix-access__error { margin: 0; padding: 12px; border: 1px solid var(--color-border-strong); border-radius: 10px; color: var(--color-text-primary); font-size: 13px; line-height: 1.6; }
.omnix-access__saved p { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 14px; line-height: 1.7; color: var(--color-text-secondary); max-block-size: 140px; overflow: auto; }
.omnix-access [hidden] { display: none !important; }
@media (min-width: 700px) { .omnix-access { align-items: center; } .omnix-access .cs-sheet__card { margin-block-end: 0; } }
@media (max-width: 420px) { .omnix-code-access .omnix-request-entry { inline-size: 100%; } .omnix-access .cs-sheet__body { padding-inline: 4px; } }
@media (prefers-reduced-motion: reduce) { .omnix-access, .omnix-access *, .omnix-request-entry { animation: none !important; transition: none !important; scroll-behavior: auto !important; } }

/* The desktop action follows the existing strip's visibility breakpoints. */
#omnixTierRequest { display: none; }
body:not(.product-agent):not(.product-brain):not(.product-code) .topbar:has(#tierSwitch) #omnixTierRequest { display: flex; }
@media (max-width: 600px) { body:not(.product-agent):not(.product-brain):not(.product-code) .topbar:has(#tierSwitch) #omnixTierRequest { display: none; } }
@media (min-width: 601px) and (max-width: 1100px) {
  body:not(.product-agent):not(.product-brain):not(.product-code) #omnixTierRequest,
  body:not(.product-agent):not(.product-brain):not(.product-code) #tierSwitch { display: none !important; }
  body:not(.product-agent):not(.product-brain):not(.product-code) #tierSelect { display: block; position: relative; flex: 0 1 auto; min-inline-size: 0; }
}
:root[data-ui="2"] #omnixTierRequest { display: none !important; }
:root[data-ui="2"] body:not(.product-code):not(.product-brain) #tierSelect { display: none; }
.omnix-request-entry--sheet { min-block-size: 44px; }
.omnix-request-entry--sheet .cs-row__t { unicode-bidi: isolate; text-transform: none; }

/* OMNIX_DEVELOPER_INBOX_V1 — settings inbox, scoped to its authenticated reviewer. */
.omnix-review-settings__row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
.omnix-review-settings__count { border-radius: 99px; padding: 6px 10px; background: var(--color-bg-subtle); color: var(--color-text-secondary); font-size: 12px; }
.omnix-review .cs-sheet__card { inline-size: min(96vw, 820px); max-block-size: min(94dvh, 960px); }
.omnix-review .cs-sheet__x, .omnix-review .cs-sheet__sp { min-inline-size: 44px; min-block-size: 44px; }
.omnix-review .cs-sheet__body { gap: 14px; padding-inline: 8px; }
.omnix-review__notice { margin: 0; padding: 12px 14px; border: 1px solid var(--color-border); border-radius: 12px; color: var(--color-text-secondary); background: var(--color-bg-subtle); font-size: 13px; line-height: 1.7; }
.omnix-review__toolbar, .omnix-review__pager { display: flex; align-items: end; justify-content: space-between; flex-wrap: wrap; gap: 12px; }
.omnix-review__toolbar label { display: grid; gap: 6px; flex: 1; color: var(--color-text-secondary); font-size: 12px; }
.omnix-review button, .omnix-review select, .omnix-review-open { min-block-size: 44px; }
.omnix-review__toolbar button, .omnix-review__pager button, .omnix-review__actions button, .omnix-review select { border: 1px solid var(--color-border-strong); border-radius: 10px; padding: 9px 14px; background: var(--color-bg-subtle); color: var(--color-text-primary); font: inherit; font-size: 13px; cursor: pointer; }
.omnix-review select { inline-size: 100%; max-inline-size: 320px; }
.omnix-review button:disabled, .omnix-review select:disabled { opacity: .5; cursor: default; }
.omnix-review button:focus-visible, .omnix-review select:focus-visible, .omnix-review summary:focus-visible, .omnix-review-open:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; }
.omnix-review__message { margin: 0; min-block-size: 20px; font-size: 13px; line-height: 1.6; color: var(--color-text-secondary); }
.omnix-review__error { margin: 0; padding: 12px; border: 1px solid var(--color-border-strong); border-radius: 10px; color: var(--color-text-primary); line-height: 1.7; font-size: 13px; }
.omnix-review__list { display: grid; gap: 12px; }
.omnix-review__item { border: 1px solid var(--color-border); border-radius: 14px; background: var(--color-bg-subtle); padding: 16px; }
.omnix-review__head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 12px; }
.omnix-review__identity { display: grid; gap: 4px; min-inline-size: 0; flex: 1 1 220px; }
.omnix-review__identity strong { color: var(--color-text-primary); font-size: 15px; overflow-wrap: anywhere; }
.omnix-review__identity bdi { color: var(--color-text-muted); font-size: 12px; overflow-wrap: anywhere; unicode-bidi: isolate; }
.omnix-review__status { flex: 0 0 auto; font-size: 12px; color: var(--color-text-secondary); border: 1px solid var(--color-border-strong); border-radius: 99px; padding: 5px 9px; }
.omnix-review__status[data-status="pending"], .omnix-review__status[data-status="approved"] { color: var(--color-accent); border-color: var(--color-accent); }
.omnix-review__reason { margin-block: 12px; border-block: 1px solid var(--color-border); }
.omnix-review__reason summary { min-block-size: 44px; padding-block: 12px; cursor: pointer; color: var(--color-text-secondary); font-size: 12px; }
.omnix-review__reason p { margin: 0 0 14px; color: var(--color-text-primary); font-size: 14px; line-height: 1.8; white-space: pre-wrap; overflow-wrap: anywhere; }
.omnix-review__footer { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; }
.omnix-review__footer time { font-size: 11px; color: var(--color-text-muted); line-height: 1.6; }
.omnix-review__actions { display: flex; flex-wrap: wrap; gap: 8px; }
.omnix-review__actions button[data-decision="approved"] { color: var(--color-on-accent); background: var(--color-accent); border-color: var(--color-accent); }
.omnix-review__pager { align-items: center; }
.omnix-review__range { color: var(--color-text-muted); font-size: 12px; }
.omnix-review__empty { padding: 28px 12px; margin: 0; text-align: center; color: var(--color-text-muted); line-height: 1.7; }
.omnix-review [hidden], .omnix-review-settings [hidden] { display: none !important; }
@media (min-width: 700px) { .omnix-review { align-items: center; } .omnix-review .cs-sheet__card { margin-block-end: 0; } }
@media (max-width: 480px) { .omnix-review .cs-sheet__body { padding-inline: 2px; } .omnix-review__item { padding: 12px; } .omnix-review__footer { align-items: stretch; flex-direction: column; } .omnix-review__actions button { flex: 1; } }
@media (prefers-reduced-motion: reduce) { .omnix-review, .omnix-review *, .omnix-review-settings * { animation: none !important; transition: none !important; scroll-behavior: auto !important; } }

/* OMNIX_TELEGRAM_SETTINGS_V1 */
.omnix-telegram { display: grid; gap: 14px; min-inline-size: 0; }
.omnix-telegram__head { display: flex; gap: 12px; align-items: flex-start; }
.omnix-telegram__head > div { min-inline-size: 0; }
.omnix-telegram__head h3 { margin: 0 0 5px; font-size: 16px; font-weight: 650; color: var(--color-text-primary); }
.omnix-telegram p { margin: 0; font-size: 13px; line-height: 1.8; color: var(--color-text-secondary); }
.omnix-telegram__icon { display: grid; place-items: center; flex: 0 0 38px; block-size: 38px; border: 1px solid var(--color-border); border-radius: 12px; background: var(--color-bg-subtle); color: var(--color-accent); }
.omnix-telegram__icon svg { inline-size: 22px; block-size: 22px; }
.omnix-telegram .omnix-telegram__status { border-inline-start: 2px solid var(--color-accent); padding-inline-start: 12px; color: var(--color-text-primary); font-weight: 600; }
.omnix-telegram__help { border-block: 1px solid var(--color-border); padding-block: 6px; }
.omnix-telegram summary { min-block-size: 44px; display: flex; align-items: center; gap: 10px; cursor: pointer; font-size: 13px; font-weight: 600; color: var(--color-text-primary); }
.omnix-telegram summary::before { content: "+"; font-size: 18px; color: var(--color-accent); }
.omnix-telegram details[open] summary::before { content: "−"; }
.omnix-telegram ol { margin: 0; padding: 0 24px 10px; font-size: 13px; line-height: 1.9; color: var(--color-text-secondary); }
.omnix-telegram li + li { margin-block-start: 7px; }
.omnix-telegram a { color: var(--color-accent); text-underline-offset: 3px; }
.omnix-telegram__form, .omnix-telegram__pair, .omnix-telegram__confirm { display: grid; gap: 10px; }
.omnix-telegram label { display: grid; gap: 8px; font-size: 13px; font-weight: 600; color: var(--color-text-primary); }
.omnix-telegram input { box-sizing: border-box; inline-size: 100%; min-inline-size: 0; min-block-size: 46px; border: 1px solid var(--color-border-strong); border-radius: 10px; padding: 10px 12px; background: var(--color-bg-subtle); color: var(--color-text-primary); font: inherit; font-size: 16px; }
.omnix-telegram__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.omnix-telegram button, .omnix-telegram__open { display: inline-flex; justify-content: center; align-items: center; gap: 6px; min-block-size: 44px; min-inline-size: 44px; box-sizing: border-box; border: 1px solid var(--color-border-strong); border-radius: 10px; padding: 9px 14px; font: inherit; font-size: 13px; font-weight: 600; background: var(--color-bg-subtle); color: var(--color-text-primary); cursor: pointer; text-decoration: none; }
.omnix-telegram .omnix-telegram__connect { justify-self: start; background: var(--color-accent); border-color: var(--color-accent); color: #fff; }
.omnix-telegram button:disabled { opacity: .5; cursor: default; }
.omnix-telegram__pair, .omnix-telegram__confirm { padding: 12px; border: 1px solid var(--color-border-strong); border-radius: 12px; background: var(--color-bg-subtle); }
.omnix-telegram code { display: block; padding: 10px; border: 1px solid var(--color-border); border-radius: 8px; background: var(--color-bg); color: var(--color-text-primary); white-space: pre-wrap; overflow-wrap: anywhere; font: 13px/1.7 ui-monospace, monospace; user-select: all; text-align: left; unicode-bidi: isolate; }
.omnix-telegram__expiry { font-size: 12px; color: var(--color-text-secondary); }
.omnix-telegram .omnix-telegram__error { color: var(--color-text-primary); border-inline-start: 2px solid #c47846; padding-inline-start: 10px; }
.omnix-telegram .omnix-telegram__foot { font-size: 12px; }
.omnix-telegram__bot { unicode-bidi: isolate; overflow-wrap: anywhere; }
.omnix-telegram [hidden] { display: none !important; }
.omnix-telegram :is(button,input,summary,a,code):focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; }
@media (max-width: 600px) { .omnix-telegram__actions > :is(button,a) { flex: 1 1 auto; } .omnix-telegram .omnix-telegram__connect { inline-size: 100%; } }
:root[data-motion="off"] .omnix-telegram * { transition: none !important; animation: none !important; }
@media (prefers-reduced-motion: reduce) { .omnix-telegram * { transition: none !important; animation: none !important; } }
/* END_OMNIX_TELEGRAM_SETTINGS_V1 */

/* Startup paints the ordinary composer immediately. An unavailable account check adds
   only this inline retry; unverified history and account actions remain private. */
.boot-connection{max-width:440px;margin:20px auto 0;padding:12px 16px;border:1px solid var(--color-border,#555);border-radius:12px;color:var(--color-text-secondary,#ccc);font:14px/1.6 var(--font-sans,system-ui,sans-serif);text-align:start}
.boot-connection[hidden]{display:none}
.boot-connection p{margin:0 0 8px}
.boot-connection button{min-height:44px;padding:8px 16px;border:1px solid var(--color-border,#555);border-radius:8px;background:var(--color-bg,#262624);color:var(--color-text-primary,#f5f4f0);font:inherit;cursor:pointer}
.boot-connection button:focus-visible{outline:2px solid currentColor;outline-offset:3px}
[data-identity-pending] #accountPill button{visibility:hidden}

/* Omnix execution: one existing fcPanel state, actual activity and private task files. */
.msg-ai--omnix .msg-ai__avatar svg { inline-size: 25px; block-size: 25px; }
.msg-ai--omnix .msg-ai__avatar { color: #22a06b; background: color-mix(in srgb, #22a06b 10%, var(--color-bg)); }
.omnix-chat-toolbar { display:flex; align-items:center; justify-content:space-between; gap:12px; min-inline-size:0; }
.omnix-chat-toolbar:has(.omnix-chat-run__status[hidden]):has(.omnix-chat-actions button[hidden] + button[hidden]) { display:none; }
.omnix-chat-toolbar:has(> .omnix-chat-run__status[hidden]):not(:has(button:not([hidden]))) { display:none; }
.omnix-chat-actions { margin-inline-start:auto; }
.omnix-chat-actions button { background:transparent; border-color:var(--color-border-hairline); }
.omnix-chat-actions [data-action=cancel] { color:var(--color-text-secondary); }
.omnix-chat-run { gap:14px; }
.omnix-chat-run .fc--omnix { margin:0; border:1px solid var(--color-border-hairline); border-radius:16px; overflow:hidden; background:var(--color-bg-subtle); box-shadow:none; }
.fc--omnix .fc__identity { margin:0; padding:16px 18px; gap:10px; border-block-end:1px solid var(--color-border-hairline); background:var(--color-bg); }
.fc--omnix .fc__identity-mark { color:#22a06b; inline-size:28px; block-size:28px; flex:none; }
.fc--omnix .fc__identity-mark svg { inline-size:100%; block-size:100%; }
.fc--omnix .fc__identity-name { font-size:14px; font-weight:650; color:var(--color-text-primary); }
.fc--omnix .omnix-chat-actions { margin-inline-start:0; }
.fc--omnix .omnix-chat-actions button { min-block-size:44px; padding:6px 10px; font-size:11px; border-color:transparent; background:transparent; }
.fc--omnix .fc__identity-status { margin-inline-start:auto; color:var(--color-text-secondary); font-size:12px; }
.fc--omnix .fc__clock { font-variant-numeric:tabular-nums; color:var(--color-text-secondary); font-size:12px; }
.fc--omnix .fc__flow { padding:16px 18px; gap:0; }
.fc-omnix__heading { display:flex; align-items:baseline; justify-content:space-between; gap:12px; margin-block-end:12px; }
.fc-omnix__heading h4,.omnix-chat-artifacts>h4 { margin:0; color:var(--color-text-primary); font:600 13px/1.6 var(--font-sans); }
.fc-omnix__count { color:var(--color-text-secondary); font:400 11px/1.6 var(--font-sans); }
.fc-omnix__empty { margin:0; padding-block:8px; color:var(--color-text-secondary); font:400 13px/1.85 var(--font-sans); }
.fc-omnix__list { list-style:none; margin:0!important; padding:0!important; display:grid; gap:5px; }
.fc-omnix__list>li { margin:0!important; padding:0!important; min-inline-size:0; }
.fc-omnix__step { border:1px solid transparent; border-radius:10px; overflow:hidden; }
.fc-omnix__step[open] { background:var(--color-bg); border-color:var(--color-border-hairline); }
.fc-omnix__step>summary { display:flex; align-items:center; gap:10px; min-block-size:48px; padding:9px 10px; list-style:none; cursor:pointer; border-radius:9px; }
.fc-omnix__step>summary::-webkit-details-marker { display:none; }
.fc-omnix__step>summary:hover { background:color-mix(in srgb,var(--color-text-primary) 4%,transparent); }
.fc-omnix__step>summary:focus-visible { outline:2px solid var(--color-accent); outline-offset:-2px; }
.fc-omnix__number { inline-size:24px; block-size:24px; display:grid; place-items:center; flex:none; border:1px solid var(--color-border-strong); border-radius:50%; color:var(--color-text-secondary); font:500 11px/1 var(--font-sans); }
.fc-omnix__step.is-done .fc-omnix__number { border-color:color-mix(in srgb,#22a06b 35%,var(--color-border-hairline)); color:#22a06b; background:color-mix(in srgb,#22a06b 9%,var(--color-bg)); }
.fc-omnix__step.is-run .fc-omnix__number { color:var(--color-text-primary); border-color:#22a06b; box-shadow:0 0 0 3px color-mix(in srgb,#22a06b 9%,transparent); }
.fc-omnix__step.is-fail .fc-omnix__number { color:var(--color-text-primary); border-style:dashed; }
.fc-omnix__label { min-inline-size:0; flex:1; color:var(--color-text-primary); font:500 12px/1.65 var(--font-mono); overflow-wrap:anywhere; }
.fc-omnix__meta { display:flex; flex-wrap:wrap; justify-content:flex-end; column-gap:10px; row-gap:2px; max-inline-size:42%; color:var(--color-text-secondary); font:400 11px/1.5 var(--font-sans); }
.fc-omnix__duration { opacity:.85; font-variant-numeric:tabular-nums; }
.fc-omnix__step .fc__chevron { flex:none; inline-size:14px; }
.fc-omnix__detail { display:grid; gap:14px; padding:0 14px 14px 44px; }
[dir=rtl] .fc-omnix__detail { padding-inline:44px 14px; }
.fc-omnix__detail h5 { margin:0 0 6px; color:var(--color-text-secondary); font:500 11px/1.6 var(--font-sans); }
.fc-omnix__detail pre { white-space:pre-wrap; overflow-wrap:anywhere; max-block-size:240px; overflow:auto; margin:0!important; padding:12px!important; border:1px solid var(--color-border-hairline); border-radius:8px; background:var(--color-bg-subtle)!important; color:var(--color-text-primary)!important; font:400 12px/1.75 var(--font-mono)!important; }
.fc-omnix__detail p { margin:0; color:var(--color-text-secondary); font:400 12px/1.7 var(--font-sans); }
.omnix-chat-capture { padding-inline:4px; font-size:11px; }
.omnix-chat-approval { border-inline-start:3px solid #22a06b; padding:18px; border-radius:12px; }
.omnix-chat-approval>button { margin-inline-end:8px; }
.omnix-chat-output { padding-block:4px; }
.omnix-chat-artifacts { display:grid; gap:12px; margin-block:4px; }
.omnix-chat-artifacts__grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr)); gap:10px; }
.omnix-chat-artifact { display:grid; grid-template-columns:34px minmax(0,1fr); gap:10px; padding:16px; border:1px solid var(--color-border-hairline); border-radius:12px; background:var(--color-bg-subtle); }
.omnix-chat-artifact__icon { inline-size:34px; block-size:38px; display:grid; place-items:center; color:#22a06b; }
.omnix-chat-artifact__icon svg { inline-size:26px; block-size:26px; }
.omnix-chat-artifact__info { display:grid; gap:3px; min-inline-size:0; align-content:center; }
.omnix-chat-artifact__info strong { color:var(--color-text-primary); font:500 13px/1.6 var(--font-sans); overflow-wrap:anywhere; }
.omnix-chat-artifact__info small { color:var(--color-text-secondary); font:400 11px/1.5 var(--font-mono); }
.omnix-chat-artifact__actions { display:flex; flex-wrap:wrap; grid-column:1/-1; gap:8px; margin-block-start:3px; }
.omnix-chat-artifact__actions :is(button,a) { display:inline-flex; align-items:center; justify-content:center; min-block-size:44px; padding:8px 14px; color:var(--color-text-primary); background:var(--color-bg); border:1px solid var(--color-border-hairline); border-radius:8px; text-decoration:none; font:500 12px/1.5 var(--font-sans); }
/* OMNIX_FILE_THUMBS_V1: the first page before the name. The box has a fixed ratio so the
   picture arriving later moves nothing; --color-bg is the same surface the action buttons
   stand on, so an empty box still reads as a place, not a hole. */
.omnix-chat-artifact__thumb { display:block; grid-column:1/-1; inline-size:100%; aspect-ratio:4/3; margin:0; padding:0; border:1px solid var(--color-border-hairline); border-radius:8px; background:var(--color-bg); overflow:hidden; cursor:pointer; }
.omnix-chat-artifact__thumb[data-kind=pdf] { aspect-ratio:1/1.3; }
.omnix-chat-artifact__thumb img { display:block; inline-size:100%; block-size:100%; object-fit:cover; object-position:top; }
.omnix-chat-artifact__thumb:focus-visible { outline:2px solid var(--color-accent); outline-offset:-2px; }
.omnix-chat-files { border:0; background:transparent; }
.omnix-chat-files summary { display:inline-flex; color:var(--color-text-secondary); background:transparent; padding-inline:0; font-size:12px; }
@media(max-width:560px) {
 .omnix-chat-run .fc--omnix { border-radius:12px; }
 .fc--omnix .fc__identity { padding:13px 12px; flex-wrap:wrap; gap:8px; }
 .fc--omnix .fc__flow { padding:12px; }
 .fc-omnix__heading { gap:8px; }
 .fc-omnix__step>summary { gap:8px; padding-inline:6px; }
 .fc-omnix__meta { max-inline-size:37%; font-size:10px; }
 .fc-omnix__detail { padding-inline:12px; }
 [dir=rtl] .fc-omnix__detail { padding-inline:12px; }
 .fc-omnix__label { font-size:11px; }
 .omnix-chat-artifact { padding:13px; }
}
@media(prefers-reduced-motion:reduce) { .fc--omnix * { animation:none!important; transition:none!important; } }

/* Omnix Code shares the existing execution identity and guarded live controls. */
.cw-thread__turn:has(> .cw-thread__omnix) > :is(.cw-thread__who,.cw-thread__bubble) { display:none; }
.cw-thread__omnix > .cw-omnix-status[hidden] { display:none; }
.cw-thread__omnix .fc--omnix { margin:0; }
.fc--omnix .cw-omnix-actions { display:flex; gap:6px; margin:0; }
.fc--omnix .cw-omnix-actions button { min-block-size:44px; padding:6px 10px; font-size:11px; border-color:transparent; background:transparent; color:var(--color-text-secondary); }

/* OMNIX_COMPACT_REPLY_V1: ordinary replies have one quiet identity; only observed tools form a timeline. */
.omnix-chat-run { gap:10px; }
.omnix-chat-run .fc--omnix.fc--omnix-compact,
.cw-thread__omnix .fc--omnix.fc--omnix-compact { padding:0; border:0; border-radius:0; background:transparent; overflow:visible; box-shadow:none; }
.fc--omnix.fc--omnix-compact .fc__identity { padding:0; min-block-size:38px; border:0; background:transparent; gap:9px; }
.fc--omnix.fc--omnix-compact .fc__identity-mark { inline-size:24px; block-size:24px; }
.fc--omnix.fc--omnix-compact .fc__identity-name { font-size:13px; font-weight:600; }
.fc--omnix.fc--omnix-compact .fc__flow { display:none!important; }
.fc--omnix.fc--omnix-compact.is-done :is(.fc__clock,.fc__identity-status),
.fc--omnix.fc--omnix-compact .fc__clock:empty { display:none!important; }
.fc--omnix:not(.fc--omnix-compact) .fc__identity { padding:9px 14px; min-block-size:48px; }
.fc--omnix:not(.fc--omnix-compact) .fc__flow { padding:10px 12px 12px; }
.fc-omnix__heading { margin-block-end:5px; padding-inline:5px; }
.fc-omnix__heading h4 { font-size:12px; font-weight:500; color:var(--color-text-secondary); }
.fc-omnix__step>summary { min-block-size:44px; padding-block:7px; }
.fc-omnix__detail { padding-block-start:4px; }
.omnix-chat-output { padding-block:0; }
.omnix-chat-output>:first-child { margin-block-start:0; }
.omnix-chat-run .omnix-chat-copy { min-block-size:44px; padding:5px 9px; font-size:12px; border-color:transparent; background:transparent; color:var(--color-text-secondary); }
@media(max-width:560px) {
 .fc--omnix.fc--omnix-compact .fc__identity { padding:0; gap:8px; }
 .fc--omnix.fc--omnix-compact .fc__identity-status { font-size:11px; }
 .fc--omnix:not(.fc--omnix-compact) .fc__identity { padding:8px 10px; }
 .fc--omnix:not(.fc--omnix-compact) .fc__flow { padding:8px; }
 .omnix-chat-run .omnix-chat-copy { min-block-size:44px; }
}
@media(prefers-reduced-motion:reduce) { .omnix-chat-output,.cw-omnix-output { animation:none!important; transition:none!important; } }
/* END_OMNIX_COMPACT_REPLY_V1 */

/* OMNIX_PENDING_RECEIPT_V1 */
.omnix-chat-panel:empty { display:none; }
.omnix-chat-toolbar .omnix-chat-run__status { flex:1; }
.omnix-chat-toolbar .omnix-chat-actions { flex:none; }
/* END_OMNIX_PENDING_RECEIPT_V1 */

/* ── SKILLS — the owner's own playbooks ─────────────────────────────────────
   Reuses the .mem-overlay / .mem-card furniture so the panel behaves exactly like
   every other modal here: same open transition, same scrim, same close. Only the
   inside is new. The card is wider than .mem-card because a rule list read at 540px
   wraps every line, and a skill you cannot read is a skill you will not maintain. */
.skills-card{width:min(680px,100%)}
.skills-tabs{display:flex;gap:4px;padding:10px 14px 0;border-bottom:1px solid var(--color-border-hairline)}
.skills-tab{background:none;border:none;border-bottom:2px solid transparent;color:var(--color-text-secondary);cursor:pointer;font:600 14px/1 var(--font-sans);padding:9px 12px;border-radius:8px 8px 0 0}
.skills-tab:hover{color:var(--color-text-primary);background:var(--color-bg-subtle)}
.skills-tab.is-on{color:var(--color-accent);border-bottom-color:var(--color-accent)}
.skills-body{overflow:auto;flex:1;padding:12px 14px 16px}
.skills-actions{display:flex;gap:8px;align-items:center;margin-bottom:10px}
.skills-add{background:var(--color-accent);color:var(--color-on-accent);border:none;border-radius:var(--radius-md);cursor:pointer;font:600 14px/1 var(--font-sans);padding:10px 16px}
.skills-add:hover{background:var(--color-accent-hover)}
.skills-search{flex:1;background:var(--color-surface);color:var(--color-text-primary);border:1px solid var(--color-border-strong);border-radius:var(--radius-md);font:400 14px/1.4 var(--font-sans);padding:10px 12px}
.skills-search:focus{outline:none;border-color:var(--color-accent);box-shadow:0 0 0 3px var(--color-accent-ring)}
.skills-note{color:var(--color-text-secondary);font:400 12.5px/1.5 var(--font-sans);margin:0 2px 10px}
.skills-list{display:flex;flex-direction:column;gap:8px}
.skill-row{display:flex;align-items:center;gap:12px;background:var(--color-surface);border:1px solid var(--color-border-hairline);border-radius:var(--radius-md);padding:12px 14px}
.skill-row:hover{border-color:var(--color-border-strong)}
/* A disabled skill stays legible — it is still yours, it is just not running. */
.skill-row.is-off{opacity:.62}
.skill-row__main{flex:1;min-width:0}
.skill-row__name{font:600 14.5px/1.35 var(--font-sans);color:var(--color-text-primary);overflow-wrap:anywhere}
.skill-row__meta{display:flex;align-items:center;gap:8px;margin-top:4px}
.skill-row__count,.skill-row__preview{color:var(--color-text-secondary);font:400 12.5px/1.5 var(--font-sans)}
.skill-row__preview{margin-top:4px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.skill-chip{background:var(--color-accent-soft);color:var(--color-accent);border-radius:999px;font:600 11.5px/1 var(--font-sans);padding:4px 9px;white-space:nowrap}
.skill-row__actions{display:flex;align-items:center;gap:6px;flex:0 0 auto}
.skill-btn{background:none;border:1px solid var(--color-border-strong);border-radius:var(--radius-sm,8px);color:var(--color-text-secondary);cursor:pointer;font:500 12.5px/1 var(--font-sans);padding:7px 11px}
.skill-btn:hover{background:var(--color-bg-subtle);color:var(--color-text-primary)}
.skill-btn--del:hover{border-color:var(--color-error);color:var(--color-error)}
.skill-toggle{background:none;border:1px solid var(--color-border-strong);border-radius:999px;color:var(--color-text-muted);cursor:pointer;font:600 12px/1 var(--font-sans);padding:7px 13px}
.skill-toggle.is-on{background:var(--color-accent-soft);border-color:var(--color-accent);color:var(--color-accent)}
.skill-editor{display:flex;flex-direction:column;gap:14px}
.skill-field{display:flex;flex-direction:column;gap:6px}
.skill-field>span{font:600 13px/1.3 var(--font-sans);color:var(--color-text-primary)}
.skill-field small{color:var(--color-text-secondary);font:400 12px/1.5 var(--font-sans)}
.skill-field input,.skill-field textarea{background:var(--color-surface);color:var(--color-text-primary);border:1px solid var(--color-border-strong);border-radius:var(--radius-md);font:400 14px/1.6 var(--font-sans);padding:10px 12px;width:100%;resize:vertical}
.skill-field input:focus,.skill-field textarea:focus{outline:none;border-color:var(--color-accent);box-shadow:0 0 0 3px var(--color-accent-ring)}
.skill-always{display:flex;align-items:center;gap:9px;font:500 13.5px/1.3 var(--font-sans);color:var(--color-text-primary);cursor:pointer}
.skill-always input{accent-color:var(--color-accent);width:17px;height:17px;cursor:pointer}
.skill-always-hint{color:var(--color-text-secondary);font:400 12px/1.5 var(--font-sans);margin-top:-8px}
/* The refusal sits with the fields it is about, not in a toast that vanishes while
   you are still reading the rule it is talking about. */
.skill-problems{list-style:none;margin:0;padding:12px 14px;background:rgba(179,38,30,.07);border:1px solid rgba(179,38,30,.28);border-radius:var(--radius-md);display:flex;flex-direction:column;gap:7px}
.skill-problems li{color:var(--color-error);font:500 13px/1.55 var(--font-sans)}
.skill-editor__actions{display:flex;gap:8px}
.skills-set__head{display:flex;align-items:flex-start;gap:11px;margin-bottom:12px}
.skills-set__icon{color:var(--color-accent);display:flex;flex:0 0 auto}
.skills-set__icon svg{width:22px;height:22px}
.skills-set__head h3{margin:0;font:600 15px/1.3 var(--font-sans)}
.skills-set__head p{margin:3px 0 0;font:400 13px/1.5 var(--font-sans);color:var(--color-text-secondary)}
@media (max-width:520px){
  .skill-row{flex-direction:column;align-items:stretch;gap:10px}
  .skill-row__actions{justify-content:flex-end}
}

/* A pasted wall of text, and a skill pinned from the "/" menu, both ride the chip the
   attachment tray already draws. Only the badge differs — it is what tells you at a
   glance that this chip is not a file you went and chose. */
.attach-file__icon{white-space:nowrap}
.attach-file--skill .attach-file__icon{background:var(--color-accent-soft,rgba(217,119,87,.10));color:var(--color-accent,#D97757)}
.attach-file--skill{border-color:var(--color-accent-ring,rgba(217,119,87,.35))}

/* A skill name is a phrase someone wrote, not a one-word command: it has to be able
   to run out of room and say so, instead of wrapping a letter per line. */
.slashm__txt{min-width:0}
.slashm__lb,.slashm__hn{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}

/* The library shelf: sections you walk into, and an origin that travels with an entry
   when it is copied out. */
.skill-row--section { cursor: pointer; text-align: start; }
.skill-row--section:hover { background: var(--color-bg-subtle); }
.skill-row__go { color: var(--color-text-muted); font-size: 20px; line-height: 1; flex: 0 0 auto; }
[dir="rtl"] .skill-row__go { transform: scaleX(-1); }
.skill-origin {
  background: var(--color-bg-subtle);
  border: 1px solid var(--color-border-hairline);
  border-radius: 999px;
  color: var(--color-text-muted);
  font: 600 10.5px/1 var(--font-sans);
  letter-spacing: .02em;
  padding: 3px 7px;
  white-space: nowrap;
}
.skills-back { flex: 0 0 auto; }

/* A section row has no action buttons to wrap, so it must NOT take the narrow-screen
   stacking the editable rows need — stacked, its chevron lands under the title on a
   line of its own and stops reading as "open this". */
@media (max-width: 520px) {
  .skill-row--section { flex-direction: row; align-items: center; }
}

/* -- SKILL TOKENS IN THE COMPOSER --------------------------------------------
   A textarea cannot colour part of its own text, so this backdrop sits exactly
   behind it and paints nothing but the highlight rectangles: the letters on screen
   are still the textarea's own, which is what keeps the caret, selection, IME and
   RTL shaping exactly as they were. Its metrics are copied from the textarea in JS
   (SKILL_MARK_KEYS) rather than restated here, so they cannot drift apart. */
.composer__row { position: relative; }
.composer__marks {
  position: absolute;
  inset: 0;
  z-index: 2;
  margin: 0;
  overflow: hidden;
  pointer-events: none;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: transparent;
  border: 0;
  background: none;
}
.composer__textarea { position: relative; z-index: 1; background: transparent; }
/* The textarea hands its glyphs to the backdrop ONLY while a token is present. The caret
   keeps a colour of its own, and the selection rectangle is still drawn down here, under
   the backdrop's text, so selected text stays readable. */
.composer__row.has-skill-tok .composer__textarea {
  color: transparent;
  caret-color: var(--color-text-primary);
  -webkit-text-fill-color: transparent;
}
.composer__row.has-skill-tok .composer__textarea::selection { color: transparent; }
/* The turquoise is the product accent, at the weight of a highlighter rather than a
   button: it has to read as "this word is special" behind live text without competing
   with the text for contrast. */
/* The token is written in the product's turquoise. Colour, not a rectangle: it is a word
   in the sentence, and a highlight behind it read as a chip pasted into the text. */
.composer__row.has-skill-tok .composer__marks { color: var(--color-text-primary); }
/* COLOUR ONLY. Nothing here may change metrics: the caret, the selection and the line
   wrapping are all computed by the textarea from its own (uncoloured) text, while these
   glyphs are painted on the layer above. A bolder weight made the token wider up here
   than it is down there, so every character after it drifted and the caret stopped
   landing where the letters were. */
.skill-tok { color: var(--color-accent); }

/* -- /prompteng --------------------------------------------------------------
   The apply button stands where send stands, but it is labelled: it does something
   different enough that an arrow would be a lie. */
.pe-apply {
  background: var(--color-accent);
  border: 0;
  border-radius: 999px;
  color: var(--color-on-accent, #fff);
  cursor: pointer;
  font: 600 13px/1 var(--font-sans);
  padding: 11px 16px;
  white-space: nowrap;
  transition: background .15s ease, opacity .15s ease;
}
.pe-apply:hover { background: var(--color-accent-hover); }
.pe-apply:disabled, .pe-apply.is-busy { cursor: default; opacity: .72; }
.pe-apply:focus-visible { outline: 2px solid var(--color-accent-deep); outline-offset: 2px; }

/* The two languages take the apply button's own place. Nothing floats, so nothing can
   be clipped, mis-positioned or pushed off a phone by a keyboard inset. */
.pe-slot { display: inline-flex; align-items: center; gap: 6px; }
.pe-apply--lang { padding: 11px 13px; }


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   سجل التنفيذ — THE EXECUTION LOG (generation 1.1 only)
   Rows that live INSIDE the answer they describe, above its markdown, never in a box beside it.
   The answer is what the reader came for; this is the margin note, and every value here is
   chosen to keep that true - muted text, hairline marks, and no colour louder than the accent.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */
.xlog { --xl-gap: 6px; margin: 2px 0 14px; }

/* The document-generation loader reuses the execution-log rows, but its rows are NOT buttons:
   no pointer, no hover highlight. The live row still shimmers via the shared .xstep[data-s=live]
   rule, so it reads as active without a second mechanism. */
.firas-file-steps .xstep__row { cursor: default; }
.firas-file-steps .xstep__row:hover { color: var(--color-text-muted); background: none; }

/* NO HEADER, AND NO FOLD. This is not a panel that reports on a turn — it is the turn: the model
   talks, reaches for something, talks again, reaches for something else, first to last. A bar
   counting «خطوتان» with a caret to tuck it away described the work from outside, which is the
   arrangement the transcript exists to replace. With nothing foldable, nothing can be hidden, so
   the per-step ticks that existed to keep a failure visible inside a folded log have no job left;
   a failed step shows as a failed row, where it happened. */
.xlog__body { display: grid; gap: var(--xl-gap); padding-block: 4px 2px; }
.xstep { position: relative; animation: xlEnter .16s ease-out both; }
@keyframes xlEnter { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@keyframes xlEnterCalm { from { opacity: 0; } to { opacity: 1; } }
/* MACHINERY, AND IT READS AS MACHINERY. A row used to be the same grey as the model's own
   sentences, so a reader could not tell a thought from an act without reading both. The rows drop
   to the muted tone and the sentences take the page's own ink — measured on both themes: 5.2 and
   5.0 here against 12.6 and 16.5 there, and every one of them above 4.5. */
.xstep__row {
  display: flex; align-items: center; gap: 11px;
  inline-size: 100%; padding: 5px 6px; border: 0; background: none; text-align: start;
  font: inherit; font-size: 13.5px; line-height: 1.55; color: var(--color-text-muted);
  border-radius: var(--radius-md); cursor: pointer;
  transition: color .14s ease, background-color .14s ease;
}
.xstep__row:hover { color: var(--color-text-primary); background: var(--color-accent-soft); }
.xstep__row:focus-visible { outline: none; background: var(--color-accent-soft); color: var(--color-text-primary); }
.xstep__row:focus-visible .xstep__mk { box-shadow: 0 0 0 2px var(--color-accent-ring); }
.xstep__row[disabled] { cursor: default; }
.xstep__row[disabled]:hover { color: var(--color-text-secondary); background: none; }
@media (pointer: coarse) { .xstep__row { min-block-size: 44px; } }

/* THE ICON WELL. It lines the marks into a true column, gives the accent a surface instead of a
   bare stroke floating in prose, and is the one element that can carry state without touching
   the text's metrics - the lesson this codebase learned when a bold highlight on a token shifted
   every glyph after it and the caret stopped landing on the letters. */
.xstep__mk {
  position: relative; inline-size: 26px; block-size: 26px; flex: 0 0 auto;
  display: grid; place-items: center; border-radius: 7px;
  background: color-mix(in srgb, var(--color-accent) 7%, transparent);
  color: var(--color-accent);
  transition: background-color .2s ease, color .2s ease, box-shadow .2s ease;
}
.xstep__mk svg { inline-size: 16px; block-size: 16px; display: block; }
/* The thread between wells, drawn BY the well so it can never drift out of alignment. */
.xstep__mk::after {
  content: ""; position: absolute; inset-block-start: 100%; inset-inline-start: 50%;
  inline-size: 1px; block-size: calc(var(--xl-gap) + 4px); transform: translateX(-50%);
  background: var(--color-border-hairline);
}
.xstep:last-child .xstep__mk::after, .xstep[data-open="true"] .xstep__mk::after { display: none; }
.xstep[data-s="done"] .xstep__mk { background: color-mix(in srgb, var(--color-accent) 5%, transparent); opacity: .85; }
.xstep[data-s="live"] .xstep__mk {
  background: color-mix(in srgb, var(--color-accent) 14%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-accent) 22%, transparent);
}
.xstep[data-s="fail"] .xstep__mk { background: color-mix(in srgb, var(--color-error) 9%, transparent); color: var(--color-error); }

.xstep__txt { flex: 1 1 auto; min-inline-size: 0; overflow-wrap: anywhere; }
/* The brightest thing in the ROW, and still below the model's voice: the page's full ink now
   belongs to what was said, and a file's name must not outshine it. */
.xstep__txt b { font-weight: var(--weight-medium, 500); color: var(--color-text-secondary); }
.xstep__fact { flex: 0 0 auto; font-size: 12px; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.xstep__chev { flex: 0 0 auto; inline-size: 13px; block-size: 13px; opacity: .4; transform: rotate(90deg); transition: transform .18s ease; }
.xstep__row[aria-expanded="true"] .xstep__chev { transform: rotate(-90deg); }

/* THE LIGHT IN THE WORDS. It runs through the glyphs of whatever is working — the row, and the
   SENTENCE the model spoke on its way to that row — in the reading direction, and it goes out the
   moment that particular step ends rather than when the turn does. @supports guards the whole
   thing: without background-clip the declaration would leave `color: transparent` on a visible
   element, which is an invisible sentence, far worse than one that does not shimmer.

   THE LIGHT ONLY EVER ADDS. Each element's base is its own resting colour, so nothing is dimmer
   while it works than when it is done — the band brightens the words it crosses and hands them
   back unchanged. And its core is mixed toward the page's own brightest ink rather than toward
   white, because white is the brightest thing on a dark page and nearly invisible on a light one.

   THE BAND IS NARROWER THAN THE LINE, which is the difference between a light travelling through
   a sentence and a sentence blinking — and the arithmetic is the whole of it. An element shows
   1/S of its background image, so a band B% wide in the image arrives B×S% wide on screen: the
   first attempt spanned 26→74 at S=340, which is 163% of the line, wider than the words it was
   supposed to be crossing. 41→59 is 61% of the line, and the halo-to-halo core inside it is 14%.

   THE TRAVEL IS BOUNDED TO MATCH. The band's centre is on the line between position 29% and 71%
   (solve 0.5S − p(S−1) for the two edges); 6% → 94% is that crossing plus half a band of run-up
   at each end, so it arrives from off the page, crosses every word, leaves, and beats once before
   coming back. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .xsay[data-s="live"] { --xl-base: var(--color-text-primary); }
  .xstep[data-s="live"] .xstep__txt { --xl-base: var(--color-text-muted); }
  :is(.xsay[data-s="live"], .xstep[data-s="live"] .xstep__txt) {
    --xl-mid: color-mix(in srgb, var(--color-accent) 70%, var(--xl-base));
    --xl-halo: var(--color-accent);
    --xl-core: color-mix(in srgb, var(--color-accent) 55%, var(--color-text-primary));
    background-image: linear-gradient(90deg,
      var(--xl-base) 0%, var(--xl-base) 41%,
      var(--xl-mid) 45%, var(--xl-halo) 48%, var(--xl-core) 50%, var(--xl-halo) 52%, var(--xl-mid) 55%,
      var(--xl-base) 59%, var(--xl-base) 100%);
    background-size: 340% 100%;
    -webkit-background-clip: text; background-clip: text; color: transparent;
    animation: var(--xl-sweep, xlSweepRtl) 2.6s linear infinite;
  }
}
/* THE ONE THING HERE THAT REALLY DOES DEPEND ON DIRECTION, carried as an inherited VALUE rather
   than selected by a rule. `[dir="ltr"] .xstep…` matches on any ltr ANCESTOR, and this app's
   <html> is ltr while each Arabic answer is rtl on itself — so both rules matched and source
   order sent the light backwards through every Arabic row. Custom properties inherit, and
   inheritance is nearest-ancestor by definition, so the [dir] closest to the row decides.

   AND THE TWO WERE THE WRONG WAY ROUND. Measured, frame by frame, by sampling the animated value:
   an INCREASING background-position moves the band leftwards. Arabic reads leftwards, so Arabic
   wants the increasing keyframes — it had the decreasing ones, and Latin had the increasing ones,
   so the light ran against the words in both languages at once. The names say which reading they
   follow now, rather than which direction the numbers happen to go. */
:root, [dir="ltr"] { --xl-sweep: xlSweepLtr; }
[dir="rtl"] { --xl-sweep: xlSweepRtl; }
@keyframes xlSweepRtl { from { background-position: 6% 0 }  to { background-position: 94% 0 } }
@keyframes xlSweepLtr { from { background-position: 94% 0 } to { background-position: 6% 0 } }
@keyframes xlBreath { 0%, 100% { opacity: .45 } 50% { opacity: 1 } }

/* ── AND THE SAME LIGHT ON THE ANSWER ITSELF ──────────────────────────────────────────────
   Everything above this line is the execution log. The sweep was never put on the chat, so the
   one surface where a reader actually watches words appear was the one surface without it.

   IT IS THE TRAILING BLOCK ONLY, and that is a performance decision rather than a taste one.
   `background-clip: text` builds a mask from every glyph inside the element, so putting this on
   the whole `.md` would re-mask the entire answer on every frame — a cost that grows with the
   reply, which is exactly what the incremental painter exists to prevent.

   AND THE TRAILING BLOCK IS STABLE, which is the fact that makes this possible. The painter
   rebuilds the tail ~18 times a second, and an animation on a node rebuilt that often never gets
   past its first frame — but reconcileStreamNode KEEPS the existing <p> and appends only the new
   suffix into it, so the element carrying the light survives and the light travels. Its identity
   changes when a paragraph closes, so the sweep restarts once per paragraph, not once per frame.

   THE DIRECTION IS ALREADY SOLVED: --xl-sweep resolves from the nearest [dir] ancestor, which is
   the answer's own body, so Arabic sweeps with its reading and Latin with its own.

   NOTHING THAT OWNS ITS COLOUR IS TOUCHED. A syntax token with no colour of its own would inherit
   `transparent` and disappear — the invisible-sentence hazard named above — so a trailing block
   holding code, a formula, a table or a picture is excluded outright.

   AND BOTH MOTION SWITCHES WRAP THE RULE RATHER THAN UNDO IT, so there is no state in which a
   stopped animation leaves `color: transparent` on a paragraph nobody can read. Where `:has()` or
   `background-clip` is missing the selector simply never matches, which fails to plain text. */
@media (prefers-reduced-motion: no-preference) {
  @supports ((-webkit-background-clip: text) or (background-clip: text)) {
    :root:not([data-motion="off"]) .msg-ai.is-generating .md
      > :is(p, h1, h2, h3, h4, h5, h6, blockquote):last-child:not(:has(pre, code, img, table, .katex, .code-card, .image-card, .video-card, .file-card, .imgload)),
    :root:not([data-motion="off"]) .msg-ai.is-generating .md
      > :is(ul, ol):last-child > li:last-child:not(:has(pre, code, img, table, .katex, .code-card)) {
      --xl-base: var(--color-text-primary);
      --xl-mid: color-mix(in srgb, var(--color-accent) 70%, var(--xl-base));
      --xl-halo: var(--color-accent);
      --xl-core: color-mix(in srgb, var(--color-accent) 55%, var(--color-text-primary));
      background-image: linear-gradient(90deg,
        var(--xl-base) 0%, var(--xl-base) 41%,
        var(--xl-mid) 45%, var(--xl-halo) 48%, var(--xl-core) 50%, var(--xl-halo) 52%, var(--xl-mid) 55%,
        var(--xl-base) 59%, var(--xl-base) 100%);
      background-size: 340% 100%;
      -webkit-background-clip: text; background-clip: text; color: transparent;
      animation: var(--xl-sweep, xlSweepRtl) 2.6s linear infinite;
    }
    /* AND THE BULLET STAYS. ::marker takes its colour by inheritance, so `transparent` above hides
       it — and unlike the text it is not clipped to the gradient, so it does not come back on the
       other side. The list item being written would simply lose its • for as long as it is being
       written. Every computed-style check passed with this broken; only the screenshot showed it. */
    :root:not([data-motion="off"]) .msg-ai.is-generating .md
      > :is(ul, ol):last-child > li:last-child::marker { color: var(--color-text-muted); }
  }
}

.xstep__det {
  display: none; margin: 2px 0 6px; padding: 11px 13px;
  border-inline-start: 2px solid var(--color-border-strong);
  background: var(--color-surface-sunken); border-radius: var(--radius-md);
  font-size: 12.5px; line-height: 1.75; color: var(--color-text-secondary);
}
.xstep[data-open="true"] .xstep__det { display: block; }
.xstep[data-s="fail"] .xstep__det { border-inline-start-color: var(--color-error); }

/* REDUCED MOTION IS NOT NO SIGNAL. This app's own convention (.skeleton, .stream-caret, the
   thread dots) is to REPLACE motion with a calm pulse rather than freeze it, because a frozen
   "working" indicator cannot be told apart from a stalled one - and the readers this setting
   protects are exactly the ones who cannot afford that ambiguity. */
@media (prefers-reduced-motion: reduce) {
  .xstep { animation-name: xlEnterCalm; animation-duration: .2s; }
  :is(.xsay[data-s="live"], .xstep[data-s="live"] .xstep__txt) {
    background-image: none; color: var(--color-accent);
    animation: xlBreath 1.6s ease-in-out infinite;
  }
  .xstep__chev, .xstep__row, .xstep__mk { transition: none; }
}
:root[data-motion="off"] :is(.xsay[data-s="live"], .xstep[data-s="live"] .xstep__txt) {
  background-image: none !important; color: var(--color-accent) !important;
  animation: xlBreath 1.6s ease-in-out infinite !important;
}

/* THE THING THE SENTENCE IS ABOUT — a file's name, a skill's title. It came from the reader, not
   from this app, and a change of ground says so more quietly than a border or an accent would. */
.xstep__name {
  background: var(--color-surface-sunken); border-radius: var(--radius-sm);
  padding: 1px 6px; font-size: 12.5px;
}
/* ON A RUNNING ROW IT GIVES UP ITS GROUND ENTIRELY, and that is not a nicety. The row paints its
   text through a gradient clipped to the glyphs, so the row's own colour is transparent; a child
   that merely INHERITS that colour while keeping an opaque background covers the text its parent
   painted behind it. Measured before this line existed: colour rgba(0,0,0,0) over background
   rgb(31,30,29) — the file name the reader recognises their own document by was a blank space for
   as long as the step ran. Without the chip the name travels with the sentence, and settles back
   into a chip when the step finishes, which is a state change that says something true. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .xstep[data-s="live"] .xstep__txt b {
    color: inherit; background: none; padding-inline: 0; border-radius: 0;
  }
}
/* A SENTENCE THAT IS STILL BEING SPOKEN, tight above the row it is about to introduce. */
.xsay[data-s="live"] { margin-block-end: 2px; }

/* A LINE THE MODEL SPOKE ON ITS WAY TO A STEP — «خلّي أشوف آخر سعر أول». Not a row: no glyph, no
   well, no thread, because a bullet beside it would claim it was an act rather than a thought. Not
   the answer either: it keeps the rows' size and colour rather than the prose measure below. The
   indent clears the column the icons run down, so that column stays unbroken while the reader's
   eye travels it. */
.xsay {
  margin: 2px 0 4px;
  padding-inline-start: 37px;            /* the icon well (26) + its gap (11) */
  font-size: 14.5px;
  line-height: 1.7;
  color: var(--color-text-primary);
  overflow-wrap: anywhere;
}
/* Tight against the row it introduces, looser from the one it follows: a sentence belongs to what
   comes AFTER it, and the spacing is what says so without a word. */
.xsay + .xstep { margin-block-start: 0; }
.xstep + .xsay { margin-block-start: 8px; }
/* A NARRATION RENDERED AS MARKDOWN — a heading, a list, a fenced snippet said on the way to a step —
   carries its OWN class. It used to carry `md`, the answer node's class, and since the log is the
   answer node's first child every `.md` lookup in the turn found the narration instead of the
   answer. So the typography it needs is written here for `.xsay--md`, at the log's measure: a
   heading stays the log's size, paragraphs and lists keep a short breath, code keeps its ground.
   And while live it breathes instead of sweeping: the sweep is a gradient clipped to the glyphs
   with the text made transparent, and a fenced block inside paints its own background over that
   gradient — the code would be invisible for as long as its step ran. */
.xsay--md > :first-child { margin-block-start: 0; }
.xsay--md > :last-child { margin-block-end: 0; }
.xsay--md :is(p, ul, ol, blockquote, table) { margin-block: 0 6px; }
.xsay--md :is(ul, ol) { padding-inline-start: 1.4em; }
.xsay--md :is(h1, h2, h3, h4, h5, h6) { font-size: inherit; line-height: inherit; font-weight: 600; margin-block: 6px 2px; }
.xsay--md pre { margin-block: 4px 8px; padding: 8px 10px; border-radius: var(--radius-sm); background: var(--color-surface-sunken); overflow-x: auto; font: 12.5px/1.55 var(--font-mono); direction: ltr; text-align: left; }
.xsay--md code { font: 12.5px/1.55 var(--font-mono); }
.xsay--md :not(pre) > code { background: var(--color-surface-sunken); border-radius: var(--radius-sm); padding: 1px 5px; }
.xsay--md table { border-collapse: collapse; }
.xsay--md :is(th, td) { border: 1px solid var(--color-border-hairline); padding: 3px 8px; }
/* (0,3,0), because the sweep it must beat is `:is(.xsay[data-s="live"], .xstep[data-s="live"] .xstep__txt)`, and
   an :is() takes the specificity of its MOST specific argument: two classes and an attribute lost to it
   regardless of order, and the fenced code went invisible again. Measured with getComputedStyle. */
.xsay.xsay--md[data-s="live"] { background-image: none; color: var(--color-text-primary); animation: xlBreath 1.6s ease-in-out infinite; }

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   فِراس كود — THE CONVERSATION IS THE WORKSPACE
   Three regions: the files, the conversation, and one deck of surfaces that open beside it.
   The editor moved INTO that deck — it is the same `.cw-ed`, addressed by the same selector by
   all twelve things that mount into it, and only its parent changed. Appended rather than edited
   in place because source order is how this file has always resolved a later decision.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */
.cw-main{grid-template-columns:200px minmax(360px,1.05fr) minmax(0,1fr)}
/* The middle. `min-height:0` on every one of these is load-bearing: without it the default
   `min-height:auto` lets the thread grow the track and the whole workspace scrolls instead. */
.cw-chat{min-width:0;min-height:0;display:flex;flex-direction:column;
  border-inline-end:1px solid var(--color-border-hairline);background:var(--color-bg)}
.cw-chat .cw-thread{flex:1 1 auto;min-height:0}
.cw-chat .cw-ai{border-top:1px solid var(--color-border-hairline)}
/* No inner measure cap: this column IS the measure, and 760px inside 600 is a no-op that only
   confuses the next person to read it. */
.cw-chat .cw-ai__in{max-width:none;margin-inline:0}
/* The editor is a panel in the deck now: it fills it, and it has no edge of its own because the
   deck already has one. `[hidden]` needs saying out loud — `display:flex` would beat it. */
.cw-right>.cw-ed{flex:1 1 auto;min-height:0;border-inline-end:0}
.cw-ed[hidden]{display:none}

@media (max-width:1180px){ .cw-main{grid-template-columns:168px minmax(320px,1fr) minmax(0,1fr)} }
@media (max-width:1000px){
  /* The files rail is the first thing to go: it is the one region whose job the deck and the
     conversation can both do — open a file from the tree, or ask for it. */
  .cw-main{grid-template-columns:minmax(300px,1fr) minmax(0,1fr)}
  .cw-main>.cw-tree{display:none}
}
@media (max-width:860px){
  /* One region at a time, chosen by the bottom nav, and the conversation is one of them rather
     than something buried inside a region that is switched off. */
  .cw-main{grid-template-columns:1fr}
  .cw[data-mob] .cw-main{display:grid!important}
  .cw[data-mob="files"] .cw-chat,.cw[data-mob="code"] .cw-chat,.cw[data-mob="preview"] .cw-chat{display:none}
  .cw[data-mob="ai"] .cw-tree,.cw[data-mob="ai"] .cw-right{display:none}
  .cw[data-mob="ai"] .cw-chat{display:flex}
  .cw[data-mob="ai"] .cw-ai{flex:0 0 auto}
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   فِراس كود — THE FRONT DOOR IS ONE BOX
   Identity, one question, one composer you can type in the moment the screen appears. Everything
   that used to stand in front of it — a name field, a labelled brief, four category chips and a
   paragraph of instructions — is gone, and what is left is sized so the box is unmistakably the
   subject of the screen.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */
.cw-home--v7 .cw-home__inner{max-width:760px}
.cw-home--v7 .cw-home__hero{margin-block-end:22px}
.cw-home--v7 .cw-home__title{font-size:clamp(24px,3.4vw,34px);line-height:1.25;margin:0}
/* The card is a frame around one control, so it carries a border and no shadow: on a dark
   technical surface a hairline separates more precisely than a blur, and it survives six themes
   where a shadow tuned for one of them does not. */
.cw-home.cw-home--v7 .cw-home__card{padding:14px;border:1px solid var(--color-border-strong);border-radius:var(--radius-md);
  background:var(--color-surface);box-shadow:none}
.cw-home.cw-home--v7 .cw-home__card:focus-within{border-color:var(--color-accent)}
.cw-home.cw-home--v7 .cw-home__card .cw-home__desc{inline-size:100%;min-block-size:132px;resize:vertical;border:0;background:none;padding:6px 4px;
  font:400 15px/1.7 var(--font-sans);color:var(--color-text-primary)}
.cw-home.cw-home--v7 .cw-home__card .cw-home__desc:focus{outline:none;border-color:transparent;box-shadow:none}
.cw-home--v7 .cw-home__acts{display:flex;align-items:center;gap:10px;margin-block-start:10px;padding-block-start:10px;
  border-block-start:1px solid var(--color-border-hairline)}
.cw-home--v7 .cw-home__acts-right{margin-inline-start:auto;display:flex;align-items:center;gap:8px}
.cw-home--v7 .cw-attach__btn{display:inline-grid;place-items:center;inline-size:32px;block-size:32px;padding:0;
  border:1px solid var(--color-border-hairline);border-radius:var(--radius-sm);background:none;
  color:var(--color-text-secondary);cursor:pointer}
.cw-home--v7 .cw-attach__btn:hover{color:var(--color-text-primary);background:var(--color-accent-soft)}
/* The icon lost its width the moment its label was removed — it carries no intrinsic size and
   was being flexed to 0. Measured: 0x15. */
.cw-home--v7 .cw-attach__btn svg{inline-size:17px;block-size:17px;flex:0 0 auto;display:block}
/* Examples read as prose, because that is what they are: sentences you can type past. */
.cw-home--v7 .cw-home__egs{display:flex;flex-wrap:wrap;gap:8px;margin-block-start:16px}
.cw-home--v7 .cw-home__egs .cw-chip{max-inline-size:100%;padding:7px 11px;border:1px solid var(--color-border-hairline);
  border-radius:var(--radius-sm);background:none;color:var(--color-text-muted);
  font:400 12.5px/1.5 var(--font-sans);text-align:start;cursor:pointer;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cw-home--v7 .cw-home__egs .cw-chip:hover{color:var(--color-text-primary);border-color:var(--color-border-strong)}
.cw-home--v7 .cw-home__recent{margin-block-start:34px}

/* ═══ رفع إلى GitHub — the panel ════════════════════════════════════════════════════════════
   A popover over the workspace, so it floats and therefore earns a shadow; everything inside it
   is separated by hairlines, like the rest of this product. */
.cw-ghpanel{position:fixed;z-index:60;inline-size:320px;max-inline-size:calc(100vw - 16px);
  display:flex;flex-direction:column;gap:10px;padding:14px;
  border:1px solid var(--color-border-strong);border-radius:var(--radius-md);
  background:var(--color-surface-raised,var(--color-surface));box-shadow:var(--shadow-lg,0 18px 40px rgba(0,0,0,.34))}
.cw-ghpanel .cw-gh__note{margin:0;font:400 12.5px/1.65 var(--font-sans);color:var(--color-text-muted)}
.cw-ghpanel .cw-gh__who{display:flex;align-items:center;gap:8px;padding-block-end:8px;
  border-block-end:1px solid var(--color-border-hairline);font-size:13px;color:var(--color-text-primary)}
.cw-ghpanel .cw-gh__out{margin-inline-start:auto;padding:3px 8px;border:1px solid var(--color-border-hairline);
  border-radius:var(--radius-sm);background:none;color:var(--color-text-muted);font-size:12px;cursor:pointer}
.cw-ghpanel .cw-gh__out:hover{color:var(--color-text-primary)}
.cw-ghpanel .cw-gh__row{display:grid;gap:4px;font:400 12px/1.4 var(--font-sans);color:var(--color-text-muted)}
.cw-ghpanel select,.cw-ghpanel input{inline-size:100%;padding:7px 9px;border:1px solid var(--color-border-hairline);
  border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text-primary);font:inherit;font-size:13px}
.cw-ghpanel select:disabled{opacity:.5}
/* THE ACCENT AS AN EDGE, NOT AS A GROUND. Filling the button with --color-accent needs an ink
   that contrasts against it, and this product has no such token: `--color-accent-ink` is unset in
   all six themes. The hardcoded fallback measured 3.2 on LIGHT, where the accent is a dark teal
   (#237A68) and dark ink on it is unreadable; the other five were fine, which is exactly how a
   one-theme contrast bug ships. A border plus the page's own ink is 10.99-17.43 everywhere, and
   it is the quieter control this product wants anyway. */
.cw-ghpanel .cw-gh__go,.cw-ghpanel .cw-gh__push{padding:9px 12px;border:1px solid var(--color-accent);
  border-radius:var(--radius-sm);background:var(--color-accent-soft);color:var(--color-text-primary);
  font:500 13px var(--font-sans);cursor:pointer}
.cw-ghpanel .cw-gh__go:hover,.cw-ghpanel .cw-gh__push:not(:disabled):hover{background:var(--color-accent);
  color:var(--color-bg)}
.cw-ghpanel .cw-gh__push:disabled{opacity:.45;cursor:default}
.cw-ghpanel .cw-gh__pair{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.cw-ghpanel .cw-gh__say{margin:0;min-block-size:16px;font:400 12px/1.5 var(--font-sans);color:var(--color-text-secondary)}
.cw-gh__label{max-inline-size:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* The folder import, in the file list's own head — the only place in this product where "a file"
   means a project file rather than an attachment. */
.cw-tree__head .cw-imp{margin-inline-start:auto;display:inline-grid;place-items:center;
  inline-size:26px;block-size:26px;padding:0;border:1px solid transparent;border-radius:var(--radius-sm);
  background:none;color:var(--color-text-muted);cursor:pointer}
.cw-tree__head .cw-imp:hover{color:var(--color-text-primary);border-color:var(--color-border-hairline);
  background:var(--color-accent-soft)}
.cw-tree__head .cw-imp:disabled{opacity:.5;cursor:default}
.cw-tree__head .cw-imp svg{inline-size:15px;block-size:15px;display:block}

/* A FOLDED PANEL IS A ZERO-WIDTH TRACK, never a removed node: removing it reloads the preview's
   iframe, loses the editor's cursor and scroll, and empties the console. The seam stays put and
   is the way back — it is already a focusable separator sitting exactly where the panel was. */
.cw-main[data-fold~="tree"] > .cw-tree,
.cw-main[data-fold~="right"] > .cw-right{inline-size:0;min-inline-size:0;overflow:hidden;
  border-inline-end:0;border-inline-start:0;opacity:0;pointer-events:none}
.cw-main[data-fold~="tree"]{grid-template-columns:0 minmax(360px,1.05fr) minmax(0,1fr)}
.cw-main[data-fold~="right"]{grid-template-columns:200px minmax(360px,1fr) 0}
.cw-main[data-fold~="tree"][data-fold~="right"]{grid-template-columns:0 minmax(0,1fr) 0}
/* The seam of a folded panel reads as a handle rather than a divider: a little wider to the
   pointer, and marked, so it does not look like the edge of the window. */
.cw-split[data-folded="1"]{background:var(--color-border-hairline)}
.cw-split[data-folded="1"]:hover,.cw-split[data-folded="1"]:focus-visible{background:var(--color-accent)}

/* IMPORTING A SKILL FROM A LINK — one field and one button, beside «مهارة جديدة». Built from the
   panel's own tokens so it belongs to it; the only layout decision is that the field takes the
   room and the button takes what it needs. */
.skills-import{display:flex;gap:8px;align-items:center;flex:1 1 260px;min-inline-size:0}
.skills-import__in{
  flex:1 1 auto;min-inline-size:0;min-block-size:36px;padding:7px 10px;
  border:1px solid var(--color-border-hairline);border-radius:var(--radius-md);
  background:var(--color-bg);color:var(--color-text-primary);
  font:400 12.5px/1.5 var(--font-mono);
}
.skills-import__in::placeholder{color:var(--color-text-muted);font-family:var(--font-sans)}
.skills-import__in:focus-visible{outline:2px solid var(--color-accent);outline-offset:1px;border-color:var(--color-accent)}
.skills-import__in:disabled{opacity:.6}
.skills-import__go{
  flex:0 0 auto;min-block-size:36px;padding:7px 14px;
  border:1px solid var(--color-border-strong);border-radius:var(--radius-md);
  background:var(--color-surface);color:var(--color-text-primary);
  font:600 12.5px/1.5 var(--font-sans);cursor:pointer;
}
.skills-import__go:hover:not(:disabled){border-color:var(--color-accent);background:var(--color-bg-subtle)}
.skills-import__go:disabled{opacity:.6;cursor:default}
/* On a narrow panel the row wraps under the button rather than squeezing the field to nothing. */
@media (max-width: 560px){ .skills-import{flex:1 1 100%} }

/* ---- Landing: the company line ----
   One quiet sentence closing the landing, carrying the only outbound link
   from the product to the company that makes it. It sits where the
   "لماذا فِراس AI؟" block used to, so the screen still ends on something
   rather than stopping dead after the stats.

   Muted, but a real visible link: a hidden or off-screen one is a spam
   signal, and the point is to be read by people as well as by crawlers. */
.landing__company {
  position: relative; z-index: 1;
  inline-size: 100%; max-inline-size: 980px;
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--color-border-hairline);
  color: var(--color-text-muted);
  font-size: var(--text-xs); line-height: var(--leading-sm);
  text-align: center;
}
.landing__company p { margin: 0; }
.landing__company a {
  color: var(--color-text-primary);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-color: var(--color-border-strong);
}
.landing__company a:hover { text-decoration-color: var(--color-accent); }
/* ══════════════════════════════════════════════════════════════════════════
   FIRAS BRAIN — polish pass (2026-10-11). Additive only.
   Everything below is written against the .fb-* block already in this file:
   the rail's row shape, the ask composer, the empty state, the source rows
   and the loading skeleton. Nothing here changes behaviour — only how
   existing states read. All colours come from tokens, not literals.
   ══════════════════════════════════════════════════════════════════════════ */

/* Rail — a row reads more like a card than a list item when it has its own
   rounded surface. Rounded corners only; the existing hairline stays. */
.fb-src { border-radius: var(--radius-md); margin-block-end: 1px; }
.fb-src .fb-src__ic { border-radius: var(--radius-sm); }
/* Hover: the accent tint deepens on the ICON tile rather than the whole row,
   which keeps the row legible while hinting it is live. */
.fb-src:hover .fb-src__ic { background: var(--fb-accent-line); }
/* Active row: the leading accent edge gets a slightly stronger colour, and
   the row's own surface lifts a step so it holds without the hover. */
.fb-src.is-active { background: var(--color-accent-soft); }
.fb-src.is-active .fb-src__ic { background: var(--color-accent); color: var(--color-on-accent); }

/* Ask composer — subtler, softer, and clearly "one surface you type into". */
.fb-ask__in {
  border-radius: var(--radius-lg);
  border-color: var(--color-border);
  background: var(--color-surface);
  box-shadow:
      0 1px 2px rgba(0,0,0,0.05),
      0 4px 16px color-mix(in srgb, var(--color-text-primary) 9%, transparent);
  transition:
      border-color var(--fb-motion),
      background  var(--fb-motion),
      box-shadow  var(--fb-motion);
}
[data-theme="dark"] .fb-ask__in {
  box-shadow:
      0 1px 2px rgba(0,0,0,0.28),
      0 4px 16px rgba(0,0,0,0.30);
}
.fb-ask__in:focus {
  border-color: var(--color-accent);
  background: var(--color-surface);
  box-shadow:
      0 0 0 3px var(--fb-accent-line),
      0 1px 2px rgba(0,0,0,0.06),
      0 6px 20px color-mix(in srgb, var(--color-accent) 12%, transparent);
}
[data-theme="dark"] .fb-ask__in:focus {
  box-shadow:
      0 0 0 3px var(--fb-accent-line),
      0 1px 2px rgba(0,0,0,0.30),
      0 6px 20px rgba(0,0,0,0.40);
}
/* Send button — same accent, one tone deeper on hover; the stop state keeps its neutral rule. */
.fb-ask__send:hover:not(:disabled) { background: var(--color-accent-hover); }
.fb-ask__send:active:not(:disabled) { background: var(--color-accent); transform: scale(0.96); }

/* Empty state — an icon container with its own soft gradient so the fresh
   reader who opens Brain sees a real surface instead of a small glyph. */
.fb-empty__ic {
  inline-size: 64px; block-size: 64px;
  background:
      radial-gradient(circle at 30% 25%, color-mix(in srgb, var(--color-accent) 30%, transparent), transparent 70%),
      var(--color-accent-soft);
  border-color: var(--fb-accent-line);
  box-shadow: 0 18px 50px color-mix(in srgb, var(--color-accent) 12%, transparent);
}
[data-theme="dark"] .fb-empty__ic {
  box-shadow: 0 18px 50px rgba(0,0,0,0.35);
}
.fb-empty__ic svg { inline-size: 30px; block-size: 30px; }
.fb-empty__title { font-size: 18px; }
.fb-empty__hint { max-inline-size: 40ch; }

/* Source rows in the ANSWER's citation list — softer landing and a clearer
   hover so they read as rows you open, not lines you pass. */
.fb-sourced {
  border-radius: var(--radius-sm);
  padding-block: 10px;
  transition:
      background var(--fb-motion),
      border-inline-start-color var(--fb-motion);
}
.fb-sourced:hover { background: var(--color-accent-soft); }
.fb-sourced__snip { color: var(--color-text-muted); }

/* Matched terms — a small chip row under the answer's source list. Rendered only when the
   server reported them; without the field nothing renders (additive). */
.fb-sourced__terms {
  display: flex; flex-wrap: wrap; gap: 5px;
  padding-block-start: 8px; margin-block-start: 2px;
  direction: ltr;
}
.fb-sourced__term {
  display: inline-flex; align-items: center;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  background: var(--color-bg-subtle);
  border: var(--border-hairline) solid var(--fb-hair);
  color: var(--color-text-muted);
  font: var(--weight-medium) 11px/1.6 var(--font-mono);
  font-variant-numeric: tabular-nums;
  direction: ltr; unicode-bidi: isolate;
  max-inline-size: 24ch;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Skeleton shimmer while .fb-rail__list's first fetch is in flight. */
.fb-rail__list.is-loading { position: relative; }
.fb-rail__list.is-loading::before {
  content: "";
  position: absolute;
  inset-inline: 12px; top: 12px;
  block-size: 58px;
  border-radius: var(--radius-md);
  background: linear-gradient(
      90deg,
      var(--color-bg-subtle) 0%,
      color-mix(in srgb, var(--color-accent) 9%, var(--color-bg-subtle)) 50%,
      var(--color-bg-subtle) 100%);
  background-size: 220% 100%;
  animation: fbSkeleton 1.6s var(--ease-out) infinite;
  pointer-events: none;
  opacity: 0.85;
}
@keyframes fbSkeleton {
  0%   { background-position: 220% 0; }
  100% { background-position: -220% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .fb-rail__list.is-loading::before { animation: none; }
}
