/*
 * خوارزم (Khwarizm) — dual-mode warm-clay theme for LibreChat.
 *
 * WHAT THIS IS
 *   The warm-neutral re-skin this stack shipped for Open WebUI in release 1,
 *   ported to LibreChat: warm "paper" surfaces in light, warm charcoal in dark, one
 *   brand accent and a serif display face held constant across both modes. Only the
 *   neutrals flip between modes, so the two UIs read as the same product.
 *
 * HOW IT PROPAGATES (the key mechanism, verified against the running build)
 *   LibreChat themes itself in two layers. The bottom layer is a primitive gray ramp
 *   --gray-20 … --gray-900, defined once in :root. The top layer is a semantic set
 *   (--surface-*, --text-*, --border-*) defined twice: on `html` for light and on
 *   `.dark` for dark. Every semantic token resolves to a var(--gray-N) reference, and
 *   every Tailwind utility resolves to a semantic token — e.g.
 *     .bg-surface-primary{background-color:#fff;background-color:var(--surface-primary)}
 *   (the literal is a fallback for old browsers; the var declaration wins).
 *
 *   So redefining the gray ramp ONCE, with a warm ramp that preserves the original
 *   lightness order, recolours the app's neutrals in BOTH modes without chasing
 *   individual utilities: light mode reads the light end, dark mode reads the dark end.
 *
 *   The exception is the handful of LIGHT-mode tokens that resolve to var(--white)
 *   rather than to the ramp (--surface-primary, --surface-chat, --surface-tertiary-alt,
 *   --presentation). Those are re-pointed explicitly below. --white itself is left
 *   alone: it is the "on-dark" ink for inverted controls, and warming it would tint
 *   text rather than surfaces.
 *
 * WHERE MODE-SPECIFIC TOKENS GO
 *   LibreChat declares its per-mode tokens on `html` and `.dark`. `:root` is a
 *   pseudo-class (0,1,0) and `html` a type selector (0,0,1), so a `:root` block here
 *   outranks the build's `html` block on specificity alone. `.dark` ties `:root` at
 *   (0,1,0), so it is decided by source order — which is why every dark override in
 *   this file is written AFTER its light counterpart, and why any token that differs
 *   per mode must be declared in BOTH blocks. Declaring only `:root` would leak the
 *   light value into dark mode.
 *
 * SELECTORS AND TOKEN NAMES were read from the RUNNING build's index CSS and JS
 *   bundle, not guessed. Component hooks used here are IDs and data-testids
 *   (#send-button, [data-testid="new-chat-button"]), not generated class hashes,
 *   so they survive a rebuild of the image.
 *
 * FONTS: the image vendors Inter as woff2 under assets/fonts and already declares
 *   @font-face for it, so --kh-font-ui resolves to the real face with no work here.
 *   No serif is vendored, so the display face falls back to Georgia. This file
 *   references NO remote asset and adds NO @font-face — nothing is fetched.
 *   *** OWED: pre-stage Source Serif 4 (and Amiri / IBM Plex Sans Arabic for Arabic
 *   coverage) as woff2 plus an @font-face block, in a follow-up. Human pre-stage —
 *   fetching them here would be a network fence. ***
 *
 * This file is the LAST stylesheet the page loads, so these rules win over the build
 * without needing !important except where noted (inline styles / literal fallbacks).
 */

