shell: the window follows a theme's corners and, for a bevelled theme, its edges

Every radius in the chrome and the panel scales with the theme's own radius (--wb-c-rs = radius / 12,
0 to 1.5): a square theme gets a square window, a round one a rounder window; themes that keep the
site's colors keep wayBetter's own corners. A theme built from raised and sunken edges (ChromeTheme
edges: 'bevel') gets frame/edges.css on html[data-wb-edges=bevel], also from the first frame: a face
color under tabs, toolbar, menus and the panel, 98.css's four edge lines derived from that face,
the active title bar's gradient across the tab row, tabs as a tab control, small raised caption
buttons, a sunken address field, selected menu rows in the title bar's color, raised and default
buttons, etched group boxes and classic scrollbars in the panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
CRTkafa 2026-10-01 14:08:01 +07:00
parent 711fc53c55
commit 260646bbc0
18 changed files with 449 additions and 216 deletions

View file

@ -9,7 +9,16 @@ export const SHELL_MONO_FONT = "'Geist Mono Variable', ui-monospace, monospace";
/** A theme as the chrome needs it: seven colors (none: the site's own colors) and two font stacks. */
export type ChromeTheme = {
colors?: { bg: string; surface: string; elevated: string; border: string; text: string; muted: string; accent: string } | null;
/** `page`, `inverse` and `onInverse` are optional: a bevelled chrome builds its face and title bar from them. */
colors?: { bg: string; surface: string; elevated: string; border: string; text: string; muted: string; accent: string; page?: string; inverse?: string; onInverse?: string } | null;
/** The theme's corner radius in px: every chrome radius scales by radius / 12 (0 to 1.5, `--wb-c-rs`). Absent: wayBetter's own. */
radius?: number | null;
/**
* How the chrome's edges are drawn. `bevel`: raised and sunken edges after 98.css (MIT, Jordan
* Scales), square corners, a face color under tabs, menus and dialogs, a title-bar gradient
* (frame/edges.css, keyed on `html[data-wb-edges]`). Absent: wayBetter's own soft edges.
*/
edges?: 'bevel' | null;
/** The chrome's font stack when the theme sets one other than SHELL_UI_FONT. */
chromeFont?: string | null;
/** The mono font stack when the theme sets one. */
@ -46,7 +55,8 @@ function legible(color: string, surface: string, ratio: number): string {
/**
* CSS custom properties for the chrome in `theme`. A theme that keeps the site's colors changes
* only the fonts; `own` keeps wayBetter's own style entirely (empty result).
* only the fonts (and keeps wayBetter's own corners and edges); `own` keeps wayBetter's own style
* entirely (empty result).
*/
export function chromeVars(theme: ChromeTheme, own = false): Record<string, string> {
if (own) return {};
@ -56,6 +66,10 @@ export function chromeVars(theme: ChromeTheme, own = false): Record<string, stri
if (theme.monoFont) vars['--wb-f-mono'] = theme.monoFont;
const colors = theme.colors;
if (!colors) return vars;
// Radii follow the theme's own corners: square themes get a square chrome, round ones a rounder one.
if (typeof theme.radius === 'number' && Number.isFinite(theme.radius)) {
vars['--wb-c-rs'] = String(Math.round(Math.min(1.5, Math.max(0, theme.radius / 12)) * 100) / 100);
}
const { bg, surface, elevated, border, text, muted, accent } = colors;
const dark = luminance(bg) < luminance(text);
const ink = legible(mix(accent, text, 0.55), elevated, 4.5);
@ -76,11 +90,38 @@ export function chromeVars(theme: ChromeTheme, own = false): Record<string, stri
'--wb-p-text-rgb': channels(text), '--wb-p-popover': elevated, '--wb-p-thumb': `linear-gradient(180deg, ${mix(elevated, text, 0.08)}, ${elevated})`,
'--wb-p-track': mix(elevated, border, 0.5), '--wb-p-knob': mix(muted, text, 0.3),
});
if (theme.edges === 'bevel') Object.assign(vars, bevelVars(colors));
return vars;
}
/**
* A bevelled chrome: the face every raised part is made of (the theme's `page`, else its raised
* surface), the four edge lines of 98.css derived from that face (white highlight, light, shadow,
* near-black), the field under text (the theme's background) and the active title bar (inverse to
* accent, its label readable on both ends).
*/
function bevelVars(colors: NonNullable<ChromeTheme['colors']>): Record<string, string> {
const face = colors.page ?? colors.elevated;
const from = colors.inverse ?? colors.accent;
const titleText = colors.onInverse && contrast(colors.onInverse, from) >= 4.5 && contrast(colors.onInverse, colors.accent) >= 3 ? colors.onInverse
: contrast('#ffffff', from) >= contrast('#000000', from) ? '#ffffff' : '#000000';
return {
'--wb-c-edges': 'bevel', '--wb-c-face': face, '--wb-c-face-text': legible(colors.text, face, 7),
'--wb-c-bevel-hi': mix(face, '#ffffff', 1), '--wb-c-bevel-lt': mix(face, '#ffffff', 0.49),
'--wb-c-bevel-sh': mix(face, '#000000', 0.335), '--wb-c-bevel-dk': mix(face, '#000000', 0.95),
'--wb-c-title-from': from, '--wb-c-title-to': colors.accent, '--wb-c-title-text': titleText,
// Selected rows (menus, lists): the title bar's own color, as in a 98 menu.
'--wb-c-select': from, '--wb-c-select-text': titleText,
};
}
/** Every variable chromeVars may set (to clear them when a pane returns to the global theme). */
export const CHROME_VAR_NAMES = Object.keys(chromeVars({
colors: { bg: '#000000', surface: '#111111', elevated: '#222222', border: '#333333', text: '#ffffff', muted: '#999999', accent: '#1d9bf0' },
chromeFont: 'A', monoFont: 'B',
chromeFont: 'A', monoFont: 'B', radius: 12, edges: 'bevel',
})).concat('--wb-c-shadow-pop');
/** The edge style a set of chrome variables asks for (`html[data-wb-edges]`), or null. */
export function chromeEdges(vars: Record<string, string>): string | null {
return vars['--wb-c-edges'] === 'bevel' ? 'bevel' : null;
}