/* ═══════════════════════════════════════════════════════════════════════════════════════════
   مَجْلِس — نظام تصميم معاون
   Majlis — the Moawen design language.
   ═══════════════════════════════════════════════════════════════════════════════════════════

   A majlis is the room where people sit facing each other and talk. That is the whole product:
   a caller and an assistant, in a room, for ninety seconds. Everything below follows from it.

   SIX RULES. They are rules, not preferences — each one is falsifiable by looking at a screen.

   1. صوت داكن، عمل فاتح — VOICE IS DARK, WORK IS LIGHT.
      A call surface (call page, widget, chat) is night: the portrait is the only lit thing and
      the rest recedes, the way a face is the only lit thing in a dark room. A work surface (the
      console) is day: warm ivory, dense, precise, readable for an hour without fatigue. This is
      not a theme toggle the user flips. It is what kind of screen this is.

   2. التدرّج يعني أن هناك محادثة — THE GRADIENT IS SEMANTIC.
      The blue→teal gradient is the mark's two figures meeting. It appears ONLY where audio is
      live: the presence ring, the live badge, the active call row. Never on a heading, never on
      a card, never as decoration. That is what makes it mean something when it does appear.

   3. الشكل من الشعار — GEOMETRY IS DERIVED, NOT CHOSEN.
      The mark is built from circles and stadiums. So: containers use --r-lg, controls use
      --r-md, and anything LIVE is --r-pill. Roundness carries state.

   4. الرقم المقاس والرقم المقدَّر لا يتشابهان — MEASURED AND ESTIMATED NEVER LOOK ALIKE.
      domain/insights.mjs refuses to report sentiment because it would be a model's opinion. The
      interface has to honour that: a counted number is ink, a model's estimate is amber and
      labelled. `.tile` vs `.tile.inferred` is the enforcement.

   5. عربية أصلية، لا مقلوبة — RTL-NATIVE, NOT MIRRORED.
      Logical properties only: no `left`, no `right`, no `margin-left`. Arabic gets its own line
      height (1.85, not 1.5) because the script has ascenders, descenders and vowel marks in the
      same column. Digits in prose are Arabic-Indic; digits in data are Western and tabular, so
      columns of numbers line up — that is the codebase's existing convention, kept.

   6. الحركة تتبع الصوت — MOTION IS DRIVEN BY AUDIO, NEVER BY A TIMER.
      The only looping animation in the product is the presence ring, and it breathes on --level,
      which is the measured audio amplitude. A spinner that spins on its own tells the caller
      nothing. Everything here respects prefers-reduced-motion.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* ── Typeface ──────────────────────────────────────────────────────────────────────────────
   IBM Plex Sans Arabic, self-hosted. It is the «كود المنصات» standard face of the Saudi Digital
   Government Authority, which matters when the tenants are اعتماد and a national centre: the
   product has to look like it belongs beside their own services, not beside a startup. */
@font-face{font-family:'IBM Plex Sans Arabic';src:url('/fonts/IBMPlexSansArabic-Regular.woff2')format('woff2');font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:'IBM Plex Sans Arabic';src:url('/fonts/IBMPlexSansArabic-Medium.woff2')format('woff2');font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:'IBM Plex Sans Arabic';src:url('/fonts/IBMPlexSansArabic-SemiBold.woff2')format('woff2');font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:'IBM Plex Sans Arabic';src:url('/fonts/IBMPlexSansArabic-Bold.woff2')format('woff2');font-weight:700;font-style:normal;font-display:swap}

/* --level is the measured audio amplitude, 0..1. Registering it makes it interpolable, so the
   ring eases between frames instead of stepping. Without @property the fallbacks below still
   work — the ring simply snaps rather than glides. */
@property --level{syntax:'<number>';inherits:true;initial-value:0}
@property --arc{syntax:'<length>';inherits:false;initial-value:4px}

:root{
  /* ── Brand, sampled from the mark ─────────────────────────────────────────────────────── */
  --navy:#0B1E36;      /* the wordmark */
  --indigo:#12318F;
  --blue:#1B47D6;      /* the left figure */
  --azure:#2090E8;
  --cyan:#17BEDC;
  --teal:#16C6B2;      /* the right figure */
  --jade:#0E9E8B;

  /* Rule 2. `to left` because this interface reads right-to-left: the gradient travels the way
     the eye does. A `to right` gradient in an RTL layout reads backwards even when nobody can
     say why. */
  --grad-brand:linear-gradient(to left,var(--indigo),var(--blue) 26%,var(--azure) 50%,var(--cyan) 72%,var(--teal));
  --grad-live:linear-gradient(to left,var(--blue),var(--azure) 45%,var(--teal));

  /* ── Night: the room a call happens in ────────────────────────────────────────────────── */
  --night-900:#04101F;
  --night-800:#081A2E;
  --night-700:#0D2440;
  --night-600:#132F52;
  --night-500:#1C3F6B;
  --on-night:#E9F1F8;
  --on-night-2:#A8BCD0;
  --on-night-3:#88A2BC;   /* 5.9:1 on --night-700. #728CA6 measured 4.48 and failed. */

  /* ── Day: the desk the console sits on ────────────────────────────────────────────────── */
  --paper:#F6F5F0;     /* the warm ivory the logo lockup sits on */
  --surface:#FFFFFF;
  --surface-2:#FBFAF6;
  --sunken:#F1F0EA;
  --line:#E4E2D9;
  --line-2:#CFCCC0;
  --ink:#0B1E36;
  --ink-2:#38495E;
  --muted:#5A6D82;     /* 4.9:1 on --paper, 4.7:1 on --sunken. Measured on the DARKEST
                          surface it is used on, not the lightest — the earlier value passed on
                          paper and failed inside a sunken well. */

  /* ── Semantic ─────────────────────────────────────────────────────────────────────────── */
  --live:#16C6B2;        --live-ink:#07766A;   --live-soft:#E6F8F5;
  /* --warn is the colour of «تقدير نموذج» and «يسري على المكالمة القادمة» — the two labels
     rule 4 exists to make unmissable. It is almost always set ON --warn-soft, so that is the
     pair that has to pass: #9C6A15 measured 4.17 against it, which is a failing contrast on the
     one sentence the rule cares most about. */
  --warn:#7E540F;        --warn-soft:#FAF1DF;  --warn-line:#DCC08A;
  --danger:#AF3A30;      --danger-soft:#FDF1EF;--danger-line:#E7B7B1;
  --info:#1B47D6;        --info-soft:#EDF1FE;

  /* ── Geometry (rule 3) ────────────────────────────────────────────────────────────────── */
  --r-xs:8px; --r-sm:12px; --r-md:18px; --r-lg:26px; --r-xl:34px; --r-pill:999px;

  /* ── Space ────────────────────────────────────────────────────────────────────────────── */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:22px; --s6:30px; --s7:40px; --s8:56px; --s9:76px;

  /* ── Type (rule 5: Arabic leading, not Latin leading) ─────────────────────────────────── */
  --font:'IBM Plex Sans Arabic',system-ui,-apple-system,'Segoe UI',sans-serif;
  --mono:ui-monospace,SFMono-Regular,Menlo,monospace;
  --t-display:clamp(30px,4.2vw,44px);
  --t-title:clamp(21px,2.5vw,28px);
  --t-head:19px; --t-sub:16px; --t-body:15px; --t-cap:13px; --t-micro:11.5px;
  --lh-tight:1.3; --lh-head:1.45; --lh-body:1.85; --lh-cap:1.7;

  /* ── Motion ───────────────────────────────────────────────────────────────────────────── */
  --ease:cubic-bezier(.2,.7,.25,1);
  --ease-both:cubic-bezier(.65,0,.35,1);
  --fast:150ms; --base:260ms; --slow:520ms;

  /* ── Elevation. Warm-tinted, because a neutral-grey shadow on ivory reads as dirt. ─────── */
  --sh1:0 1px 2px #0b1e360a,0 2px 8px #0b1e3608;
  --sh2:0 2px 4px #0b1e360d,0 14px 30px -10px #0b1e3617;
  --sh3:0 24px 64px -22px #0b1e3630;
  --glow:0 0 0 1px #16c6b21f,0 0 34px -6px #16c6b23d;
}

/* ═══ Surface contract ═══════════════════════════════════════════════════════════════════
   Every component below reads --bg/--fg/--dim/--edge/--raise. A surface sets those five, and
   the components follow. That is why one card definition serves both themes and no component
   ever names a theme. */