/* ── Brand tokens — light is the :root default ─────────────────────────────── */
:root {
	--kh-bg: #EDE9DE;
	--kh-sidebar: #E7E2D6;
	--kh-surface: #F4F1E8;
	--kh-border: #D9D3C4;
	--kh-text: #2A2824;
	--kh-text-2: #6E6A5F;
	--kh-text-3: #A39C8C;
	--kh-font-ui: 'Inter', 'IBM Plex Sans Arabic', system-ui, sans-serif;
	--kh-font-display: 'Source Serif 4', 'Amiri', Georgia, serif;
	--kh-radius: 16px;

	/* ACCENT — the logo's own hue (21°, sampled from docs/brand/icon.png), held at
	 * whatever lightness each job needs to clear WCAG AA. The mark's as-drawn orange
	 * #F7681A is NOT used directly anywhere: a near-white label on it is only 2.92:1,
	 * and it is only 2.68:1 against the light paper, so it fails both as a button
	 * fill and as a bare graphic. Same hue, darker, does both.
	 *
	 *   --kh-accent      fills controls whose LABEL IS TEXT, so it is measured
	 *                    against that label, not against the page:
	 *                    #FCFBF7 on #C84B07 = 4.54:1 (AA text)
	 *                    and it still reads as a shape: 4.16:1 on #F4F1E8
	 *   --kh-link        accent used AS TEXT directly on a surface, so it is measured
	 *                    against the worst surface it can land on — in light mode the
	 *                    DARKEST one (the sidebar rail), since the ink is dark:
	 *                    #AF4206 on #E7E2D6 = 4.51:1, on #F4F1E8 = 5.16:1 */
	--kh-accent: #C84B07;
	--kh-accent-hover: #AF4206;
	--kh-accent-tint: #F8EDE7;
	--kh-accent-on: #FCFBF7;
	--kh-link: #AF4206;
}

.dark {
	--kh-bg: #262624;
	--kh-sidebar: #1E1D1B;
	--kh-surface: #30302D;
	--kh-border: #3A3A37;
	--kh-text: #ECEAE3;
	--kh-text-2: #B5B0A6;
	--kh-text-3: #6E6A62;

	/* Same hue, lifted instead of darkened — on dark surfaces the accent has to be
	 * lighter than its label rather than darker:
	 *   #211F1B on #F65D09 = 5.08:1 (AA text on a filled control)
	 *   #F76D21 on #30302D = 4.52:1 (AA text for accent used as text; worst surface
	 *                                here is the LIGHTEST one, the ink being light) */
	--kh-accent: #F65D09;
	--kh-accent-hover: #F8732B;
	--kh-accent-tint: #331F15;
	--kh-accent-on: #211F1B;
	--kh-link: #F76D21;
}

/* ── Remap LibreChat's primitive gray ramp to a warm ramp ──────────────────────
 * Lightness order matches the stock ramp (20/50 lightest → 900 darkest), so every
 * contrast relationship the app was designed around is preserved; only the hue turns
 * warm. Anchored on the brand tokens: 20–300 = light surfaces and borders, 800–900 =
 * dark surfaces, the middle = shared secondary text. Mode-independent by design —
 * there is deliberately no .dark override here, because light and dark read opposite
 * ends of this one ramp. The dark end is pinned to the dark-mode brand tokens so
 * nothing falls back to the stock near-black (#0d0d0d), which reads cold next to the
 * warm surfaces. */
:root {
	--gray-20: #EDE9DE;
	--gray-50: #F4F1E8;
	--gray-100: #EDE9DE;
	--gray-200: #E7E2D6;
	--gray-300: #D9D3C4;
	--gray-400: #A39C8C;
	--gray-500: #6E6A5F;
	--gray-600: #4A463E;
	--gray-700: #3A3A37;
	--gray-800: #30302D;
	--gray-850: #262624;
	--gray-900: #1E1D1B;
}

/* ── Pin the three structural surfaces to their brand roles, in BOTH modes ─────
 * The ramp remap alone gets the incidental surfaces right, but it cannot get the
 * page/sidebar relationship right, because LibreChat's own ramp assignments do not
 * agree with khwarizm's depth order:
 *
 *   role            LibreChat token          khwarizm tone
 *   page            --surface-primary        --kh-bg
 *   sidebar rail    --surface-primary-alt    --kh-sidebar   (a step DEEPER than page)
 *   raised surface  --surface-secondary      --kh-surface   (a step LIGHTER than page)
 *
 * Stock light puts the rail (gray-50) LIGHTER than the page (white), and stock dark
 * puts the rail (gray-850) LIGHTER than the page (gray-900). khwarizm reads the other
 * way round in both modes — the rail recedes, the reading surface lifts — so these two
 * are assigned by role rather than left to the ramp. --surface-secondary already lands
 * on --kh-surface through the ramp; it is restated here so all three read together.
 *
 * The remaining two are light-mode tokens that resolve to var(--white) rather than to
 * the ramp, which the remap cannot reach at all: without them the chat column and the
 * preview surface would stay pure white inside an otherwise warm shell.
 * --presentation is LibreChat's slide/preview surface; it tracks the chat surface
 * upstream in both modes, so it does here too.
 *
 * Deliberately NOT overridden: --header-primary and --surface-dialog. LibreChat still
 * declares both, but nothing in the shipped build reads them — no utility class, no
 * var() reference — so setting them would be dead weight. The header and dialogs draw
 * their colour from the surface tokens above instead. */
