/* ═══════════════════════════════════════════════════════════════════════════
   KATIB — THE PORTAL'S SHARED DESIGN LANGUAGE                      Phase 27
   ═══════════════════════════════════════════════════════════════════════════

   WHY THIS FILE EXISTS
   Every page carries its own <style> block — between 150 and 1,180 lines of
   it — and they were written months apart against four different ideas of
   what the portal looks like. The result is a product where /audio, /files
   and /quran-ikhtebar each solved "a header with a nav in it" separately, and
   where a tester moving between two pages of the same portal can tell they
   were built by different hands. Phase 12 fixed exactly this for LAYOUT by
   introducing responsive.css. This is the same move for APPEARANCE.

   HOW IT COMPOSES — three sheets, in this order, on every page:

       inline <style>   →  the page's own components (untouched)
       katib.css        →  THE LANGUAGE: tokens, motion, chrome, primitives
       responsive.css   →  layout at every width (still last, still wins)

   Linked AFTER the inline block so its token block wins at equal specificity,
   and BEFORE responsive.css so the layout layer keeps the last word. Anything
   here that fights responsive.css is a bug in this file, not in that one.

   THE LEVER IS THE TOKEN NAMES, NOT THE RULES. index.html, manzoori.html,
   audio.html, files.html, quran-ikhtebar.html and login.html all draw their
   colours from the SAME custom-property names (--bg-page, --gold-500,
   --radius-md, --shadow-sm …). So redefining those names here re-skins six
   pages and every component on them at once, without editing a single one of
   their stylesheets. That is the whole reason this is survivable: the pages
   opt in by linking one file, and nothing else about them changes.

   WHAT THIS DELIBERATELY DOES NOT TOUCH
     · --font-lsd. Kanz-al-Marjaan is the LSD face; invariant #9 and
       test_the_lsd_font_token_is_untouched both say so.
     · --font-ui-lsd's ORDER. Kanz must stay ahead of every generic or the
       declaration silently does nothing (invariant #9, mutation-checked).
     · --font-quran. Amiri Quran is its own token and never an alias.
     · .seg-control's existence. It is the only way off a page and is never
       display:none at any width (pinned since Phase 12). It is RESTYLED into
       a pill nav here, never replaced — a rename would have taken the pin
       with it.
     · admin.css / style.css. /admin and /dashboard run the LeedAB paper-ink
       system and are the last pages in the rollout, not the first.

   ROUNDED CORNERS AND SOFT SHADOWS ARE NOW CORRECT. DESIGN_SYSTEM.md's
   "no rounded corners, no drop shadows except offset" describes the LeedAB
   marketing system and governed this portal until 2026-09-04, when Mohammed
   chose a soft, airy direction for the tester-facing pages. Invariant #8 was
   amended in the same edit that added this file. If you are reading this
   because a review flagged a radius: it is deliberate, and the amendment is
   in CLAUDE.md.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ───────────────────────────────────────────────────────────────────────────
   1 · TOKENS — the re-skin. Redefining these reaches every page at once.
   ─────────────────────────────────────────────────────────────────────────── */

:root {
  /* Grounds. Warmer and lower-contrast than the old paper: the page is a
     surface things float ON, so the panels are what read as white, not the
     background. */
  --bg-page:  #F4F1EB;
  --bg-white: #FFFFFF;
  --bg-cream: #FAF8F4;
  --bg-warm:  #F0ECE4;

  /* Ink. Three steps and a fourth for the barely-there. More brown than the
     old near-black, which read as cold against amber. */
  --text-primary:   #221A12;
  --text-secondary: #6A5B49;
  --text-muted:     #9C8D7B;
  --text-light:     #B6A896;

  /* Lines. One hairline and one that can hold an edge on its own. */
  --border-light:  #E9E2D6;
  --border-medium: #DBD1C1;

  /* AMBER IS THE ONLY ACCENT, and it does three jobs at three weights:
     -600 for text on a light ground (it is the only step that passes
     contrast at body size), -500 for edges and marks, -400/-300 for fills
     and gradients, -100 for the tint behind a selected thing. */
  --gold-600: #8A6117;
  --gold-500: #C08A2E;
  --gold-400: #E0AF55;
  --gold-300: #ECC77F;
  --gold-200: #F5E3BE;
  --gold-100: #FBF1DC;

  /* The brown ramp survives because the logo mark, the active nav pill and
     the LSD bubbles are built from it. Warmed a step so it sits with amber
     rather than under it. */
  --brown-900: #221A12;
  --brown-800: #2F231A;
  --brown-700: #453425;
  --brown-600: #5E4834;
  --brown-500: #7C6146;
  --brown-400: #9C7D5C;
  --brown-300: #BC9E7A;
  --brown-200: #D9C2A2;
  --brown-100: #EFE1CC;
  --brown-50:  #FAF5ED;

  /* SOFT, NOT SHARP. Every radius went up and the scale kept its shape, so a
     page that asked for --radius-sm still gets the smallest one. */
  --radius-sm:   12px;
  --radius-md:   16px;
  --radius-lg:   22px;
  --radius-xl:   28px;
  --radius-full: 999px;

  /* Shadows are DEPTH, not decoration: a tight contact shadow plus a wide,
     very soft ambient one. The old three were a single blur at three sizes,
     which is what made every card look stamped on. */
  --shadow-sm: 0 1px 2px rgba(34, 26, 18, .04), 0 12px 30px -22px rgba(34, 26, 18, .5);
  --shadow-md: 0 2px 4px rgba(34, 26, 18, .05), 0 20px 40px -26px rgba(34, 26, 18, .52);
  --shadow-lg: 0 3px 6px rgba(34, 26, 18, .06), 0 34px 60px -30px rgba(34, 26, 18, .58);

  /* Status colours keep their meaning and lose their fluorescence. */
  --red:   #C0503C;
  --green: #2F8F5B;

  /* MOTION IS A TOKEN SET, so "good animation" is one decision made once
     rather than a number typed at forty call sites. The easing is a strong
     ease-out: things arrive quickly and settle, which reads as responsive
     rather than sluggish. Nothing in this portal eases IN — an element that
     starts slowly reads as lag. */
  --k-ease:     cubic-bezier(.32, .72, 0, 1);
  --k-ease-pop: cubic-bezier(.34, 1.4, .5, 1);
  --k-fast:     .16s;
  --k-base:     .26s;
  --k-slow:     .42s;
  --transition: var(--k-fast) var(--k-ease);

  /* The amber glow behind a focal point. Two stops so it can be dropped into
     a radial-gradient anywhere without restating the colour. */
  --k-glow:     rgba(224, 175, 85, .40);
  --k-glow-out: rgba(224, 175, 85, 0);
}


/* ───────────────────────────────────────────────────────────────────────────
   2 · MOTION — one reveal, one guard.

   REDUCED MOTION IS HANDLED ONCE, HERE, AT THE END OF THE FILE (§ 8) rather
   than beside each animation. Every honest attempt to remember it per-rule in
   this repo has missed at least one.
   ─────────────────────────────────────────────────────────────────────────── */

@keyframes k-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
@keyframes k-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* A page-load stagger. `--k-i` is the item's index; the delay is derived from
   it so a list can be staggered without writing a delay per child.
   IT ANIMATES FROM A VISIBLE RESTING STATE ONWARDS, never parks at opacity 0
   waiting for an observer: an animation that fails to fire must leave the
   content readable, or a JS error becomes a blank page. */
.k-rise {
  animation: k-rise var(--k-base) var(--k-ease) both;
  animation-delay: calc(var(--k-i, 0) * 55ms);
}


/* ───────────────────────────────────────────────────────────────────────────
   3 · CHROME — the header every tester-facing page shares.

   These five selectors exist on /audio, /files, /quran-ikhtebar, /app and
   /manzoori already. Restyling them here is what makes the portal read as one
   product before a single page's structure has been touched.
   ─────────────────────────────────────────────────────────────────────────── */

.header {
  /* The bar stops being a slab: no fill, no rule, no shadow. What separates
     it from the page is the gap under it, not a line across it. */
  background: transparent;
  border-bottom: 0;
  box-shadow: none;
  height: auto;
  min-height: 64px;
  padding-top: 12px;
  padding-bottom: 12px;
  gap: 14px;
}

.logo-mark {
  border-radius: var(--radius-sm);
  background: linear-gradient(150deg, var(--gold-400), var(--gold-500));
  color: #2A1D06;
  box-shadow: 0 6px 14px -8px var(--gold-500);
  transition: transform var(--k-fast) var(--k-ease-pop);
}
.logo-mark:hover { transform: scale(1.06) rotate(-3deg); }