[data-surface='day'],:root{
  --bg:var(--paper); --bg-2:var(--surface); --bg-3:var(--surface-2); --bg-sunk:var(--sunken);
  --fg:var(--ink); --fg-2:var(--ink-2); --dim:var(--muted);
  --edge:var(--line); --edge-2:var(--line-2); --raise:var(--sh2);
  --accent:var(--live-ink);
}
[data-surface='night']{
  --bg:var(--night-900); --bg-2:var(--night-800); --bg-3:var(--night-700); --bg-sunk:#030B16;
  --fg:var(--on-night); --fg-2:var(--on-night-2); --dim:var(--on-night-3);
  --edge:#ffffff14; --edge-2:#ffffff26; --raise:0 24px 70px -28px #000c;
  --accent:var(--teal);
  color-scheme:dark;
}
/* A surface must PAINT itself, not merely redefine variables.
   `color` is an inherited property: a night panel nested inside a day page redefines --fg for
   its subtree, but its children keep inheriting the day page's computed `color` — dark ink on a
   dark panel, invisible. Declaring colour and background on the surface itself is what makes
   [data-surface] composable, which is the whole point of having two of them. */
[data-surface]{color:var(--fg);background:var(--bg)}

/* ═══ Reset ══════════════════════════════════════════════════════════════════════════════ */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--fg);
  font-family:var(--font);font-size:var(--t-body);line-height:var(--lh-body);
  font-weight:400;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
}
img,svg,canvas,video{display:block;max-width:100%}
/* `hidden` MUST WIN.
 *
 * The UA stylesheet says [hidden]{display:none}, which any class setting `display` outranks —
 * so `<span class="badge" hidden>` stayed on screen, because .badge sets display:inline-flex.
 * The console showed «تغييرات غير محفوظة» over a document with no changes, and the call pages
 * showed a «مباشر» badge before a call started. Every component here sets display, so this has
 * to be stated once, globally, rather than remembered per component. */
[hidden]{display:none!important}
:where(button,input,select,textarea){font:inherit;color:inherit}
:where(a){color:var(--accent);text-underline-offset:3px}
:where(h1,h2,h3,h4){margin:0;line-height:var(--lh-head);font-weight:600;letter-spacing:-.01em}
:where(p){margin:0}
:where(ul,ol){margin:0;padding:0;list-style:none}
:focus-visible{outline:2px solid var(--teal);outline-offset:3px;border-radius:var(--r-xs)}

/* ═══ Type ═══════════════════════════════════════════════════════════════════════════════ */
.display{font-size:var(--t-display);font-weight:700;line-height:var(--lh-tight);letter-spacing:-.02em}
.title{font-size:var(--t-title);font-weight:700;line-height:var(--lh-tight);letter-spacing:-.015em}
.head{font-size:var(--t-head);font-weight:600}
.sub{font-size:var(--t-sub);font-weight:500}
.cap{font-size:var(--t-cap);line-height:var(--lh-cap);color:var(--dim)}
.micro{font-size:var(--t-micro);line-height:1.6;font-weight:500;color:var(--dim)}
/* An eyebrow in Arabic cannot use letter-spacing — it breaks the joins between letters. Weight
   and colour carry it instead. This is the single most common way an Arabic interface is given
   away as a translated Latin one. */
.eyebrow{font-size:var(--t-micro);font-weight:600;color:var(--accent)}
.num{font-variant-numeric:tabular-nums;font-feature-settings:'tnum';direction:ltr;unicode-bidi:isolate}
.mono{font-family:var(--mono);direction:ltr;unicode-bidi:isolate;text-align:start}
/* A number WITH ITS UNIT. `840 ms` inside an Arabic sentence reorders into `ms 840` unless the
   pair is isolated together — isolating only the digits is the mistake that produces it, and it
   is the single most common bidi bug in an Arabic dashboard. */
.unit{direction:ltr;unicode-bidi:isolate;font-variant-numeric:tabular-nums;white-space:nowrap}

/* ═══ Layout ═════════════════════════════════════════════════════════════════════════════ */
.shell{min-height:100dvh;display:flex;flex-direction:column}
.wrap{width:100%;max-width:1160px;margin-inline:auto;padding-inline:var(--s5)}
.wrap-narrow{max-width:760px}
.stack{display:flex;flex-direction:column}
.stack-2{gap:var(--s2)}.stack-3{gap:var(--s3)}.stack-4{gap:var(--s4)}
.stack-5{gap:var(--s5)}.stack-6{gap:var(--s6)}.stack-7{gap:var(--s7)}
.row{display:flex;align-items:center;gap:var(--s3)}
.row-wrap{flex-wrap:wrap}
.push{margin-inline-start:auto}

/* ═══ Topbar ═════════════════════════════════════════════════════════════════════════════
   A hairline, not a slab. The header of a voice product should not compete with the voice. */
