/* CroweLM chat theme layer, served at /static/custom.css (linked from app.html).
 *
 * Source of truth for this file is the Crowe Logic Desktop renderer
 * (crowe-logic-desktop/renderer/styles.css). The desktop app carries the
 * canonical two-theme token set, so rather than invent a second palette here,
 * this layer ports those tokens 1:1 and maps them onto the surfaces OWUI
 * actually paints. One brand, one ladder, two products.
 *
 * What tailwind already handles: tailwind.config.js maps the gray ladder to the
 * Crowe neutrals and aliases blue/sky/indigo/emerald/primary to the gold ramp,
 * so most component colour lands at build time. What tailwind cannot reach is
 * literal `white` and `black`, which OWUI uses for light-scheme surfaces and for
 * the primary buttons on the auth page an anonymous visitor lands on first.
 * That is most of the work below.
 *
 * Token names are prefixed --clm- rather than carried over bare. The desktop
 * owns its namespace; a chat app embedding a fork does not, and names like
 * --line or --panel are too generic to claim inside someone else's stylesheet.
 * The correspondence is otherwise exact, so a change upstream in the desktop
 * tokens is a find-and-replace here, not a re-derivation.
 *
 * Scope discipline: selectors are either token declarations, tailwind utility
 * classes OWUI demonstrably emits, or ARIA/semantic roles. No deep component
 * targeting against OWUI internals, which churn on every upstream merge and
 * would make this file a maintenance debt rather than a theme.
 */

/* ── Typeface: self-hosted, no third-party font CDN ────────────────────────
 * The previous revision of this file pulled Fraunces/Inter/JetBrains Mono from
 * fonts.googleapis.com. Those are the same three variable faces already
 * shipping inside the desktop app, so they are served from /static/fonts here
 * instead: one less third-party origin on a page behind auth, no layout shift
 * waiting on a CDN, and the licence files sit beside them. */
@font-face {
	font-family: 'Fraunces';
	src: url('/static/fonts/fraunces-var.woff2') format('woff2-variations');
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: 'Inter';
	src: url('/static/fonts/inter-var.woff2') format('woff2-variations');
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: 'JetBrains Mono';
	src: url('/static/fonts/jetbrains-mono-var.woff2') format('woff2-variations');
	font-weight: 100 800;
	font-style: normal;
	font-display: swap;
}

/* ── Paper theme (light) ───────────────────────────────────────────────────
 * Ported from the desktop `:root`. Note this is a neutral paper, not the warm
 * parchment the earlier CroweLM layer used. The desktop settled on neutral
 * deliberately: warmth is the accent's job, and gold can only read as warm
 * against something that is not. */
:root {
	--clm-cream: #f6f6f8; /* page */
	--clm-panel: #fafafc; /* raised one step */
	--clm-raised: #ffffff; /* cards, menus, the top step */
	--clm-ink: #17171a;
	--clm-muted: #63636c;

	--clm-gold: #b8893a;
	--clm-gold-soft: #c9a227;
	--clm-gold-deep: #8c682c;
	/* Gold as text needs the darker step on paper to clear 4.5:1; as a border or
	   a fill it does not. Focus indicators need 3:1 against the surface they sit
	   on, which the mid gold does not manage on paper either. */
	--clm-gold-text: var(--clm-gold-deep);
	--clm-focus: var(--clm-gold-deep);
	--clm-blue: #2e5aad;

	--clm-line: rgba(23, 23, 26, 0.11);
	--clm-line-strong: rgba(23, 23, 26, 0.22);
	--clm-hair-top: rgba(255, 255, 255, 0.65);

	--clm-z1: 0 1px 2px rgba(26, 23, 20, 0.05);
	--clm-z2: 0 1px 2px rgba(26, 23, 20, 0.05), 0 10px 28px -14px rgba(26, 23, 20, 0.18),
		inset 0 1px 0 var(--clm-hair-top);
	--clm-z3: 0 2px 4px rgba(26, 23, 20, 0.06), 0 22px 56px -20px rgba(26, 23, 20, 0.26),
		inset 0 1px 0 var(--clm-hair-top);

	--clm-code-bg: rgba(26, 23, 20, 0.06);
	--clm-overlay: rgba(26, 23, 20, 0.38);
	--clm-accent-wash: rgba(184, 137, 58, 0.12);
	--clm-focus-halo: rgba(184, 137, 58, 0.16);
	--clm-selection: rgba(184, 137, 58, 0.26);
	--clm-on-gold: #14110c;

	--clm-display: 'Fraunces', Georgia, serif;
	--clm-body: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
	--clm-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
	--clm-ease: cubic-bezier(0.16, 1, 0.3, 1);
}