html {
	--surface-primary: var(--kh-bg);
	--surface-primary-alt: var(--kh-sidebar);
	--surface-secondary: var(--kh-surface);
	--surface-chat: var(--kh-surface);
	--surface-tertiary-alt: var(--kh-surface);
	--presentation: var(--kh-surface);
}

/* Dark repeats the same role assignments. --surface-chat is pinned here too because
 * stock dark maps it to --gray-700, which after the remap is the border tone and reads
 * muddy behind message text; pinning it keeps the chat column one clear step above the
 * page, matching the light-mode split. */
.dark {
	--surface-primary: var(--kh-bg);
	--surface-primary-alt: var(--kh-sidebar);
	--surface-secondary: var(--kh-surface);
	--surface-chat: var(--kh-surface);
	--presentation: var(--kh-surface);
}

/* ── Base page + UI font ──────────────────────────────────────────────────────
 * The font flows to descendants; LibreChat sets no competing family on body. */
body {
	background-color: var(--kh-bg);
	color: var(--kh-text);
	font-family: var(--kh-font-ui);
}

/* ── Markdown links → accent, AA-split per mode ────────────────────────────────
 * The typography plugin funnels every prose link through one variable, so this is
 * the whole job — no per-element selectors needed. Underline is already applied by
 * the plugin, so colour is not the only cue. */
.prose {
	--tw-prose-links: var(--kh-link);
	--tw-prose-invert-links: var(--kh-link);
}

/* ══ BUTTONS ═══════════════════════════════════════════════════════════════════
 * LibreChat draws a "primary action" from four independent sources, all of them
 * some shade of the upstream green/teal. Recolouring one and not the others is what
 * leaves an app half-rebranded, so all four are handled here.
 *
 *   1. .btn-primary / .btn-secondary   hard-coded #10a37f / #10a39c, no var to reach
 *   2. --surface-submit(-hover)        semantic, resolves to --green-700 / --green-800
 *   3. shadcn --primary                bare HSL channels; drives checkboxes, radios,
 *                                      spinners and .ring-primary
 *   4. auth-screen greens              bg-green-500 / hover:bg-green-600 etc., applied
 *                                      as extra utilities ON TOP of .btn-primary
 *
 * The green ramp itself (--green-100…900) is deliberately NOT remapped: it also
 * carries success states — "Saved!" toasts, validation ticks — where green is
 * meaning, not branding. Only the action tokens move.
 *
 * Destructive controls keep the red ramp untouched, for the same reason. */

/* 1 + 4. The .btn base already supplies padding, radius and transition; only colour
 * changes. Doubling the class to .btn.btn-primary lifts these to (0,2,0), which
 * outranks the single-class utilities the auth screen piles on top
 * (.bg-green-500, .text-white) without resorting to !important. The :hover rules
 * reach (0,3,0) for the same reason against .hover\:bg-green-600:hover at (0,2,0). */
.btn.btn-primary {
	background-color: var(--kh-accent);
	color: var(--kh-accent-on);
	border-color: transparent;
}

.btn.btn-primary:hover:not(:disabled) {
	background-color: var(--kh-accent-hover);
	border-color: transparent;
}

/* The auth screen's only "disabled" cue is `disabled:bg-green-400` — a paler green.
 * That utility ties this file's rule on specificity and loses on source order, so
 * without restating it a disabled submit would look exactly like a live one. */
.btn.btn-primary:disabled {
	background-color: var(--kh-accent);
	opacity: 0.5;
}

/* An icon inside a primary button inherits `currentColor` in most of the build, but
 * a few carry their own text utility; pinning fill/stroke keeps the glyph on the
 * label ink rather than on a neutral that would vanish into the accent. */
.btn.btn-primary svg {
	color: var(--kh-accent-on);
}