.topbar{
  position:sticky;top:0;z-index:20;
  background:color-mix(in srgb,var(--bg) 86%,transparent);
  backdrop-filter:saturate(1.6) blur(14px);
  border-block-end:1px solid var(--edge);
}
.topbar-in{display:flex;align-items:center;gap:var(--s4);min-height:62px}
.brand{display:flex;align-items:center;gap:var(--s3);text-decoration:none;color:inherit}
.brand svg,.brand img{width:30px;height:auto;flex:none}
.brand b{font-size:var(--t-sub);font-weight:700;letter-spacing:-.01em}
.brand small{display:block;font-size:var(--t-micro);color:var(--dim);font-weight:400;margin-block-start:1px}

/* ═══ Surfaces ═══════════════════════════════════════════════════════════════════════════ */
.card{
  background:var(--bg-2);border:1px solid var(--edge);border-radius:var(--r-lg);
  padding:var(--s5);
}
.card-quiet{background:transparent;border-color:var(--edge)}
.card-sunk{background:var(--bg-sunk);border-color:transparent}
.card-raised{box-shadow:var(--raise)}
.card > header{display:flex;align-items:center;gap:var(--s3);margin-block-end:var(--s3)}
.card > header h2,.card > header h3{font-size:var(--t-head);margin:0}
.divide{border:0;border-block-start:1px solid var(--edge);margin-block:var(--s4)}

/* ═══ Presence ring — «نَفَس» ════════════════════════════════════════════════════════════
   THE SIGNATURE. Two arcs, one per figure in the mark: the blue arc is the caller, the teal arc
   is the assistant. They are the logo, animated by the actual audio.

   It replaces two things the product had: a green «٧» orb that meant nothing, and a three-step
   «journey» list that told the caller about LiveKit. A caller does not need to know the name of
   the transport. They need to know one thing — whose turn it is — and that is the only thing
   this draws.

       idle        both arcs at rest, apart
       connecting  arcs sweep toward each other
       listening   the caller arc lifts and breathes on the microphone level
       thinking    a sweep travels the ring — the knowledge base is being searched
       speaking    the assistant arc lifts and breathes on the output level
       ended       the two arcs close into one thin ring

   `--level` is set by whatever is already measuring audio (AvatarAudioMotion). If nothing sets
   it, it stays 0 and the ring is calm — a broken meter must never look like a live call. */