/* ── Graphite theme (dark) ─────────────────────────────────────────────────
 * Ported from the desktop `body.dark`. The greys are grey, a hair cool, so they
 * read as neutral rather than beige; the gold is the only warm thing on screen.
 * Depth comes from stepped surfaces and a lit top hairline, not heavier shadow,
 * so each step lightens slightly and shadows go near-black rather than
 * warm-black. */
html.dark {
	--clm-cream: #0a0a0b;
	--clm-panel: #141416;
	--clm-raised: #1c1c1f;
	--clm-ink: #e9e9ec;
	--clm-muted: #9a9aa2;

	--clm-gold: #d2ad62;
	--clm-gold-soft: #e7c984;
	--clm-gold-deep: #9a7e44;
	--clm-gold-text: var(--clm-gold);
	--clm-focus: var(--clm-gold);
	--clm-blue: #6b9be0;

	--clm-line: rgba(255, 255, 255, 0.08);
	--clm-line-strong: rgba(255, 255, 255, 0.16);
	--clm-hair-top: rgba(255, 255, 255, 0.13);

	--clm-z1: 0 1px 2px rgba(3, 3, 5, 0.42);
	--clm-z2: 0 4px 12px rgba(3, 3, 5, 0.48), inset 0 1px 0 var(--clm-hair-top);
	--clm-z3: 0 12px 32px rgba(3, 3, 5, 0.54), inset 0 1px 0 var(--clm-hair-top);

	--clm-code-bg: rgba(233, 233, 236, 0.08);
	--clm-overlay: rgba(4, 4, 6, 0.64);
	--clm-accent-wash: rgba(210, 173, 98, 0.14);
	--clm-focus-halo: rgba(210, 173, 98, 0.22);
	--clm-selection: rgba(210, 173, 98, 0.32);
}

/* ── Type ──────────────────────────────────────────────────────────────────
 * OWUI sets its font stack on body, so this is a straight substitution rather
 * than an override war. Headings take the display face; anything monospaced
 * (code, pre, the model id chips) takes JetBrains Mono.
 *
 * The h1/h2/h3 rule below only ever reached MARKDOWN, never the app. OWUI
 * builds every heading in its own chrome as a `div` carrying size utilities:
 * the sign-in title is `<div class="text-2xl font-normal">`, and a live page
 * contains ZERO h1, h2 or h3 elements. So Fraunces downloaded on every visit
 * and painted nothing, and the app read as unbranded while the stylesheet,
 * the woff2 files and every server-side check all looked correct.
 *
 * The fix is an explicit `clm-display` hook applied in the components at the
 * three places the product actually speaks in its own voice: the sign-in
 * title, the new-chat greeting, and the model name above a fresh thread.
 * Chasing `.text-2xl` instead would have been the same mistake one layer
 * down, since that class is also on ordinary body copy. */
body {
	font-family: var(--clm-body);
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}
h1,
h2,
h3,
.clm-display {
	font-family: var(--clm-display);
	font-weight: 600;
	letter-spacing: -0.01em;
}
code,
pre,
kbd,
samp,
.font-mono {
	font-family: var(--clm-mono);
}
:not(pre) > code {
	background-color: var(--clm-code-bg);
	border-radius: 4px;
	padding: 0.1em 0.35em;
}

/* ── Surfaces ─────────────────────────────────────────────────────────────
 * Deliberately almost empty. An earlier version of this file carried a stack
 * of !important overrides for .bg-white, .bg-gray-50 and .text-black, on the
 * theory that tailwind could not reach the literals. That was true of tailwind
 * 3. Since 0.11 the palette is CSS-first and `--color-white` / `--color-black`
 * are theme tokens, so those surfaces are set correctly at build time in
 * src/tailwind.css and overriding them here would just fight the build.
 *
 * It also fixes a class of miss the override approach could never catch: 0.11's
 * auth page uses `dark:bg-black`, which is the class token `dark:bg-black` and
 * is therefore not matched by a `.bg-black` selector at all. */