/* 1b. .btn-secondary sits next to a primary in the same button row (Save beside
 * Save & Submit). Giving it the accent as an OUTLINE keeps the hierarchy legible —
 * one solid action, one quiet one — instead of two competing fills. */
.btn.btn-secondary {
	background-color: transparent;
	border-color: var(--kh-accent);
	color: var(--kh-link);
}

.btn.btn-secondary:hover:not(:disabled) {
	background-color: var(--kh-accent-tint);
	color: var(--kh-link);
}

/* 2. The submit surface, used by the agent builder and other confirm dialogs. Both
 * modes are declared because the build sets this token on `html` AND `.dark`. */
:root {
	--surface-submit: var(--kh-accent);
	--surface-submit-hover: var(--kh-accent-hover);
}

.dark {
	--surface-submit: var(--kh-accent);
	--surface-submit-hover: var(--kh-accent-hover);
}

/* 3. shadcn's --primary pair. These are consumed as hsl(var(--primary)), so they
 * must be BARE CHANNELS, not colours — hence the literals rather than var(--kh-*).
 * Values are the same colours as the accent tokens above:
 *   light  21 93% 41% = #C84B07 with 48 45% 98% = #FCFBF7 on it → 4.54:1
 *   dark   21 93% 50% = #F65D09 with 40 10% 12% = #211F1B on it → 5.08:1
 * Reaching this pair is what turns checkboxes, radios and loading spinners accent
 * rather than leaving them the stock near-black/near-white. */
:root {
	--primary: 21 93% 41%;
	--primary-foreground: 48 45% 98%;
}

.dark {
	--primary: 21 93% 50%;
	--primary-foreground: 40 10% 12%;
}

/* ── The auth screens' green, WITHOUT touching green as a status colour ───────
 * The sign-in and register screens are the last green holdout: their links, their
 * focused input borders and their floating labels all carry raw green utilities
 * rather than any token, so nothing above reaches them.
 *
 * These CANNOT be fixed by remapping the --green-* ramp, and the utilities cannot be
 * overridden globally either, because the very same classes carry MEANING elsewhere
 * in the app:
 *
 *   .bg-green-500      the "connected" dot on an MCP server — sibling to
 *                      bg-blue-500 / bg-red-500 / bg-gray-400 for the other states
 *   .bg-green-100 …    the usage badge — sibling to bg-yellow-100 / bg-red-100 on
 *                      the same scale
 *   .border-green-500  success alerts, several with role="alert"
 *
 * Turning those orange would delete the distinction they exist to draw. What
 * separates the two groups is not the class but the ELEMENT: the branding greens
 * land on <a>, <input> and <label>, the status greens on <div>. Qualifying by tag
 * therefore hits every branded green and no semantic one — and it also gains a
 * point of specificity over the bare utility, so no !important is needed.
 *
 * Read as a rule: a green LINK or a green FOCUS RING is branding and becomes the
 * accent; a green DOT, BADGE or ALERT is a status and stays green. */
a.text-green-600 {
	color: var(--kh-link);
}

a.hover\:text-green-700:hover,
a.focus\:text-green-700:focus {
	color: var(--kh-accent-hover);
}

/* Underline colour is animated from transparent on hover/focus, so it has to track
 * the text colour or the link would underline itself in the old teal. */
a.hover\:decoration-green-700:hover,
a.focus\:decoration-green-700:focus {
	text-decoration-color: var(--kh-accent-hover);
}

.dark a.dark\:text-green-500,
.dark a.dark\:text-green-400 {
	color: var(--kh-link);
}

.dark a.dark\:hover\:text-green-400:hover,
.dark a.dark\:hover\:text-green-300:hover,
.dark a.dark\:focus\:text-green-400:focus {
	color: var(--kh-accent-hover);
}

.dark a.dark\:hover\:decoration-green-400:hover,
.dark a.dark\:focus\:decoration-green-400:focus {
	text-decoration-color: var(--kh-accent-hover);
}

/* The focused email/password fields. Both states are listed because the build sets
 * :focus and :focus-visible separately on the same input. */
input.focus\:border-green-500:focus,
input.focus-visible\:border-green-500:focus-visible {
	border-color: var(--kh-accent);
}

