/* Design tokens. Every colour, space and radius in the application resolves through a variable declared here. Component CSS must never hard-code a hex value -- that is what makes adding a theme a matter of writing one new block rather than auditing every stylesheet. Themes are selected with data-theme on . `moria` is the default and is declared on :root so the page is styled even before the theme script runs. */ :root { /* --- Type ------------------------------------------------------------- */ --font-display: "Iowan Old Style", "Palatino Linotype", Palatino, Palladio, "URW Palladio L", "Book Antiqua", Baskerville, Georgia, serif; --font-body: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; --font-mono: ui-monospace, "SF Mono", "JetBrains Mono", "Fira Code", "Cascadia Code", Menlo, Consolas, monospace; --text-xs: 0.75rem; --text-sm: 0.8125rem; --text-base: 0.9375rem; --text-md: 1rem; --text-lg: 1.125rem; --text-xl: 1.375rem; --text-2xl: 1.75rem; --text-3xl: 2.25rem; --leading-tight: 1.25; --leading-normal: 1.6; --leading-relaxed: 1.75; /* --- Space (4px scale) ------------------------------------------------ */ --sp-1: 0.25rem; --sp-2: 0.5rem; --sp-3: 0.75rem; --sp-4: 1rem; --sp-5: 1.25rem; --sp-6: 1.5rem; --sp-8: 2rem; --sp-10: 2.5rem; --sp-12: 3rem; --sp-16: 4rem; /* --- Radius & shadow -------------------------------------------------- */ --radius-sm: 4px; --radius: 8px; --radius-md: 10px; --radius-lg: 12px; --radius-xl: 18px; --radius-full: 999px; /* --- Controls ---------------------------------------------------------- Every button, input and select resolves its height from these. That is the whole reason things line up: a row of mixed controls has one height, not whatever each element's padding and font happened to add up to. */ --control-h: 2.25rem; --control-h-sm: 1.75rem; --control-h-lg: 2.75rem; --control-px: 0.75rem; --control-px-sm: 0.5rem; /* --- Layout ----------------------------------------------------------- */ --sidebar-width: 17.5rem; --inspector-width: 24rem; /* Wider than the inspector because the content is not prose: eighty columns of --font-mono do not fit in 24rem, and a terminal narrower than eighty re-wraps everything a program prints. */ --terminal-width: 34rem; --terminal-width-min: 24rem; --thread-max-width: 48rem; --header-height: 3.5rem; /* The terminal's own type. xterm holds this as a number rather than reading it from CSS, so terminal.js parses it back out -- it must stay a plain pixel value. */ --terminal-font-size: 13px; /* --- Stacking ---------------------------------------------------------- These were four bare numbers scattered across two stylesheets, which is fine until something new has to sit between two of them and nobody can say what is already there. */ --z-raised: 5; --z-handle: 10; --z-dropdown: 30; --z-panel: 40; --z-overlay: 50; --z-toast: 60; --transition-fast: 120ms ease; --transition: 200ms ease; /* The focus treatment, written once. Three components spelled it out. It resolves --accent-soft at the point of use, so it follows the theme even though it is declared above them. */ --ring: 0 0 0 3px var(--accent-soft); } /* --------------------------------------------------------------------------- MORIA (default, dark) Deep stone and lamplight: the halls under the mountain. Surfaces are cool and near-neutral so the mallorn and mithril accents carry all the colour. --------------------------------------------------------------------------- */ :root, :root[data-theme="moria"] { color-scheme: dark; --bg: #101317; --bg-sunken: #0B0E11; --surface: #171B21; --surface-raised: #1E242B; --surface-hover: #232A32; --surface-active: #2A323B; --border: #2A313A; --border-strong: #3A434E; --ink: #E4E8EC; --ink-muted: #A2ADB8; --ink-faint: #6E7883; --ink-inverse: #0B0E11; /* Mithril: the cool primary, used for focus and interactive accents. */ --accent: #8FB3CC; --accent-hover: #A9C6DA; --accent-ink: #0B0E11; --accent-soft: rgba(143, 179, 204, 0.14); /* Mallorn: the brand accent, and the assistant's mark. A yellow-leaning leaf green, so it stays warm against the mithril blue rather than turning the palette into two cool accents that compete. */ --leaf: #9BCC5A; --leaf-hover: #B1DD74; --leaf-soft: rgba(155, 204, 90, 0.14); /* Ember: destructive actions and errors. */ --danger: #E2795A; --danger-hover: #EC8E72; --danger-soft: rgba(226, 121, 90, 0.14); /* Success leans teal rather than leaf. Two greens a hue apart read as one colour rendered inconsistently -- an unread dot beside a brand badge has to be tellable from it at a glance. */ --success: #5FBFA0; --success-soft: rgba(95, 191, 160, 0.14); --warning: #DFAE58; --warning-soft: rgba(223, 174, 88, 0.14); --bubble-user: #232B34; --bubble-assistant: transparent; --code-bg: #0C0F13; --code-border: #262D36; --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); --shadow: 0 4px 14px rgba(0, 0, 0, 0.45); --shadow-lg: 0 12px 34px rgba(0, 0, 0, 0.55); --scrim: rgba(6, 8, 10, 0.66); /* The sixteen ANSI colours, for the terminal panel. A shell picks its own colours -- `ls --color`, a git diff, htop's meters -- and until these existed it got xterm's defaults, which are a different palette from this one and read as a foreign window pasted into the application. Where a slot has an obvious counterpart above it takes it, so an error in the terminal is the same ember as an error anywhere else. "black" is not #000: it is what a program picks for a dim background or faint text, and on a near-black surface a true black is invisible. */ --ansi-black: #1A1F26; --ansi-red: #E2795A; --ansi-green: #9BCC5A; --ansi-yellow: #DFAE58; --ansi-blue: #8FB3CC; --ansi-magenta: #C08FCC; --ansi-cyan: #5FBFA0; --ansi-white: #C6CDD4; --ansi-bright-black: #4A535E; --ansi-bright-red: #EC8E72; --ansi-bright-green: #B1DD74; --ansi-bright-yellow: #EFC87A; --ansi-bright-blue: #A9C6DA; --ansi-bright-magenta: #D4A9DE; --ansi-bright-cyan: #7FD4BA; --ansi-bright-white: #E4E8EC; } /* --------------------------------------------------------------------------- SHIRE (light) Parchment, ink and moss: warm, low-contrast, easy to read for a long time. Backgrounds are deliberately off-white -- pure #FFF next to the leaf accent reads as clinical rather than as paper. --------------------------------------------------------------------------- */ :root[data-theme="shire"] { color-scheme: light; --bg: #F6F1E4; --bg-sunken: #EDE6D4; --surface: #FDFBF5; --surface-raised: #FFFFFF; --surface-hover: #F1EADA; --surface-active: #E7DEC9; --border: #DED3BB; --border-strong: #C6B896; --ink: #2C2419; --ink-muted: #6A5C48; --ink-faint: #94856D; --ink-inverse: #FDFBF5; /* Hobbit-door blue-green: the cool primary. */ --accent: #3E6B7A; --accent-hover: #325867; --accent-ink: #FDFBF5; --accent-soft: rgba(62, 107, 122, 0.12); /* The same mallorn, darkened until it holds its own as text on parchment. */ --leaf: #4C7A22; --leaf-hover: #3C6318; --leaf-soft: rgba(76, 122, 34, 0.13); --danger: #A6432B; --danger-hover: #8C3722; --danger-soft: rgba(166, 67, 43, 0.11); --success: #2C7360; --success-soft: rgba(44, 115, 96, 0.12); --warning: #98701A; --warning-soft: rgba(152, 112, 26, 0.13); --bubble-user: #EDE4CF; --bubble-assistant: transparent; --code-bg: #F2EBD9; --code-border: #DED3BB; --shadow-sm: 0 1px 2px rgba(72, 58, 34, 0.09); --shadow: 0 4px 14px rgba(72, 58, 34, 0.11); --shadow-lg: 0 12px 34px rgba(72, 58, 34, 0.16); --scrim: rgba(44, 36, 25, 0.4); /* The same sixteen on parchment, and the reason they are not the Moria set lightened: every one of these has to be readable as *text* on #F2EBD9, so the whole palette is darkened rather than brightened. "bright" therefore means more emphatic here, not lighter -- a light terminal theme that made bright yellow actually bright would render it invisible. */ --ansi-black: #2C2419; --ansi-red: #A6432B; --ansi-green: #4C7A22; --ansi-yellow: #98701A; --ansi-blue: #3E6B7A; --ansi-magenta: #7A3E6B; --ansi-cyan: #2C7360; --ansi-white: #6A5C48; --ansi-bright-black: #94856D; --ansi-bright-red: #8C3722; --ansi-bright-green: #3C6318; --ansi-bright-yellow: #7A5A14; --ansi-bright-blue: #325867; --ansi-bright-magenta: #63325A; --ansi-bright-cyan: #235C4D; --ansi-bright-white: #453A2A; } /* Respect a stated preference for reduced motion everywhere, at once. */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } }