feat(terminal): add semantic --terminal-background/foreground CSS vars

Adds terminal color tokens to the adaptive theme system, derived from
the Shiki syntax theme's raw bg/fg colors. Registered in Tailwind as
bg-terminal / text-terminal-foreground. Terminal panel and xterm instance
now adapt automatically to whatever syntax theme the user picks.

- adaptive-theme.ts: emit --terminal-background, --terminal-foreground
- tailwind.config.js: register terminal.DEFAULT + terminal.foreground
- TerminalInstance already reads these vars with Tokyo Night fallbacks
This commit is contained in:
Taylor Ho
2026-05-21 23:18:05 -07:00
parent 91f7ea9a10
commit fde8351a01
4 changed files with 64 additions and 53 deletions

View File

@@ -8,59 +8,62 @@ import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import "@xterm/xterm/css/xterm.css";
// Tokyo Night — high-contrast dark terminal surface.
const DARK_THEME: ITheme = {
background: "#1a1b26",
foreground: "#c0caf5",
cursor: "#c0caf5",
selectionBackground: "#33467c",
black: "#15161e",
red: "#f7768e",
green: "#9ece6a",
yellow: "#e0af68",
blue: "#7aa2f7",
magenta: "#bb9af7",
cyan: "#7dcfff",
white: "#a9b1d6",
brightBlack: "#414868",
brightRed: "#f7768e",
brightGreen: "#9ece6a",
brightYellow: "#e0af68",
brightBlue: "#7aa2f7",
brightMagenta: "#bb9af7",
brightCyan: "#7dcfff",
brightWhite: "#c0caf5",
};
// Light mode: terminal stays dark (high contrast against light app chrome).
// Uses the same Tokyo Night bg with slightly warmer foreground for readability.
const LIGHT_THEME: ITheme = {
background: "#1a1b26",
foreground: "#c8d3f5",
cursor: "#c8d3f5",
selectionBackground: "#2f3549",
black: "#1a1b26",
red: "#ff757f",
green: "#c3e88d",
yellow: "#ffc777",
blue: "#82aaff",
magenta: "#c099ff",
cyan: "#86e1fc",
white: "#c8d3f5",
brightBlack: "#545c7e",
brightRed: "#ff757f",
brightGreen: "#c3e88d",
brightYellow: "#ffc777",
brightBlue: "#82aaff",
brightMagenta: "#c099ff",
brightCyan: "#86e1fc",
brightWhite: "#e4f0fb",
};
/**
* Read the semantic terminal CSS vars and build an xterm.js ITheme.
* Falls back to a neutral dark palette if vars are not yet applied.
*/
function getTerminalTheme(): ITheme {
return document.documentElement.classList.contains("dark")
? DARK_THEME
: LIGHT_THEME;
const style = getComputedStyle(document.documentElement);
const hslToHex = (hslValue: string): string | undefined => {
if (!hslValue?.trim()) return undefined;
// Parse "H S% L%" format from CSS var
const parts = hslValue.trim().split(/\s+/);
if (parts.length < 3) return undefined;
const h = Number.parseFloat(parts[0]);
const s = Number.parseFloat(parts[1]) / 100;
const l = Number.parseFloat(parts[2]) / 100;
const a = s * Math.min(l, 1 - l);
const f = (n: number) => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color)
.toString(16)
.padStart(2, "0");
};
return `#${f(0)}${f(8)}${f(4)}`;
};
const bg =
hslToHex(style.getPropertyValue("--terminal-background")) ?? "#1a1b26";
const fg =
hslToHex(style.getPropertyValue("--terminal-foreground")) ?? "#c0caf5";
const isDark = document.documentElement.classList.contains("dark");
return {
background: bg,
foreground: fg,
cursor: fg,
selectionBackground: isDark ? "#33467c" : "#2f3549",
black: bg,
red: isDark ? "#f7768e" : "#ff757f",
green: isDark ? "#9ece6a" : "#c3e88d",
yellow: isDark ? "#e0af68" : "#ffc777",
blue: isDark ? "#7aa2f7" : "#82aaff",
magenta: isDark ? "#bb9af7" : "#c099ff",
cyan: isDark ? "#7dcfff" : "#86e1fc",
white: fg,
brightBlack: isDark ? "#414868" : "#545c7e",
brightRed: isDark ? "#f7768e" : "#ff757f",
brightGreen: isDark ? "#9ece6a" : "#c3e88d",
brightYellow: isDark ? "#e0af68" : "#ffc777",
brightBlue: isDark ? "#7aa2f7" : "#82aaff",
brightMagenta: isDark ? "#bb9af7" : "#c099ff",
brightCyan: isDark ? "#7dcfff" : "#86e1fc",
brightWhite: fg,
};
}
type TerminalDataPayload = {

View File

@@ -90,7 +90,7 @@ export function TerminalPanel({ channelId, isOpen }: TerminalPanelProps) {
onPointerDown={handleResizeStart}
>
{/* Terminal content */}
<div className="min-h-0 flex-1 bg-[#1a1b26] p-2">
<div className="min-h-0 flex-1 bg-terminal p-2">
<TerminalInstance channelId={channelId} isVisible={isOpen} />
</div>
</div>

View File

@@ -258,6 +258,10 @@ export function createThemeVars(
"--status-deleted": accentRed,
"--status-modified": accentOrange,
// Terminal
"--terminal-background": hexToHsl(syntaxBg),
"--terminal-foreground": hexToHsl(syntaxFg),
// Warning
"--ui-warning": accentOrange,
"--ui-warning-bg": overlay(accentOrange, isDark ? 0.1 : 0.08),

View File

@@ -45,6 +45,10 @@ export default {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
terminal: {
DEFAULT: "hsl(var(--terminal-background))",
foreground: "hsl(var(--terminal-foreground))",
},
sidebar: {
DEFAULT: "hsl(var(--sidebar-background))",
foreground: "hsl(var(--sidebar-foreground))",