/* The floating label that lifts and recolours when its input takes focus. Compiled
 * by Tailwind as a sibling combinator off .peer, so the tag qualifier goes on the
 * label at the end of the selector. */
.peer:focus ~ label.peer-focus\:text-green-600,
.peer:focus ~ label.peer-focus\:text-green-500 {
	color: var(--kh-link);
}

.dark .peer:focus ~ label.dark\:peer-focus\:text-green-500 {
	color: var(--kh-link);
}

/* ── Send button → accent ─────────────────────────────────────────────────────
 * Stock is a filled circle that inverts per mode: .bg-text-primary with a
 * .text-surface-primary glyph. Filling it with the accent makes "send" the one
 * solid-weight action in the composer, in both modes, and the glyph rides on
 * --kh-accent-on for contrast rather than on the (now warm) surface token. */
#send-button {
	background-color: var(--kh-accent);
	color: var(--kh-accent-on);
}

#send-button:hover:not(:disabled) {
	background-color: var(--kh-accent-hover);
}

/* The glyph is an svg carrying .text-surface-primary, which would otherwise repaint
 * it the page colour and erase it against the accent fill. */
#send-button svg {
	color: var(--kh-accent-on);
}

/* ── New chat → accent-outlined pill ──────────────────────────────────────────
 * Stock is a plain hover-wash icon square. Outlining rather than filling it marks
 * "start a new chat" as the primary entry action while reserving solid weight for
 * send. --kh-accent flips per mode, so one rule covers light and dark. */
[data-testid='new-chat-button'] {
	border: 1px solid var(--kh-accent);
	color: var(--kh-link);
}

[data-testid='new-chat-button']:hover {
	background-color: var(--kh-accent-tint);
}

[data-testid='new-chat-button'] svg {
	color: var(--kh-link);
}

/* ── Focus rings → accent ─────────────────────────────────────────────────────
 * --ring is LibreChat's shadcn ring token and expects bare HSL channels, so it gets
 * the accent expressed that way; --ring-primary takes a normal colour. outline-color
 * covers the controls that ring via outline instead. */
:root {
	--ring: 21 93% 41%;
	--ring-primary: var(--kh-accent);
}

.dark {
	--ring: 21 93% 50%;
}

*:focus-visible {
	outline-color: var(--kh-accent);
}

/* ── Brand marks ──────────────────────────────────────────────────────────────
 * The auth screen hard-codes <img src="assets/logo.svg">, so the lockup is swapped
 * via `content`, which replaces an element's rendered box without the build knowing.
 * Both files are the same artwork; only the ink differs (see librechat-config/brand/).
 *
 * BOTH modes are swapped, including light — which looks redundant, since the shim
 * also overwrites assets/logo.svg with that very artwork. It is not. That URL never
 * changes and dist assets are served with a two-day max-age, so a browser that had
 * loaded the stock UI kept painting the OLD LibreChat mark from disk cache. The bug
 * was visible in light mode only, because dark already pointed at a filename stock
 * never shipped and so could not be stale. Pointing both modes at names introduced
 * by this theme makes the swap immune to that cache by construction.
 *
 * The app icons — favicons, apple-touch, maskable — are NOT swapped: they are
 * painted by the browser onto tab strips and launchers whose colour this stylesheet
 * neither controls nor can predict, so they ship on an opaque brand-cream tile. */
img[src$='logo.svg'] {
	content: url('/assets/khwarizm-logo.svg');
}

.dark img[src$='logo.svg'] {
	content: url('/assets/khwarizm-logo-dark.svg');
}

/* The auth screen boxes that lockup in a `h-10 w-full` wrapper and fits the image
 * with object-contain. Upstream's mark is a wide horizontal wordmark, so 40px of
 * height suits it; khwarizm's is a squarer lockup, which the same box shrinks to
 * roughly 54px wide — legible in a favicon, not as the only brand element on the
 * first screen anyone sees.
 *
 * The WRAPPER is resized rather than the image: the image is already told to fill
 * its parent and contain itself, so it follows the box and keeps its aspect ratio
 * on its own. clamp() lets it grow on a laptop without crowding a phone, where the
 * form matters more than the mark. Mode-independent — the two inks are the same
 * artwork at the same size.
 *
 * :has() carries the specificity of its argument, so this is (0,1,2) against the
 * utility's (0,1,0) and wins without !important. */
