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:
parent
711fc53c55
commit
260646bbc0
18 changed files with 449 additions and 216 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue