Initial public release

This commit is contained in:
Bohdan Triapitsyn
2025-12-07 19:32:53 +02:00
commit 4b2edf7318
319 changed files with 81600 additions and 0 deletions
+758
View File
@@ -0,0 +1,758 @@
import type { Theme } from '@/types/theme';
import { SEMANTIC_TYPOGRAPHY } from '@/lib/typography';
const hexToRgb = (value: string | undefined | null): string | null => {
if (!value || typeof value !== 'string') {
return null;
}
const normalized = value.trim();
if (!normalized.startsWith('#')) {
return null;
}
let hex = normalized.slice(1);
if (hex.length === 3 || hex.length === 4) {
hex = hex
.split('')
.map((char) => char + char)
.join('');
}
if (hex.length === 8) {
hex = hex.slice(0, 6);
}
if (hex.length !== 6) {
return null;
}
const int = Number.parseInt(hex, 16);
if (Number.isNaN(int)) {
return null;
}
const r = (int >> 16) & 255;
const g = (int >> 8) & 255;
const b = int & 255;
return `${r} ${g} ${b}`;
};
export class CSSVariableGenerator {
private inheritanceMap: Map<string, string> = new Map();
constructor() {
this.initializeInheritanceMap();
}
generate(theme: Theme): string {
const cssVars: string[] = [];
cssVars.push(...this.generateTailwindVariables(theme));
cssVars.push(...this.generatePrimaryColors(theme.colors.primary));
cssVars.push(...this.generateSurfaceColors(theme.colors.surface));
cssVars.push(...this.generateInteractiveColors(theme.colors.interactive));
cssVars.push(...this.generateStatusColors(theme.colors.status));
cssVars.push(...this.generateSyntaxColors(theme.colors.syntax));
cssVars.push(...this.generateComponentColors(theme.colors, theme));
cssVars.push(...this.generateTypographyVariables());
if (theme.config) {
cssVars.push(...this.generateConfigVariables(theme.config));
}
return cssVars.join('\n');
}
private generateTailwindVariables(theme: Theme): string[] {
const vars: string[] = [];
vars.push(` --background: ${theme.colors.surface.background} !important;`);
vars.push(` --foreground: ${theme.colors.surface.foreground} !important;`);
vars.push(` --muted: ${theme.colors.surface.muted} !important;`);
vars.push(` --muted-foreground: ${theme.colors.surface.mutedForeground} !important;`);
vars.push(` --card: ${theme.colors.surface.elevated} !important;`);
vars.push(` --card-foreground: ${theme.colors.surface.elevatedForeground} !important;`);
vars.push(` --popover: ${theme.colors.surface.elevated} !important;`);
vars.push(` --popover-foreground: ${theme.colors.surface.elevatedForeground} !important;`);
vars.push(` --border: ${theme.colors.interactive.border} !important;`);
vars.push(` --input: ${theme.colors.interactive.border} !important;`);
vars.push(` --primary: ${theme.colors.primary.base} !important;`);
vars.push(` --primary-foreground: ${theme.colors.primary.foreground} !important;`);
vars.push(` --secondary: ${theme.colors.surface.muted} !important;`);
vars.push(` --secondary-foreground: ${theme.colors.surface.mutedForeground} !important;`);
vars.push(` --accent: ${theme.colors.surface.subtle} !important;`);
vars.push(` --accent-foreground: ${theme.colors.surface.foreground} !important;`);
vars.push(` --destructive: ${theme.colors.status.error} !important;`);
vars.push(` --destructive-foreground: ${theme.colors.status.errorForeground} !important;`);
vars.push(` --ring: ${theme.colors.interactive.focusRing} !important;`);
if (theme.config?.radius?.md) {
vars.push(` --radius: ${theme.config.radius.md} !important;`);
}
const sidebarBaseRgb = hexToRgb(theme.colors.surface.muted);
const sidebarAccentRgb = hexToRgb(theme.colors.surface.subtle);
const sidebarBorderRgb = hexToRgb(theme.colors.interactive.border);
vars.push(` --sidebar-base: ${theme.colors.surface.muted} !important;`);
if (sidebarBaseRgb) {
vars.push(` --sidebar-base-rgb: ${sidebarBaseRgb} !important;`);
}
vars.push(` --sidebar: var(--sidebar-base) !important;`);
vars.push(` --sidebar-foreground: ${theme.colors.surface.mutedForeground} !important;`);
vars.push(` --sidebar-primary: ${theme.colors.primary.base} !important;`);
vars.push(` --sidebar-primary-foreground: ${theme.colors.primary.foreground} !important;`);
vars.push(` --sidebar-accent-base: ${theme.colors.surface.subtle} !important;`);
if (sidebarAccentRgb) {
vars.push(` --sidebar-accent-base-rgb: ${sidebarAccentRgb} !important;`);
}
vars.push(` --sidebar-accent: var(--sidebar-accent-base) !important;`);
vars.push(` --sidebar-accent-foreground: ${theme.colors.surface.foreground} !important;`);
vars.push(` --sidebar-border: ${theme.colors.interactive.border} !important;`);
if (sidebarBorderRgb) {
vars.push(` --sidebar-border-rgb: ${sidebarBorderRgb} !important;`);
}
vars.push(` --sidebar-ring: ${theme.colors.interactive.focusRing} !important;`);
const isDark = theme.metadata.variant === 'dark';
const strongAlpha = isDark ? 0.8 : 0.95;
const softAlpha = isDark ? 0.7 : 0.9;
if (sidebarBaseRgb) {
vars.push(
` --sidebar-overlay-strong: rgb(${sidebarBaseRgb} / ${strongAlpha}) !important;`,
);
vars.push(
` --sidebar-overlay-soft: rgb(${sidebarBaseRgb} / ${softAlpha}) !important;`,
);
} else {
const base = theme.colors.surface.muted;
vars.push(
` --sidebar-overlay-strong: ${this.opacity(base, strongAlpha)} !important;`,
);
vars.push(
` --sidebar-overlay-soft: ${this.opacity(base, softAlpha)} !important;`,
);
}
if (theme.colors.charts?.series && Array.isArray(theme.colors.charts.series)) {
theme.colors.charts.series.forEach((color: string, i: number) => {
vars.push(` --chart-${i + 1}: ${color};`);
});
}
if (theme.colors.loading) {
vars.push(` --loading-spinner: ${theme.colors.loading.spinner || theme.colors.primary.base};`);
vars.push(` --loading-spinner-track: ${theme.colors.loading.spinnerTrack || theme.colors.surface.muted};`);
} else {
vars.push(` --loading-spinner: ${theme.colors.primary.base};`);
vars.push(` --loading-spinner-track: ${theme.colors.surface.muted};`);
}
return vars;
}
apply(theme: Theme): void {
const cssVars = this.generate(theme);
const style = document.createElement('style');
style.id = 'opencode-theme-variables';
let styleContent = '';
if (theme.metadata.variant === 'dark') {
styleContent = `:root {\n${cssVars}\n}\n\n.dark {\n${cssVars}\n}`;
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
} else {
styleContent = `:root {\n${cssVars}\n}\n\n:root:not(.dark) {\n${cssVars}\n}`;
document.documentElement.classList.remove('dark');
document.documentElement.classList.add('light');
}
style.textContent = styleContent;
const existing = document.getElementById('opencode-theme-variables');
if (existing) {
existing.remove();
}
document.head.appendChild(style);
document.documentElement.setAttribute('data-theme', theme.metadata.variant);
}
private generatePrimaryColors(primary: Theme['colors']['primary']): string[] {
const vars: string[] = [];
vars.push(` --primary-base: ${primary.base};`);
vars.push(` --primary-hover: ${primary.hover || this.darken(primary.base, 10)};`);
vars.push(` --primary-active: ${primary.active || this.darken(primary.base, 20)};`);
vars.push(` --primary-foreground: ${primary.foreground || '#ffffff'};`);
vars.push(` --primary-muted: ${primary.muted || this.opacity(primary.base, 0.5)};`);
vars.push(` --primary-emphasis: ${primary.emphasis || this.lighten(primary.base, 10)};`);
return vars;
}
private generateSurfaceColors(surface: Theme['colors']['surface']): string[] {
const vars: string[] = [];
vars.push(` --surface-background: ${surface.background};`);
vars.push(` --surface-foreground: ${surface.foreground};`);
vars.push(` --surface-muted: ${surface.muted};`);
vars.push(` --surface-muted-foreground: ${surface.mutedForeground};`);
vars.push(` --surface-elevated: ${surface.elevated};`);
vars.push(` --surface-elevated-foreground: ${surface.elevatedForeground};`);
vars.push(` --surface-overlay: ${surface.overlay};`);
vars.push(` --surface-subtle: ${surface.subtle};`);
return vars;
}
private generateInteractiveColors(interactive: Theme['colors']['interactive']): string[] {
const vars: string[] = [];
vars.push(` --interactive-border: ${interactive.border};`);
vars.push(` --interactive-border-hover: ${interactive.borderHover};`);
vars.push(` --interactive-border-focus: ${interactive.borderFocus};`);
vars.push(` --interactive-selection: ${interactive.selection};`);
vars.push(` --interactive-selection-foreground: ${interactive.selectionForeground};`);
vars.push(` --interactive-focus: ${interactive.focus};`);
vars.push(` --interactive-focus-ring: ${interactive.focusRing};`);
vars.push(` --interactive-cursor: ${interactive.cursor};`);
vars.push(` --interactive-hover: ${interactive.hover};`);
vars.push(` --interactive-active: ${interactive.active};`);
return vars;
}
private generateStatusColors(status: Theme['colors']['status']): string[] {
const vars: string[] = [];
vars.push(` --status-error: ${status.error};`);
vars.push(` --status-error-foreground: ${status.errorForeground};`);
vars.push(` --status-error-background: ${status.errorBackground};`);
vars.push(` --status-error-border: ${status.errorBorder};`);
vars.push(` --status-warning: ${status.warning};`);
vars.push(` --status-warning-foreground: ${status.warningForeground};`);
vars.push(` --status-warning-background: ${status.warningBackground};`);
vars.push(` --status-warning-border: ${status.warningBorder};`);
vars.push(` --status-success: ${status.success};`);
vars.push(` --status-success-foreground: ${status.successForeground};`);
vars.push(` --status-success-background: ${status.successBackground};`);
vars.push(` --status-success-border: ${status.successBorder};`);
vars.push(` --status-info: ${status.info};`);
vars.push(` --status-info-foreground: ${status.infoForeground};`);
vars.push(` --status-info-background: ${status.infoBackground};`);
vars.push(` --status-info-border: ${status.infoBorder};`);
return vars;
}
private generateSyntaxColors(syntax: Theme['colors']['syntax']): string[] {
const vars: string[] = [];
vars.push(` --syntax-background: ${syntax.base.background};`);
vars.push(` --syntax-foreground: ${syntax.base.foreground};`);
vars.push(` --syntax-comment: ${syntax.base.comment};`);
vars.push(` --syntax-keyword: ${syntax.base.keyword};`);
vars.push(` --syntax-string: ${syntax.base.string};`);
vars.push(` --syntax-number: ${syntax.base.number};`);
vars.push(` --syntax-function: ${syntax.base.function};`);
vars.push(` --syntax-variable: ${syntax.base.variable};`);
vars.push(` --syntax-type: ${syntax.base.type};`);
vars.push(` --syntax-operator: ${syntax.base.operator};`);
const tokens = this.generateSyntaxTokens(syntax);
for (const [key, value] of Object.entries(tokens)) {
vars.push(` --syntax-${this.kebabCase(key)}: ${value};`);
}
return vars;
}
private generateSyntaxTokens(syntax: Theme['colors']['syntax']): Record<string, string> {
const base = syntax.base;
const tokens = syntax.tokens || {};
return {
commentDoc: tokens.commentDoc || this.lighten(base.comment, 10),
stringEscape: tokens.stringEscape || this.darken(base.string, 20),
stringInterpolation: tokens.stringInterpolation || base.variable,
stringRegex: tokens.stringRegex || this.adjustHue(base.string, 15),
keywordControl: tokens.keywordControl || base.keyword,
keywordOperator: tokens.keywordOperator || base.operator,
keywordImport: tokens.keywordImport || this.lighten(base.keyword, 10),
keywordReturn: tokens.keywordReturn || this.emphasize(base.keyword),
functionCall: tokens.functionCall || this.lighten(base.function, 5),
functionBuiltin: tokens.functionBuiltin || this.darken(base.function, 10),
method: tokens.method || base.function,
methodCall: tokens.methodCall || this.lighten(base.function, 5),
variableBuiltin: tokens.variableBuiltin || this.emphasize(base.variable),
variableProperty: tokens.variableProperty || this.lighten(base.variable, 10),
variableReadonly: tokens.variableReadonly || base.number,
parameter: tokens.parameter || base.variable,
typePrimitive: tokens.typePrimitive || this.darken(base.type, 10),
typeInterface: tokens.typeInterface || base.type,
className: tokens.className || this.emphasize(base.type),
enum: tokens.enum || base.type,
boolean: tokens.boolean || base.number,
null: tokens.null || this.opacity(base.number, 0.7),
constant: tokens.constant || base.number,
punctuation: tokens.punctuation || this.opacity(base.foreground, 0.7),
delimiter: tokens.delimiter || this.opacity(base.foreground, 0.8),
bracket: tokens.bracket || base.foreground,
tag: tokens.tag || base.keyword,
tagAttribute: tokens.tagAttribute || base.variable,
tagAttributeValue: tokens.tagAttributeValue || base.string,
tagBracket: tokens.tagBracket || this.opacity(base.foreground, 0.8),
decorator: tokens.decorator || base.function,
annotation: tokens.annotation || base.function,
namespace: tokens.namespace || this.opacity(base.type, 0.8),
module: tokens.module || this.opacity(base.type, 0.8),
...tokens
};
}
private generateComponentColors(colors: Theme['colors'], theme: Theme): string[] {
const vars: string[] = [];
if (colors.markdown) {
vars.push(...this.generateMarkdownColors(colors.markdown, theme));
} else {
vars.push(...this.generateDefaultMarkdownColors(theme));
}
if (colors.chat) {
vars.push(...this.generateChatColors(colors.chat, theme));
} else {
vars.push(...this.generateDefaultChatColors(theme));
}
if (colors.tools) {
vars.push(...this.generateToolColors(colors.tools, theme));
} else {
vars.push(...this.generateDefaultToolColors(theme));
}
return vars;
}
private generateMarkdownColors(markdown: Record<string, string>, theme: Theme): string[] {
const vars: string[] = [];
const primary = theme.colors.primary.base;
const chatBackground = theme.colors.chat?.background || theme.colors.surface.background;
vars.push(` --markdown-heading1: ${markdown.heading1 || primary};`);
vars.push(` --markdown-heading2: ${markdown.heading2 || this.opacity(primary, 0.9)};`);
vars.push(` --markdown-heading3: ${markdown.heading3 || this.opacity(primary, 0.8)};`);
vars.push(` --markdown-heading4: ${markdown.heading4 || theme.colors.surface.foreground};`);
vars.push(` --markdown-link: ${markdown.link || primary};`);
vars.push(` --markdown-link-hover: ${markdown.linkHover || theme.colors.primary.hover || this.darken(primary, 10)};`);
vars.push(` --markdown-inline-code: ${markdown.inlineCode || theme.colors.syntax.base.string};`);
vars.push(` --markdown-inline-code-bg: ${markdown.inlineCodeBackground || chatBackground};`);
vars.push(` --markdown-blockquote: ${markdown.blockquote || theme.colors.surface.mutedForeground};`);
vars.push(` --markdown-blockquote-border: ${markdown.blockquoteBorder || theme.colors.interactive.border};`);
vars.push(` --markdown-list-marker: ${markdown.listMarker || this.opacity(primary, 0.6)};`);
vars.push(` --markdown-bold: ${markdown.bold || theme.colors.surface.foreground};`);
vars.push(` --markdown-italic: ${markdown.italic || this.opacity(theme.colors.surface.foreground, 0.9)};`);
vars.push(` --markdown-strikethrough: ${markdown.strikethrough || theme.colors.surface.mutedForeground};`);
vars.push(` --markdown-hr: ${markdown.hr || theme.colors.interactive.border};`);
return vars;
}
private generateDefaultMarkdownColors(theme: Theme): string[] {
const vars: string[] = [];
const primary = theme.colors.primary.base;
const chatBackground = theme.colors.chat?.background || theme.colors.surface.background;
vars.push(` --markdown-heading1: ${primary};`);
vars.push(` --markdown-heading2: ${this.opacity(primary, 0.9)};`);
vars.push(` --markdown-heading3: ${this.opacity(primary, 0.8)};`);
vars.push(` --markdown-heading4: ${theme.colors.surface.foreground};`);
vars.push(` --markdown-link: ${primary};`);
vars.push(` --markdown-link-hover: ${theme.colors.primary.hover || this.darken(primary, 10)};`);
vars.push(` --markdown-inline-code: ${theme.colors.syntax.base.string};`);
vars.push(` --markdown-inline-code-bg: ${chatBackground};`);
vars.push(` --markdown-blockquote: ${theme.colors.surface.mutedForeground};`);
vars.push(` --markdown-blockquote-border: ${theme.colors.interactive.border};`);
vars.push(` --markdown-list-marker: ${this.opacity(primary, 0.6)};`);
vars.push(` --markdown-bold: ${theme.colors.surface.foreground};`);
vars.push(` --markdown-italic: ${this.opacity(theme.colors.surface.foreground, 0.9)};`);
vars.push(` --markdown-strikethrough: ${theme.colors.surface.mutedForeground};`);
vars.push(` --markdown-hr: ${theme.colors.interactive.border};`);
return vars;
}
private generateChatColors(chat: Record<string, string>, theme: Theme): string[] {
const vars: string[] = [];
const chatBackground = chat.background || theme.colors.surface.background;
vars.push(` --chat-background: ${chatBackground};`);
vars.push(` --chat-user-message: ${chat.userMessage || theme.colors.surface.foreground};`);
vars.push(` --chat-user-message-bg: ${chat.userMessageBackground || theme.colors.surface.elevated};`);
vars.push(` --chat-assistant-message: ${chat.assistantMessage || theme.colors.surface.foreground};`);
vars.push(` --chat-assistant-message-bg: ${chat.assistantMessageBackground || theme.colors.surface.muted};`);
vars.push(` --chat-timestamp: ${chat.timestamp || theme.colors.surface.mutedForeground};`);
vars.push(` --chat-divider: ${chat.divider || theme.colors.interactive.border};`);
vars.push(` --chat-typing: ${chat.typing || theme.colors.surface.mutedForeground};`);
return vars;
}
private generateDefaultChatColors(theme: Theme): string[] {
const vars: string[] = [];
vars.push(` --chat-background: ${theme.colors.surface.background};`);
vars.push(` --chat-user-message: ${theme.colors.surface.foreground};`);
vars.push(` --chat-user-message-bg: ${theme.colors.surface.elevated};`);
vars.push(` --chat-assistant-message: ${theme.colors.surface.foreground};`);
vars.push(` --chat-assistant-message-bg: ${theme.colors.surface.muted};`);
vars.push(` --chat-timestamp: ${theme.colors.surface.mutedForeground};`);
vars.push(` --chat-divider: ${theme.colors.interactive.border};`);
vars.push(` --chat-typing: ${theme.colors.surface.mutedForeground};`);
return vars;
}
private generateToolColors(tools: Theme['colors']['tools'], theme: Theme): string[] {
const vars: string[] = [];
vars.push(` --tools-background: ${tools?.background || this.opacity(theme.colors.surface.muted, 0.2)};`);
vars.push(` --tools-border: ${tools?.border || this.opacity(theme.colors.interactive.border, 0.3)};`);
vars.push(` --tools-header-hover: ${tools?.headerHover || this.opacity(theme.colors.surface.muted, 0.3)};`);
vars.push(` --tools-icon: ${tools?.icon || theme.colors.surface.mutedForeground};`);
vars.push(` --tools-title: ${tools?.title || theme.colors.surface.foreground};`);
vars.push(` --tools-description: ${tools?.description || this.opacity(theme.colors.surface.mutedForeground, 0.6)};`);
if (tools?.edit) {
vars.push(` --tools-edit-added: ${tools.edit.added || theme.colors.status.success};`);
vars.push(` --tools-edit-added-bg: ${tools.edit.addedBackground || theme.colors.status.successBackground};`);
vars.push(` --tools-edit-removed: ${tools.edit.removed || theme.colors.status.error};`);
vars.push(` --tools-edit-removed-bg: ${tools.edit.removedBackground || theme.colors.status.errorBackground};`);
vars.push(` --tools-edit-modified: ${tools.edit.modified || theme.colors.status.info};`);
vars.push(` --tools-edit-modified-bg: ${tools.edit.modifiedBackground || theme.colors.status.infoBackground};`);
vars.push(` --tools-edit-line-number: ${tools.edit.lineNumber || this.opacity(theme.colors.surface.mutedForeground, 0.6)};`);
} else {
vars.push(` --tools-edit-added: ${theme.colors.status.success};`);
vars.push(` --tools-edit-added-bg: ${theme.colors.status.successBackground};`);
vars.push(` --tools-edit-removed: ${theme.colors.status.error};`);
vars.push(` --tools-edit-removed-bg: ${theme.colors.status.errorBackground};`);
vars.push(` --tools-edit-modified: ${theme.colors.status.info};`);
vars.push(` --tools-edit-modified-bg: ${theme.colors.status.infoBackground};`);
vars.push(` --tools-edit-line-number: ${this.opacity(theme.colors.surface.mutedForeground, 0.6)};`);
}
return vars;
}
private generateDefaultToolColors(theme: Theme): string[] {
const vars: string[] = [];
vars.push(` --tools-background: ${this.opacity(theme.colors.surface.muted, 0.2)};`);
vars.push(` --tools-border: ${this.opacity(theme.colors.interactive.border, 0.3)};`);
vars.push(` --tools-header-hover: ${this.opacity(theme.colors.surface.muted, 0.3)};`);
vars.push(` --tools-icon: ${theme.colors.surface.mutedForeground};`);
vars.push(` --tools-title: ${theme.colors.surface.foreground};`);
vars.push(` --tools-description: ${this.opacity(theme.colors.surface.mutedForeground, 0.6)};`);
vars.push(` --tools-edit-added: ${theme.colors.status.success};`);
vars.push(` --tools-edit-added-bg: ${this.addTransparency(this.removeTransparency(theme.colors.status.successBackground), 0.15)};`);
vars.push(` --tools-edit-removed: ${theme.colors.status.error};`);
vars.push(` --tools-edit-removed-bg: ${this.addTransparency(this.removeTransparency(theme.colors.status.errorBackground), 0.15)};`);
vars.push(` --tools-edit-modified: ${theme.colors.status.info};`);
vars.push(` --tools-edit-modified-bg: ${this.addTransparency(this.removeTransparency(theme.colors.status.infoBackground), 0.15)};`);
vars.push(` --tools-edit-line-number: ${this.opacity(theme.colors.surface.mutedForeground, 0.6)};`);
return vars;
}
private generateConfigVariables(config: Theme['config']): string[] {
const vars: string[] = [];
if (!config) return vars;
if (config.fonts) {
if (config.fonts.sans) {
vars.push(` --font-sans: ${config.fonts.sans};`);
vars.push(` --font-family-sans: ${config.fonts.sans};`);
}
if (config.fonts.mono) {
vars.push(` --font-mono: ${config.fonts.mono};`);
vars.push(` --font-family-mono: ${config.fonts.mono};`);
}
if (config.fonts.heading) vars.push(` --font-heading: ${config.fonts.heading};`);
}
if (config.radius) {
if (config.radius.none) vars.push(` --radius-none: ${config.radius.none};`);
if (config.radius.sm) vars.push(` --radius-sm: ${config.radius.sm};`);
if (config.radius.md) vars.push(` --radius-md: ${config.radius.md};`);
if (config.radius.lg) vars.push(` --radius-lg: ${config.radius.lg};`);
if (config.radius.xl) vars.push(` --radius-xl: ${config.radius.xl};`);
if (config.radius.full) vars.push(` --radius-full: ${config.radius.full};`);
}
if (config.transitions) {
if (config.transitions.fast) vars.push(` --transition-fast: ${config.transitions.fast};`);
if (config.transitions.normal) vars.push(` --transition-normal: ${config.transitions.normal};`);
if (config.transitions.slow) vars.push(` --transition-slow: ${config.transitions.slow};`);
}
return vars;
}
private generateTypographyVariables(): string[] {
const vars: string[] = [];
vars.push(' /* Semantic Typography Variables */');
vars.push(' --ui-regular-font-weight: 400;');
vars.push(' /* Markdown content - all markdown elements use same size */');
vars.push(` --text-markdown: ${SEMANTIC_TYPOGRAPHY.markdown};`);
vars.push(' /* Code content - all code elements use same size */');
vars.push(` --text-code: ${SEMANTIC_TYPOGRAPHY.code};`);
vars.push(' /* UI headers - dialog titles, panel headers */');
vars.push(` --text-ui-header: ${SEMANTIC_TYPOGRAPHY.uiHeader};`);
vars.push(' /* UI labels - buttons, menus, navigation */');
vars.push(` --text-ui-label: ${SEMANTIC_TYPOGRAPHY.uiLabel};`);
vars.push(' /* Metadata - timestamps, status, helper text */');
vars.push(` --text-meta: ${SEMANTIC_TYPOGRAPHY.meta};`);
vars.push(' /* Micro text - badges, shortcuts, indicators */');
vars.push(` --text-micro: ${SEMANTIC_TYPOGRAPHY.micro};`);
vars.push(' /* Heading line height and letter spacing */');
vars.push(' --h1-line-height: 1.25rem;');
vars.push(' --h2-line-height: 1.25rem;');
vars.push(' --h3-line-height: 1.5rem;');
vars.push(' --h4-line-height: 1.5rem;');
vars.push(' --h5-line-height: 1.5rem;');
vars.push(' --h6-line-height: 1.5rem;');
vars.push(' --h1-letter-spacing: -0.025em;');
vars.push(' --h2-letter-spacing: -0.02em;');
vars.push(' --h3-letter-spacing: -0.015em;');
vars.push(' --h4-letter-spacing: -0.01em;');
vars.push(' --h5-letter-spacing: 0;');
vars.push(' --h6-letter-spacing: 0.01em;');
vars.push(' /* UI element line height and letter spacing */');
vars.push(' --ui-button-line-height: 1.375rem;');
vars.push(' --ui-button-letter-spacing: 0.02em;');
vars.push(' --ui-button-font-weight: 500;');
vars.push(' --ui-label-line-height: 1rem;');
vars.push(' --ui-label-letter-spacing: 0.03em;');
vars.push(' --ui-label-font-weight: 500;');
vars.push(' --ui-caption-line-height: 1rem;');
vars.push(' --ui-caption-letter-spacing: 0.025em;');
vars.push(' --ui-caption-font-weight: var(--ui-regular-font-weight, 400);');
vars.push(' /* Markdown line height and letter spacing */');
vars.push(' --markdown-body-line-height: 1.5rem;');
vars.push(' --markdown-body-letter-spacing: 0;');
vars.push(' --markdown-body-font-weight: var(--ui-regular-font-weight, 400);');
vars.push(' --markdown-h1-line-height: 1.25rem;');
vars.push(' --markdown-h1-letter-spacing: -0.025em;');
vars.push(' --markdown-h2-line-height: 1.25rem;');
vars.push(' --markdown-h2-letter-spacing: -0.02em;');
vars.push(' --markdown-h3-line-height: 1.5rem;');
vars.push(' --markdown-h3-letter-spacing: -0.015em;');
vars.push(' --markdown-h4-line-height: 1.5rem;');
vars.push(' --markdown-h4-letter-spacing: -0.01em;');
vars.push(' --markdown-h5-line-height: 1.5rem;');
vars.push(' --markdown-h5-letter-spacing: 0;');
vars.push(' --markdown-h6-line-height: 1.5rem;');
vars.push(' --markdown-h6-letter-spacing: 0.01em;');
vars.push(' --markdown-list-line-height: 1.375rem;');
vars.push(' --markdown-code-block-line-height: 1rem;');
vars.push(' --ui-button-small-line-height: 1.25rem;');
vars.push(' --ui-button-small-letter-spacing: 0.02em;');
vars.push(' --ui-button-small-font-weight: 500;');
vars.push(' --markdown-body-small-line-height: 1.375rem;');
vars.push(' --markdown-body-small-letter-spacing: 0;');
vars.push(' --markdown-body-small-font-weight: var(--ui-regular-font-weight, 400);');
vars.push(' --ui-button-large-line-height: 1.5rem;');
vars.push(' --ui-button-large-letter-spacing: 0.02em;');
vars.push(' --ui-button-large-font-weight: 500;');
vars.push(' --markdown-body-large-line-height: 1.625rem;');
vars.push(' --markdown-body-large-letter-spacing: 0;');
vars.push(' --markdown-body-large-font-weight: var(--ui-regular-font-weight, 400);');
vars.push(' /* Code line height and letter spacing */');
vars.push(' --code-inline-line-height: 1rem;');
vars.push(' --code-inline-letter-spacing: 0;');
vars.push(' --code-inline-font-weight: 400;');
vars.push(' --code-block-line-height: 1.4rem;');
vars.push(' --code-block-letter-spacing: 0;');
vars.push(' --code-block-font-weight: 400;');
vars.push(' --code-line-numbers-line-height: 1.25rem;');
vars.push(' --code-line-numbers-letter-spacing: 0;');
vars.push(' --code-line-numbers-font-weight: 400;');
vars.push(' /* Additional UI element line height and letter spacing */');
vars.push(' --ui-badge-line-height: 1rem;');
vars.push(' --ui-badge-letter-spacing: 0.025em;');
vars.push(' --ui-badge-font-weight: 500;');
vars.push(' --ui-tooltip-line-height: 1rem;');
vars.push(' --ui-tooltip-letter-spacing: 0.025em;');
vars.push(' --ui-tooltip-font-weight: var(--ui-regular-font-weight, 400);');
vars.push(' --ui-input-line-height: 1.375rem;');
vars.push(' --ui-input-letter-spacing: 0.02em;');
vars.push(' --ui-input-font-weight: var(--ui-regular-font-weight, 400);');
vars.push(' --ui-helper-text-line-height: 1rem;');
vars.push(' --ui-helper-text-letter-spacing: 0.025em;');
vars.push(' --ui-helper-text-font-weight: var(--ui-regular-font-weight, 400);');
vars.push(' /* Additional markdown line height and letter spacing */');
vars.push(' --markdown-blockquote-line-height: 1.5rem;');
vars.push(' --markdown-blockquote-letter-spacing: 0;');
vars.push(' --markdown-blockquote-font-weight: var(--ui-regular-font-weight, 400);');
vars.push(' --markdown-list-letter-spacing: 0;');
vars.push(' --markdown-list-font-weight: var(--ui-regular-font-weight, 400);');
vars.push(' --markdown-link-line-height: 1.5rem;');
vars.push(' --markdown-link-letter-spacing: 0;');
vars.push(' --markdown-link-font-weight: var(--ui-regular-font-weight, 400);');
vars.push(' --markdown-code-line-height: 1.35;');
vars.push(' --markdown-code-letter-spacing: 0;');
vars.push(' --markdown-code-font-weight: 400;');
vars.push(' --markdown-code-block-letter-spacing: 0;');
vars.push(' --markdown-code-block-font-weight: 400;');
return vars;
}
private initializeInheritanceMap(): void {
this.inheritanceMap.set('header.background', 'surface.background');
this.inheritanceMap.set('header.foreground', 'surface.foreground');
this.inheritanceMap.set('header.logoTint', 'primary.base');
this.inheritanceMap.set('header.divider', 'interactive.border');
this.inheritanceMap.set('sidebar.background', 'surface.muted');
this.inheritanceMap.set('sidebar.foreground', 'surface.mutedForeground');
this.inheritanceMap.set('sidebar.hover', 'interactive.hover');
this.inheritanceMap.set('sidebar.active', 'primary.base');
this.inheritanceMap.set('sidebar.activeForeground', 'primary.foreground');
}
private opacity(color: string, alpha: number): string {
if (color.startsWith('#')) {
return `${color}${Math.round(alpha * 255).toString(16).padStart(2, '0')}`;
}
if (color.startsWith('rgb')) {
return color.replace('rgb', 'rgba').replace(')', `, ${alpha})`);
}
return color;
}
private removeTransparency(color: string): string {
if (color.startsWith('#')) {
if (color.length === 9) {
return color.slice(0, 7);
}
if (color.length === 5) {
return color.slice(0, 4);
}
return color;
}
if (color.startsWith('rgba')) {
return color.replace('rgba', 'rgb').replace(/,\s*[\d.]+\)$/, ')');
}
return color;
}
private addTransparency(color: string, opacity: number): string {
if (color.startsWith('#')) {
const hex = color.slice(1);
if (hex.length === 3) {
const r = parseInt(hex[0] + hex[0], 16);
const g = parseInt(hex[1] + hex[1], 16);
const b = parseInt(hex[2] + hex[2], 16);
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
} else if (hex.length === 6) {
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
}
}
if (color.startsWith('rgb')) {
return color.replace('rgb', 'rgba').replace(')', `, ${opacity})`);
}
return color;
}
private darken(color: string, percent: number): string {
if (color.startsWith('#')) {
const num = parseInt(color.slice(1), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) - amt;
const G = (num >> 8 & 0x00FF) - amt;
const B = (num & 0x0000FF) - amt;
return '#' + (0x1000000 + (R < 255 ? R < 0 ? 0 : R : 255) * 0x10000 +
(G < 255 ? G < 0 ? 0 : G : 255) * 0x100 +
(B < 255 ? B < 0 ? 0 : B : 255)).toString(16).slice(1);
}
return color;
}
private lighten(color: string, percent: number): string {
if (color.startsWith('#')) {
const num = parseInt(color.slice(1), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) + amt;
const G = (num >> 8 & 0x00FF) + amt;
const B = (num & 0x0000FF) + amt;
return '#' + (0x1000000 + (R < 255 ? R < 0 ? 0 : R : 255) * 0x10000 +
(G < 255 ? G < 0 ? 0 : G : 255) * 0x100 +
(B < 255 ? B < 0 ? 0 : B : 255)).toString(16).slice(1);
}
return color;
}
private adjustHue(color: string, degrees: number): string {
return this.lighten(color, degrees / 10);
}
private emphasize(color: string): string {
return this.lighten(color, 15);
}
private kebabCase(str: string): string {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
}
@@ -0,0 +1,445 @@
import type { Theme } from '@/types/theme';
interface MonacoTokenRule {
token: string;
foreground?: string;
fontStyle?: string;
}
interface MonacoThemeData {
base: 'vs' | 'vs-dark';
inherit: boolean;
rules: MonacoTokenRule[];
colors: Record<string, string>;
}
type Monaco = {
editor: {
defineTheme: (themeName: string, themeData: MonacoThemeData) => void;
};
};
const MONACO_LIGHT_THEME_ID = 'openchamber-flexoki-light';
const MONACO_DARK_THEME_ID = 'openchamber-flexoki-dark';
let lastRegisteredThemeId: string | null = null;
const getMonacoFromGlobal = (): Monaco | null => {
if (typeof window === 'undefined') return null;
const anyWindow = window as typeof window & { monaco?: Monaco };
if (anyWindow.monaco?.editor?.defineTheme) return anyWindow.monaco;
return null;
};
const flexokiDarkTheme: MonacoThemeData = {
base: 'vs-dark',
inherit: true,
rules: [
{ token: '', foreground: 'CECDC3' },
{ token: 'source', foreground: 'CECDC3' },
{ token: 'comment', foreground: '878580' },
{ token: 'comment.block', foreground: '878580' },
{ token: 'comment.line', foreground: '878580' },
{ token: 'comment.block.documentation', foreground: '575653' },
{ token: 'string', foreground: '3AA99F' },
{ token: 'string.quoted', foreground: '3AA99F' },
{ token: 'string.template', foreground: '3AA99F' },
{ token: 'string.regexp', foreground: '3AA99F' },
{ token: 'string.escape', foreground: 'CECDC3' },
{ token: 'constant.character.escape', foreground: 'CECDC3' },
{ token: 'number', foreground: '8B7EC8' },
{ token: 'number.hex', foreground: '8B7EC8' },
{ token: 'number.float', foreground: '8B7EC8' },
{ token: 'constant.numeric', foreground: '8B7EC8' },
{ token: 'constant.language', foreground: 'D0A215' },
{ token: 'constant.language.boolean', foreground: 'D0A215' },
{ token: 'constant.language.null', foreground: 'D0A215' },
{ token: 'keyword', foreground: '4385BE' },
{ token: 'keyword.control', foreground: '4385BE' },
{ token: 'keyword.other', foreground: '4385BE' },
{ token: 'keyword.control.import', foreground: 'D14D41' },
{ token: 'keyword.control.from', foreground: 'D14D41' },
{ token: 'keyword.control.export', foreground: 'D14D41' },
{ token: 'keyword.control.exception', foreground: 'CE5D97' },
{ token: 'keyword.control.trycatch', foreground: 'CE5D97' },
{ token: 'keyword.operator', foreground: 'D14D41' },
{ token: 'operator', foreground: 'D14D41' },
{ token: 'storage', foreground: '4385BE' },
{ token: 'storage.type', foreground: '4385BE' },
{ token: 'storage.modifier', foreground: '4385BE' },
{ token: 'entity.name.function', foreground: 'DA702C', fontStyle: 'bold' },
{ token: 'support.function', foreground: 'DA702C', fontStyle: 'bold' },
{ token: 'meta.function-call', foreground: 'DA702C' },
{ token: 'entity.name.function.method', foreground: '879A39' },
{ token: 'entity.name.class', foreground: 'DA702C' },
{ token: 'entity.name.type.class', foreground: 'DA702C' },
{ token: 'support.class', foreground: 'DA702C' },
{ token: 'entity.name.type', foreground: 'D0A215' },
{ token: 'entity.name.type.interface', foreground: 'D0A215' },
{ token: 'support.type', foreground: 'D0A215' },
{ token: 'entity.name.type.struct', foreground: 'DA702C' },
{ token: 'entity.name.type.enum', foreground: 'DA702C' },
{ token: 'entity.name.type.parameter', foreground: 'DA702C' },
{ token: 'variable', foreground: 'CECDC3' },
{ token: 'variable.other', foreground: 'CECDC3' },
{ token: 'variable.parameter', foreground: 'CECDC3' },
{ token: 'variable.other.object', foreground: '879A39' },
{ token: 'variable.other.readwrite.alias', foreground: '879A39' },
{ token: 'variable.language', foreground: 'CE5D97' },
{ token: 'variable.language.this', foreground: 'CE5D97' },
{ token: 'variable.language.super', foreground: 'CE5D97' },
{ token: 'variable.other.property', foreground: '4385BE' },
{ token: 'support.variable.property', foreground: '4385BE' },
{ token: 'variable.other.constant', foreground: 'CECDC3' },
{ token: 'meta.object-literal.key', foreground: 'DA702C' },
{ token: 'support.type.property-name', foreground: 'DA702C' },
{ token: 'entity.name.tag', foreground: '4385BE' },
{ token: 'tag', foreground: '4385BE' },
{ token: 'support.class.component', foreground: 'CE5D97' },
{ token: 'entity.other.attribute-name', foreground: 'D0A215' },
{ token: 'entity.name.namespace', foreground: 'D0A215' },
{ token: 'entity.name.module', foreground: 'D14D41' },
{ token: 'meta.decorator', foreground: 'D0A215' },
{ token: 'entity.name.function.decorator', foreground: 'D0A215' },
{ token: 'entity.name.label', foreground: 'CE5D97' },
{ token: 'meta.preprocessor', foreground: 'CE5D97' },
{ token: 'entity.name.function.preprocessor', foreground: '4385BE' },
{ token: 'punctuation', foreground: '878580' },
{ token: 'delimiter', foreground: '878580' },
{ token: 'delimiter.bracket', foreground: '878580' },
{ token: 'markup.heading', foreground: 'D0A215' },
{ token: 'markup.bold', foreground: 'D0A215', fontStyle: 'bold' },
{ token: 'markup.italic', foreground: '3AA99F', fontStyle: 'italic' },
{ token: 'markup.underline.link', foreground: '4385BE' },
{ token: 'markup.inline.raw', foreground: '3AA99F' },
{ token: 'invalid', foreground: 'D14D41' },
{ token: 'invalid.illegal', foreground: 'D14D41' },
{ token: 'string.key.json', foreground: 'DA702C' },
{ token: 'string.value.json', foreground: '3AA99F' },
{ token: 'support.type.property-name.css', foreground: 'CECDC3' },
{ token: 'support.constant.property-value.css', foreground: '3AA99F' },
{ token: 'type', foreground: 'D0A215' },
{ token: 'type.identifier', foreground: 'D0A215' },
{ token: 'identifier', foreground: 'CECDC3' },
],
colors: {
'editor.background': '#100F0F',
'editor.foreground': '#CECDC3',
'editor.lineHighlightBackground': '#1C1B1A',
'editor.selectionBackground': '#CECDC333',
'editor.selectionHighlightBackground': '#CECDC333',
'editor.inactiveSelectionBackground': '#282726',
'editor.findMatchBackground': '#AD8301',
'editor.findMatchHighlightBackground': '#AD8301cc',
'editor.hoverHighlightBackground': '#343331',
'editor.rangeHighlightBackground': '#403E3C',
'editorCursor.foreground': '#CECDC3',
'editorLineNumber.foreground': '#403E3C',
'editorLineNumber.activeForeground': '#CECDC3',
'editorGutter.background': '#100F0F',
'editorGutter.modifiedBackground': '#3AA99F',
'editorGutter.addedBackground': '#879A39',
'editorGutter.deletedBackground': '#D14D41',
'diffEditor.insertedTextBackground': '#66800B25',
'diffEditor.removedTextBackground': '#AF302925',
'diffEditor.insertedLineBackground': '#66800B15',
'diffEditor.removedLineBackground': '#AF302915',
'diffEditor.insertedTextBorder': '#00000000',
'diffEditor.removedTextBorder': '#00000000',
'editorBracketMatch.background': '#282726',
'editorBracketMatch.border': '#343331',
'editorWhitespace.foreground': '#403E3C',
'editorIndentGuide.background1': '#343331',
'editorIndentGuide.activeBackground1': '#575653',
'editorWidget.background': '#1C1B1A',
'editorWidget.border': '#343331',
'editorSuggestWidget.background': '#100F0F',
'editorSuggestWidget.border': '#343331',
'editorSuggestWidget.foreground': '#CECDC3',
'editorSuggestWidget.selectedBackground': '#343331',
'editorHoverWidget.background': '#282726',
'editorHoverWidget.border': '#343331',
'editorInlayHint.foreground': '#878580',
'editorInlayHint.background': '#343331',
'editorError.foreground': '#D14D41',
'editorWarning.foreground': '#DA702C',
'editorInfo.foreground': '#4385BE',
'input.background': '#1C1B1A',
'input.foreground': '#CECDC3',
'input.border': '#343331',
'input.placeholderForeground': '#878580',
'dropdown.background': '#1C1B1A',
'dropdown.foreground': '#CECDC3',
'dropdown.border': '#343331',
'dropdown.listBackground': '#100F0F',
'focusBorder': '#343331',
'scrollbarSlider.background': '#34333180',
'scrollbarSlider.hoverBackground': '#403E3C',
'scrollbarSlider.activeBackground': '#575653',
},
};
const flexokiLightTheme: MonacoThemeData = {
base: 'vs',
inherit: true,
rules: [
{ token: '', foreground: '100F0F' },
{ token: 'source', foreground: '100F0F' },
{ token: 'comment', foreground: '6F6E69' },
{ token: 'comment.block', foreground: '6F6E69' },
{ token: 'comment.line', foreground: '6F6E69' },
{ token: 'comment.block.documentation', foreground: 'B7B5AC' },
{ token: 'string', foreground: '24837B' },
{ token: 'string.quoted', foreground: '24837B' },
{ token: 'string.template', foreground: '24837B' },
{ token: 'string.regexp', foreground: '24837B' },
{ token: 'string.escape', foreground: '100F0F' },
{ token: 'constant.character.escape', foreground: '100F0F' },
{ token: 'number', foreground: '5E409D' },
{ token: 'number.hex', foreground: '5E409D' },
{ token: 'number.float', foreground: '5E409D' },
{ token: 'constant.numeric', foreground: '5E409D' },
{ token: 'constant.language', foreground: 'AD8301' },
{ token: 'constant.language.boolean', foreground: 'AD8301' },
{ token: 'constant.language.null', foreground: 'AD8301' },
{ token: 'keyword', foreground: '205EA6' },
{ token: 'keyword.control', foreground: '205EA6' },
{ token: 'keyword.other', foreground: '205EA6' },
{ token: 'keyword.control.import', foreground: 'AF3029' },
{ token: 'keyword.control.from', foreground: 'AF3029' },
{ token: 'keyword.control.export', foreground: 'AF3029' },
{ token: 'keyword.control.exception', foreground: 'A02F6F' },
{ token: 'keyword.control.trycatch', foreground: 'A02F6F' },
{ token: 'keyword.operator', foreground: 'AF3029' },
{ token: 'operator', foreground: 'AF3029' },
{ token: 'storage', foreground: '205EA6' },
{ token: 'storage.type', foreground: '205EA6' },
{ token: 'storage.modifier', foreground: '205EA6' },
{ token: 'entity.name.function', foreground: 'BC5215', fontStyle: 'bold' },
{ token: 'support.function', foreground: 'BC5215', fontStyle: 'bold' },
{ token: 'meta.function-call', foreground: 'BC5215' },
{ token: 'entity.name.function.method', foreground: '66800B' },
{ token: 'entity.name.class', foreground: 'BC5215' },
{ token: 'entity.name.type.class', foreground: 'BC5215' },
{ token: 'support.class', foreground: 'BC5215' },
{ token: 'entity.name.type', foreground: 'AD8301' },
{ token: 'entity.name.type.interface', foreground: 'AD8301' },
{ token: 'support.type', foreground: 'AD8301' },
{ token: 'entity.name.type.struct', foreground: 'BC5215' },
{ token: 'entity.name.type.enum', foreground: 'BC5215' },
{ token: 'entity.name.type.parameter', foreground: 'BC5215' },
{ token: 'variable', foreground: '100F0F' },
{ token: 'variable.other', foreground: '100F0F' },
{ token: 'variable.parameter', foreground: '100F0F' },
{ token: 'variable.other.object', foreground: '66800B' },
{ token: 'variable.other.readwrite.alias', foreground: '66800B' },
{ token: 'variable.language', foreground: 'A02F6F' },
{ token: 'variable.language.this', foreground: 'A02F6F' },
{ token: 'variable.language.super', foreground: 'A02F6F' },
{ token: 'variable.other.property', foreground: '205EA6' },
{ token: 'support.variable.property', foreground: '205EA6' },
{ token: 'variable.other.constant', foreground: '100F0F' },
{ token: 'meta.object-literal.key', foreground: 'BC5215' },
{ token: 'support.type.property-name', foreground: 'BC5215' },
{ token: 'entity.name.tag', foreground: '205EA6' },
{ token: 'tag', foreground: '205EA6' },
{ token: 'support.class.component', foreground: 'A02F6F' },
{ token: 'entity.other.attribute-name', foreground: 'AD8301' },
{ token: 'entity.name.namespace', foreground: 'AD8301' },
{ token: 'entity.name.module', foreground: 'AF3029' },
{ token: 'meta.decorator', foreground: 'AD8301' },
{ token: 'entity.name.function.decorator', foreground: 'AD8301' },
{ token: 'entity.name.label', foreground: 'A02F6F' },
{ token: 'meta.preprocessor', foreground: 'A02F6F' },
{ token: 'entity.name.function.preprocessor', foreground: '205EA6' },
{ token: 'punctuation', foreground: '6F6E69' },
{ token: 'delimiter', foreground: '6F6E69' },
{ token: 'delimiter.bracket', foreground: '6F6E69' },
{ token: 'markup.heading', foreground: 'AD8301' },
{ token: 'markup.bold', foreground: 'AD8301', fontStyle: 'bold' },
{ token: 'markup.italic', foreground: '24837B', fontStyle: 'italic' },
{ token: 'markup.underline.link', foreground: '205EA6' },
{ token: 'markup.inline.raw', foreground: '24837B' },
{ token: 'invalid', foreground: 'AF3029' },
{ token: 'invalid.illegal', foreground: 'AF3029' },
{ token: 'string.key.json', foreground: 'BC5215' },
{ token: 'string.value.json', foreground: '24837B' },
{ token: 'support.type.property-name.css', foreground: '100F0F' },
{ token: 'support.constant.property-value.css', foreground: '24837B' },
{ token: 'type', foreground: 'AD8301' },
{ token: 'type.identifier', foreground: 'AD8301' },
{ token: 'identifier', foreground: '100F0F' },
],
colors: {
'editor.background': '#FFFCF0',
'editor.foreground': '#100F0F',
'editor.lineHighlightBackground': '#F2F0E5',
'editor.selectionBackground': '#100F0F44',
'editor.selectionHighlightBackground': '#100F0F44',
'editor.inactiveSelectionBackground': '#E6E4D9',
'editor.findMatchBackground': '#D0A215',
'editor.findMatchHighlightBackground': '#D0A215cc',
'editor.hoverHighlightBackground': '#DAD8CE',
'editor.rangeHighlightBackground': '#CECDC3',
'editorCursor.foreground': '#100F0F',
'editorLineNumber.foreground': '#CECDC3',
'editorLineNumber.activeForeground': '#100F0F',
'editorGutter.background': '#FFFCF0',
'editorGutter.modifiedBackground': '#24837B',
'editorGutter.addedBackground': '#66800B',
'editorGutter.deletedBackground': '#AF3029',
'diffEditor.insertedTextBackground': '#66800B25',
'diffEditor.removedTextBackground': '#AF302925',
'diffEditor.insertedLineBackground': '#66800B15',
'diffEditor.removedLineBackground': '#AF302915',
'diffEditor.insertedTextBorder': '#00000000',
'diffEditor.removedTextBorder': '#00000000',
'editorBracketMatch.background': '#E6E4D9',
'editorBracketMatch.border': '#DAD8CE',
'editorWhitespace.foreground': '#CECDC3',
'editorIndentGuide.background1': '#DAD8CE',
'editorIndentGuide.activeBackground1': '#B7B5AC',
'editorWidget.background': '#F2F0E5',
'editorWidget.border': '#DAD8CE',
'editorSuggestWidget.background': '#FFFCF0',
'editorSuggestWidget.border': '#DAD8CE',
'editorSuggestWidget.foreground': '#100F0F',
'editorSuggestWidget.selectedBackground': '#DAD8CE',
'editorHoverWidget.background': '#E6E4D9',
'editorHoverWidget.border': '#DAD8CE',
'editorInlayHint.foreground': '#6F6E69',
'editorInlayHint.background': '#DAD8CE',
'editorError.foreground': '#AF3029',
'editorWarning.foreground': '#BC5215',
'editorInfo.foreground': '#205EA6',
'input.background': '#F2F0E5',
'input.foreground': '#100F0F',
'input.border': '#DAD8CE',
'input.placeholderForeground': '#6F6E69',
'dropdown.background': '#F2F0E5',
'dropdown.foreground': '#100F0F',
'dropdown.border': '#DAD8CE',
'dropdown.listBackground': '#FFFCF0',
'focusBorder': '#DAD8CE',
'scrollbarSlider.background': '#DAD8CE80',
'scrollbarSlider.hoverBackground': '#CECDC3',
'scrollbarSlider.activeBackground': '#B7B5AC',
},
};
export const getMonacoThemeIdForTheme = (theme: Theme): string => {
return theme.metadata.variant === 'dark' ? MONACO_DARK_THEME_ID : MONACO_LIGHT_THEME_ID;
};
export const ensureMonacoThemeRegistered = (theme: Theme, monacoOverride?: Monaco): void => {
const monaco = monacoOverride ?? getMonacoFromGlobal();
if (!monaco) return;
const themeId = getMonacoThemeIdForTheme(theme);
if (lastRegisteredThemeId === themeId) return;
const themeData = theme.metadata.variant === 'dark' ? flexokiDarkTheme : flexokiLightTheme;
monaco.editor.defineTheme(themeId, themeData);
lastRegisteredThemeId = themeId;
};
@@ -0,0 +1,208 @@
import type { Theme } from '@/types/theme';
export function generateSyntaxTheme(theme: Theme) {
const syntax = theme.colors.syntax;
const surface = theme.colors.surface;
return {
'code[class*="language-"]': {
color: syntax.base.foreground,
background: 'transparent',
fontFamily: 'Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace',
fontSize: '1em',
textAlign: 'left' as const,
whiteSpace: 'pre',
wordSpacing: 'normal',
wordBreak: 'normal' as const,
wordWrap: 'normal' as const,
lineHeight: '1.5',
MozTabSize: '4',
OTabSize: '4',
tabSize: '4',
WebkitHyphens: 'none' as const,
MozHyphens: 'none' as const,
msHyphens: 'none' as const,
hyphens: 'none' as const,
},
'pre[class*="language-"]': {
color: syntax.base.foreground,
background: 'transparent',
fontFamily: 'Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace',
fontSize: '1em',
textAlign: 'left' as const,
whiteSpace: 'pre',
wordSpacing: 'normal',
wordBreak: 'normal' as const,
wordWrap: 'normal' as const,
lineHeight: '1.5',
MozTabSize: '4',
OTabSize: '4',
tabSize: '4',
WebkitHyphens: 'none' as const,
MozHyphens: 'none' as const,
msHyphens: 'none' as const,
hyphens: 'none' as const,
padding: '0',
margin: '0',
overflow: 'auto',
},
comment: {
color: syntax.base.comment,
fontStyle: 'italic',
},
prolog: {
color: syntax.base.comment,
},
doctype: {
color: syntax.base.comment,
},
cdata: {
color: syntax.base.comment,
},
punctuation: {
color: syntax.tokens?.punctuation || surface.mutedForeground,
},
property: {
color: syntax.tokens?.variableProperty || syntax.base.variable,
},
tag: {
color: syntax.tokens?.tag || syntax.base.keyword,
},
'attr-name': {
color: syntax.tokens?.tagAttribute || syntax.base.variable,
},
'attr-value': {
color: syntax.tokens?.tagAttributeValue || syntax.base.string,
},
boolean: {
color: syntax.tokens?.boolean || syntax.base.number,
},
number: {
color: syntax.base.number,
},
constant: {
color: syntax.tokens?.constant || syntax.base.number,
},
symbol: {
color: syntax.tokens?.constant || syntax.base.number,
},
string: {
color: syntax.base.string,
},
char: {
color: syntax.base.string,
},
function: {
color: syntax.base.function,
},
builtin: {
color: syntax.tokens?.functionBuiltin || syntax.base.function,
},
'class-name': {
color: syntax.tokens?.className || syntax.base.type,
},
namespace: {
color: syntax.tokens?.namespace || syntax.base.type,
opacity: 0.8,
},
keyword: {
color: syntax.base.keyword,
},
atrule: {
color: syntax.base.keyword,
},
selector: {
color: syntax.base.function,
},
operator: {
color: syntax.base.operator,
},
variable: {
color: syntax.base.variable,
},
regex: {
color: syntax.tokens?.regex || syntax.base.string,
},
url: {
color: syntax.base.function,
textDecoration: 'underline',
},
entity: {
color: syntax.base.function,
cursor: 'help',
},
'.language-css .token.string': {
color: syntax.base.string,
},
'.style .token.string': {
color: syntax.base.string,
},
deleted: {
color: theme.colors.status.error,
backgroundColor: theme.colors.status.errorBackground,
},
inserted: {
color: theme.colors.status.success,
backgroundColor: theme.colors.status.successBackground,
},
title: {
color: theme.colors.primary.base,
fontWeight: 'bold',
},
'code-block': {
color: syntax.base.string,
},
'code-snippet': {
color: syntax.base.string,
},
list: {
color: syntax.base.variable,
},
hr: {
color: surface.mutedForeground,
},
table: {
color: syntax.base.function,
},
blockquote: {
color: surface.mutedForeground,
fontStyle: 'italic',
},
important: {
color: syntax.base.keyword,
fontWeight: 'bold',
},
bold: {
fontWeight: 'bold',
},
italic: {
fontStyle: 'italic',
},
strike: {
textDecoration: 'line-through',
},
decorator: {
color: syntax.tokens?.decorator || syntax.base.function,
},
annotation: {
color: syntax.tokens?.decorator || syntax.base.function,
},
};
}
@@ -0,0 +1,197 @@
import type { Theme } from '@/types/theme';
export const flexokiDarkTheme: Theme = {
metadata: {
id: 'flexoki-dark',
name: 'Flexoki Dark',
description: 'An inky color scheme for prose and code - dark variant',
author: 'Steph Ango',
version: '1.0.0',
variant: 'dark',
tags: ['dark', 'warm', 'natural', 'ink']
},
colors: {
primary: {
base: '#EC8B49',
hover: '#DA702C',
active: '#F9AE77',
foreground: '#100F0F',
muted: '#EC8B4980',
emphasis: '#F9AE77'
},
surface: {
background: '#100F0F',
foreground: '#CECDC3',
muted: '#1C1B1A',
mutedForeground: '#878580',
elevated: '#282726',
elevatedForeground: '#CECDC3',
overlay: '#00000080',
subtle: '#343331'
},
interactive: {
border: '#343331',
borderHover: '#403E3C',
borderFocus: '#EC8B49',
selection: '#CECDC330',
selectionForeground: '#CECDC3',
focus: '#EC8B49',
focusRing: '#EC8B4950',
cursor: '#CECDC3',
hover: '#343331',
active: '#403E3C'
},
status: {
error: '#D14D41',
errorForeground: '#100F0F',
errorBackground: '#AF302920',
errorBorder: '#AF302950',
warning: '#DA702C',
warningForeground: '#100F0F',
warningBackground: '#BC521520',
warningBorder: '#BC521550',
success: '#A0AF54',
successForeground: '#100F0F',
successBackground: '#66800B20',
successBorder: '#66800B50',
info: '#4385BE',
infoForeground: '#100F0F',
infoBackground: '#205EA620',
infoBorder: '#205EA650'
},
syntax: {
base: {
background: '#1C1B1A',
foreground: '#CECDC3',
comment: '#878580',
keyword: '#4385BE',
string: '#3AA99F',
number: '#8B7EC8',
function: '#DA702C',
variable: '#CECDC3',
type: '#D0A215',
operator: '#D14D41'
},
tokens: {
commentDoc: '#575653',
stringEscape: '#CECDC3',
keywordImport: '#D14D41',
storageModifier: '#4385BE',
functionCall: '#DA702C',
method: '#879A39',
variableProperty: '#4385BE',
variableOther: '#879A39',
variableGlobal: '#CE5D97',
variableLocal: '#282726',
parameter: '#CECDC3',
constant: '#CECDC3',
class: '#DA702C',
className: '#DA702C',
interface: '#D0A215',
struct: '#DA702C',
enum: '#DA702C',
typeParameter: '#DA702C',
namespace: '#D0A215',
module: '#D14D41',
tag: '#4385BE',
jsxTag: '#CE5D97',
tagAttribute: '#D0A215',
tagAttributeValue: '#3AA99F',
boolean: '#D0A215',
decorator: '#D0A215',
label: '#CE5D97',
punctuation: '#878580',
macro: '#4385BE',
preprocessor: '#CE5D97',
regex: '#3AA99F',
url: '#4385BE',
key: '#DA702C',
exception: '#CE5D97'
},
highlights: {
diffAdded: '#879A39',
diffAddedBackground: '#66800B20',
diffRemoved: '#D14D41',
diffRemovedBackground: '#AF302920',
diffModified: '#4385BE',
diffModifiedBackground: '#205EA620',
lineNumber: '#403E3C',
lineNumberActive: '#CECDC3'
}
},
markdown: {
heading1: '#D0A215',
heading2: '#DA702C',
heading3: '#4385BE',
heading4: '#CECDC3',
link: '#4385BE',
linkHover: '#205EA6',
inlineCode: '#A0AF54',
inlineCodeBackground: '#1C1B1A',
blockquote: '#878580',
blockquoteBorder: '#343331',
listMarker: '#D0A21599'
},
chat: {
userMessage: '#CECDC3',
userMessageBackground: '#282726',
assistantMessage: '#CECDC3',
assistantMessageBackground: '#100F0F',
timestamp: '#878580',
divider: '#343331'
},
tools: {
background: '#1C1B1A50',
border: '#34333180',
headerHover: '#34333150',
icon: '#878580',
title: '#CECDC3',
description: '#878580',
edit: {
added: '#879A39',
addedBackground: '#66800B25',
removed: '#D14D41',
removedBackground: '#AF302925',
lineNumber: '#403E3C'
}
}
},
config: {
fonts: {
sans: '"IBM Plex Mono", monospace',
mono: '"IBM Plex Mono", monospace',
heading: '"IBM Plex Mono", monospace'
},
radius: {
none: '0',
sm: '0.125rem',
md: '0.375rem',
lg: '0.5rem',
xl: '0.75rem',
full: '9999px'
},
transitions: {
fast: '150ms ease',
normal: '250ms ease',
slow: '350ms ease'
}
}
};
@@ -0,0 +1,197 @@
import type { Theme } from '@/types/theme';
export const flexokiLightTheme: Theme = {
metadata: {
id: 'flexoki-light',
name: 'Flexoki Light',
description: 'An inky color scheme for prose and code - light variant',
author: 'Steph Ango',
version: '1.0.0',
variant: 'light',
tags: ['light', 'warm', 'natural', 'paper']
},
colors: {
primary: {
base: '#EC8B49',
hover: '#F9AE77',
active: '#DA702C',
foreground: '#FFFCF0',
muted: '#EC8B4980',
emphasis: '#F9AE77'
},
surface: {
background: '#FFFCF0',
foreground: '#100F0F',
muted: '#F2F0E5',
mutedForeground: '#6F6E69',
elevated: '#E6E4D9',
elevatedForeground: '#100F0F',
overlay: '#100F0F20',
subtle: '#DAD8CE'
},
interactive: {
border: '#DAD8CE',
borderHover: '#CECDC3',
borderFocus: '#EC8B49',
selection: '#100F0F44',
selectionForeground: '#100F0F',
focus: '#EC8B49',
focusRing: '#EC8B4940',
cursor: '#100F0F',
hover: '#DAD8CE',
active: '#CECDC3'
},
status: {
error: '#AF3029',
errorForeground: '#FFFCF0',
errorBackground: '#D14D4120',
errorBorder: '#D14D4150',
warning: '#BC5215',
warningForeground: '#FFFCF0',
warningBackground: '#DA702C20',
warningBorder: '#DA702C50',
success: '#66800B',
successForeground: '#FFFCF0',
successBackground: '#879A3920',
successBorder: '#879A3950',
info: '#205EA6',
infoForeground: '#FFFCF0',
infoBackground: '#4385BE20',
infoBorder: '#4385BE50'
},
syntax: {
base: {
background: '#F2F0E5',
foreground: '#100F0F',
comment: '#6F6E69',
keyword: '#205EA6',
string: '#24837B',
number: '#5E409D',
function: '#BC5215',
variable: '#100F0F',
type: '#AD8301',
operator: '#AF3029'
},
tokens: {
commentDoc: '#B7B5AC',
stringEscape: '#100F0F',
keywordImport: '#AF3029',
storageModifier: '#205EA6',
functionCall: '#BC5215',
method: '#66800B',
variableProperty: '#205EA6',
variableOther: '#66800B',
variableGlobal: '#A02F6F',
variableLocal: '#E6E4D9',
parameter: '#100F0F',
constant: '#100F0F',
class: '#BC5215',
className: '#BC5215',
interface: '#AD8301',
struct: '#BC5215',
enum: '#BC5215',
typeParameter: '#BC5215',
namespace: '#AD8301',
module: '#AF3029',
tag: '#205EA6',
jsxTag: '#A02F6F',
tagAttribute: '#AD8301',
tagAttributeValue: '#24837B',
boolean: '#AD8301',
decorator: '#AD8301',
label: '#A02F6F',
punctuation: '#6F6E69',
macro: '#205EA6',
preprocessor: '#A02F6F',
regex: '#24837B',
url: '#205EA6',
key: '#BC5215',
exception: '#A02F6F'
},
highlights: {
diffAdded: '#66800B',
diffAddedBackground: '#879A3920',
diffRemoved: '#AF3029',
diffRemovedBackground: '#D14D4120',
diffModified: '#205EA6',
diffModifiedBackground: '#4385BE20',
lineNumber: '#CECDC3',
lineNumberActive: '#100F0F'
}
},
markdown: {
heading1: '#AD8301',
heading2: '#BC5215',
heading3: '#205EA6',
heading4: '#100F0F',
link: '#205EA6',
linkHover: '#4385BE',
inlineCode: '#24837B',
inlineCodeBackground: '#F2F0E5',
blockquote: '#6F6E69',
blockquoteBorder: '#DAD8CE',
listMarker: '#AD830199'
},
chat: {
userMessage: '#100F0F',
userMessageBackground: '#F2F0E5',
assistantMessage: '#100F0F',
assistantMessageBackground: '#FFFCF0',
timestamp: '#6F6E69',
divider: '#DAD8CE'
},
tools: {
background: '#F2F0E550',
border: '#DAD8CE80',
headerHover: '#DAD8CE',
icon: '#6F6E69',
title: '#100F0F',
description: '#6F6E69',
edit: {
added: '#66800B',
addedBackground: '#66800B25',
removed: '#AF3029',
removedBackground: '#AF302925',
lineNumber: '#CECDC3'
}
}
},
config: {
fonts: {
sans: '"IBM Plex Mono", monospace',
mono: '"IBM Plex Mono", monospace',
heading: '"IBM Plex Mono", monospace'
},
radius: {
none: '0',
sm: '0.125rem',
md: '0.375rem',
lg: '0.5rem',
xl: '0.75rem',
full: '9999px'
},
transitions: {
fast: '150ms ease',
normal: '250ms ease',
slow: '350ms ease'
}
}
};
+21
View File
@@ -0,0 +1,21 @@
import type { Theme } from '@/types/theme';
import { flexokiLightTheme } from './flexoki-light';
import { flexokiDarkTheme } from './flexoki-dark';
export const themes: Theme[] = [
flexokiLightTheme,
flexokiDarkTheme,
];
export {
flexokiLightTheme,
flexokiDarkTheme,
};
export function getThemeById(id: string): Theme | undefined {
return themes.find(theme => theme.metadata.id === id);
}
export function getDefaultTheme(prefersDark: boolean): Theme {
return prefersDark ? flexokiDarkTheme : flexokiLightTheme;
}