.header-title { font-size: 18px; letter-spacing: -.01em; }

/* THE PILL NAV. Still `.seg-control`, still the only way off the page, still
   never display:none — restyled, not replaced. The active item is a filled
   pill rather than an inverted block, so the control reads as a set of
   choices rather than a toolbar. */
.seg-control {
  background: var(--bg-white);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-full);
  padding: 4px;
  gap: 2px;
  box-shadow: var(--shadow-sm);
  overflow: visible;
}
.seg-control a {
  height: 32px;
  padding: 0 15px;
  border-right: 0;
  border-radius: var(--radius-full);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--text-secondary);
  transition: background var(--k-fast) var(--k-ease), color var(--k-fast) var(--k-ease);
}
.seg-control a.active {
  background: var(--brown-800);
  color: var(--gold-200);
}
.seg-control a:not(.active):hover {
  background: var(--bg-warm);
  color: var(--text-primary);
}

/* The secondary header buttons. `.header-link` was a 36px SQUARE holding a
   WORD on two of these shells, which is how ACCOUNT/FEEDBACK/FILES/ADMIN came
   to draw on top of one another. A pill sized by its content cannot do that,
   whatever it is asked to hold. */
.header-link,
.header-btn {
  height: 36px;
  min-width: 36px;
  width: auto;
  padding: 0 14px;
  border-radius: var(--radius-full);
  border-color: var(--border-light);
  font-size: 12px;
  letter-spacing: .02em;
  box-shadow: var(--shadow-sm);
  transition: background var(--k-fast) var(--k-ease), color var(--k-fast) var(--k-ease),
              border-color var(--k-fast) var(--k-ease), transform var(--k-fast) var(--k-ease);
}
.header-link:hover,
.header-btn:hover { transform: translateY(-1px); }
.header-link.active { background: var(--brown-800); border-color: var(--brown-800); color: var(--gold-200); }

.quota-pill {
  border-radius: var(--radius-full);
  border-color: var(--border-light);
  background: var(--bg-white);
  box-shadow: var(--shadow-sm);
  padding: 6px 14px;
}


/* ───────────────────────────────────────────────────────────────────────────
   4 · THE STUDIO SHELL — rail + canvas.

   The same shape as the conversation rail /app already ships, so the portal
   has ONE idea of "your things are on the left, the work is in the middle".
   ─────────────────────────────────────────────────────────────────────────── */

.k-shell {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
  padding: 0 16px 20px;
  align-items: start;
  width: 100%;
  max-width: 1240px;
  margin: 0 auto;
}

.k-rail {
  background: var(--bg-white);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  position: relative;
  overflow: hidden;
}
/* The amber wash at the foot of the rail. Decorative, and the one place in
   the portal where the accent is allowed to be atmospheric rather than
   meaningful — it marks the corner the account block lives in. */
.k-rail::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 190px;
  pointer-events: none;
  background: radial-gradient(120% 84% at 50% 118%, var(--k-glow), var(--k-glow-out) 70%);
}
.k-rail > * { position: relative; z-index: 1; }
.k-rail-grow { flex: 1; }

.k-newbtn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  padding: 11px 14px;
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-full);
  background: var(--bg-white);
  color: var(--text-primary);
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--k-fast) var(--k-ease), border-color var(--k-fast) var(--k-ease),
              color var(--k-fast) var(--k-ease);
}
.k-newbtn:hover { background: var(--gold-100); border-color: var(--gold-500); color: var(--gold-600); }
.k-newbtn svg { width: 16px; height: 16px; }

.k-rail-label {
  font-size: 11.5px;
  color: var(--text-muted);
  font-weight: 500;
  padding: 0 6px;
  margin-bottom: 6px;
}

.k-rmenu { display: flex; flex-direction: column; gap: 2px; }
.k-rmenu button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-secondary);
  font-family: var(--font-ui);
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  transition: background var(--k-fast) var(--k-ease), color var(--k-fast) var(--k-ease);
}
.k-rmenu button:hover { background: var(--bg-cream); color: var(--text-primary); }
.k-rmenu button[aria-current] { background: var(--bg-warm); color: var(--text-primary); font-weight: 600; }
.k-rmenu svg { width: 17px; height: 17px; flex: none; color: var(--text-muted); }
.k-rmenu button[aria-current] svg { color: var(--gold-500); }

.k-rlist { display: flex; flex-direction: column; gap: 1px; }
.k-rlist button {
  display: block;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-secondary);
  /* INVARIANT #9. A RAIL TITLE IS ALWAYS LSD -- it is the opening words of
     the recording, which is the only title this data has. Under --font-ui
     that text renders in whatever Arabic face the browser picks, because the
     stack ENDS IN GENERICS and both system-ui and sans-serif supply Arabic:
     a Kanz listed after them is never consulted and the declaration silently
     does nothing. The var() fallback restates the stack rather than trusting
     every page that links this file to have declared the token. */
  font-family: var(--font-ui-lsd, 'Inter', 'KanzAlMarjaan', system-ui, -apple-system, sans-serif);
  font-size: 13.5px;
  text-align: left;
  cursor: pointer;
  /* One line, ellipsised. A rail item that wraps to three lines turns the
     list into a wall and the rail into a second page. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: background var(--k-fast) var(--k-ease), color var(--k-fast) var(--k-ease);
}
.k-rlist button:hover { background: var(--bg-cream); color: var(--text-primary); }
.k-rlist button[aria-current] { background: var(--bg-warm); color: var(--text-primary); font-weight: 600; }
/* The row that was just created. It is the only thing on the page the user is
   looking for after a four-minute wait. */
.k-rlist button.is-fresh { color: var(--gold-600); font-weight: 600; }

.k-canvas {
  background: var(--bg-white);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 40px 30px 28px;
  min-height: 520px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
}
/* The glow behind the focal point, painted on the canvas rather than on the
   orb, so it stays put while the orb animates. */
.k-canvas::before {
  content: "";
  position: absolute;
  inset: -26% 0 auto;
  height: 440px;
  pointer-events: none;
  opacity: .55;
  background: radial-gradient(52% 60% at 50% 50%, var(--k-glow), var(--k-glow-out) 68%);
}
.k-canvas > * { position: relative; z-index: 1; width: 100%; }

.k-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 640px;
  margin: 0 auto;
}


/* ───────────────────────────────────────────────────────────────────────────
   5 · THE FOCAL POINT — one orb, three states.
   ─────────────────────────────────────────────────────────────────────────── */

.k-orb {
  width: 108px;
  height: 108px;
  display: grid;
  place-items: center;
  position: relative;
  margin-bottom: 24px;
}
.k-orb-ring {
  position: absolute;
  border-radius: 50%;
  border: 1px solid var(--gold-500);
  opacity: .26;
  /* THE RINGS WERE EATING EVERY CLICK ON THE RECORD BUTTON, and nothing said
     so. They are absolutely positioned and `.k-orb-btn` is not, so they paint
     ABOVE it whatever the DOM order -- and r3 is `inset: -22px`, i.e. larger
     than the orb, so it covered the button completely. A transparent element
     with no background still hit-tests, so the tap landed on a decorative
     span with no handler and the page simply did nothing. Found by the
     browser check timing out on a click, which is the ONLY way this shows up:
     it throws nothing, logs nothing, and looks exactly like a page that was
     never wired. Same signal as `.brand-art`: decoration takes no taps. */
  pointer-events: none;
}
.k-orb-ring.r1 { inset:   8px; }
.k-orb-ring.r2 { inset:  -6px; opacity: .16; }
.k-orb-ring.r3 { inset: -22px; opacity: .09; }

.k-orb-btn {
  width: 76px;
  height: 76px;
  border: 0;
  border-radius: 50%;
  display: grid;
  place-items: center;
  cursor: pointer;
  color: #2A1D06;
  background: linear-gradient(155deg, var(--gold-400), var(--gold-500));
  box-shadow: 0 10px 26px -12px var(--gold-500), inset 0 1px 0 rgba(255, 255, 255, .45);
  transition: transform var(--k-fast) var(--k-ease-pop);
}
.k-orb-btn:hover  { transform: scale(1.05); }
.k-orb-btn:active { transform: scale(.97); }
.k-orb-btn svg { width: 30px; height: 30px; }
.k-orb-btn:disabled { cursor: default; }

/* RECORDING. The ripples are the only thing on the page that repeats
   indefinitely, and they are what makes a phone left face-down on a table
   still look alive when it is picked back up. */