div:has(> img[src$='logo.svg']) {
	height: clamp(4.5rem, 13vw, 7rem);
}

/* Same swap for the square mark in the guide panel's header. */
.dark .kh-guide-head img {
	content: url('/assets/khwarizm-mark-dark.svg');
}

/* ── The mark as the sidebar's permanent presence ─────────────────────────────
 * The rail's top control is one button in two states — data-testid flips between
 * open-sidebar-button and close-sidebar-button as it toggles — and the rail itself
 * is on screen whether the sidebar is expanded or collapsed. Putting the mark there
 * is therefore what makes the logo ALWAYS visible, in both states and at every
 * breakpoint, without adding an element for React to reconcile away.
 *
 * The stock chevron is hidden rather than removed, and the mark is painted as a
 * background on the button itself. That leaves the button's own accessible name
 * ("Open sidebar" / "Close sidebar"), its tooltip and its keyboard shortcut fully
 * intact — the control still announces what it DOES, not what it depicts, so
 * nothing is lost by the glyph changing. */
[data-testid='open-sidebar-button'],
[data-testid='close-sidebar-button'] {
	background-image: url('/assets/khwarizm-mark.svg');
	background-repeat: no-repeat;
	background-position: center;
	/* Proportional, not a pixel pair: the rail's toggle is a `size="icon"` button
	 * whose box upstream is free to change, and a fixed 24px both read small inside
	 * it and would clip if the button ever shrank. A percentage of the padding box
	 * fills the control as far as it can go while leaving the mark visually inset. */
	background-size: 76%;
}

.dark [data-testid='open-sidebar-button'],
.dark [data-testid='close-sidebar-button'] {
	background-image: url('/assets/khwarizm-mark-dark.svg');
}

[data-testid='open-sidebar-button'] svg,
[data-testid='close-sidebar-button'] svg {
	visibility: hidden;
}

/* ── Display face for the greeting ────────────────────────────────────────────
 * The landing greeting has no id of its own — it is rendered by the split-text
 * animator, which passes through only a size class that varies with the greeting's
 * length (text-2xl / text-xl / text-lg). Its container is the stable hook: the
 * .transform-gpu.pb-16 pairing appears exactly once in the bundle, on the landing
 * view. plaintext bidi lets an Arabic greeting resolve RTL from its first strong
 * character without flipping the Latin UI around it. */
.transform-gpu.pb-16 :is(.text-lg, .text-xl, .text-2xl) {
	font-family: var(--kh-font-display);
	unicode-bidi: plaintext;
}

/* ── Footer on the pre-login screens ──────────────────────────────────────────
 * Everywhere the user is signed in, the footer text comes from CUSTOM_FOOTER and
 * needs nothing here. The LOGIN and REGISTER screens are the exception: LibreChat's
 * /api/config builds a separate payload for anonymous callers that has no path to
 * customFooter at all, so those screens fall back to upstream's hard-coded
 * "[LibreChat vX](https://librechat.ai) - Every AI for Everyone" regardless of how
 * the variable is set. That is the first screen anyone sees.
 *
 * Scoped by :has() on the auth lockup, which is the one element that renders on the
 * auth screens and nowhere else — so this cannot reach the signed-in footer, where
 * the real text is already correct. (The dark-mode rule above swaps that image's
 * rendered content, not its src, so the match holds in both modes.)
 *
 * The text itself is NOT written here. It comes from --kh-footer, which the startup
 * shim generates from the same CUSTOM_FOOTER value the app uses, keeping the release
 * number to a single source of truth. The literal below is only a fallback for the
 * case where the variable is unset. */
body:has(img[src$='logo.svg']) [role='contentinfo'] > * {
	display: none;
}

body:has(img[src$='logo.svg']) [role='contentinfo']::after {
	content: var(--kh-footer, 'Khwarizm');
	color: var(--kh-text-2);
}