.nafas{position:relative;display:grid;place-items:center;aspect-ratio:1;width:100%;max-width:340px;margin-inline:auto;--level:0}
.nafas-arc{
  position:absolute;inset:0;border-radius:50%;--arc:5px;
  -webkit-mask:radial-gradient(farthest-side,#0000 calc(100% - var(--arc)),#000 calc(100% - var(--arc)));
          mask:radial-gradient(farthest-side,#0000 calc(100% - var(--arc)),#000 calc(100% - var(--arc)));
  opacity:.3;transition:opacity var(--base) var(--ease),transform var(--base) var(--ease),--arc var(--base) var(--ease);
}
/* Caller: the lower arc, in the mark's blue. Assistant: the upper arc, in its teal. They sit
   opposite each other, facing — which is the majlis, drawn. */
.nafas-caller{background:conic-gradient(from 128deg,#0000 0 2%,var(--blue) 12%,var(--azure) 30%,#0000 44% 100%)}
.nafas-agent{background:conic-gradient(from 308deg,#0000 0 2%,var(--teal) 12%,var(--cyan) 30%,#0000 44% 100%)}
.nafas-sweep{
  position:absolute;inset:0;border-radius:50%;--arc:3px;opacity:0;
  background:conic-gradient(#0000 0 68%,var(--cyan) 86%,var(--teal) 96%,#0000 100%);
  -webkit-mask:radial-gradient(farthest-side,#0000 calc(100% - var(--arc)),#000 calc(100% - var(--arc)));
          mask:radial-gradient(farthest-side,#0000 calc(100% - var(--arc)),#000 calc(100% - var(--arc)));
  transition:opacity var(--base) var(--ease);
}
/* The lit floor under the portrait. On night it is the only light source in the frame. */
.nafas-core{
  position:relative;width:82%;aspect-ratio:1;border-radius:50%;overflow:hidden;
  display:grid;place-items:center;
  background:radial-gradient(120% 100% at 50% 8%,var(--bg-3),var(--bg-sunk));
  box-shadow:inset 0 0 0 1px var(--edge);
}
.nafas-core > img,.nafas-core > saudi-avatar,.nafas-core > canvas{width:100%;height:100%;object-fit:cover;object-position:50% 12%}
/* The approved portraits are rendered on a near-white card. Dropped straight onto the night
   surface that reads as a sticker: a bright disc with a hard edge. The vignette lets the
   shoulders fall away into the dark so the face is LIT rather than pasted — which is the whole
   claim rule 1 makes about a night surface. */
[data-surface='night'] .nafas-core::after{
  content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(112% 92% at 50% 24%,#0000 46%,#04101f4d 76%,#04101fcc 100%);
}

[data-call='connecting'] .nafas-caller,[data-call='connecting'] .nafas-agent{opacity:.75;animation:nafas-converge 1.6s var(--ease-both) infinite alternate}
[data-call='listening'] .nafas-caller{opacity:1;--arc:9px;transform:scale(calc(1 + var(--level) * .05))}
[data-call='listening'] .nafas-agent{opacity:.18}
[data-call='speaking'] .nafas-agent{opacity:1;--arc:9px;transform:scale(calc(1 + var(--level) * .05))}
[data-call='speaking'] .nafas-caller{opacity:.18}
[data-call='thinking'] .nafas-caller,[data-call='thinking'] .nafas-agent{opacity:.28}
[data-call='thinking'] .nafas-sweep{opacity:1;animation:nafas-spin 1.5s linear infinite}
[data-call='ended'] .nafas-arc{opacity:.16;--arc:2px;transform:none;animation:none}
[data-call='speaking'] .nafas-core,[data-call='listening'] .nafas-core{box-shadow:inset 0 0 0 1px var(--edge-2),var(--glow)}

@keyframes nafas-spin{to{transform:rotate(1turn)}}
@keyframes nafas-converge{from{transform:rotate(-7deg)}to{transform:rotate(7deg)}}

/* The same idea at 10px, for a table row or a topbar: whether this one is live. */
.pip{width:9px;height:9px;border-radius:50%;background:var(--edge-2);flex:none}
.pip-live{background:var(--grad-live);box-shadow:0 0 0 3px #16c6b224}
.pip-warn{background:var(--warn)}
.pip-off{background:var(--dim);opacity:.5}

/* ═══ Controls ═══════════════════════════════════════════════════════════════════════════
   Rule 3: a control is --r-md; a control that starts or ends a live thing is --r-pill. */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
  padding:11px var(--s5);border-radius:var(--r-md);border:1px solid var(--edge-2);
  background:var(--bg-2);color:var(--fg);font-size:var(--t-body);font-weight:500;
  cursor:pointer;transition:background var(--fast) var(--ease),border-color var(--fast) var(--ease),transform var(--fast) var(--ease);
  text-decoration:none;
}
.btn:hover{background:var(--bg-3);border-color:var(--dim)}
.btn:active{transform:translateY(1px)}
.btn[disabled],.btn:disabled{opacity:.4;cursor:not-allowed;transform:none}

.btn-primary{background:var(--navy);border-color:var(--navy);color:#fff;font-weight:600}
.btn-primary:hover{background:#13304f;border-color:#13304f}
[data-surface='night'] .btn-primary{background:var(--on-night);border-color:var(--on-night);color:var(--navy)}
[data-surface='night'] .btn-primary:hover{background:#fff;border-color:#fff}

/* The one button that starts a conversation. It gets the gradient (rule 2) and the pill
   (rule 3), and nothing else in the product is allowed to look like it. */
.btn-call{
  border-radius:var(--r-pill);border:0;color:#fff;font-weight:700;font-size:var(--t-sub);
  padding:15px var(--s7);background:var(--grad-live);
  box-shadow:0 10px 30px -10px #1b47d685,0 2px 0 0 #ffffff26 inset;
}
.btn-call:hover{filter:brightness(1.07)}
.btn-end{border-radius:var(--r-pill);background:var(--danger-soft);border-color:var(--danger-line);color:var(--danger);font-weight:600}
[data-surface='night'] .btn-end{background:#4a1a15;border-color:#7a2b23;color:#ffc9c1}
.btn-round{border-radius:var(--r-pill);padding-inline:var(--s5)}
.btn-ghost{background:transparent;border-color:var(--edge)}
.btn-sm{padding:7px var(--s4);font-size:var(--t-cap);border-radius:var(--r-sm)}
.btn-icon{padding:9px;border-radius:var(--r-pill);width:38px;height:38px}

/* ═══ Fields ═════════════════════════════════════════════════════════════════════════════
   Label above, description under the label, control below, bound under the control. The console
   today puts the label in a 220px column beside the control, which in Arabic means a ragged
   left edge of wrapped labels against a straight right edge of inputs. Stacked reads better and
   survives translation of the label into a longer phrase. */
.field{display:grid;gap:var(--s2);padding-block:var(--s4);border-block-start:1px solid var(--edge)}
.field:first-of-type{border-block-start:0;padding-block-start:0}
.field > label{font-size:var(--t-cap);font-weight:600;color:var(--fg)}
.field .desc{display:block;font-weight:400;color:var(--dim);font-size:var(--t-micro);margin-block-start:2px;line-height:1.7}
.field .bound{font-size:var(--t-micro);color:var(--dim)}
.field .error{color:var(--danger);font-size:var(--t-micro);font-weight:600}
.input,.field input[type=text],.field input[type=password],.field input[type=number],.field select,.field textarea,select.input,textarea.input{
  width:100%;padding:10px var(--s3);border:1px solid var(--edge-2);border-radius:var(--r-sm);
  background:var(--bg-2);color:var(--fg);font-size:var(--t-body);
  transition:border-color var(--fast) var(--ease),box-shadow var(--fast) var(--ease);
}
.input:hover,.field select:hover{border-color:var(--dim)}
.input:focus,.field input:focus,.field select:focus,.field textarea:focus{
  outline:0;border-color:var(--teal);box-shadow:0 0 0 3px #16c6b229;
}
textarea.input,.field textarea{min-height:96px;resize:vertical;line-height:var(--lh-body)}
.field.invalid .input,.field.invalid input,.field.invalid select{border-color:var(--danger);box-shadow:0 0 0 3px #af3a3020}

/* A toggle that reads as a toggle, and whose ON state is the brand gradient — because ON here
   almost always means "a caller will experience this". */
.switch{display:inline-flex;align-items:center;gap:var(--s3);cursor:pointer;font-size:var(--t-body)}
/* Hide the native input ONLY when the markup supplies its own .track to draw instead.
   admin.js writes `<label class="switch"><input type="checkbox">مفعّلة</label>` with no track and
   styles the input itself — an unconditional rule here made every toggle in the console
   transparent while still occupying 44×26px, so the console rendered a row of live settings with
   no visible controls at all. A component may only hide something it also replaces. */
.switch:has(.track) input{position:absolute;opacity:0;width:0;height:0}
.switch .track{
  width:46px;height:27px;border-radius:var(--r-pill);background:var(--edge-2);flex:none;
  position:relative;transition:background var(--base) var(--ease);
}
.switch .track::after{
  content:'';position:absolute;inset-block-start:3px;inset-inline-start:3px;
  width:21px;height:21px;border-radius:50%;background:#fff;box-shadow:var(--sh1);
  transition:translate var(--base) var(--ease);
}
.switch input:checked + .track{background:var(--grad-live)}
.switch input:checked + .track::after{translate:19px 0}
[dir=rtl] .switch input:checked + .track::after{translate:-19px 0}
.switch input:focus-visible + .track{outline:2px solid var(--teal);outline-offset:3px}

/* ═══ Chips & tabs ═══════════════════════════════════════════════════════════════════════ */
.chips{display:flex;gap:var(--s2);flex-wrap:wrap}
.chip{
  padding:8px var(--s4);border-radius:var(--r-pill);border:1px solid var(--edge);
  background:var(--bg-2);color:var(--fg-2);font-size:var(--t-cap);font-weight:500;cursor:pointer;
  transition:all var(--fast) var(--ease);
}
.chip:hover{border-color:var(--dim);color:var(--fg)}
.chip[aria-selected=true],.chip.is-on{background:var(--navy);border-color:var(--navy);color:#fff;font-weight:600}
[data-surface='night'] .chip[aria-selected=true]{background:var(--on-night);border-color:var(--on-night);color:var(--navy)}

.badge{
  display:inline-flex;align-items:center;gap:6px;padding:4px 11px;border-radius:var(--r-pill);
  font-size:var(--t-micro);font-weight:600;background:var(--bg-3);color:var(--dim);
  border:1px solid var(--edge);
}
.badge-live{background:var(--live-soft);color:var(--live-ink);border-color:#bfece4}
[data-surface='night'] .badge-live{background:#0c3b36;color:#7ff0df;border-color:#12564e}
.badge-warn{background:var(--warn-soft);color:var(--warn);border-color:var(--warn-line)}
.badge-danger{background:var(--danger-soft);color:var(--danger);border-color:var(--danger-line)}

/* «يسري على المكالمة القادمة» — a settings change never interrupts a call in progress, because
   every call takes its snapshot at the start. The interface has to say so where it is true. */
.nextcall{
  display:inline-flex;align-items:center;gap:5px;font-size:var(--t-micro);font-weight:600;
  color:var(--warn);background:var(--warn-soft);border:1px solid var(--warn-line);
  border-radius:var(--r-xs);padding:2px 7px;
}

/* ═══ Conversation ═══════════════════════════════════════════════════════════════════════
   A transcript is not a chat. Nobody types here — this is a record of speech, and it should
   read like one: the speaker named once, the words given room, and the caller's own words
   given the caller's colour. */
/* `overflow` is NOT declared here. A transcript inside a fixed-height rail wants its own
   scrollbar; the same transcript inside a panel that already scrolls wants none, and nesting the
   two gives a scrollbar inside a scrollbar. The container decides — see .rail .transcript. */
.transcript{display:flex;flex-direction:column;gap:var(--s4);overscroll-behavior:contain;min-height:0}
.msg{display:grid;gap:5px;max-width:82%;animation:msg-in var(--base) var(--ease) both}
.msg .who{font-size:var(--t-micro);font-weight:600;color:var(--dim)}
.msg .said{
  padding:var(--s3) var(--s4);border-radius:var(--r-md);background:var(--bg-3);
  border:1px solid var(--edge);line-height:var(--lh-body);
}
/* The caller sits on the reading edge; the assistant answers from the other side. Two people,
   facing. Corner radii are cut on the side each speaker sits on, so the shapes point at
   each other. */
.msg-user{margin-inline-end:auto}
.msg-user .said{border-start-end-radius:var(--r-xs);background:var(--info-soft);border-color:#d5ddfb}
[data-surface='night'] .msg-user .said{background:#122b55;border-color:#1d3f75}
.msg-agent{margin-inline-start:auto;text-align:start}
.msg-agent .who{color:var(--accent)}
.msg-agent .said{border-start-start-radius:var(--r-xs)}
@keyframes msg-in{from{opacity:0;translate:0 8px}to{opacity:1;translate:0 0}}

/* ═══ Evidence ═══════════════════════════════════════════════════════════════════════════
   The retrieved answer, verbatim, before the assistant paraphrased it. This is the product's
   integrity feature and it was buried at the bottom of a card; it is a first-class surface. */
.evidence{border:1px solid var(--edge);border-radius:var(--r-md);background:var(--bg-2);overflow:hidden}
.evidence > header{display:flex;align-items:center;gap:var(--s2);padding:var(--s3) var(--s4);background:var(--bg-3);border-block-end:1px solid var(--edge)}
.evidence .q{display:block;font-size:var(--t-micro);font-weight:600;color:var(--dim);padding:var(--s3) var(--s4) 0;white-space:pre-wrap;overflow-wrap:anywhere}
.evidence .a{padding:var(--s2) var(--s4) var(--s4);line-height:var(--lh-body);white-space:pre-wrap;overflow-wrap:anywhere}
.evidence pre{margin:0;padding:var(--s3);background:var(--bg-sunk);border-radius:var(--r-xs);font:12px/1.7 var(--mono);direction:ltr;text-align:start;white-space:pre-wrap;overflow-wrap:anywhere}

/* ═══ Data ═══════════════════════════════════════════════════════════════════════════════ */
.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:var(--s3)}
.tile{
  background:var(--bg-2);border:1px solid var(--edge);border-radius:var(--r-md);
  padding:var(--s4);display:grid;gap:3px;align-content:start;
}
.tile-title{font-size:var(--t-cap);color:var(--dim);font-weight:500}
.tile-value{font-size:31px;font-weight:700;line-height:1.15;letter-spacing:-.02em;font-variant-numeric:tabular-nums;color:var(--fg)}
.tile-note{font-size:var(--t-micro);color:var(--dim);line-height:1.6}
/* Rule 4, enforced. An estimate does not get to look like a measurement: different colour,
   a hatched edge you can see from across the room, and the word «تقدير» attached to the title
   by the stylesheet so nobody can forget to write it. */
.tile.inferred{background:var(--warn-soft);border-color:var(--warn-line)}
.tile.inferred .tile-value{color:var(--warn)}
.tile.inferred .tile-title::after{content:' · تقدير نموذج';color:var(--warn);font-weight:600}
.tile.inferred::before{
  content:'';position:absolute;inset-block:0;inset-inline-start:0;width:4px;
  background:repeating-linear-gradient(135deg,var(--warn) 0 3px,transparent 3px 7px);
}
.tile.inferred{position:relative;overflow:hidden}

table.data{width:100%;border-collapse:collapse;font-size:var(--t-cap)}
table.data th{
  text-align:start;font-weight:600;color:var(--dim);font-size:var(--t-micro);
  padding:var(--s2) var(--s3);border-block-end:1px solid var(--edge-2);white-space:nowrap;
}
table.data td{padding:var(--s3);border-block-end:1px solid var(--edge);vertical-align:middle}
table.data tbody tr{transition:background var(--fast) var(--ease)}
table.data tbody tr[tabindex]:hover,table.data tbody tr[tabindex]:focus-visible{background:var(--bg-3);cursor:pointer;outline:none}
table.data td .num,table.data td.num{font-variant-numeric:tabular-nums}

/* A meter that belongs in a table cell: the shape of a distribution without a chart library.
 *
 * Named `.meter`, not `.bar`. It WAS `.bar`, and admin.css calls its sticky action bar `.bar`
 * too — so a 6px data meter's height silently clamped the console's save bar to 26px and its
 * buttons hung out of the bottom of it. A generic name in a shared stylesheet is a landmine for
 * every page that loads it; this one says what it is. */
.meter{height:6px;border-radius:var(--r-pill);background:var(--bg-sunk);overflow:hidden}
.meter > i{display:block;height:100%;border-radius:inherit;background:var(--grad-live)}

/* ═══ Sticky action bar ══════════════════════════════════════════════════════════════════
   It states WHAT changed, not just that something did. diffSettings already computes the list;
   showing the count is the difference between "I pressed save" and "I know what I saved". */
.actionbar{
  position:sticky;inset-block-end:0;z-index:10;
  display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;
  padding:var(--s3) var(--s5);background:color-mix(in srgb,var(--bg-2) 92%,transparent);
  backdrop-filter:blur(12px);border-block-start:1px solid var(--edge);
  border-end-start-radius:var(--r-lg);border-end-end-radius:var(--r-lg);
  margin:calc(var(--s5) * -1);margin-block-start:var(--s5);
}

/* ═══ Banners ════════════════════════════════════════════════════════════════════════════ */
/* BLOCK, not flex. A flex container turns every inline child into a flex item, so a <b> inside
   a sentence becomes its own column and the sentence falls apart — which is exactly what
   happened the first time this was written with `display:flex`. */
.note{display:block;padding:var(--s3) var(--s4);border-radius:var(--r-md);border:1px solid var(--edge);background:var(--bg-3);font-size:var(--t-cap);line-height:var(--lh-cap)}
.note b,.note strong{font-weight:700}
.note-warn{background:var(--warn-soft);border-color:var(--warn-line);color:var(--warn)}
.note-danger{background:var(--danger-soft);border-color:var(--danger-line);color:var(--danger)}
.note-live{background:var(--live-soft);border-color:#bfece4;color:var(--live-ink)}

/* ═══ Empty & loading ════════════════════════════════════════════════════════════════════ */
.empty{display:grid;gap:var(--s2);justify-items:center;text-align:center;padding:var(--s7) var(--s4);color:var(--dim);font-size:var(--t-cap)}
.skeleton{background:linear-gradient(90deg,var(--bg-3) 25%,var(--bg-sunk) 50%,var(--bg-3) 75%);background-size:200% 100%;animation:sk 1.4s linear infinite;border-radius:var(--r-xs)}
@keyframes sk{to{background-position:-200% 0}}

/* On a night surface the platform scrollbar is a bright slab across the darkest thing on the
   page — the one place the browser's default actively fights the design. */
[data-surface='night']{scrollbar-width:thin;scrollbar-color:#ffffff26 transparent}
[data-surface='night'] ::-webkit-scrollbar{width:8px;height:8px}
[data-surface='night'] ::-webkit-scrollbar-thumb{background:#ffffff26;border-radius:var(--r-pill)}
[data-surface='night'] ::-webkit-scrollbar-track{background:transparent}

/* ═══ Utilities ══════════════════════════════════════════════════════════════════════════ */
.center{text-align:center}
.dim{color:var(--dim)}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
.offstage{position:absolute;inset-block-start:-9999px;inset-inline-start:-9999px;width:1px;height:1px;overflow:hidden}

/* ═══ Reduced motion (rule 6) ════════════════════════════════════════════════════════════
   The ring still CHANGES between states — it just stops breathing. A caller who cannot see
   motion must still be able to tell whose turn it is, so state is carried by opacity and
   thickness, both of which survive this block. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}
  .nafas-sweep{opacity:1;animation:none}
  [data-call='thinking'] .nafas-sweep{border:2px dashed var(--teal);background:none;-webkit-mask:none;mask:none}
}

/* ═══ Narrow ═════════════════════════════════════════════════════════════════════════════ */
@media (max-width:760px){
  .wrap{padding-inline:var(--s4)}
  .nafas{max-width:270px}
  .msg{max-width:92%}
  .actionbar{margin-inline:calc(var(--s4) * -1)}
}