.k-orb.is-live .k-orb-btn {
  background: linear-gradient(155deg, #E4796A, var(--red));
  color: #FFF4F0;
  box-shadow: 0 10px 26px -12px var(--red), inset 0 1px 0 rgba(255, 255, 255, .35);
}
.k-orb.is-live .k-orb-ring.r2,
.k-orb.is-live .k-orb-ring.r3 {
  border-color: var(--red);
  animation: k-ripple 2.4s ease-out infinite;
}
.k-orb.is-live .k-orb-ring.r3 { animation-delay: 1.2s; }
@keyframes k-ripple {
  from { transform: scale(.86); opacity: .32; }
  to   { transform: scale(1.22); opacity: 0; }
}

/* WORKING. A slow breath, not a spinner: a spinner says "blocked", a breath
   says "carrying on", and this wait is four minutes long. */
.k-orb.is-busy .k-orb-btn { animation: k-breathe 2.8s ease-in-out infinite; }
@keyframes k-breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.06); }
}


/* ───────────────────────────────────────────────────────────────────────────
   6 · SPEECH, CHOICES, THE COMPOSER — the rest of the studio.
   ─────────────────────────────────────────────────────────────────────────── */

.k-greet {
  font-family: var(--font-display);
  font-size: clamp(24px, 4.2vw, 34px);
  font-weight: 500;
  letter-spacing: -.02em;
  line-height: 1.2;
  text-align: center;
  text-wrap: balance;
  color: var(--text-primary);
}
/* The italic amber span is the design system's signature move and the one
   place the page raises its voice. */
.k-greet em { font-style: italic; color: var(--gold-600); }

.k-sub {
  margin-top: 12px;
  color: var(--text-secondary);
  text-align: center;
  font-size: 15px;
  line-height: 1.6;
  max-width: 46ch;
}

.k-chips {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 24px;
}
.k-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 15px;
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-full);
  background: var(--bg-white);
  color: var(--text-secondary);
  font-family: var(--font-ui);
  font-size: 13.5px;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color var(--k-fast) var(--k-ease), color var(--k-fast) var(--k-ease),
              transform var(--k-fast) var(--k-ease), background var(--k-fast) var(--k-ease);
}
.k-chip:hover { border-color: var(--gold-500); color: var(--text-primary); transform: translateY(-1px); }
.k-chip svg { width: 16px; height: 16px; color: var(--text-muted); }
.k-chip[aria-pressed="true"] {
  background: var(--gold-100);
  border-color: var(--gold-500);
  color: var(--gold-600);
  font-weight: 600;
}
.k-chip[aria-pressed="true"] svg { color: var(--gold-500); }

/* THE COMPOSER IS THE RECORDER. Where a chat app puts the message box this
   page puts the recording: choose a file on the left, what comes back in the
   middle, the amber button on the right. The shape is the point — it is the
   one control layout every person on a phone already knows. */
.k-composer {
  margin-top: 24px;
  width: 100%;
  background: var(--bg-white);
  border: 1px solid var(--border-medium);
  border-radius: 24px;
  padding: 16px 16px 12px;
  box-shadow: var(--shadow-md);
  transition: border-color var(--k-base) var(--k-ease), box-shadow var(--k-base) var(--k-ease);
}
.k-composer:hover,
.k-composer.is-hot { border-color: var(--gold-500); box-shadow: var(--shadow-lg); }
.k-composer.is-hot { background: var(--gold-100); }

.k-comp-top { display: flex; align-items: center; gap: 13px; padding: 6px 4px 14px; }
.k-comp-icon {
  width: 38px;
  height: 38px;
  flex: none;
  border-radius: 50%;
  background: var(--bg-warm);
  color: var(--gold-600);
  display: grid;
  place-items: center;
}
.k-comp-icon svg { width: 18px; height: 18px; }
.k-comp-text { min-width: 0; }
.k-comp-text b { display: block; font-size: 15px; font-weight: 600; color: var(--text-primary); }
.k-comp-text small { display: block; font-size: 13px; color: var(--text-muted); }
.k-comp-bot {
  display: flex;
  align-items: center;
  gap: 9px;
  border-top: 1px solid var(--border-light);
  padding-top: 12px;
}

.k-mini {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 13px;
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-full);
  background: var(--bg-white);
  color: var(--text-secondary);
  font-family: var(--font-ui);
  font-size: 13px;
  cursor: pointer;
  transition: background var(--k-fast) var(--k-ease), color var(--k-fast) var(--k-ease);
}
.k-mini:hover { background: var(--bg-cream); color: var(--text-primary); }
.k-mini svg { width: 15px; height: 15px; }

.k-go {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 10px 20px;
  border: 0;
  border-radius: var(--radius-full);
  background: linear-gradient(150deg, var(--gold-400), var(--gold-500));
  color: #2A1D06;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 8px 20px -10px var(--gold-500);
  transition: transform var(--k-fast) var(--k-ease), filter var(--k-fast) var(--k-ease);
}
.k-go:hover { transform: translateY(-1px); filter: brightness(1.04); }
.k-go:disabled { opacity: .5; cursor: default; transform: none; filter: none; }
.k-go svg { width: 16px; height: 16px; }
.k-go.is-stop {
  background: linear-gradient(150deg, #E4796A, var(--red));
  color: #FFF4F0;
  box-shadow: 0 8px 20px -10px var(--red);
}

.k-foot-note { margin-top: 18px; text-align: center; color: var(--text-muted); font-size: 12.5px; }
.k-formats { margin-top: 10px; display: flex; gap: 7px; justify-content: center; flex-wrap: wrap; }
.k-formats span {
  padding: 4px 11px;
  border: 1px solid var(--border-light);
  border-radius: var(--radius-full);
  background: var(--bg-cream);
  font-family: var(--font-mono, 'JetBrains Mono', monospace);
  font-size: 10px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* THE WAIT: one line and a clock. Not the six-stage pipeline — that is our
   architecture, not the user's business. The sweep is what says the sentence
   is live rather than stuck. */
.k-readout { margin-top: 4px; display: flex; flex-direction: column; align-items: center; gap: 14px; }
.k-said {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(21px, 3.4vw, 29px);
  letter-spacing: -.015em;
  text-align: center;
  background: linear-gradient(96deg, var(--text-secondary) 24%, var(--gold-400) 48%, var(--text-secondary) 72%);
  background-size: 260% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: k-sweep 2.8s ease-in-out infinite;
}
@keyframes k-sweep {
  from { background-position: 140% 0; }
  to   { background-position: -40% 0; }
}
.k-clock {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 7px 15px;
  border: 1px solid var(--border-light);
  border-radius: var(--radius-full);
  background: var(--bg-cream);
  font-family: var(--font-mono, 'JetBrains Mono', monospace);
  font-size: 12.5px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.k-clock i { width: 7px; height: 7px; border-radius: 50%; background: var(--gold-500); animation: k-blink 1.5s ease-in-out infinite; }
.k-clock.is-rec i { background: var(--red); }
@keyframes k-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: .22; }
}
.k-quiet { color: var(--text-muted); font-size: 13px; text-align: center; max-width: 42ch; line-height: 1.6; }
.k-quiet b { color: var(--text-secondary); }

.k-wave { display: flex; align-items: center; gap: 3px; height: 26px; }
.k-wave i {
  width: 3px;
  border-radius: 2px;
  background: var(--red);
  opacity: .75;
  animation: k-bob 1.1s ease-in-out infinite;
}
@keyframes k-bob {
  0%, 100% { height: 5px; }
  50%      { height: 24px; }
}

/* THE FINISHED DOCUMENT. Sized like the thing the user came back for, with
   the miniature sheet on the left — a page carrying the minutes form's own
   red double rule, which is what a reader recognises before reading a word. */
.k-sheetcard {
  display: grid;
  grid-template-columns: 82px 1fr;
  gap: 18px;
  align-items: center;
  width: 100%;
  padding: 18px;
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  background: var(--bg-white);
  box-shadow: var(--shadow-md);
  text-align: left;
}
.k-mini-sheet {
  aspect-ratio: 5 / 6.6;
  border: 1px solid var(--border-medium);
  border-radius: 8px;
  background: var(--bg-cream);
  padding: 9px 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.k-mini-sheet .rr { height: 4px; margin-bottom: 3px; border-top: 3px solid var(--red); border-bottom: 1px solid var(--red); }
.k-mini-sheet .ln { height: 3px; border-radius: 2px; background: var(--border-medium); }
.k-sheetcard h3 { font-family: var(--font-display); font-size: 19px; font-weight: 600; margin: 0; }
.k-tags { display: flex; gap: 7px; flex-wrap: wrap; margin-top: 10px; }
.k-tag {
  padding: 3px 10px;
  border: 1px solid var(--border-light);
  border-radius: var(--radius-full);
  background: var(--bg-cream);
  font-size: 11.5px;
  color: var(--text-secondary);
}
.k-tag.is-on { background: var(--gold-100); border-color: var(--gold-500); color: var(--gold-600); font-weight: 600; }
.k-tag.is-ok { background: transparent; border-color: var(--green); color: var(--green); }
.k-acts { display: flex; gap: 9px; margin-top: 16px; flex-wrap: wrap; justify-content: center; }

.k-empty { color: var(--text-muted); font-size: 13.5px; text-align: center; padding: 18px 0; }


/* ───────────────────────────────────────────────────────────────────────────
   7 · WIDTH — the same ladder responsive.css uses (481 / 640 / 901), so the
   portal has one set of breakpoints and not two.

   Authored phone-first: the base rules ARE the phone layout, and the rail
   only becomes a column when there is room for one.
   ─────────────────────────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  /* On a phone the rail is a strip above the work, not a column beside it:
     scrolled sideways, it costs one row instead of a third of the screen. */
  .k-rail-grow { display: none; }
  .k-rmenu,
  .k-rlist { flex-direction: row; overflow-x: auto; gap: 8px; padding-bottom: 2px; }
  .k-rmenu button,
  .k-rlist button { flex: 0 0 auto; max-width: 220px; }
  .k-rlist button { border: 1px solid var(--border-light); }
  .k-canvas { min-height: 0; padding: 32px 18px 24px; }
}

@media (min-width: 901px) {
  .k-shell { grid-template-columns: 262px 1fr; gap: 14px; padding: 0 18px 18px; }
  .k-rail  { min-height: 560px; }
}

@media (max-width: 560px) {
  .k-shell { padding: 0 12px 12px; gap: 10px; }
  .k-canvas { padding: 26px 14px 20px; border-radius: var(--radius-lg); }
  .k-orb { width: 96px; height: 96px; margin-bottom: 18px; }
  .k-orb-btn { width: 68px; height: 68px; }
  .k-composer { border-radius: 20px; padding: 13px 13px 10px; }
  .k-comp-bot { flex-wrap: wrap; }
  /* The primary action goes full width and FIRST on a phone, above the two
     secondary pills — a thumb reaches the bottom of the screen, and this is
     the only control on it that matters. */
  .k-go { width: 100%; margin-left: 0; order: -1; padding: 13px 20px; }
  .k-sheetcard { grid-template-columns: 1fr; }
  .k-mini-sheet { width: 74px; }
  .k-chips { gap: 7px; margin-top: 18px; }
}


/* ───────────────────────────────────────────────────────────────────────────
   8 · THE MOTION GUARD — once, for everything above.

   `prefers-reduced-motion` is a medical setting, not a preference: for a
   vestibular user the ripples and the breathing orb are the difference
   between a usable page and an unusable one. Blanket, because the alternative
   is remembering it at forty call sites and missing one.
   ─────────────────────────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .k-rise,
  .k-orb-ring,
  .k-orb-btn,
  .k-said,
  .k-clock i,
  .k-wave i,
  /* Section 20's empty-chat screen: the stagger, the breathing mark and its
     two rings. Listed here rather than beside themselves, which is the whole
     point of this block being blanket. */
  .welcome > *,
  .welcome-logo,
  .welcome-logo::before,
  .welcome-logo::after {
    animation: none !important;
  }
  /* The sweep paints the text in a gradient it cannot show without moving, so
     the static form has to put the colour back or the sentence is invisible. */
  .k-said {
    background: none;
    -webkit-text-fill-color: currentColor;
    color: var(--text-secondary);
  }
  .k-wave i { height: 14px; }
}