/* ── One theme control, and no guide, on the auth screens ─────────────────────
 * The auth layout mounts its own theme toggle in an `absolute bottom-0 left-0`
 * wrapper. With this theme's dock in the opposite corner that put TWO mode switches
 * on the login and register screens; upstream's is the one that goes, since the dock
 * also carries the guide and has to stay for the signed-in app.
 *
 * The guide button is hidden here too. It describes what to do once you are inside —
 * uploading documents, picking agents — none of which is actionable before sign-in,
 * and the panel's own auto-open already waits for the composer for that reason.
 *
 * Both are scoped through the auth lockup rather than decided once at boot, and that
 * is the point: sign-in is a CLIENT-SIDE navigation, so nothing reloads when the app
 * takes over. A boot-time choice would strand the guide button hidden for the whole
 * session. :has() re-evaluates as the lockup unmounts, so the dock returns to full
 * strength the moment the app mounts, with no route watching in JS.
 *
 * The margin utility is part of the selector on purpose. Four other elements in the
 * bundle carry absolute+bottom-0+left-0 — a scrim, a tab underline, a caption bar —
 * and while none of them render on an auth screen, matching the toggle's own
 * `md:m-4` means this rule cannot start hiding one of them if that ever changes. */
body:has(img[src$='logo.svg']) .absolute.bottom-0.left-0.md\:m-4,
body:has(img[src$='logo.svg']) #khwarizm-help {
	display: none;
}

/* ── Composer: warm edge, shared corner radius ────────────────────────────────
 * The composer should read as a raised writing surface rather than as part of the
 * page. bg-surface-chat appears exactly once in the bundle — on the composer shell —
 * so it needs no further qualifying. Stock already rounds it; the radius here only
 * aligns it with --kh-radius so composer and dialogs share one corner. */
.bg-surface-chat {
	border-color: var(--kh-border);
	border-radius: var(--kh-radius);
}

/* ══ ONBOARDING GUIDE ══════════════════════════════════════════════════════════
 * Styles for the panel injected by khwarizm-guide.js. Both elements are appended to
 * <body> and never enter React's tree, so nothing here can be clobbered by a
 * re-render — see the header of that file for why that placement was chosen.
 *
 * Everything below resolves through the brand tokens above, so the panel follows
 * light/dark with the rest of the app and needs no palette of its own. */

/* The persistent controls. Fixed to the viewport's end-bottom corner, which is empty
 * in the chat view at every breakpoint the app supports; logical properties
 * (inset-inline-end) keep them on the correct side if the document ever goes RTL. */
#khwarizm-dock {
	position: fixed;
	inset-inline-end: 1rem;
	inset-block-end: 1rem;
	z-index: 60;
	display: flex;
	align-items: center;
	gap: 0.4rem;
}

#khwarizm-dock button {
	display: flex;
	align-items: center;
	gap: 0.45rem;
	padding: 0.5rem 0.85rem;
	border: 1px solid var(--kh-accent);
	border-radius: 999px;
	background-color: var(--kh-surface);
	color: var(--kh-link);
	font-family: var(--kh-font-ui);
	font-size: 0.8125rem;
	font-weight: 500;
	line-height: 1;
	cursor: pointer;
	box-shadow: 0 1px 3px rgb(0 0 0 / 0.12);
	transition: background-color 0.12s ease-in-out;
}

#khwarizm-dock button:hover {
	background-color: var(--kh-accent-tint);
}

/* Icon-only, so it stays square rather than inheriting the labelled pill's padding. */
#khwarizm-theme {
	padding: 0.5rem;
}

/* Which glyph shows is a pure function of the active mode, so it is decided in CSS
 * off the same `.dark` class the app toggles — no script has to keep it in step.
 * Each button shows the mode it would switch TO. */
#khwarizm-theme .kh-sun {
	display: none;
}

.dark #khwarizm-theme .kh-sun {
	display: block;
}

.dark #khwarizm-theme .kh-moon {
	display: none;
}

/* Below the composer's mobile breakpoint the label would crowd the send button, so
 * the guide control collapses to its glyph alone. */
@media (max-width: 640px) {
	#khwarizm-help .kh-help-label {
		display: none;
	}

	#khwarizm-help {
		padding: 0.5rem;
	}
}

/* The panel is a native <dialog>, which brings the top layer, Esc-to-close, focus
 * trapping and inertness of the page behind it without a line of script. */