body {
	background-color: var(--clm-cream);
}

/* Primary action. Targeted by role rather than by utility class, because the
 * utility churns: 0.9 used a solid `bg-gray-900` fill here and 0.11 uses a
 * translucent `bg-gray-700/5`. A previous revision of this file chased the 0.9
 * class names and silently matched nothing after the redesign, which is how a
 * theme can be served correctly and still look entirely unbranded. A submit
 * button inside the auth form is the one control on that screen that should
 * carry the accent, and it stays that regardless of how it is painted. */
form button[type='submit'] {
	background-color: var(--clm-gold) !important;
	color: var(--clm-on-gold) !important;
	font-weight: 600;
}
form button[type='submit']:hover {
	background-color: var(--clm-gold-soft) !important;
}

/* Splash. app.html hardcodes #fff / #000 in a trailing inline <style>, which
 * wins on source order, so these two carry !important by necessity. Without
 * them the first paint of every cold load is a white or pure-black flash before
 * the app's own surfaces arrive. */
html:not(.dark) #splash-screen {
	background-color: var(--clm-cream) !important;
}
html.dark #splash-screen {
	background-color: var(--clm-cream) !important;
}

/* ── The logotype ─────────────────────────────────────────────────────────
 * The sidebar brand is typed text set in the UI font, which means the app
 * spells the name rather than signs it. The desktop draws it instead: Fraunces
 * with the mycelial rotor set into both `o` counters and a gold rotor for the
 * tittle on the `i`. That file is the brand, so the sidebar wears it here too.
 *
 * The text stays in the DOM and is pushed out of view rather than removed, so
 * the accessible name of the link is still the product name. Two files rather
 * than a mask: the gold tittle has to survive the theme switch, and a mask
 * would flatten it to one colour.
 *
 * Tradeoff worth stating: WEBUI_NAME is admin-configurable for white-label
 * deployments, and this rule will show the Crowe logotype whatever it is set
 * to. That is correct for this fork and wrong for a resold instance, so the
 * white-label applicator should drop this block rather than override it. */
#sidebar-webui-name {
	width: 92px;
	height: 21px;
	text-indent: -200vw;
	overflow: hidden;
	white-space: nowrap;
	background: url('/static/brand/wordmark.svg') left center / contain no-repeat;
}
html.dark #sidebar-webui-name {
	background-image: url('/static/brand/wordmark-dark.svg');
}

/* ── The assistant avatar ─────────────────────────────────────────────────
 * The default profile image is favicon.png, which is the mark with ink arms.
 * On a graphite page those arms disappear and the avatar reduces to a floating
 * gold dot, which is the brand at its least recognisable. The dark variant
 * already ships, so dark mode swaps to it.
 *
 * The two `/static/favicon.png` selectors below were written to be narrow, and
 * were so narrow they matched nothing that mattered. An assistant avatar is
 * NOT served from that path: both the message avatar and the new-chat
 * placeholder point at `/api/v1/models/model/profile/image?id=...&lang=...`,
 * which ends in a query string, so a `[src$=...]` match can never fire. The
 * result was the exact failure the paragraph above describes as already
 * handled: on a graphite page the arms vanished and the avatar reduced to a
 * floating gold dot. Once the mark started turning, that dot was all that
 * turned.
 *
 * Matching the endpoint fixes both surfaces. Tradeoff worth stating: a model
 * carrying its OWN profile image goes through the same endpoint, so this swaps
 * that too in dark mode. Every model here uses the default mark, and a wrong
 * avatar is a smaller harm than an invisible one, but a resold instance with
 * per-model art should drop this selector. */
html.dark img[src*='/models/model/profile/image'],
html.dark img[src$='/static/favicon.png'],
html.dark img[src$='/favicon.png'] {
	content: url('/static/favicon-dark.png');
}

/* ── The whirl ────────────────────────────────────────────────────────────
 * The mark is a rotor: six mycelial arms around a gold hub. It is already the
 * thing on screen while the model works, so it turns rather than sitting
 * still, and the app has one idea of "thinking" instead of a borrowed dot.
 *
 * Only the waiting moment, for now. A streaming spin was built and pulled: it
 * has to end on `message.done`, and on this deployment `done` never becomes
 * true, so the mark span forever after the answer had finished. The same stuck
 * flag is why the copy/regenerate row never appears. Once that is fixed, the
 * spin can extend through streaming by putting `clm-thinking` back on the
 * avatar (and, under @lg, on the model-name row, since the avatar is hidden
 * there). This whirl is safe because Skeleton is gated on `!hasResponseContent`,
 * which does flip.
 *
 * Speed is deliberate. A revolution takes 2.8s, which is slow enough to read
 * as growth and not as a progress spinner promising imminent completion, and
 * linear because any easing on a continuous rotation reads as a stutter. The
 * hub is close to centre but not exactly on it, so the turn has a faint wobble
 * that keeps it from looking machined. */
@keyframes clm-whirl {
	to {
		transform: rotate(360deg);
	}
}

.clm-whirl {
	background: url('/static/brand/mark-simple.svg') center / contain no-repeat;
	animation: clm-whirl 2.8s linear infinite;
}
html.dark .clm-whirl {
	background-image: url('/static/brand/mark-simple-dark.svg');
}

/* Kept for when `message.done` can be trusted again: this is the streaming
 * half of the motion. Nothing sets clm-thinking today. */
.assistant-message-profile-image.clm-thinking {
	animation: clm-whirl 2.8s linear infinite;
}

/* Motion is the whole point here, so the reduced-motion fallback still has to
 * say "working": the rotor breathes in place instead of turning. */
@media (prefers-reduced-motion: reduce) {
	.clm-whirl,
	.assistant-message-profile-image.clm-thinking {
		animation: clm-whirl-rest 1.8s ease-in-out infinite;
	}
	@keyframes clm-whirl-rest {
		0%,
		100% {
			opacity: 0.45;
		}
		50% {
			opacity: 1;
		}
	}
}

/* ── Floating surfaces ─────────────────────────────────────────────────────
 * Menus, dialogs and listboxes are the things that genuinely sit above the
 * page, so they get the top surface step, the lit hairline and a real shadow.
 * Selectors are ARIA roles on purpose: they either match a floating surface or
 * they match nothing, and they survive an upstream component rewrite. */
[role='dialog'],
[role='menu'],
[role='listbox'] {
	background-color: var(--clm-raised);
	border: 1px solid var(--clm-line);
	border-top-color: var(--clm-hair-top);
	box-shadow: var(--clm-z3);
}

/* ── Accent, focus, selection ──────────────────────────────────────────────
 * One focus treatment across the app: a gold ring at the contrast-safe step for
 * the theme, plus a low-alpha halo. Keyboard-only, so a mouse user never sees a
 * ring they did not ask for. */
:focus-visible {
	outline: 2px solid var(--clm-focus);
	outline-offset: 2px;
	border-radius: 3px;
}
input:focus-visible,
textarea:focus-visible {
	outline: none;
	border-color: var(--clm-focus) !important;
	box-shadow: 0 0 0 3px var(--clm-focus-halo);
}
::selection {
	background-color: var(--clm-selection);
	color: var(--clm-ink);
}
a {
	color: var(--clm-gold-text);
	text-underline-offset: 2px;
}
a:hover {
	color: var(--clm-gold);
}
hr {
	border-color: var(--clm-line);
}

/* ── Scrollbars ───────────────────────────────────────────────────────────
 * A stock scrollbar is the fastest way for a themed app to look like a themed
 * app rather than a designed one. Thin, on-token, and it stays out of the way
 * until the pointer is near it. */
* {
	scrollbar-width: thin;
	scrollbar-color: var(--clm-line-strong) transparent;
}
::-webkit-scrollbar {
	width: 10px;
	height: 10px;
}
::-webkit-scrollbar-track {
	background: transparent;
}
::-webkit-scrollbar-thumb {
	background-color: var(--clm-line-strong);
	border: 3px solid transparent;
	background-clip: content-box;
	border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover {
	background-color: var(--clm-muted);
}

/* Respect the setting rather than the guess. Everything above is colour and
 * type, but the transitions OWUI ships are not, and this file is the last place
 * that can turn them off. */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
	}
}