/* ───────────────────────────────────────────────────────────────────────────
   9 · THE LSD FACE, DECLARED ONCE FOR EVERY PAGE THAT LINKS THIS FILE.

   `@font-face` used to be declared per page, in each page's own <style>. Six
   pages had it; /account, /feedback, /pending and /reset-password did NOT --
   so a name, a role profile or a line of feedback written in LSD on any of
   those four rendered in whatever Arabic face the browser picked. Invariant
   #9 says Arabic is always Kanz, and a page that never declares the face
   cannot keep that promise however carefully its tokens are written.

   A BROWSER ONLY DOWNLOADS A FONT A RULE ACTUALLY USES, so declaring it here
   costs the pages with no Arabic on them nothing.

   The token values are IDENTICAL to the ones the six pages already declare,
   so this changes nothing where they exist and supplies them where they do
   not. Kanz sits AHEAD of every generic in --font-ui-lsd, which is the whole
   point of that token: system-ui and sans-serif both supply Arabic, so a Kanz
   listed after them is never consulted.
   ─────────────────────────────────────────────────────────────────────────── */

@font-face {
  font-family: 'KanzAlMarjaan';
  src: url('/static/fonts/Kanz-al-Marjaan.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
  font-display: block;
}

:root {
  --font-lsd: 'KanzAlMarjaan', 'Traditional Arabic', 'Scheherazade New', serif;
  --font-ui-lsd: 'Inter', 'KanzAlMarjaan', system-ui, -apple-system, sans-serif;
}

/* Kanz ships OpenType GSUB tables that collapse LSD doubled letters into
   single ligated glyphs, and Chrome does not enable contextual/discretionary
   ligatures for Arabic by default. Without the opt-in you see raw character
   pairs instead of the intended letterforms.

   SCOPED AWAY FROM QURANIC TEXT. `:not(.quran-text)` must stay a SIMPLE
   argument -- `:not(.quran-text *)` needs Selectors 4, and an unsupported
   selector invalidates the whole comma-separated rule, which would drop the
   opt-in everywhere else on older iOS Safari. */
[lang="ar"]:not(.quran-text),
[dir="rtl"]:not(.quran-text) {
  font-feature-settings: "liga" 1, "calt" 1, "rlig" 1, "dlig" 1, "ccmp" 1;
  font-variant-ligatures: contextual common-ligatures discretionary-ligatures;
  text-rendering: optimizeLegibility;
}


/* ───────────────────────────────────────────────────────────────────────────
   10 · THE PORTAL NAV — one collapsible rail, on every page.

   WHY IT IS IN THE RAIL AND NOT THE HEADER. The header had run out of room
   for it once already: `.header-link` was a 36px square holding a WORD, so
   ACCOUNT / FEEDBACK / FILES / ADMIN drew on top of one another (Phase 26.1).
   And four pages -- /account, /feedback, /pending, /reset-password -- had no
   nav at all, only a "back" link, so the way out of them depended on where
   you had come from.

   IT IS THE SAME RAIL THE PAGE OWN LIST LIVES IN. Two zones, nav then the
   list: /audio puts its minutes library under it, /app its conversations,
   /account nothing at all. One rail, one collapse, one account block --
   rather than a nav bar competing with a rail for the same edge.
   ─────────────────────────────────────────────────────────────────────────── */

.k-nav { display: flex; flex-direction: column; gap: 2px; }
.k-nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  transition: background var(--k-fast) var(--k-ease), color var(--k-fast) var(--k-ease);
}
.k-nav a:hover { background: var(--bg-cream); color: var(--text-primary); }
.k-nav a[aria-current] { background: var(--bg-warm); color: var(--text-primary); font-weight: 600; }
.k-nav svg { width: 17px; height: 17px; flex: none; color: var(--text-muted); }
.k-nav a[aria-current] svg { color: var(--gold-500); }
.k-nav-sep { height: 1px; margin: 8px 4px; background: var(--border-light); }

/* The brand block at the head of the rail, and the control that collapses it. */
.k-rail-head { display: flex; align-items: center; gap: 10px; }
.k-rail-brand { display: flex; align-items: center; gap: 10px; min-width: 0; text-decoration: none; color: inherit; }
.k-rail-brand .mk {
  width: 32px; height: 32px; flex: none;
  border-radius: 10px;
  display: grid; place-items: center;
  background: linear-gradient(150deg, var(--gold-400), var(--gold-500));
  color: #2A1D06;
  font-family: var(--font-lsd);
  font-size: 16px;
}
.k-rail-brand b {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -.01em;
  white-space: nowrap;
}
.k-collapse {
  margin-left: auto;
  width: 32px; height: 32px;
  flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--border-light);
  border-radius: var(--radius-sm);
  background: var(--bg-white);
  color: var(--text-muted);
  cursor: pointer;
  transition: background var(--k-fast) var(--k-ease), color var(--k-fast) var(--k-ease);
}
.k-collapse:hover { background: var(--bg-cream); color: var(--text-primary); }
.k-collapse svg { width: 16px; height: 16px; }

/* The account block at the foot. */
.k-me {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px;
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  background: var(--bg-white);
  box-shadow: var(--shadow-sm);
  width: 100%;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-ui);
  transition: background var(--k-fast) var(--k-ease);
}
.k-me:hover { background: var(--bg-cream); }
.k-me .av {
  width: 32px; height: 32px; flex: none;
  border-radius: 50%;
  background: var(--gold-100);
  color: var(--gold-600);
  display: grid; place-items: center;
  font-weight: 700; font-size: 13px;
}
.k-me .who { min-width: 0; flex: 1; }
.k-me .who b {
  display: block; font-size: 13.5px; font-weight: 600; color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* A display name can be LSD. */
  font-family: var(--font-ui-lsd, 'Inter', 'KanzAlMarjaan', system-ui, sans-serif);
}
.k-me .who small {
  display: block; font-size: 11.5px; color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* COLLAPSED IS AN ICON-ONLY RAIL, NOT A HIDDEN ONE — and that is a bug fix,
   not a preference.

   The first cut collapsed the rail to zero width with `visibility: hidden`,
   copying the chat rail from Phase 26.1. That works there because the chat
   page's toggle lives in the HEADER, outside the thing it collapses. Here the
   toggle sits in the rail, so hiding the rail hid the only control that
   brings it back — and the preference is persisted, so it followed the user
   from page to page. A portal with no navigation and no way to restore it.

   An icon-only rail cannot fail that way: every destination stays clickable,
   nothing leaves the tab order, and the control that widens it again is
   exactly where it was. The labels are `display: none`, so they leave the
   accessibility tree with their icons still carrying an accessible name from
   the link's `title`. */
@media (min-width: 901px) {
  .k-rail { transition: width var(--k-base) var(--k-ease), padding var(--k-base) var(--k-ease); }

  .k-shell.is-collapsed { grid-template-columns: 68px 1fr; }
  .k-shell.is-collapsed .k-rail { padding: 12px 10px; }

  /* Everything that is a word rather than a mark. */
  .k-shell.is-collapsed .k-rail-brand b,
  .k-shell.is-collapsed .k-nav-label,
  .k-shell.is-collapsed .k-rail-label,
  .k-shell.is-collapsed .k-rlist,
  .k-shell.is-collapsed .k-newbtn span,
  .k-shell.is-collapsed .k-me .who,
  .k-shell.is-collapsed .k-me > svg { display: none; }

  /* THE BACKSTOP. Hiding labels by selector only works for labels somebody
     remembered to wrap -- the library filters shipped as bare text nodes and
     wrapped to three lines each inside a 68px column, spilling over the page.
     Clipping the rail means the worst a future unwrapped label can do is be
     invisible, instead of drawing on top of the work. */
  .k-shell.is-collapsed .k-rail { overflow: hidden; }
  .k-shell.is-collapsed .k-rail * { white-space: nowrap; }

  .k-shell.is-collapsed .k-rail-head { flex-direction: column; gap: 8px; }
  .k-shell.is-collapsed .k-collapse { margin-left: 0; }
  .k-shell.is-collapsed .k-nav a,
  .k-shell.is-collapsed .k-rmenu button { justify-content: center; padding: 10px 0; }
  .k-shell.is-collapsed .k-newbtn { padding: 11px 0; }
  .k-shell.is-collapsed .k-me { justify-content: center; padding: 7px; }
}

/* On a phone the rail is a strip above the work, and there is no column to
   collapse. `.is-collapsed` is cancelled BY NAME here -- (0,2,0) outranks
   `.k-rail` (0,1,0), so somebody who collapsed the rail on a laptop and then
   opened the same browser on a phone would otherwise find the nav gone
   entirely, at the one width where it is the only way off the page. */
@media (max-width: 900px) {
  .k-shell.is-collapsed { grid-template-columns: 1fr; }
  .k-shell.is-collapsed .k-rail,
  .k-rail {
    width: auto;
    min-width: 0;
    visibility: visible;
    border-width: 1px;
    padding: 14px;
  }
  .k-nav { flex-direction: row; overflow-x: auto; gap: 6px; }
  .k-nav a { flex: 0 0 auto; }
  .k-nav-sep { display: none; }
  /* The collapse control is meaningless when there is no column to collapse. */
  .k-collapse { display: none; }
}


/* ───────────────────────────────────────────────────────────────────────────
   11 · THE ACCOUNT MENU, and the document page.
   ─────────────────────────────────────────────────────────────────────────── */

/* Anchored to the account block so the menu opens ABOVE it -- the block sits
   at the foot of the rail, and a menu opening downwards would be off-screen. */
.k-menu-wrap { position: relative; }
.k-menu {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 30;
  background: var(--bg-white);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 5px;
  display: flex;
  flex-direction: column;
  animation: k-rise var(--k-fast) var(--k-ease) both;
}
.k-menu-item {
  display: block;
  width: 100%;
  padding: 9px 11px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-secondary);
  font-family: var(--font-ui);
  font-size: 13.5px;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--k-fast) var(--k-ease), color var(--k-fast) var(--k-ease);
}
.k-menu-item:hover { background: var(--bg-cream); color: var(--text-primary); }
.k-menu-item.is-danger:hover { background: var(--bg-warm); color: var(--red); }
.k-menu-sep { height: 1px; margin: 5px 4px; background: var(--border-light); }

/* THE DOCUMENT PAGE — /account, /feedback and the rest read top-to-bottom and
   are not a studio. Same panel, same edges as `.k-canvas`; it simply does not
   centre its contents or reserve a screen of height for a focal point. */
.k-page {
  background: var(--bg-white);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 30px;
  min-width: 0;
}
.k-page-head { margin-bottom: 22px; }
.k-page-head h1 {
  font-family: var(--font-display);
  font-size: clamp(24px, 3.4vw, 31px);
  font-weight: 500;
  letter-spacing: -.02em;
  margin: 0;
  color: var(--text-primary);
}
.k-page-head p { margin: 8px 0 0; color: var(--text-secondary); font-size: 14.5px; max-width: 62ch; }
.k-page-body { max-width: 680px; }

@media (max-width: 560px) {
  .k-page { padding: 20px 15px; border-radius: var(--radius-lg); }
}


/* ───────────────────────────────────────────────────────────────────────────
   12 · WHY THE CHAT RAIL DOES *NOT* CARRY THE PORTAL NAV.

   It did, briefly, and that was wrong twice over.

   THE NAV WOULD HAVE BEEN ON /app TWICE. The seg-control is still in the chat
   header, so a rail nav beside it is a second copy of the same link list --
   and "the secondary nav is reachable exactly once" is the rule that exists
   because leaving a copy in the header is what made the bar overflow in
   Phase 26.1. Two copies also drift: the rail's list gates FILES on the
   server's verdict and a forgotten header copy would not.

   AND THE HEADER COPY IS THE ONE THAT HAS TO STAY. Below 901px the chat rail
   is a CLOSED SHEET behind the composer's history button, so a nav that lived
   only inside it would be a nav a phone user has to know to go looking for --
   the exact dead end Phase 12 fixed by refusing to hide `.seg-control`.

   /audio can put its nav in the rail precisely because its rail is never a
   sheet: at narrow widths it becomes a visible strip above the work. The two
   pages differ because their rails differ, not by oversight.
   ─────────────────────────────────────────────────────────────────────────── */

/* ───────────────────────────────────────────────────────────────────────────
   13 · THE SHELL FILLS THE SCREEN.

   The first cut capped the shell at 1240px and centred it, which on a desktop
   left the product floating in the middle of a very wide page with a third of
   the screen empty on either side. A tool is not an article: the rail wants
   the edge it is anchored to, and the canvas wants the room.

   `align-items: stretch` (it was `start`) is what makes the rail and the
   canvas the same height, so the rail's account block sits at the BOTTOM OF
   THE SCREEN rather than wherever the nav happened to end.
   ─────────────────────────────────────────────────────────────────────────── */

.k-shell {
  max-width: none;
  align-items: stretch;
  /* The shell IS the page on every surface that uses it -- these pages render
     no header above it -- so it owns the viewport height. dvh tracks the
     Safari URL bar collapsing; the min-height is what stops a short page
     leaving the rail floating half way up the screen. */
  min-height: 100dvh;
  /* Tight to the edge. The rail is anchored furniture, not a card floating in
     the middle of a very wide page. */
  padding: 12px 12px 12px 10px;
}
.k-canvas,
.k-page { min-height: 0; }

@media (max-width: 900px) {
  /* On a phone the rail is a strip and the canvas grows with its content, so
     a forced viewport height would only add a dead band under the fold. */
  .k-shell { min-height: 0; padding: 12px; }
}