#khwarizm-guide {
	width: min(46rem, calc(100vw - 2rem));
	max-height: min(44rem, calc(100vh - 4rem));
	padding: 0;
	border: 1px solid var(--kh-border);
	border-radius: var(--kh-radius);
	background-color: var(--kh-surface);
	color: var(--kh-text);
	font-family: var(--kh-font-ui);
	overflow: hidden;
}

#khwarizm-guide::backdrop {
	background-color: rgb(0 0 0 / 0.45);
}

.kh-guide-shell {
	display: flex;
	flex-direction: column;
	max-height: inherit;
}

.kh-guide-head {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	padding: 1.1rem 1.35rem;
	border-block-end: 1px solid var(--kh-border);
	background-color: var(--kh-bg);
}

.kh-guide-head img {
	width: 2rem;
	height: 2rem;
	flex: none;
}

.kh-guide-title {
	margin: 0;
	font-family: var(--kh-font-display);
	font-size: 1.15rem;
	font-weight: 600;
	line-height: 1.2;
}

.kh-guide-sub {
	margin: 0.15rem 0 0;
	color: var(--kh-text-2);
	font-size: 0.8125rem;
	line-height: 1.3;
}

/* Pushed to the end of the header row so the close control lands in the corner the
 * eye already checks, in either writing direction. */
.kh-guide-x {
	margin-inline-start: auto;
	flex: none;
	padding: 0.35rem 0.6rem;
	border: 0;
	border-radius: 8px;
	background: transparent;
	color: var(--kh-text-2);
	font-size: 1.1rem;
	line-height: 1;
	cursor: pointer;
}

.kh-guide-x:hover {
	background-color: var(--kh-accent-tint);
	color: var(--kh-link);
}

.kh-guide-body {
	padding: 0.35rem 1.35rem 1.1rem;
	overflow-y: auto;
	font-size: 0.875rem;
	line-height: 1.6;
}

.kh-guide-body section {
	padding-block: 0.9rem;
	border-block-end: 1px solid var(--kh-border);
}

.kh-guide-body section:last-of-type {
	border-block-end: 0;
}

.kh-guide-body h3 {
	display: flex;
	align-items: baseline;
	gap: 0.55rem;
	margin: 0 0 0.35rem;
	font-size: 0.95rem;
	font-weight: 600;
}

/* The step number is a counter chip rather than a list marker, so the heading text
 * stays a real heading for assistive tech. */
.kh-guide-n {
	flex: none;
	min-width: 1.4rem;
	padding: 0.1rem 0;
	border-radius: 999px;
	background-color: var(--kh-accent-tint);
	color: var(--kh-link);
	font-size: 0.75rem;
	font-weight: 600;
	text-align: center;
}

.kh-guide-body p {
	margin: 0 0 0.4rem;
	color: var(--kh-text-2);
}

.kh-guide-body ul {
	margin: 0.2rem 0 0;
	padding-inline-start: 1.1rem;
	color: var(--kh-text-2);
}

.kh-guide-body li {
	margin-block: 0.15rem;
}

.kh-guide-body strong {
	color: var(--kh-text);
	font-weight: 600;
}

/* Inline keycaps and UI labels. Uses the border tone rather than the accent so a
 * paragraph with several of them does not turn into a field of orange. */
.kh-guide-body kbd {
	padding: 0.05rem 0.35rem;
	border: 1px solid var(--kh-border);
	border-radius: 5px;
	background-color: var(--kh-bg);
	color: var(--kh-text);
	font-family: inherit;
	font-size: 0.8em;
	white-space: nowrap;
}

.kh-guide-foot {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	padding: 0.85rem 1.35rem;
	border-block-start: 1px solid var(--kh-border);
	background-color: var(--kh-bg);
}

.kh-guide-note {
	color: var(--kh-text-3);
	font-size: 0.75rem;
	line-height: 1.35;
}

.kh-guide-done {
	margin-inline-start: auto;
	flex: none;
	padding: 0.5rem 1.1rem;
	border: 1px solid transparent;
	border-radius: 999px;
	background-color: var(--kh-accent);
	color: var(--kh-accent-on);
	font-family: inherit;
	font-size: 0.8125rem;
	font-weight: 500;
	cursor: pointer;
}

.kh-guide-done:hover {
	background-color: var(--kh-accent-hover);
}