/* ───────────────────────────────────────────────────────────────────────────
   14 · /app AND /manzoori IN THE SAME LANGUAGE.

   APPEARANCE ONLY, ON THE CLASSES THEY ALREADY HAVE. The chat rail carries a
   desktop collapse, a phone sheet, a scrim, a rename field and 61 browser
   assertions, and its mechanics are the most-tested surface in the portal.
   Re-plumbing it to share PortalShell would risk all of that to change how it
   LOOKS -- so this restyles `.conv-rail`, `.chat-main` and `.input-row` into
   the studio's panels and leaves every behaviour exactly where it is. Same
   split as Phase 12: structure stays, appearance moves.

   SCOPED TO THE DESKTOP. Below 901px `.conv-rail` is a fixed-position SHEET
   over the transcript (responsive.css), and giving a sheet a floating panel's
   margins and radius would leave a gap down its edge with the scrim showing
   through. The phone keeps the layout that was measured on real devices.
   ─────────────────────────────────────────────────────────────────────────── */

@media (min-width: 901px) {
  .chat-shell {
    gap: 14px;
    padding: 4px 18px 18px;
    background: transparent;
  }

  .conv-rail {
    background: var(--bg-white);
    border: 1px solid var(--border-light);
    border-right: 1px solid var(--border-light);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: 12px;
    overflow: hidden;
    position: relative;
  }
  /* The amber wash at the foot, the same one the katib rail carries. */
  .conv-rail::after {
    content: "";
    position: absolute;
    inset: auto 0 0;
    height: 180px;
    pointer-events: none;
    background: radial-gradient(120% 84% at 50% 118%, var(--k-glow), var(--k-glow-out) 70%);
  }
  .conv-rail > * { position: relative; z-index: 1; }

  /* Collapsed, the rail must take its margins with it or the gap it leaves
     reads as a rendering fault rather than a closed panel. */
  .conv-rail.collapsed { padding-left: 0; padding-right: 0; border-width: 0; }

  .chat-main {
    background: var(--bg-white);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
  }

  .conv-new {
    border-radius: var(--radius-full);
    border: 1px solid var(--border-medium);
    background: var(--bg-white);
    /* THE COLOUR IS NOT OPTIONAL. The page's own sheet sets
       `color: var(--gold-100)` to sit on `--brown-700`; this rule replaced
       the background with white and left the text where it was, so NEW CHAT
       -- the primary action on the page -- was near-white on white. Visible
       only once the header stopped drawing the eye away from it. */
    color: var(--text-primary);
    font-weight: 600;
  }
  .conv-new:hover { background: var(--gold-100); border-color: var(--gold-500); color: var(--gold-600); }

  /* THE COMPOSER, matching the one on /audio: a single rounded surface that
     lifts when it has focus. `:focus-within`, not `:hover` -- this is where
     the user is typing, and a border that only responds to a mouse says
     nothing on a keyboard. */
  .input-row {
    border-radius: 24px;
    border: 1px solid var(--border-medium);
    background: var(--bg-white);
    box-shadow: var(--shadow-md);
    transition: border-color var(--k-base) var(--k-ease), box-shadow var(--k-base) var(--k-ease);
  }
  .input-row:focus-within { border-color: var(--gold-500); box-shadow: var(--shadow-lg); }

  .send-btn {
    border-radius: var(--radius-full);
    background: linear-gradient(150deg, var(--gold-400), var(--gold-500));
    color: #2A1D06;
    border: 0;
    box-shadow: 0 8px 20px -10px var(--gold-500);
    transition: transform var(--k-fast) var(--k-ease), filter var(--k-fast) var(--k-ease);
  }
  .send-btn:hover:not(:disabled) { transform: translateY(-1px); filter: brightness(1.04); }
}


/* ───────────────────────────────────────────────────────────────────────────
   15 · THE MINUTES FORM.

   This is the half of a minute NO MODEL CAN KNOW -- subject, idara, both
   dates, times, place, chairman, minute taker, who attended -- and it is the
   longest stretch of typing in the product. It had the old flat inputs while
   everything around it had been redrawn, which on the one screen that asks
   for sustained attention is exactly where the seam shows.

   THE ONE EXCEPTION TO THE ARABIC THUMB RULE LIVES HERE and is not touched:
   `.mform-input[dir="rtl"]` holds ordinary Arabic typed at a keyboard --
   names, rooms, subjects -- which has NOT been through lsd_text.postprocess,
   and Kanz draws that as different letters. Those fields keep a plain Arabic
   face. The split is composed-vs-typed, and this is the typed side.
   ─────────────────────────────────────────────────────────────────────────── */

.mform-label {
  color: var(--text-secondary);
  font-weight: 600;
  letter-spacing: .01em;
}

.mform-input,
.mform-textarea {
  border: 1px solid var(--border-medium);
  border-radius: var(--radius-sm);
  background: var(--bg-white);
  color: var(--text-primary);
  padding: 10px 12px;
  transition: border-color var(--k-fast) var(--k-ease), box-shadow var(--k-fast) var(--k-ease);
}
.mform-input:hover,
.mform-textarea:hover { border-color: var(--border-medium); }
.mform-input:focus,
.mform-textarea:focus {
  outline: none;
  border-color: var(--gold-500);
  /* A ring rather than a heavier border: a border that thickens on focus
     shifts the field by a pixel and nudges everything beside it. */
  box-shadow: 0 0 0 3px var(--gold-100);
}
.mform-textarea { border-radius: var(--radius-md); }

.mform-online,
.mform-add,
.mform-remove {
  border-radius: var(--radius-full);
  border: 1px solid var(--border-medium);
  background: var(--bg-white);
  color: var(--text-secondary);
  transition: background var(--k-fast) var(--k-ease), color var(--k-fast) var(--k-ease),
              border-color var(--k-fast) var(--k-ease);
}
.mform-add:hover { background: var(--gold-100); border-color: var(--gold-500); color: var(--gold-600); }
.mform-remove:hover { background: var(--bg-warm); border-color: var(--red); color: var(--red); }
.mform-online:hover { background: var(--bg-cream); color: var(--text-primary); }

.mform-hint { color: var(--text-muted); }
.mform-saved { color: var(--green); font-weight: 600; }

/* Two columns is right for a desk and wrong for a thumb: on a phone a
   half-width "Place" field holds about six characters before it scrolls. */
@media (max-width: 700px) {
  .mform-grid,
  .mform-rolls { grid-template-columns: 1fr; }
}


/* ───────────────────────────────────────────────────────────────────────────
   16 · THE RAIL SCROLLS ITSELF, INSTEAD OF PUSHING THE PRODUCT OFF THE SCREEN.

   Section 13 gave the shell `min-height: 100dvh` and `align-items: stretch`
   so the rail would reach the bottom of the screen. Nothing capped it at the
   TOP of the screen, so the arithmetic ran the other way: the history list is
   as tall as your history, the rail grew to hold all of it, stretch made the
   canvas match, and the whole page became as tall as the rail.

   MEASURED on a real account with a normal amount of history, at 1440x900:
   the shell was 1497px tall inside a 900px window. The canvas centred its
   hero in that 1497px, which put the orb at y=483 -- half a screen of empty
   cream above the one thing on the page -- and the composer at y=840..978,
   i.e. THE RECORD BUTTON WAS BELOW THE FOLD. At 1366x768, the laptop most of
   these testers actually use, the fold lands at 768 and you see the orb, the
   headline, and nothing else: no mode chips, no drop zone, no record button.
   A page whose whole argument is "the composer is the recorder" was shipping
   with the composer off the screen.

   It also quietly falsified section 13's own claim that the account block
   "sits at the bottom of the SCREEN" -- measured, `.k-me` was at y=1442.

   THE FIX IS TO BOUND BOTH COLUMNS AND LET THE LIST SCROLL INSIDE THE RAIL.
   A tool's chrome is fixed and its content moves; only an article scrolls as
   one piece. `.k-rlist` is `flex: 0 1 auto` so it takes its content's height
   when there is room and gives it back when there is not, while
   `.k-rail-grow` (`flex: 1 1 auto`, zero-height) still pushes the account
   block down on the pages that have no list at all -- /files, /account,
   /feedback. Reversing those two flex settings puts the growable spacer in
   competition with the list and leaves a rail that is half blank.

   `min-height: 0` on both is the load-bearing half: a flex item's automatic
   minimum size is its CONTENT, so without it neither will shrink and the cap
   above them does nothing at all. Same rule that put the send button off an
   iPhone SE in Phase 26.1, in the other axis.
   ─────────────────────────────────────────────────────────────────────────── */

@media (min-width: 901px) {
  /* 24px = the shell's 12px top and bottom padding from section 13. */
  .k-rail,
  .k-canvas { max-height: calc(100dvh - 24px); }

  .k-rail { min-height: 0; }
  .k-rail-grow { flex: 1 1 auto; min-height: 0; }

  /* THE SHRINK HAS TO REACH THE FLEX ITEM, AND `.k-rlist` IS NOT ONE.
     MinutesRail groups each caption with the thing it captions, so the rail's
     children are those wrappers -- the first cut styled `.k-rlist` and the
     cap above it did nothing at all: measured, the rail was clipped at 888px
     with its account block still laid out at y=1442, hidden by the rail's own
     `overflow: hidden` rather than moved. The chain has to be unbroken from
     the item down to the scroller, which is what `.k-rail-scroll` names. */
  /* EVERYTHING ELSE IN THE RAIL REFUSES TO SHRINK. The default `flex-shrink:
     1` let the filter block give up height it still had content for, and
     because a flex item's overflow is visible, those four filter rows simply
     DREW ON TOP OF the history beneath them -- the Phase 26.1 `.header-link`
     overlap again, in the other axis. Exactly one child may absorb the
     shortfall, and it is the one that scrolls. */
  .k-rail > * { flex: none; }
  .k-rail-scroll {
    display: flex;
    flex-direction: column;
    flex: 0 1 auto;
    min-height: 0;
  }
  .k-rail > .k-rail-grow { flex: 1 1 auto; }
  .k-rail-scroll .k-rlist {
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
    /* Reaching the end of the history must not then scroll the page behind
       it -- the rail is the one thing on screen that is supposed to stay put. */
    overscroll-behavior: contain;
  }
  /* AND THE ROWS DO NOT SHRINK EITHER. `.k-rlist` is a column flex container,
     so once it was shorter than its content every button shrank a little
     instead of the list scrolling: rows drawn at half height, one title
     bleeding into the next, and no scrollbar to say anything was hidden. A
     list that scrolls needs its items to keep their size -- that IS the
     overflow it is scrolling. */
  .k-rail-scroll .k-rlist > * { flex: none; }

  /* The canvas holds the work, so it is the other scroller: a rendered minute
     runs for pages and must not drag the rail up with it. */
  .k-canvas {
    overflow-y: auto;
    overscroll-behavior: contain;
    /* SAFE CENTRING. `justify-content: center` on a scroll container whose
       content overflows pushes the first child's top out past the scroll
       origin, where no scrollbar can reach it -- the top of a long minute
       becomes unreadable. `margin-block: auto` centres exactly the same way
       when there is room and simply collapses when there is not. */
    justify-content: flex-start;
  }
  .k-canvas > * { flex: none; }
  .k-canvas > .k-stack { margin-block: auto; }
}


/* ───────────────────────────────────────────────────────────────────────────
   17 · THE PHONE'S FIRST SCREEN IS THE RECORDER, NOT THE MENU.

   Measured at 390x844: the rail was 392px -- 46% of the screen -- so the
   canvas began at y=414, the orb at 441, and the composer ran 818..1032,
   entirely below the fold. Someone opening /audio on their phone saw a
   navigation list and had to scroll to find out the page does anything.

   The two captions and the filter strip are what cost that. They are the
   least-used things in the rail and the first two are pure labelling: the
   history strip is recognisably a list of your recordings without a line
   above it saying so. The filters are a refinement of a list you can already
   see -- kept on a desktop, where the rail is a column with room to spare,
   and dropped where the screen is the scarce thing.

   NOT DROPPED: the nav strip (`.seg-control`'s rule -- the way off the page
   is never hidden at any width), New minute, the history itself, or the
   account block, which is the only route to sign out on a phone.
   ─────────────────────────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .k-rail { gap: 10px; padding: 12px; }
  .k-rail-label { display: none; }
  .k-rmenu { display: none; }
  .k-canvas { padding: 24px 18px 22px; }
}


/* ───────────────────────────────────────────────────────────────────────────
   18 · AND THE REST OF THE PHONE'S FIRST SCREEN.

   Section 17 got the rail down from 392px to 281px. Measured again at
   390x844 that still put the composer at 705..935 -- the RECORD BUTTON, the
   one control this page is built around, 91px below the fold on the handset
   most testers will open it on. Everything here is spent buying those 91px
   back out of vertical rhythm that a phone cannot afford: the orb is the
   focal point and does not need to be 96px to say so, the three mode chips
   are one scrollable row rather than two wrapped ones, and the hero's copy
   sits closer to the headline it belongs to.

   The chips DO NOT wrap here. A wrapped row is two rows tall and reorders
   itself as the label lengths change; a single row that scrolls is one row
   at every width, and the same gesture the history strip above it already
   uses. `flex-wrap: wrap` is right on a desktop and is the thing being
   overridden, not an oversight.
   ─────────────────────────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .k-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    /* CENTRED WHILE THEY FIT, SCROLLED FROM THE START ONCE THEY DO NOT.
       `justify-content: center` on an overflowing scroller spends the
       overflow at BOTH ends and only one of them has a scroll origin, so the
       first chip is cut off and unreachable -- measured at 390px, "Just the
       words" was clipped against the left edge. `safe center` is supposed to
       cover this and did not here. A fit-content box centred by auto margins
       does it with no keyword at all: it shrinks to the chips when they fit
       and stops at the full width when they do not. */
    justify-content: flex-start;
    width: fit-content;
    max-width: 100%;
    margin-inline: auto;
    margin-top: 16px;
    padding-bottom: 2px;
  }
  .k-chips > * { flex: 0 0 auto; }
  .k-canvas { padding: 20px 16px 20px; }
}

@media (max-width: 560px) {
  .k-orb { width: 80px; height: 80px; margin-bottom: 12px; }
  .k-orb-btn { width: 58px; height: 58px; }
  .k-orb-btn svg { width: 25px; height: 25px; }
  .k-chips { margin-top: 10px; }
  .k-rail { gap: 8px; }
  .k-canvas { padding: 16px 14px 18px; }
}


/* ───────────────────────────────────────────────────────────────────────────
   19 · /app AND /manzoori LOSE THE PORTAL BAR.

   Mohammed, 2026-09-04, on a screenshot of that bar: "i dont need this in
   chat and manzoori page as well". These were the last two pages spending a
   64px strip across the top of the screen on a brand mark, an email address,
   a quota pill and four links -- all four of which the rail already had a
   place for. This is chrome only: the transcript, the composer, the grading
   form, the annotate flow and the rail's own mechanics are untouched.

   THE COLLAPSE HAD TO CHANGE SHAPE, AND THAT IS THE ONE RISKY PART. The
   toggle used to live in the HEADER, outside the column it collapsed, which
   is precisely what licensed `width: 0; visibility: hidden` -- there was
   always a visible control to bring the rail back. With the header gone the
   toggle sits INSIDE the rail, so hiding the rail hides the toggle, and
   `chat.rail.collapsed` is persisted: one click and the page has no
   navigation and no way to restore it, on every future visit. That exact bug
   shipped on /audio in 42aebab. Collapsed is an ICON-ONLY rail here for the
   same reason it is one there: every destination stays clickable, nothing
   leaves the tab order, and the toggle does not move.

   This sheet loads AFTER each page's inline <style>, so the `.conv-rail
   .collapsed` override below wins on order at equal specificity (0,2,0) --
   the inline rule is left in place rather than edited, because it is written
   once in index.html and once in manzoori.html and those two are exactly the
   pair this conversion exists to stop maintaining twice.
   ─────────────────────────────────────────────────────────────────────────── */

.conv-rail-top {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 12px 4px;
  flex-shrink: 0;
}
.conv-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  text-decoration: none;
  color: inherit;
}
.conv-brand .mk {
  width: 30px;
  height: 30px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 9px;
  background: linear-gradient(150deg, var(--gold-400), var(--gold-600));
  color: #2A1D06;
  font-family: var(--font-lsd, 'KanzAlMarjaan', serif);
  font-size: 17px;
}
.conv-brand b {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 600;
  color: var(--brown-900);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.conv-collapse {
  margin-left: auto;
  flex: none;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border: 1px solid var(--border-light);
  border-radius: var(--radius-sm);
  background: var(--bg-white);
  color: var(--text-muted);
  cursor: pointer;
  transition: background var(--k-fast) var(--k-ease), color var(--k-fast) var(--k-ease);
}
.conv-collapse:hover { background: var(--bg-cream); color: var(--text-primary); }
.conv-collapse svg { width: 17px; height: 17px; }

.conv-rail-nav { padding: 6px 8px 0; flex-shrink: 0; }
.conv-rail-nav .k-nav a { font-size: 13.5px; padding: 8px 10px; }

.conv-rail-quota { padding: 6px 12px 0; flex-shrink: 0; }
.conv-rail-quota .quota-pill { width: 100%; justify-content: center; }

/* The phone's copy of the nav. Hidden here; shown in the phone block. */
.chat-nav-strip { display: none; }

@media (min-width: 901px) {
  /* ICON-ONLY, NEVER HIDDEN. See the block comment above -- this is the whole
     reason the collapse could not be left as it was. */
  .conv-rail.collapsed {
    width: 68px;
    visibility: visible;
    overflow: hidden;
    border-right: 1px solid var(--border-light);
  }
  .conv-rail.collapsed * { white-space: nowrap; }
  .conv-rail.collapsed .conv-brand b,
  .conv-rail.collapsed .k-nav-label,
  .conv-rail.collapsed .conv-rail-quota,
  .conv-rail.collapsed .rail-user-text,
  .conv-rail.collapsed .rail-user-caret { display: none; }
  /* THE LIST IS HIDDEN, NOT REMOVED, and the difference is where the account
     block ends up. `.conv-list` is `flex: 1` -- it is the thing that pushes
     the account block to the foot of the rail -- so `display: none` collapsed
     it to nothing and the avatar rode up to sit under the nav, half way up a
     900px column. `visibility: hidden` keeps the box, and keeps the promise
     the original collapse was written for: every conversation button leaves
     the tab order, so a keyboard user cannot land on a row nobody can see. */
  .conv-rail.collapsed .conv-list { visibility: hidden; }
  /* NEW CHAT is a word, not an icon, so collapsed it becomes the one glyph it
     can afford. Hiding it outright would take the primary action off the page
     for anyone who left the rail closed. */
  .conv-rail.collapsed .conv-new {
    font-size: 0;
    padding: 9px 0;
  }
  .conv-rail.collapsed .conv-new::after {
    content: "+";
    font-size: 17px;
    font-weight: 700;
  }
  .conv-rail.collapsed .conv-rail-top { flex-direction: column; gap: 8px; padding: 12px 8px 4px; }
  .conv-rail.collapsed .conv-collapse { margin-left: 0; }
  .conv-rail.collapsed .conv-rail-head,
  .conv-rail.collapsed .conv-rail-nav { padding-left: 8px; padding-right: 8px; }
  .conv-rail.collapsed .k-nav a { justify-content: center; padding: 10px 0; }
  .conv-rail.collapsed .rail-user { justify-content: center; padding: 7px; }
}

@media (max-width: 900px) {
  /* THE SHEET DOES NOT CARRY THE NAV, and that is the entire point of the
     second mount. Below 901px `.conv-rail` is a bottom sheet behind the
     composer's history button; a nav in there is the only way off /app
     hidden behind a button, which is the dead end
     test_cross_page_nav_is_never_hidden exists to prevent. */
  .conv-rail-nav { display: none; }
  /* And the collapse toggle collapses a column that does not exist here --
     the sheet has its own close control. */
  .conv-rail-top { display: none; }

  .chat-nav-strip {
    display: block;
    flex: none;
    padding: 8px 12px;
    background: var(--bg-page);
    border-bottom: 1px solid var(--border-light);
  }
  .chat-nav-strip .k-nav {
    flex-direction: row;
    overflow-x: auto;
    gap: 6px;
    /* The row scrolls rather than wrapping: one row at every width, and the
       same gesture the rest of the phone chrome already uses. */
    flex-wrap: nowrap;
  }
  .chat-nav-strip .k-nav a { flex: 0 0 auto; }
  .chat-nav-strip .k-nav-sep { display: none; }
}


/* ───────────────────────────────────────────────────────────────────────────
   20 · THE EMPTY CHAT.

   Mohammed, 2026-09-05: "can we make this attractive as well? like the
   minutes page - this looks a little bland - not too much but a little
   animation or something like that". It is the first thing a tester sees on
   /app and on /manzoori, it is what they look at while deciding whether to
   type anything at all, and it was a mark, two lines and a paragraph pinned
   to the top of a mostly empty column.

   APPEARANCE ONLY, ON THE CLASSES THAT ARE ALREADY THERE. `.welcome` and its
   four children are untouched in ChatPage.tsx, so nothing about the transcript,
   the composer, grading or the annotate flow can be disturbed by any of this.
   The two rings are `::before` and `::after` on the mark for the same reason:
   a ring that needs a <span> needs a JSX edit on the most-tested page pair in
   the portal, to draw a circle.

   THE MARK STAYS DARK. /audio's orb is amber because it is a BUTTON -- the one
   thing on that page you are meant to press. Nothing here is pressable, so
   copying the amber would put the page's loudest object on something inert and
   send every reader to press it. The rings and the halo carry the accent
   instead, which is what makes this the same language rather than the same
   picture.

   AND THE RINGS TAKE NO TAPS. `pointer-events: none`, because that is exactly
   how the record button on /audio spent Phase 27 being dead: decoration
   painted over content, hit-testing normally, swallowing every click in
   silence. Nothing is behind these two -- and they get the rule anyway, since
   the cost is nothing and the failure is invisible.
   ─────────────────────────────────────────────────────────────────────────── */

/* The welcome is CENTRED in the transcript column now, not stacked at the top
   of it. `.messages` is the element with a definite height to measure against
   -- `.chat-container` is the flex item that has one -- so the min-height goes
   there and `.welcome` grows into it. */
.messages { min-height: 100%; }

.welcome {
  flex: 1;
  justify-content: center;
  position: relative;
  /* Was 50vh with 50px of top padding, which is what pinned it high. */
  min-height: 0;
  padding: 24px;
}
/* The ambient wash, the same one `.k-canvas` carries behind its orb. Painted
   on the container rather than the mark so it stays put while the mark moves. */
.welcome::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(38% 42% at 50% 34%, var(--k-glow), var(--k-glow-out) 70%);
  opacity: .5;
}
.welcome > * { position: relative; z-index: 1; }

.welcome-logo {
  position: relative;
  border-radius: 26px;
  box-shadow: 0 18px 40px -18px rgba(44, 24, 16, .55);
  /* The breathing is declared with the stagger further down, because the two
     have to be ONE `animation` shorthand -- `.welcome > *` and `.welcome-logo`
     have equal specificity, so a second declaration up here would simply be
     overwritten by the stagger and the mark would sit still. */
}
.welcome-logo::before,
.welcome-logo::after {
  content: "";
  position: absolute;
  inset: -14px;
  border-radius: 50%;
  border: 1px solid var(--gold-500);
  pointer-events: none;
  animation: k-ripple 5.5s var(--k-ease) infinite;
}
.welcome-logo::after {
  inset: -28px;
  animation-delay: 1.8s;
}

.welcome-heading {
  font-size: clamp(26px, 3.4vw, 34px);
  letter-spacing: -.01em;
  /* A two-word second line under a five-word first is worse than an even
     split, and these headings are configuration -- they change per page. */
  text-wrap: balance;
}
/* THE ARABIC LINE IS THE ACCENT. It is the one piece of this screen that is
   the language the portal is FOR, so it carries the gold rather than sitting
   a shade below the heading in the same brown. Its face is untouched:
   `--font-lsd` is Kanz and invariant #9 is not negotiable. */
.welcome-arabic {
  font-size: 26px;
  color: var(--gold-600);
  margin-bottom: 18px;
}
.welcome-desc {
  max-width: 44ch;
  line-height: 1.72;
  color: var(--text-muted);
}

/* THE STAGGER, and it animates from a visible resting state onwards. `both`
   holds the FROM state before the delay elapses, which is the one thing that
   could leave this screen blank if an animation never fired -- so the delays
   are all under a fifth of a second and the guard at section 8 removes them
   entirely for anyone who has asked for less motion. */
.welcome > * { animation: k-rise var(--k-base) var(--k-ease) both; }
/* Barely there, and deliberately slow: this is ambience, not a status. The
   breathing orb on /audio runs at 2.8s because it IS telling you something is
   happening; nothing is happening here. */
.welcome-logo    { animation: k-breathe 7s ease-in-out infinite, k-rise var(--k-base) var(--k-ease) both; }
.welcome-heading { animation-delay: 60ms; }
.welcome-arabic  { animation-delay: 120ms; }
.welcome-desc    { animation-delay: 180ms; }

@media (max-width: 560px) {
  .welcome-arabic { font-size: 22px; }
  .welcome-logo::after { display: none; }
}
