From 4025ed2be2f39550be35b5fb2162065cfe09cefa Mon Sep 17 00:00:00 2001 From: Steven Ickman Date: Tue, 24 Feb 2026 14:08:55 -0800 Subject: [PATCH 01/13] added missing scripts to support screenshots --- .architecture/ARCHITECTURE.md | 26 +- UPSTREAM_CHANGES.md | 71 ----- default-pages/application.html | 1 + default-pages/json_tools.html | 1 + default-pages/my_notes.html | 1 + default-pages/neon_asteroids.html | 1 + default-pages/oregon_trail.html | 1 + default-pages/sidebar_page.html | 1 + default-pages/solar_explorer.html | 4 +- default-pages/solar_tutorial.html | 1 + default-pages/two-panel_page.html | 1 + default-pages/us_map.html | 1 + default-pages/us_map_1850.html | 1 + default-pages/western_cities_1850.html | 1 + extending_synthos.md | 42 +-- package.json | 2 +- required-pages/pages.html | 1 + required-pages/settings.html | 1 + required-pages/synthos_apis.html | 1 + required-pages/synthos_scripts.html | 1 + src/builders/types.ts | 47 ++-- src/connectors/registry.ts | 32 ++- src/customizer/Customizer.ts | 41 +-- src/files.ts | 57 ++++ src/init.ts | 187 +++++++------ src/pages.ts | 38 +-- src/service/transformPage.ts | 178 ++++++------- src/service/useApiRoutes.ts | 42 +-- src/service/useConnectorRoutes.ts | 12 +- src/service/usePageRoutes.ts | 15 +- src/themes.ts | 29 +- {page-scripts => static-files}/helpers-v2.js | 0 {page-scripts => static-files}/page-v2.js | 0 tests/pages.spec.ts | 16 +- tests/transformPage.spec.ts | 264 ++++++------------- 35 files changed, 515 insertions(+), 603 deletions(-) delete mode 100644 UPSTREAM_CHANGES.md rename {page-scripts => static-files}/helpers-v2.js (100%) rename {page-scripts => static-files}/page-v2.js (100%) diff --git a/.architecture/ARCHITECTURE.md b/.architecture/ARCHITECTURE.md index 695924d..6aec86a 100644 --- a/.architecture/ARCHITECTURE.md +++ b/.architecture/ARCHITECTURE.md @@ -67,7 +67,7 @@ synthtabs/ ├── default-pages/ Starter HTML templates (copied on init) ├── default-scripts/ OS-specific shell script templates ├── default-themes/ Theme CSS + JSON (Nebula Dusk/Dawn) -├── page-scripts/ Versioned page scripts (page-v2.js, helpers-v2.js) +├── static-files/ Versioned static files (page-v2.js, helpers-v2.js) ├── required-pages/ System pages (builder, settings, pages, scripts, apis) ├── service-connectors/ 28+ connector JSON definitions ├── migration-rules/ Markdown rules for page upgrades @@ -75,7 +75,7 @@ synthtabs/ ├── teams-default-pages/ [TEAMS] Custom page templates ├── teams-default-scripts/ [TEAMS] Custom shell scripts ├── teams-default-themes/ [TEAMS] Custom themes -├── teams-page-scripts/ [TEAMS] Custom versioned page scripts +├── teams-static-files/ [TEAMS] Custom versioned static files ├── teams-required-pages/ [TEAMS] Custom system pages ├── teams-service-connectors/ [TEAMS] Custom connectors └── .synthos/ USER DATA (created at runtime, git-ignored) @@ -127,13 +127,15 @@ with an injected ` + diff --git a/default-pages/json_tools.html b/default-pages/json_tools.html index 1f07c51..68eb9f1 100644 --- a/default-pages/json_tools.html +++ b/default-pages/json_tools.html @@ -7,6 +7,7 @@ + diff --git a/default-pages/my_notes.html b/default-pages/my_notes.html index b06f214..18fe22f 100644 --- a/default-pages/my_notes.html +++ b/default-pages/my_notes.html @@ -8,6 +8,7 @@ + diff --git a/default-pages/neon_asteroids.html b/default-pages/neon_asteroids.html index 455f722..375a158 100644 --- a/default-pages/neon_asteroids.html +++ b/default-pages/neon_asteroids.html @@ -6,6 +6,7 @@ + diff --git a/default-pages/oregon_trail.html b/default-pages/oregon_trail.html index 02c7ac0..b450bce 100644 --- a/default-pages/oregon_trail.html +++ b/default-pages/oregon_trail.html @@ -8,6 +8,7 @@ + diff --git a/default-pages/sidebar_page.html b/default-pages/sidebar_page.html index ffd7c0e..9379eb8 100644 --- a/default-pages/sidebar_page.html +++ b/default-pages/sidebar_page.html @@ -6,6 +6,7 @@ + diff --git a/default-pages/solar_explorer.html b/default-pages/solar_explorer.html index 07321e4..0139130 100644 --- a/default-pages/solar_explorer.html +++ b/default-pages/solar_explorer.html @@ -404,7 +404,9 @@ background: rgba(255,255,255,0.1); color: #f093fb; } - + + +
SynthOS
diff --git a/default-pages/solar_tutorial.html b/default-pages/solar_tutorial.html index 24cec70..e031679 100644 --- a/default-pages/solar_tutorial.html +++ b/default-pages/solar_tutorial.html @@ -123,6 +123,7 @@ } + diff --git a/default-pages/two-panel_page.html b/default-pages/two-panel_page.html index a05fd96..6b764a7 100644 --- a/default-pages/two-panel_page.html +++ b/default-pages/two-panel_page.html @@ -7,6 +7,7 @@ + diff --git a/default-pages/us_map.html b/default-pages/us_map.html index 3d589bc..fe13ed3 100644 --- a/default-pages/us_map.html +++ b/default-pages/us_map.html @@ -8,6 +8,7 @@ + diff --git a/default-pages/western_cities_1850.html b/default-pages/western_cities_1850.html index 040caf4..c1d2769 100644 --- a/default-pages/western_cities_1850.html +++ b/default-pages/western_cities_1850.html @@ -8,6 +8,7 @@ + diff --git a/extending_synthos.md b/extending_synthos.md index 81b0dae..2912f67 100644 --- a/extending_synthos.md +++ b/extending_synthos.md @@ -65,31 +65,33 @@ class MyApp extends Customizer { ### Content folders -Override these to supply your own default pages, themes, scripts, etc. Each getter returns an absolute path to a folder on disk. +Override these to supply your own default pages, themes, scripts, etc. Each getter returns a `string[]` array of folder paths. The sentinel value `'default'` is resolved at config time to the built-in SynthOS folder for that getter. This lets forks layer custom folders alongside (or instead of) the defaults — first folder wins on name collisions. -| Getter | Default location | Contents | -|--------|-----------------|----------| -| `requiredPagesFolder` | `required-pages/` | Built-in system pages (builder, settings). | -| `defaultPagesFolder` | `default-pages/` | Starter page templates copied on first init. | -| `defaultThemesFolder` | `default-themes/` | Theme CSS/JSON files. | -| `defaultScriptsFolder` | `default-scripts/` | Platform-specific terminal scripts. | -| `pageScriptsFolder` | `page-scripts/` | Versioned page runtime scripts (page-v2.js, etc.). | -| `serviceConnectorsFolder` | `service-connectors/` | Connector JSON definitions. | +| Getter | Default | Contents | +|--------|---------|----------| +| `requiredPagesFolders` | `['default']` | Built-in system pages (builder, settings). | +| `defaultPagesFolders` | `['default']` | Starter page templates copied on first init. | +| `defaultThemesFolders` | `['default']` | Theme CSS/JSON files. | +| `defaultScriptsFolders` | `['default']` | Platform-specific terminal scripts. | +| `staticFilesFolders` | `['default']` | Versioned static files (page-v2.js, etc.). | +| `serviceConnectorsFolders` | `['default']` | Connector JSON definitions. | ```typescript import path from 'path'; class MyApp extends Customizer { - get defaultPagesFolder() { - return path.join(__dirname, '../my-pages'); + // Custom pages take priority, built-in pages used as fallback + get defaultPagesFolders() { + return [path.join(__dirname, '../my-pages'), 'default']; } - get defaultThemesFolder() { - return path.join(__dirname, '../my-themes'); + // Replace built-in themes entirely + get defaultThemesFolders() { + return [path.join(__dirname, '../my-themes')]; } } ``` -When you don't override a folder, SynthOS uses its own built-in assets from the npm package. +When you don't override a folder, the `['default']` array resolves to the built-in SynthOS assets from the npm package. ### Feature flags @@ -330,12 +332,12 @@ These are handled internally and npm consumers won't encounter them: |--------|---------|---------| | `productName` | `string` | `'SynthOS'` | | `localFolder` | `string` | `'.synthos'` | -| `requiredPagesFolder` | `string` | Built-in required-pages | -| `defaultPagesFolder` | `string` | Built-in default-pages | -| `defaultThemesFolder` | `string` | Built-in default-themes | -| `defaultScriptsFolder` | `string` | Built-in default-scripts | -| `pageScriptsFolder` | `string` | Built-in page-scripts | -| `serviceConnectorsFolder` | `string` | Built-in service-connectors | +| `requiredPagesFolders` | `string[]` | `['default']` → built-in required-pages | +| `defaultPagesFolders` | `string[]` | `['default']` → built-in default-pages | +| `defaultThemesFolders` | `string[]` | `['default']` → built-in default-themes | +| `defaultScriptsFolders` | `string[]` | `['default']` → built-in default-scripts | +| `staticFilesFolders` | `string[]` | `['default']` → built-in static-files | +| `serviceConnectorsFolders` | `string[]` | `['default']` → built-in service-connectors | | `tabsListRoute` | `string` | `'/pages'` | ### Customizer methods (call on instance) diff --git a/package.json b/package.json index 1c81f54..5250a6a 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "default-pages", "default-scripts", "default-themes", - "page-scripts", + "static-files", "required-pages", "service-connectors", "migration-rules", diff --git a/required-pages/pages.html b/required-pages/pages.html index 3d74315..44b5b3e 100644 --- a/required-pages/pages.html +++ b/required-pages/pages.html @@ -80,6 +80,7 @@ + diff --git a/required-pages/settings.html b/required-pages/settings.html index 655e61c..907f3bc 100644 --- a/required-pages/settings.html +++ b/required-pages/settings.html @@ -8,6 +8,7 @@ +
diff --git a/required-pages/synthos_apis.html b/required-pages/synthos_apis.html index 3f2567f..aa62b7a 100644 --- a/required-pages/synthos_apis.html +++ b/required-pages/synthos_apis.html @@ -8,6 +8,7 @@ + diff --git a/required-pages/synthos_scripts.html b/required-pages/synthos_scripts.html index 23500b1..a8308bf 100644 --- a/required-pages/synthos_scripts.html +++ b/required-pages/synthos_scripts.html @@ -8,6 +8,7 @@ + diff --git a/src/builders/types.ts b/src/builders/types.ts index 2cea2c6..da068b0 100644 --- a/src/builders/types.ts +++ b/src/builders/types.ts @@ -26,20 +26,17 @@ Each operation must be one of: { "op": "style-element", "nodeId": "", "style": "" } — sets the style attribute of the target element (must be unlocked) -{ "op": "update-lines", "nodeId": "", "startLine": , "endLine": , "content": "" } - — replaces lines startLine..endLine (inclusive, 1-based) in a script/style block +{ "op": "search-replace", "nodeId": "", "search": "", "replace": "" } + — finds exact text within a script/style block and replaces it. Use empty string for replace to delete. -{ "op": "delete-lines", "nodeId": "", "startLine": , "endLine": } - — removes lines startLine..endLine (inclusive, 1-based) from a script/style block +{ "op": "search-insert", "nodeId": "", "after": "", "content": "" } + — inserts new content immediately after the matched text in a script/style block. -{ "op": "insert-lines", "nodeId": "", "afterLine": , "content": "" } - — inserts lines after line n (1-based; 0 = before first line) in a script/style block - -Script and style blocks have line numbers prefixed (e.g. "01: let x = 1;"). Use these for -line-range ops. Do not include line number prefixes in your content. For small edits to large -scripts/styles, prefer update-lines/delete-lines/insert-lines over update to reduce output. -When using multiple line-range ops on the same block, apply from bottom to top (highest line -numbers first) to avoid line drift. +For partial edits to large scripts/styles, use search-replace or search-insert instead of +replacing the entire block with update. +Copy the search/after text exactly as it appears in the source. +When making multiple edits to the same block, ensure each search string targets distinct text. +To delete code, use search-replace with an empty replace string. Return ONLY the JSON array. Example: [ @@ -112,35 +109,23 @@ export const CHANGE_OPS_SCHEMA: Record = { { type: 'object', properties: { - op: { type: 'string', const: 'update-lines' }, - nodeId: { type: 'string' }, - startLine: { type: 'integer' }, - endLine: { type: 'integer' }, - content: { type: 'string' }, - }, - required: ['op', 'nodeId', 'startLine', 'endLine', 'content'], - additionalProperties: false, - }, - { - type: 'object', - properties: { - op: { type: 'string', const: 'delete-lines' }, + op: { type: 'string', const: 'search-replace' }, nodeId: { type: 'string' }, - startLine: { type: 'integer' }, - endLine: { type: 'integer' }, + search: { type: 'string' }, + replace: { type: 'string' }, }, - required: ['op', 'nodeId', 'startLine', 'endLine'], + required: ['op', 'nodeId', 'search', 'replace'], additionalProperties: false, }, { type: 'object', properties: { - op: { type: 'string', const: 'insert-lines' }, + op: { type: 'string', const: 'search-insert' }, nodeId: { type: 'string' }, - afterLine: { type: 'integer' }, + after: { type: 'string' }, content: { type: 'string' }, }, - required: ['op', 'nodeId', 'afterLine', 'content'], + required: ['op', 'nodeId', 'after', 'content'], additionalProperties: false, }, ], diff --git a/src/connectors/registry.ts b/src/connectors/registry.ts index f3ae531..8d880bf 100644 --- a/src/connectors/registry.ts +++ b/src/connectors/registry.ts @@ -12,19 +12,31 @@ function loadConnectorJson(dir: string): ConnectorDefinition | null { }; } -export function loadConnectorRegistry(connectorsDir: string): ConnectorDefinition[] { - if (!fs.existsSync(connectorsDir)) return []; - return fs.readdirSync(connectorsDir, { withFileTypes: true }) - .filter(d => d.isDirectory()) - .map(d => loadConnectorJson(path.join(connectorsDir, d.name))) - .filter((d): d is ConnectorDefinition => d !== null) - .sort((a, b) => a.name.localeCompare(b.name)); +export function loadConnectorRegistry(connectorsDirs: string[]): ConnectorDefinition[] { + const seen = new Set(); + const results: ConnectorDefinition[] = []; + for (const dir of connectorsDirs) { + if (!fs.existsSync(dir)) continue; + const entries = fs.readdirSync(dir, { withFileTypes: true }) + .filter(d => d.isDirectory()); + for (const d of entries) { + const def = loadConnectorJson(path.join(dir, d.name)); + if (def && !seen.has(def.id)) { + seen.add(def.id); + results.push(def); + } + } + } + return results.sort((a, b) => a.name.localeCompare(b.name)); } let _registry: ConnectorDefinition[] | undefined; -export function getConnectorRegistry(connectorsDir?: string): ConnectorDefinition[] { - if (!_registry || connectorsDir) { - _registry = loadConnectorRegistry(connectorsDir ?? path.join(__dirname, '../../service-connectors')); +let _registryKey: string | undefined; +export function getConnectorRegistry(connectorsDirs?: string[]): ConnectorDefinition[] { + const key = connectorsDirs ? connectorsDirs.join('|') : undefined; + if (!_registry || (connectorsDirs && key !== _registryKey)) { + _registry = loadConnectorRegistry(connectorsDirs ?? [path.join(__dirname, '../../service-connectors')]); + _registryKey = key; } return _registry; } diff --git a/src/customizer/Customizer.ts b/src/customizer/Customizer.ts index c4e42fe..2ef81aa 100644 --- a/src/customizer/Customizer.ts +++ b/src/customizer/Customizer.ts @@ -1,7 +1,6 @@ import { Application } from 'express'; import { SynthOSConfig } from '../init'; import { ContextSection } from '../builders/types'; -import path from 'path'; export type RouteInstaller = (config: SynthOSConfig, app: Application) => void; @@ -28,35 +27,39 @@ export class Customizer { // --- Content folder paths --- // Override these in a derived class to point to your fork's folders. + // Each getter returns `string[]`. The sentinel value `'default'` is + // resolved by `createConfig()` to the built-in SynthOS folder for that + // getter. Forks can return additional paths alongside (or instead of) + // `'default'` to layer custom folders. - /** Folder containing built-in system pages (builder, settings, etc.) */ - get requiredPagesFolder(): string { - return path.join(__dirname, '../../required-pages'); + /** Folders containing built-in system pages (builder, settings, etc.) */ + get requiredPagesFolders(): string[] { + return ['default']; } - /** Folder containing starter page templates copied on init */ - get defaultPagesFolder(): string { - return path.join(__dirname, '../../default-pages'); + /** Folders containing starter page templates copied on init */ + get defaultPagesFolders(): string[] { + return ['default']; } - /** Folder containing theme CSS/JSON files */ - get defaultThemesFolder(): string { - return path.join(__dirname, '../../default-themes'); + /** Folders containing theme CSS/JSON files */ + get defaultThemesFolders(): string[] { + return ['default']; } - /** Folder containing versioned page scripts (page-v2.js, etc.) */ - get pageScriptsFolder(): string { - return path.join(__dirname, '../../page-scripts'); + /** Folders containing versioned static files (page-v2.js, helpers-v2.js, etc.) */ + get staticFilesFolders(): string[] { + return ['default']; } - /** Folder containing connector JSON definitions */ - get serviceConnectorsFolder(): string { - return path.join(__dirname, '../../service-connectors'); + /** Folders containing connector JSON definitions */ + get serviceConnectorsFolders(): string[] { + return ['default']; } - /** Folder containing default scripts copied on init */ - get defaultScriptsFolder(): string { - return path.join(__dirname, '../../default-scripts'); + /** Folders containing default scripts copied on init */ + get defaultScriptsFolders(): string[] { + return ['default']; } /** Route path for the "browse all pages/tabs" listing page. diff --git a/src/files.ts b/src/files.ts index 819cbf1..402618c 100644 --- a/src/files.ts +++ b/src/files.ts @@ -72,3 +72,60 @@ export async function copyFolderRecursive(srcFolder: string, destFolder: string) export async function deleteFolder(dirPath: string): Promise { await fs.rm(dirPath, { recursive: true }); } + +// --- Multi-folder helpers --- + +/** + * Search folders in order, return the full path to the first existing match + * for the given filename. + */ +export async function findFileInFolders(folders: string[], filename: string): Promise { + for (const folder of folders) { + const candidate = path.join(folder, filename); + if (await checkIfExists(candidate)) { + return candidate; + } + } + return undefined; +} + +/** + * Merge file listings from multiple folders. First folder takes priority on + * name collisions (earlier occurrence wins). + */ +export async function listFilesFromFolders(folders: string[]): Promise { + const seen = new Set(); + const result: string[] = []; + for (const folder of folders) { + if (!await checkIfExists(folder)) continue; + const files = await listFiles(folder); + for (const f of files) { + if (!seen.has(f)) { + seen.add(f); + result.push(f); + } + } + } + return result; +} + +/** + * Copy files from multiple source folders into a single destination. + * First folder takes priority on duplicate filenames (copy is skipped + * if the file already exists in dest from an earlier folder). + */ +export async function copyFilesFromFolders(folders: string[], destFolder: string): Promise { + await ensureFolderExists(destFolder); + const copied = new Set(); + for (const folder of folders) { + if (!await checkIfExists(folder)) continue; + const files = await fs.readdir(folder); + for (const file of files) { + if (copied.has(file)) continue; + copied.add(file); + const srcPath = path.join(folder, file); + const destPath = path.join(destFolder, file); + await fs.copyFile(srcPath, destPath); + } + } +} diff --git a/src/init.ts b/src/init.ts index 0eb09de..aa552b7 100644 --- a/src/init.ts +++ b/src/init.ts @@ -1,6 +1,6 @@ import * as fs from 'fs/promises'; import path from "path"; -import { checkIfExists, copyFile, copyFiles, deleteFile, ensureFolderExists, listFiles, saveFile } from "./files"; +import { checkIfExists, copyFile, copyFilesFromFolders, deleteFile, ensureFolderExists, findFileInFolders, listFiles, listFilesFromFolders, saveFile } from "./files"; import { PAGE_VERSION, getRequiredPages } from "./pages"; import { DefaultSettings } from "./settings"; import { getOutdatedThemes, parseThemeFilename } from "./themes"; @@ -9,33 +9,59 @@ import { Customizer } from './customizer'; export interface SynthOSConfig { localFolder: string; pagesFolder: string; - requiredPagesFolder: string; - defaultPagesFolder: string; - defaultScriptsFolder: string; - defaultThemesFolder: string; - pageScriptsFolder: string; - serviceConnectorsFolder: string; + requiredPagesFolders: string[]; + defaultPagesFolders: string[]; + defaultScriptsFolders: string[]; + defaultThemesFolders: string[]; + staticFilesFolders: string[]; + serviceConnectorsFolders: string[]; requiredPages: string[]; debug: boolean; debugPageUpdates: boolean; } +/** + * Resolve a folder array from a Customizer getter: replace the `'default'` + * sentinel with the built-in SynthOS path. + */ +function resolveFolders(folders: string[], builtInPath: string): string[] { + return folders.map(f => f === 'default' ? builtInPath : f); +} + export async function createConfig( pagesFolder = '.synthos', options?: { debug?: boolean; debugPageUpdates?: boolean }, customizer?: Customizer ): Promise { - const requiredPagesFolder = customizer?.requiredPagesFolder ?? path.join(__dirname, '../required-pages'); - const requiredPages = await getRequiredPages(requiredPagesFolder); + const requiredPagesFolders = resolveFolders( + customizer?.requiredPagesFolders ?? ['default'], + path.join(__dirname, '../required-pages') + ); + const requiredPages = await getRequiredPages(requiredPagesFolders); return { localFolder: pagesFolder, pagesFolder: path.join(process.cwd(), pagesFolder), - requiredPagesFolder, - defaultPagesFolder: customizer?.defaultPagesFolder ?? path.join(__dirname, '../default-pages'), - defaultScriptsFolder: customizer?.defaultScriptsFolder ?? path.join(__dirname, '../default-scripts'), - defaultThemesFolder: customizer?.defaultThemesFolder ?? path.join(__dirname, '../default-themes'), - pageScriptsFolder: customizer?.pageScriptsFolder ?? path.join(__dirname, '../page-scripts'), - serviceConnectorsFolder: customizer?.serviceConnectorsFolder ?? path.join(__dirname, '../service-connectors'), + requiredPagesFolders, + defaultPagesFolders: resolveFolders( + customizer?.defaultPagesFolders ?? ['default'], + path.join(__dirname, '../default-pages') + ), + defaultScriptsFolders: resolveFolders( + customizer?.defaultScriptsFolders ?? ['default'], + path.join(__dirname, '../default-scripts') + ), + defaultThemesFolders: resolveFolders( + customizer?.defaultThemesFolders ?? ['default'], + path.join(__dirname, '../default-themes') + ), + staticFilesFolders: resolveFolders( + customizer?.staticFilesFolders ?? ['default'], + path.join(__dirname, '../static-files') + ), + serviceConnectorsFolders: resolveFolders( + customizer?.serviceConnectorsFolders ?? ['default'], + path.join(__dirname, '../service-connectors') + ), requiredPages, debug: options?.debug ?? false, debugPageUpdates: options?.debugPageUpdates ?? false @@ -63,21 +89,15 @@ export async function init(config: SynthOSConfig, includeDefaultPages: boolean = console.log(`Copying default scripts to ${config.localFolder} folder...`); const scriptsFolder = path.join(config.pagesFolder, 'scripts'); await ensureFolderExists(scriptsFolder); - switch (process.platform) { - case 'win32': - await copyFile(path.join(config.defaultScriptsFolder, 'windows-terminal.json'), scriptsFolder); - break; - case 'darwin': - await copyFile(path.join(config.defaultScriptsFolder, 'mac-terminal.json'), scriptsFolder); - break; - case 'android': - await copyFile(path.join(config.defaultScriptsFolder, 'android-terminal.json'), scriptsFolder); - break; - case 'linux': - default: - await copyFile(path.join(config.defaultScriptsFolder, 'linux-terminal.json'), scriptsFolder); - break; - } + const scriptFilename = ({ + win32: 'windows-terminal.json', + darwin: 'mac-terminal.json', + android: 'android-terminal.json', + } as Record)[process.platform] ?? 'linux-terminal.json'; + const scriptSrc = await findFileInFolders(config.defaultScriptsFolders, scriptFilename); + if (scriptSrc) { + await copyFile(scriptSrc, scriptsFolder); + } await saveFile(path.join(scriptsFolder, 'example.sh'), `#!/bin/bash\n\n# This is an example script\n\n# You can run this script using the following command:\n# sh ${config.localFolder}/scripts/example.sh\n\n# This script will print "Hello, World!" to the console\n\necho "Hello, World!"\n`); @@ -85,12 +105,12 @@ export async function init(config: SynthOSConfig, includeDefaultPages: boolean = console.log(`Copying default themes to ${config.localFolder} folder...`); const themesFolder = path.join(config.pagesFolder, 'themes'); await ensureFolderExists(themesFolder); - await copyFiles(config.defaultThemesFolder, themesFolder); + await copyFilesFromFolders(config.defaultThemesFolders, themesFolder); // Copy pages if (includeDefaultPages) { console.log(`Copying default pages to ${config.localFolder} folder...`); - await copyDefaultPages(config.defaultPagesFolder, config.pagesFolder); + await copyDefaultPages(config.defaultPagesFolders, config.pagesFolder); } return true; @@ -102,20 +122,14 @@ async function repairMissingFolders(config: SynthOSConfig): Promise { if (!await checkIfExists(scriptsFolder)) { console.log(`Restoring default scripts to ${config.localFolder} folder...`); await ensureFolderExists(scriptsFolder); - switch (process.platform) { - case 'win32': - await copyFile(path.join(config.defaultScriptsFolder, 'windows-terminal.json'), scriptsFolder); - break; - case 'darwin': - await copyFile(path.join(config.defaultScriptsFolder, 'mac-terminal.json'), scriptsFolder); - break; - case 'android': - await copyFile(path.join(config.defaultScriptsFolder, 'android-terminal.json'), scriptsFolder); - break; - case 'linux': - default: - await copyFile(path.join(config.defaultScriptsFolder, 'linux-terminal.json'), scriptsFolder); - break; + const scriptFilename = ({ + win32: 'windows-terminal.json', + darwin: 'mac-terminal.json', + android: 'android-terminal.json', + } as Record)[process.platform] ?? 'linux-terminal.json'; + const scriptSrc = await findFileInFolders(config.defaultScriptsFolders, scriptFilename); + if (scriptSrc) { + await copyFile(scriptSrc, scriptsFolder); } await saveFile(path.join(scriptsFolder, 'example.sh'), `#!/bin/bash\n\n# This is an example script\n\n# You can run this script using the following command:\n# sh ${config.localFolder}/scripts/example.sh\n\n# This script will print "Hello, World!" to the console\n\necho "Hello, World!"\n`); } @@ -125,13 +139,13 @@ async function repairMissingFolders(config: SynthOSConfig): Promise { if (!await checkIfExists(themesFolder)) { console.log(`Restoring default themes to ${config.localFolder} folder...`); await ensureFolderExists(themesFolder); - await copyFiles(config.defaultThemesFolder, themesFolder); + await copyFilesFromFolders(config.defaultThemesFolders, themesFolder); } else { // Upgrade outdated themes — copy newer versioned CSS from defaults const outdated = await getOutdatedThemes(config); if (outdated.length > 0) { console.log(`Upgrading ${outdated.length} theme(s): ${outdated.join(', ')}...`); - const defaultFiles = await listFiles(config.defaultThemesFolder); + const defaultFiles = await listFilesFromFolders(config.defaultThemesFolders); for (const themeName of outdated) { // Remove old versioned CSS files for this theme const localFiles = await listFiles(themesFolder); @@ -141,11 +155,14 @@ async function repairMissingFolders(config: SynthOSConfig): Promise { await deleteFile(path.join(themesFolder, f)); } } - // Copy the new versioned CSS from defaults + // Copy the new versioned CSS from defaults — search all default folders for (const f of defaultFiles) { const parsed = parseThemeFilename(f); if (parsed && parsed.name === themeName) { - await copyFile(path.join(config.defaultThemesFolder, f), themesFolder); + const src = await findFileInFolders(config.defaultThemesFolders, f); + if (src) { + await copyFile(src, themesFolder); + } } } } @@ -159,7 +176,7 @@ async function repairMissingFolders(config: SynthOSConfig): Promise { const htmlFiles = (await listFiles(config.pagesFolder)).filter(f => f.endsWith('.html')); if (htmlFiles.length === 0) { console.log(`Restoring default pages to ${config.localFolder}/pages/ folder...`); - await copyDefaultPages(config.defaultPagesFolder, config.pagesFolder); + await copyDefaultPages(config.defaultPagesFolders, config.pagesFolder); } else { await ensureFolderExists(pagesSubdir); } @@ -213,40 +230,48 @@ async function migrateFlatPages(pagesFolder: string, localFolder: string): Promi } } -async function copyDefaultPages(srcFolder: string, destFolder: string): Promise { +async function copyDefaultPages(srcFolders: string[], destFolder: string): Promise { const pagesDir = path.join(destFolder, 'pages'); await ensureFolderExists(pagesDir); - const files = await fs.readdir(srcFolder); const now = new Date().toISOString(); - for (const file of files) { - if (!file.endsWith('.html')) continue; - const pageName = file.replace(/\.html$/, ''); - const pageFolder = path.join(pagesDir, pageName); - await ensureFolderExists(pageFolder); - await fs.copyFile(path.join(srcFolder, file), path.join(pageFolder, 'page.html')); - - // Read companion .json metadata from source folder, fall back to defaults - let metadata: Record = {}; - const jsonPath = path.join(srcFolder, `${pageName}.json`); - if (await checkIfExists(jsonPath)) { - try { - const raw = await fs.readFile(jsonPath, 'utf-8'); - metadata = JSON.parse(raw); - } catch { - // use defaults + const seen = new Set(); + + for (const srcFolder of srcFolders) { + if (!await checkIfExists(srcFolder)) continue; + const files = await fs.readdir(srcFolder); + for (const file of files) { + if (!file.endsWith('.html')) continue; + const pageName = file.replace(/\.html$/, ''); + if (seen.has(pageName)) continue; // first folder wins + seen.add(pageName); + + const pageFolder = path.join(pagesDir, pageName); + await ensureFolderExists(pageFolder); + await fs.copyFile(path.join(srcFolder, file), path.join(pageFolder, 'page.html')); + + // Read companion .json metadata from source folder, fall back to defaults + let metadata: Record = {}; + const jsonPath = path.join(srcFolder, `${pageName}.json`); + if (await checkIfExists(jsonPath)) { + try { + const raw = await fs.readFile(jsonPath, 'utf-8'); + metadata = JSON.parse(raw); + } catch { + // use defaults + } } + const fullMetadata = { + title: typeof metadata.title === 'string' ? metadata.title : '', + categories: Array.isArray(metadata.categories) ? metadata.categories : [], + pinned: typeof metadata.pinned === 'boolean' ? metadata.pinned : false, + showInAll: typeof metadata.showInAll === 'boolean' ? metadata.showInAll : true, + createdDate: now, + lastModified: now, + pageVersion: typeof metadata.pageVersion === 'number' ? metadata.pageVersion + : typeof metadata.uxVersion === 'number' ? metadata.uxVersion : PAGE_VERSION, + mode: metadata.mode === 'locked' ? 'locked' : 'unlocked', + }; + await saveFile(path.join(pageFolder, 'page.json'), JSON.stringify(fullMetadata, null, 4)); } - const fullMetadata = { - title: typeof metadata.title === 'string' ? metadata.title : '', - categories: Array.isArray(metadata.categories) ? metadata.categories : [], - pinned: typeof metadata.pinned === 'boolean' ? metadata.pinned : false, - showInAll: typeof metadata.showInAll === 'boolean' ? metadata.showInAll : true, - createdDate: now, - lastModified: now, - pageVersion: typeof metadata.pageVersion === 'number' ? metadata.pageVersion - : typeof metadata.uxVersion === 'number' ? metadata.uxVersion : PAGE_VERSION, - mode: metadata.mode === 'locked' ? 'locked' : 'unlocked', - }; - await saveFile(path.join(pageFolder, 'page.json'), JSON.stringify(fullMetadata, null, 4)); } } diff --git a/src/pages.ts b/src/pages.ts index b72dad2..8e2f853 100644 --- a/src/pages.ts +++ b/src/pages.ts @@ -1,4 +1,4 @@ -import {checkIfExists, deleteFile, deleteFolder, ensureFolderExists, listFiles, listFolders, loadFile, saveFile} from './files'; +import {checkIfExists, deleteFile, deleteFolder, ensureFolderExists, findFileInFolders, listFiles, listFilesFromFolders, listFolders, loadFile, saveFile} from './files'; import path from 'path'; // Page State Cache @@ -6,10 +6,10 @@ const _pages: { [name: string]: string } = {}; /** * Derive the list of required page names by scanning *.html files - * in the given requiredPagesFolder directory. + * across one or more requiredPages folders. */ -export async function getRequiredPages(requiredPagesFolder: string): Promise { - const files = await listFiles(requiredPagesFolder); +export async function getRequiredPages(requiredPagesFolders: string[]): Promise { + const files = await listFilesFromFolders(requiredPagesFolders); return files .filter(f => f.endsWith('.html')) .map(f => f.replace(/\.html$/, '')); @@ -31,7 +31,7 @@ export interface PageInfo { export type PageMetadata = Omit; -export async function loadPageMetadata(pagesFolder: string, name: string, fallbackFolder?: string): Promise { +export async function loadPageMetadata(pagesFolder: string, name: string, fallbackFolders?: string[]): Promise { // 1. Try user override: /pages//page.json const metadataPath = path.join(pagesFolder, 'pages', name, 'page.json'); if (await checkIfExists(metadataPath)) { @@ -44,12 +44,12 @@ export async function loadPageMetadata(pagesFolder: string, name: string, fallba } } - // 2. Try fallback: fallbackFolder/.json - if (fallbackFolder) { - const fallbackPath = path.join(fallbackFolder, `${name}.json`); - if (await checkIfExists(fallbackPath)) { + // 2. Try fallback folders: fallbackFolder/.json + if (fallbackFolders) { + const fallbackFile = await findFileInFolders(fallbackFolders, `${name}.json`); + if (fallbackFile) { try { - const raw = await loadFile(fallbackPath); + const raw = await loadFile(fallbackFile); const parsed = JSON.parse(raw); return parseMetadata(parsed); } catch { @@ -92,7 +92,7 @@ const DEFAULT_METADATA: PageMetadata = { mode: 'unlocked', }; -export async function listPages(pagesFolder: string, fallbackPagesFolder: string): Promise { +export async function listPages(pagesFolder: string, fallbackPagesFolders: string[]): Promise { const pageMap = new Map(); // Folder-based pages under pages/ subdirectory @@ -137,13 +137,13 @@ export async function listPages(pagesFolder: string, fallbackPagesFolder: string } } - // Add pages from fallback (required) pages folder - const fallbackFiles = (await listFiles(fallbackPagesFolder)).filter(file => file.endsWith('.html')); - for (const file of fallbackFiles) { + // Add pages from fallback (required) pages folders + const fallbackFiles = await listFilesFromFolders(fallbackPagesFolders); + for (const file of fallbackFiles.filter(f => f.endsWith('.html'))) { const name = file.replace(/\.html$/, ''); if (!pageMap.has(name)) { // System page not yet in map — check for user override, then fallback .json - const metadata = await loadPageMetadata(pagesFolder, name, fallbackPagesFolder); + const metadata = await loadPageMetadata(pagesFolder, name, fallbackPagesFolders); pageMap.set(name, { name, title: metadata?.title ?? '', @@ -238,13 +238,13 @@ export async function copyPage( targetName: string, title: string, categories: string[], - requiredPagesFolder: string + requiredPagesFolders: string[] ): Promise { - // Load source HTML from user folder, then try required folder as fallback + // Load source HTML from user folder, then try required folders as fallback let html = await loadPageState(pagesFolder, sourceName, true); if (!html) { - const requiredPath = path.join(requiredPagesFolder, `${sourceName}.html`); - if (await checkIfExists(requiredPath)) { + const requiredPath = await findFileInFolders(requiredPagesFolders, `${sourceName}.html`); + if (requiredPath) { html = await loadFile(requiredPath); } } diff --git a/src/service/transformPage.ts b/src/service/transformPage.ts index a9a0101..176f0b8 100644 --- a/src/service/transformPage.ts +++ b/src/service/transformPage.ts @@ -27,9 +27,8 @@ export type ChangeOp = | { op: "delete"; nodeId: string } | { op: "insert"; parentId: string; position: "prepend" | "append" | "before" | "after"; html: string } | { op: "style-element"; nodeId: string; style: string } - | { op: "update-lines"; nodeId: string; startLine: number; endLine: number; content: string } - | { op: "delete-lines"; nodeId: string; startLine: number; endLine: number } - | { op: "insert-lines"; nodeId: string; afterLine: number; content: string }; + | { op: "search-replace"; nodeId: string; search: string; replace: string } + | { op: "search-insert"; nodeId: string; after: string; content: string }; export type ChangeList = ChangeOp[]; @@ -51,50 +50,44 @@ export async function transformPage(args: TransformPageArgs): Promise', - content: numberedHtml, + content: annotatedHtml, instructions: '', }; - // 4. Determine newBuild: if isBuilder, count .chat-message in annotated HTML + // 3. Determine newBuild: if isBuilder, count .chat-message in annotated HTML let newBuild = false; if (args.isBuilder) { - const $ = cheerio.load(numberedHtml, { decodeEntities: false }); + const $ = cheerio.load(annotatedHtml, { decodeEntities: false }); const messageCount = $('#chatMessages .chat-message').length; newBuild = messageCount <= 1; } - // 5. Call builder + // 4. Call builder const result = await builder.run(currentPage, additionalSections, message, newBuild, args.attachments); - // 6. Switch on result kind + // 5. Switch on result kind switch (result.kind) { case 'transforms': { - const applied = applyChangeList(numberedHtml, result.changes); - const stripped = stripLineNumbers(applied); - const clean = stripNodeIds(stripped); + const applied = applyChangeList(annotatedHtml, result.changes); + const clean = stripNodeIds(applied); const deduped = deduplicateInlineScripts(clean); const safe = ensureScriptsBeforeBodyClose(deduped); return { completed: true, value: { html: safe, changeCount: result.changes.length } }; } case 'reply': { const productName = args.productName ?? 'SynthOS'; - const withReply = appendChatReply(numberedHtml, message, result.text, productName); - const stripped = stripLineNumbers(withReply); - const clean = stripNodeIds(stripped); + const withReply = appendChatReply(annotatedHtml, message, result.text, productName); + const clean = stripNodeIds(withReply); const deduped = deduplicateInlineScripts(clean); const safe = ensureScriptsBeforeBodyClose(deduped); return { completed: true, value: { html: safe, changeCount: 0 } }; } case 'error': { - const stripped = stripLineNumbers(numberedHtml); - const cleanOriginal = stripNodeIds(stripped); + const cleanOriginal = stripNodeIds(annotatedHtml); const errorMessage = result.error.message; const errorHtml = injectError(cleanOriginal, 'Something went wrong try again', errorMessage); return { completed: true, value: { html: errorHtml, changeCount: 0 } }; @@ -102,8 +95,7 @@ export async function transformPage(args: TransformPageArgs): Promise` and `'; - const result = addLineNumbers(html); - assert.ok(result.includes('01: .a { color: red; }')); - assert.ok(result.includes('02: .b { color: blue; }')); + it('finds exact match and returns correct positions', () => { + const result = normalizedIndexOf('let x = 1;', 'x = 1'); + assert.ok(result !== null); + assert.strictEqual(result.start, 4); + assert.strictEqual(result.end, 9); }); - it('uses 3-digit padding when 100+ lines', () => { - const lines = Array.from({ length: 100 }, (_, i) => `let v${i} = ${i};`).join('\n'); - const html = ``; - const result = addLineNumbers(html); - assert.ok(result.includes('001: let v0 = 0;')); - assert.ok(result.includes('100: let v99 = 99;')); + it('matches despite whitespace differences', () => { + const haystack = 'let x =\n 1;'; + const needle = 'x = 1;'; + const result = normalizedIndexOf(haystack, needle); + assert.ok(result !== null); + // Should span from 'x' to end of ';' + const matched = haystack.slice(result.start, result.end); + assert.ok(matched.includes('x')); + assert.ok(matched.includes('1;')); }); - it('skips scripts with src attribute', () => { - const html = ''; - const result = addLineNumbers(html); - assert.ok(!result.includes('01:')); - }); - - it('skips scripts with type="application/json"', () => { - const html = ''; - const result = addLineNumbers(html); - assert.ok(!result.includes('01:')); - }); - - it('leaves empty script blocks unchanged', () => { - const html = ''; - const result = addLineNumbers(html); - assert.ok(result.includes('')); + it('handles newlines vs spaces', () => { + const haystack = 'function foo() {\n return 1;\n}'; + const needle = 'foo() { return 1; }'; + const result = normalizedIndexOf(haystack, needle); + assert.ok(result !== null); }); }); // --------------------------------------------------------------------------- -// stripLineNumbers +// applyChangeList — search-replace / search-insert ops // --------------------------------------------------------------------------- -describe('stripLineNumbers', () => { - it('strips 2-digit line number prefixes', () => { - const html = ''; - const result = stripLineNumbers(html); - assert.ok(result.includes('let x = 1;\nlet y = 2;')); - assert.ok(!result.includes('01:')); - }); - - it('strips 3-digit line number prefixes', () => { - const html = ''; - const result = stripLineNumbers(html); - assert.ok(result.includes('let x = 1;\nlet y = 2;')); - assert.ok(!result.includes('001:')); - }); - - it('passes through lines without prefix unchanged', () => { - const html = ''; - const result = stripLineNumbers(html); - assert.ok(result.includes('no prefix here\nhas prefix')); - }); - - it('handles mixed lines (some with, some without prefixes)', () => { - const html = ''; - const result = stripLineNumbers(html); - assert.ok(result.includes('line one\nplain line\nline three')); - }); - - it('skips scripts with src attribute', () => { - const html = ''; - const result = stripLineNumbers(html); - assert.ok(result.includes('01: should stay')); - }); - - it('skips scripts with type="application/json"', () => { - const html = ''; - const result = stripLineNumbers(html); - assert.ok(result.includes('01: should stay')); - }); -}); - -// --------------------------------------------------------------------------- -// addLineNumbers -> stripLineNumbers roundtrip -// --------------------------------------------------------------------------- - -describe('addLineNumbers -> stripLineNumbers roundtrip', () => { - it('roundtrip preserves script content', () => { - const original = ''; - const numbered = addLineNumbers(original); - assert.ok(numbered.includes('01:')); - const stripped = stripLineNumbers(numbered); - assert.ok(!stripped.includes('01:')); - assert.ok(stripped.includes('let x = 1;\nlet y = 2;\nreturn x + y;')); - }); - - it('roundtrip preserves style content', () => { - const original = ''; - const numbered = addLineNumbers(original); - const stripped = stripLineNumbers(numbered); - assert.ok(stripped.includes('.a { color: red; }\n.b { color: blue; }')); - }); -}); - -// --------------------------------------------------------------------------- -// applyChangeList — line-range ops -// --------------------------------------------------------------------------- - -describe('applyChangeList — line-range ops', () => { +describe('applyChangeList — search-replace / search-insert ops', () => { const scriptHtml = '' + - '' + + '' + ''; - it('update-lines replaces a range of lines', () => { + it('search-replace replaces exact text match', () => { const changes: ChangeList = [ - { op: 'update-lines', nodeId: '5', startLine: 2, endLine: 3, content: 'let b = 20;\nlet c = 30;' }, + { op: 'search-replace', nodeId: '5', search: 'let b = 2;', replace: 'let b = 20;' }, ]; const result = applyChangeList(scriptHtml, changes); assert.ok(result.includes('let b = 20;')); - assert.ok(result.includes('let c = 30;')); - assert.ok(result.includes('01: let a = 1;')); - assert.ok(result.includes('04: let d = 4;')); - }); - - it('update-lines strips line number prefixes from model-provided content', () => { - const changes: ChangeList = [ - { op: 'update-lines', nodeId: '5', startLine: 2, endLine: 2, content: '02: let b = 99;' }, - ]; - const result = applyChangeList(scriptHtml, changes); - assert.ok(result.includes('let b = 99;')); - // Should not have double line number prefix - assert.ok(!result.includes('02: 02:')); + assert.ok(!result.includes('let b = 2;')); + assert.ok(result.includes('let a = 1;')); + assert.ok(result.includes('let c = 3;')); }); - it('update-lines can expand (replace 1 line with 3)', () => { + it('search-replace with empty replace deletes text', () => { const changes: ChangeList = [ - { op: 'update-lines', nodeId: '5', startLine: 3, endLine: 3, content: 'let c1 = 31;\nlet c2 = 32;\nlet c3 = 33;' }, + { op: 'search-replace', nodeId: '5', search: 'let c = 3;\n', replace: '' }, ]; const result = applyChangeList(scriptHtml, changes); - assert.ok(result.includes('let c1 = 31;')); - assert.ok(result.includes('let c2 = 32;')); - assert.ok(result.includes('let c3 = 33;')); - // Lines after should still be present - assert.ok(result.includes('04: let d = 4;')); + assert.ok(!result.includes('let c = 3;')); + assert.ok(result.includes('let b = 2;')); + assert.ok(result.includes('let d = 4;')); }); - it('update-lines can contract (replace 3 lines with 1)', () => { + it('search-replace falls back to normalized match', () => { + // Script has single spaces, search has different whitespace + const html = '' + + '' + + ''; const changes: ChangeList = [ - { op: 'update-lines', nodeId: '5', startLine: 2, endLine: 4, content: 'let combined = 234;' }, + { op: 'search-replace', nodeId: '5', search: 'function foo() { return 1; }', replace: 'function foo() { return 2; }' }, ]; - const result = applyChangeList(scriptHtml, changes); - assert.ok(result.includes('let combined = 234;')); - assert.ok(result.includes('01: let a = 1;')); - assert.ok(result.includes('05: let e = 5;')); - assert.ok(!result.includes('let b = 2;')); + const result = applyChangeList(html, changes); + assert.ok(result.includes('return 2;')); }); - it('delete-lines removes a range of lines', () => { + it('search-replace warns on no match found', () => { const changes: ChangeList = [ - { op: 'delete-lines', nodeId: '5', startLine: 2, endLine: 3 }, + { op: 'search-replace', nodeId: '5', search: 'nonexistent text', replace: 'replacement' }, ]; + // Should not throw const result = applyChangeList(scriptHtml, changes); - assert.ok(!result.includes('let b = 2;')); - assert.ok(!result.includes('let c = 3;')); - assert.ok(result.includes('01: let a = 1;')); - assert.ok(result.includes('04: let d = 4;')); + // Original content preserved + assert.ok(result.includes('let a = 1;')); + assert.ok(!result.includes('replacement')); }); - it('delete-lines removes a single line', () => { + it('search-replace works on style blocks', () => { + const styleHtml = ''; const changes: ChangeList = [ - { op: 'delete-lines', nodeId: '5', startLine: 3, endLine: 3 }, + { op: 'search-replace', nodeId: '3', search: '.b { color: blue; }', replace: '.b { color: purple; }' }, ]; - const result = applyChangeList(scriptHtml, changes); - assert.ok(!result.includes('let c = 3;')); - assert.ok(result.includes('02: let b = 2;')); - assert.ok(result.includes('04: let d = 4;')); + const result = applyChangeList(styleHtml, changes); + assert.ok(result.includes('.b { color: purple; }')); + assert.ok(!result.includes('color: blue')); }); - it('insert-lines inserts after a specific line', () => { + it('search-insert inserts after matched text', () => { const changes: ChangeList = [ - { op: 'insert-lines', nodeId: '5', afterLine: 2, content: 'let inserted = true;' }, + { op: 'search-insert', nodeId: '5', after: 'let b = 2;', content: '\nlet inserted = true;' }, ]; const result = applyChangeList(scriptHtml, changes); assert.ok(result.includes('let inserted = true;')); - // Verify ordering: line 2 content, then inserted, then line 3 content - const idx2 = result.indexOf('02: let b = 2;'); + const idxB = result.indexOf('let b = 2;'); const idxInserted = result.indexOf('let inserted = true;'); - const idx3 = result.indexOf('03: let c = 3;'); - assert.ok(idx2 < idxInserted); - assert.ok(idxInserted < idx3); - }); - - it('insert-lines at afterLine 0 inserts before first line', () => { - const changes: ChangeList = [ - { op: 'insert-lines', nodeId: '5', afterLine: 0, content: '// header comment' }, - ]; - const result = applyChangeList(scriptHtml, changes); - const idxComment = result.indexOf('// header comment'); - const idxFirst = result.indexOf('01: let a = 1;'); - assert.ok(idxComment < idxFirst); + const idxC = result.indexOf('let c = 3;'); + assert.ok(idxB < idxInserted); + assert.ok(idxInserted < idxC); }); - it('insert-lines at end inserts after last line', () => { + it('search-insert warns on no match found', () => { const changes: ChangeList = [ - { op: 'insert-lines', nodeId: '5', afterLine: 5, content: '// footer comment' }, + { op: 'search-insert', nodeId: '5', after: 'nonexistent text', content: '\nnew stuff' }, ]; const result = applyChangeList(scriptHtml, changes); - const idxLast = result.indexOf('05: let e = 5;'); - const idxComment = result.indexOf('// footer comment'); - assert.ok(idxLast < idxComment); + assert.ok(result.includes('let a = 1;')); + assert.ok(!result.includes('new stuff')); }); - it('warns but does not throw on missing node for line-range ops', () => { + it('warns but does not throw on missing node for search ops', () => { const ops: ChangeList = [ - { op: 'update-lines', nodeId: '999', startLine: 1, endLine: 1, content: 'x' }, - { op: 'delete-lines', nodeId: '999', startLine: 1, endLine: 1 }, - { op: 'insert-lines', nodeId: '999', afterLine: 1, content: 'x' }, + { op: 'search-replace', nodeId: '999', search: 'x', replace: 'y' }, + { op: 'search-insert', nodeId: '999', after: 'x', content: 'y' }, ]; for (const change of ops) { const result = applyChangeList(scriptHtml, [change]); - // Should not throw, content preserved assert.ok(result.includes('let a = 1;')); } }); - - it('works on '; - const changes: ChangeList = [ - { op: 'update-lines', nodeId: '3', startLine: 2, endLine: 2, content: '.b { color: purple; }' }, - ]; - const result = applyChangeList(styleHtml, changes); - assert.ok(result.includes('.b { color: purple; }')); - assert.ok(!result.includes('color: blue')); - }); }); // --------------------------------------------------------------------------- @@ -950,7 +836,7 @@ describe('transformPage', () => { assert.strictEqual(capturedNewBuild, false); }); - it('applies update-lines through full pipeline and strips line numbers', async () => { + it('applies search-replace through full pipeline', async () => { const pageWithScript = `

SynthOS: Welcome!

@@ -960,14 +846,14 @@ describe('transformPage', () => { `; const builder = makeBuilder(async (currentPage) => { - // The current page should have line numbers - assert.ok(currentPage.content.includes('01:'), 'currentPage should contain line numbers'); + // The current page should NOT have line numbers + assert.ok(!currentPage.content.includes('01:'), 'currentPage should not contain line numbers'); // Find the script node id const scriptNodeId = findNodeId(currentPage.content, 'id="page-script"'); return { kind: 'transforms', changes: [ - { op: 'update-lines', nodeId: scriptNodeId, startLine: 1, endLine: 1, content: 'let count = 42;' }, + { op: 'search-replace', nodeId: scriptNodeId, search: 'let count = 0;', replace: 'let count = 42;' }, ], }; }); @@ -977,8 +863,6 @@ describe('transformPage', () => { assert.ok(result.value); // Edit should be applied assert.ok(result.value.html.includes('let count = 42;')); - // Line numbers should be stripped - assert.ok(!result.value.html.match(/\d{2}: /), 'No line number prefixes should remain'); // Node ids should be stripped assert.ok(!result.value.html.includes('data-node-id')); assert.strictEqual(result.value.changeCount, 1); From e4ff8e77f4e4928cc82a20d1eb141ae35d1c381c Mon Sep 17 00:00:00 2001 From: Steven Ickman Date: Tue, 24 Feb 2026 15:03:00 -0800 Subject: [PATCH 02/13] new themes --- CLAUDE.md | 2 + Component-Explorer.html | 1966 +++++++++ Style-Guide.html | 1175 ++++++ default-themes/nebula-dawn.v3.css | 199 + default-themes/nebula-dusk.v3.css | 201 + migration-rules/v2-to-v3.md | 111 + static-files/fluentlm-instructions.llmd | 868 ++++ static-files/fluentlm-instructions.md | 1595 ++++++++ static-files/fluentlm.css | 4844 +++++++++++++++++++++++ static-files/fluentlm.js | 3602 +++++++++++++++++ static-files/fluentlm.min.css | 1 + static-files/fluentlm.min.js | 1 + static-files/theme-dark.css | 169 + static-files/theme-light.css | 169 + 14 files changed, 14903 insertions(+) create mode 100644 Component-Explorer.html create mode 100644 Style-Guide.html create mode 100644 default-themes/nebula-dawn.v3.css create mode 100644 default-themes/nebula-dusk.v3.css create mode 100644 migration-rules/v2-to-v3.md create mode 100644 static-files/fluentlm-instructions.llmd create mode 100644 static-files/fluentlm-instructions.md create mode 100644 static-files/fluentlm.css create mode 100644 static-files/fluentlm.js create mode 100644 static-files/fluentlm.min.css create mode 100644 static-files/fluentlm.min.js create mode 100644 static-files/theme-dark.css create mode 100644 static-files/theme-light.css diff --git a/CLAUDE.md b/CLAUDE.md index 1b4ba9b..f500330 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -314,6 +314,8 @@ Documentation should explain \*\*why\*\*, not restate code. \- **Architecture context:** Always read `.architecture/ARCHITECTURE.md` at the start of a task to understand the current system design before making changes. +\- **Page changes:** Always read `static-files/fluentlm-instructions.md` before making changes to pages or the page transformation pipeline. + --- diff --git a/Component-Explorer.html b/Component-Explorer.html new file mode 100644 index 0000000..540020b --- /dev/null +++ b/Component-Explorer.html @@ -0,0 +1,1966 @@ + + + + + + FluentLM + + + + + + + + + + + +
+ + + + + +
+ + +
+
Awarity Icons
+
+
Icons registered by awarity-v2.js
+
+
Awarity
+
Database
+
Org
+
Flow
+
Processing
+
TextDocument
+
Refresh
+
Tag
+
Download
+
FolderOpen
+
ViewList
+
Copy
+
Beaker
+
Play
+
ChevronLeft
+
ChevronUp
+
ChevronDown
+
Edit
+
CheckMark
+
Cancel
+
Back
+
Clock
+
+
+
+ + +
+
Confirmation Modal
+ +
+
Delete Confirmation
+

+ awarity.showConfirmation({ title, message, type: 'delete' }) — returns Promise<boolean> +

+
+ + +
+
+ +
+
Normal Confirmation
+

+ awarity.showConfirmation({ title, message, type: 'normal' }) — returns Promise<boolean> +

+
+ + +
+
+
+ + +
+
File & Folder Picker
+ +
+
Folder Picker
+
+ +
+ + +
+
+
+ +
+
File Picker
+
+ +
+ + +
+
+
+
+ + +
+
Button
+ +
+
Variants
+
+ + + + + +
+
+ +
+
With Icons
+
+ + + + + + + +
+
+ +
+
Compound
+
+ + +
+
+ +
+
Split Button
+
+ + +
+
+
+ + +
+
Text & Label
+ +
+
Text Sizes
+
+ tiny (10px) + small (12px) + medium (14px — default) + mediumPlus (16px) + large (18px) + xLarge (20px / semibold) + xxLarge (28px / semibold) +
+
+ +
+
+
Text Colors
+
+ Default body text + Secondary text + Disabled text + Error text + Success text + Semibold text + Bold text +
+
+ +
+
Labels
+
+ + + +
+
+
+
+ + + + + +
+
TextField
+ +
+
+
Standard
+
+
+ + +
+
+ + +
+
+ + + Must be a positive number +
+
+ + +
+
+
+ +
+
Variants
+
+
+ + +
+
+ + +
+
+ +
+ https:// + +
+
+
+ + +
+
+
+
+
+ + +
+
Checkbox
+ +
+
+ + + + +
+
+
+ + +
+
Toggle
+ +
+
+ + + +
+
+
+ + + + + +
+
Stack
+ +
+
+
Vertical (default)
+
+
Row 1
+
Row 2
+
Row 3
+
+
+ +
+
Horizontal + grow
+
+
Grows
+
Fixed
+
Fixed
+
+
+ +
+
Horizontal + center
+
+
Centered
+
Items
+
+
+ +
+
Horizontal + wrap
+
+
Tag 1
+
Tag 2
+
Tag 3
+
Tag 4
+
Tag 5
+
+
+
+
+ + +
+
Separator
+ +
+
+ Above separator +
+ Below separator +
+
+
+ Left +
+ Middle +
+ Right +
+
+
+ + +
+
Spinner
+ +
+
+
+
+ xSmall +
+
+
+ small +
+
+
+ medium +
+
+
+ Loading… +
+
+
+
+ + +
+
MessageBar
+ +
+
This is an informational message.
+
Operation completed successfully.
+
Please review before continuing.
+
Immediate action is required.
+
Something went wrong. Please try again.
+
This action is blocked by your organization.
+
+
+ + +
+
Dialog
+
+ + +
+ +
+
+
+

Confirm Action

+ +
+
+

Are you sure you want to proceed? This action cannot be undone.

+
+ +
+
+ +
+
+
+

Welcome

+ +
+
+

This dialog has a large header variant for prominent headings.

+
+ +
+
+
+ + +
+
Panel
+
+ + +
+ +
+
+
+

Settings

+ +
+
+
+
+ + +
+ +
+
+ +
+ +
+
+
+

Medium Panel

+ +
+
+

This is a medium-width panel (592px).

+
+
+
+ + +
+
Callout
+ +
+
+
+ Callout Title + This is a callout with helpful information anchored to the button above. +
+
+
+ + +
+
CommandBar
+
+
+
+ + + + + + +
+
+ + +
+
+
+
+ + + + + + + + +
+
Pivot
+
+
+
+ + + + +
+
+

Overview content goes here. This is the first tab panel.

+
+
+

Details content with more information.

+
+
+

History of changes and activity.

+
+
+
+
+ + +
+
DetailsList
+
+
+
+
+ +
+
Name
+
Status
+
Modified
+
Owner
+
+
+
+
Quarterly Report.docx
+
Active
+
Feb 20, 2026
+
Jane Doe
+
+
+
+
Budget 2026.xlsx
+
Draft
+
Feb 18, 2026
+
John Smith
+
+
+
+
Presentation.pptx
+
Active
+
Feb 15, 2026
+
Alex Johnson
+
+
+
+
+ + + + + +
+
ContextualMenu
+
+ +
Right-click here
+
+ +
+
File
+ + + +
+ +
+ +
+ + + +
+
+ + +
+
ProgressIndicator
+
+
+
+
+ Uploading files… +
+ 65% complete +
+
+ Complete +
+
+
+ Processing… +
+
+
+
+
+
+ + +
+
Tooltip
+
+ Hover for tooltip + + Help link +
+
+ + +
+
Persona
+
+
+
Sizes & Presence
+
+
+
JD
+
+ Jane Doe +
+
+
+
JS
+
+ John Smith + Software Engineer +
+
+
+
AJ
+
+ Alex Johnson + Product Manager + Contoso Ltd +
+
+
+
MK
+
+ Maria Kim + Designer +
+
+
+
+
+
+ + +
+
ChoiceGroup
+
+
+
Vertical (default)
+
+ + + + +
+
+
+
Horizontal
+
+ + + +
+
+
+
+ + +
+
Shimmer
+
+
+
Lines
+
+
+
+
+
+
+
+
Row with avatar
+
+
+
+
+
+
+
+
+
+
Block
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
List
+
+
+
Bordered & Scrollable
+
+
+ Selected Item +
+
+ Second Item +
+
+
+ Two-line Item + Secondary text here +
+
+
+ Disabled Item +
+
+ Fifth Item +
+
+ Sixth Item +
+
+
+
+
Compact
+
+
Item Alpha
+
Item Beta
+
Item Gamma
+
Item Delta
+
+
+
+
+ + +
+
GroupedList
+
+
+
+ +
+
Document.docx
+
Report.xlsx
+
Slides.pptx
+
+
+
+ +
+
OldFile.doc
+
Locked.pdf
+
+
+
+
+
+ + +
+
Rating
+
+
+
Interactive
+
+ + + + + +
+
+
+
Readonly (4 stars)
+
+ + + + + +
+
+
+
Large
+
+ + + + + +
+
+
+
+ + +
+
Facepile
+
+
+
Default (all visible)
+
+ JD + AB + MK +
+
+
+
With overflow (max 3)
+
+ JD + AB + MK + TW + SR +
+
+
+
With add button
+
+ JD + AB + +
+
+
+
+ + +
+
SwatchColorPicker
+
+
+
Square
+
+ + + + + + + + + + +
+
+
+
Circle
+
+ + + + + +
+
+
+
+ + +
+
DocumentCard
+
+
+
+
+
+
+ Quarterly Report.docx +
+
+ JD +
+ Jane Doe + Modified Feb 20 +
+
+
+
+
+
+
+
+ Budget 2026.xlsx + John Smith · Feb 18 +
+
+
+
+ + +
+
SpinButton
+
+
+
Default
+
+ +
+ + + +
+
+
+
+
Disabled
+
+ +
+ + + +
+
+
+
+
+ + +
+
Slider
+
+
+
Horizontal
+
+
+ +
+
+ + 60 +
+
+
+
+
Disabled
+
+
+ +
+
+ + 40 +
+
+
+
+
+ + +
+
ComboBox
+
+
+
Single select
+
+ +
+ + +
+
+
Apple
+
Banana
+
Cherry
+
Grape
+
Mango
+
Orange
+
Peach
+
+
+
+
+
Multi-select
+
+ +
+ + +
+
+
Cheese
+
Pepperoni
+
Mushrooms
+
Olives
+
Onions
+
+
+
+
+
+ + +
+
TeachingBubble
+ +
+
+
+

Welcome to FluentLM!

+ +
+
+ This is a teaching bubble. It draws attention to a feature and provides guidance to help users understand it. +
+ +
+
+ + +
+
HoverCard
+
+ Hover for details +
+
+
+
Jane Doe
+
Software Engineer · Contoso
+
+
+
+

jane.doe@contoso.com

+

Building 42, Room 3014

+
+
+
+
+ + +
+
Coachmark
+
+ Click the pulsing dot to see the teaching bubble: +
+
+
+
+
+
+
+

New Feature

+ +
+
+ This coachmark highlights a new feature. The beacon disappears once dismissed. +
+ +
+
+
+ + +
+
DatePicker
+
+
+
Default
+
+ +
+ + +
+
+
+
+
With constraints
+
+ +
+ + +
+
+
+
+
+ + +
+
TagPicker
+
+
+
Default
+
+ +
+ +
+
+
React
+
Angular
+
Vue
+
Svelte
+
TypeScript
+
JavaScript
+
+
+
+
+
Max 3 tags
+
+ +
+ +
+
+
Apple
+
Banana
+
Cherry
+
Grape
+
Mango
+
+
+
+
+
+
+
PeoplePicker
+
+ +
+ +
+
+
+ JD + + Jane Doe + Software Engineer + +
+
+ JS + + John Smith + Product Manager + +
+
+ AW + + Alice Wang + Designer + +
+
+ BJ + + Bob Johnson + QA Engineer + +
+
+
+
+
+
Disabled
+
+ +
+ + React + + + Vue + + +
+
+
+
+
+
+ + +
+
OverflowSet
+
+
Resize the container to see overflow
+
+
+
+ + + + + +
+ +
+
+
+
+
With far-side items
+
+
+
+ + + + +
+ +
+ + +
+
+
+
+
+ + +
+
TimePicker
+
+
+
Default (24h, 30-min)
+
+ +
+ + +
+
+
+
+
12-hour AM/PM
+
+ +
+ + +
+
+
+
+
Business hours (15-min)
+
+ +
+ + +
+
+
+
+
+ + +
+
Palette
+
+
Brand / Theme
+
+
themeDarker
#004578
+
themeDark
#005a9e
+
themeDarkAlt
#106ebe
+
themePrimary
#0078d4
+
themeSecondary
#2b88d8
+
themeTertiary
#71afe5
+
themeLight
#c7e0f4
+
themeLighter
#deecf9
+
themeLighterAlt
#eff6fc
+
+
+
+
Neutrals
+
+
black
+
neutralDark
+
neutralPrimary
+
neutralSecondary
+
neutralTertiary
+
neutralQuaternary
+
neutralLight
+
neutralLighter
+
neutralLighterAlt
+
white
+
+
+
+
Status
+
+
red
+
orange
+
yellow
+
green
+
teal
+
blue
+
purple
+
magenta
+
+
+
+ + +
+
Typography
+
+
+ tiny — 10px + small — 12px + medium — 14px (default) + mediumPlus — 16px + large — 18px + xLarge — 20px / semibold + xxLarge — 28px / semibold + superLarge — 42px +
+
+
+ + +
+
Elevation
+
+
depth 0
+
depth 4
+
depth 8
+
depth 16
+
depth 64
+
+
+ + +
+
Spacing
+
+
+
+ --spacingS2 — 4px +
+
+
+ --spacingS1 — 8px +
+
+
+ --spacingM — 16px +
+
+
+ --spacingL1 — 20px +
+
+
+ --spacingL2 — 32px +
+
+
+ +
+
+ + + + + + + diff --git a/Style-Guide.html b/Style-Guide.html new file mode 100644 index 0000000..89416cb --- /dev/null +++ b/Style-Guide.html @@ -0,0 +1,1175 @@ + + + + + + FluentLM — Stylable Properties + + + + + + + + + + + + +
+
+
FluentLM
+
Design System
+
+
+ + + +
+ + 1 / 22 + +
+
+
+ + +
+ + +
+
+

FluentLM

+

A complete design token and component system for building Fluent-styled web applications. Every property shown here is fully stylable.

+
to navigate · click a theme above to switch
+
+
+ + +
+

Theme Accent Ramp

+

The 9-stop accent ramp drives all interactive elements — buttons, links, focus rings, selections, and icons. Override these tokens per-theme to rebrand the entire system.

+
+

Semantic Usage

+ + + + + + + + + +
TokenUsed By
--themePrimaryPrimary buttons, active tabs, active nav borders, filter pills, links
--themeSecondaryInput focus borders, input icons, menu icons, hover accents
--themeDarkAltPrimary button hover, input checked hover
--themeDarkPrimary button pressed states
--themeTertiarySubtle accents, icon tints
--themeLightLight accent backgrounds, selection highlights
--themeLighterLightest accent wash, hover tints
+
+ + +
+

Neutral Palette

+

A 15-stop neutral ramp from black to white. These feed into all semantic surface, text, and border tokens.

+
+
+ + +
+

Accent Colors

+

Named accent colors for status indicators, data visualization, and brand expression.

+
+
+ + +
+

Typography

+

A complete type scale with matching line heights. Use the .flm-text--* utility classes or reference tokens directly.

+
+
+

Type Scale

+
+
+
+

Font Weights

+
+ Light (100) + Semilight (300) + Regular (400) + Semibold (600) + Bold (700) +
+

Monospace

+
+ const result = await awarity.ask(query);
+ console.log(result.answer); +
+

Text Utilities

+
+ flm-text--secondary + flm-text--disabled + flm-text--error + flm-text--success +
+
+
+
+ + +
+

Spacing, Radius & Elevation

+

Standardized spacing, corner radius, and shadow tokens for consistent depth and layout.

+
+
+

Spacing

+
+
+
+

Border Radius

+
+
+
+ roundedCorner2 (2px) +
+
+
+ roundedCorner4 (4px) +
+
+
+ roundedCorner6 (6px) +
+
+

Motion

+ + + + + +
--duration1100ms
--duration2200ms
--duration3300ms
--duration4400ms
+
+
+

Elevation

+
+
elevation4
+
elevation8
+
elevation16
+
elevation64
+
+
+
+
+ + +
+

Buttons

+

All button variants, states, and compositions. 16 button tokens control every visual state.

+
+
+

Variants

+
+ + + + +
+
Disabled
+
+ + + +
+
With Icons
+
+ + + +
+
+
+

Compound Button

+ +

Split Button

+
+ +
+

Button Tokens

+ + + + + + +
--buttonBackground
--buttonBackgroundHovered
--primaryButtonBackground
--primaryButtonBackgroundHovered
--buttonBorder
+
+
+
+ + +
+

Text Fields

+

Input fields with labels, required states, error states, prefixes/suffixes, and textarea support.

+
+
+

Standard

+
+
+ + +
+
+ + +
+
+ + + This field contains invalid characters. +
+
+ + +
+
+
+
+

Variants

+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + +
+
+

Input Tokens

+ + + + + +
--inputBackground
--inputBorder
--inputFocusBorderAlt
--inputText
+
+
+
+ + +
+

Dropdowns, ComboBox & Search

+

Selection controls with filtering, multi-select, and search capabilities.

+
+
+

Dropdown

+
+
+ +
+
+
+ +
+
+

ComboBox

+
+
+
+ + +
+
+
+
+
+
+

SearchBox

+
+ + +
+

TagPicker

+
+
+
+ finance + regulatory + +
+
+
legal
+
compliance
+
technical
+
+
+
+

Swatch Color Picker

+
+ + + + + + +
+
+
+
+ + +
+

Selection Controls

+

Checkboxes, radio groups, toggles, sliders, spin buttons, and rating stars.

+
+
+

Checkbox

+
+ + + +
+

ChoiceGroup (Radio)

+
+ + + +
+
+
+

Toggle

+
+ + + +
+

Slider

+
+
+ +
+
Confidence65
+
+

SpinButton

+
+
+ + + +
+
+

Rating

+
+ + + + + +
+
+
+
+ + +
+

Date & Time Pickers

+

Calendar and time selection controls with min/max constraints and formatting options.

+
+
+

DatePicker

+
+
+
+ + +
+
+
+

DatePicker with Error

+
+
+
+ + +
+ Invalid date format. +
+
+
+
+

TimePicker

+
+
+
+ + +
+
+
+

TimePicker Disabled

+
+
+
+ + +
+
+
+
+
+
+ + +
+

CommandBar & Breadcrumb

+

Application-level navigation and action chrome.

+
+
+

CommandBar

+
+
+ + + + + +
+
+ +
+
+
+
+

Breadcrumb

+
+
Default
+
    +
  1. Home
  2. +
  3. Catalogs
  4. +
  5. sec-filings
  6. +
+
+
Large
+
    +
  1. Home
  2. +
  3. Workflows
  4. +
+
+
Small
+
    +
  1. Home
  2. +
  3. Structure
  4. +
  5. Labels
  6. +
+
+
+
+
+
+ + +
+

DetailsList & Lists

+

Data tables and list components with selection, grouping, and compact variants.

+
+

DetailsList

+
+
+
+
Name
+
Categories
+
Documents
+
Status
+
+
+
+
sec-filings
+
finance, regulatory
+
2,341
+
Ready
+
+
+
+
legal-contracts
+
legal, compliance
+
847
+
Missing
+
+
+
+
research-papers
+
research
+
512
+
Error
+
+
+
+
+
+

GroupedList

+
+
+
Finance 2
+
+
sec-filings
+
compliance-docs
+
+
+
+
Research 1
+
+
research-papers
+
+
+
+
+
+

List Tokens

+ + + + + +
--listBackground
--listItemBackgroundHovered
--listItemBackgroundChecked
--listText
+
+
+
+ + +
+

Pivots & Nav

+

Tab navigation (Pivots) and hierarchical navigation (Nav) components.

+
+
+

Pivot — Tabs Style

+
+
+ + + + +
+
Panel content for the Edit tab.
+
Panel content for the Abstracts tab.
+
Panel content for the Usage tab.
+
+

Pivot — Links Style

+ +
+
+

Nav

+
+
+
Catalogs
+ +
+
+
Workflows
+ +
+ Settings + Disabled Link +
+
+
+
+ + +
+

Status & Loading

+

Message bars for status communication, progress indicators, spinners, and loading shimmers.

+
+
+

Message Bars

+
+
This is an informational message.
+
Operation completed successfully.
+
Abstracts are missing for 847 documents.
+
Classification model is nearing token limits.
+
Failed to connect to catalog source.
+
Access denied to this resource.
+
+

Status Tokens

+ + + + + +
--infoBackground--infoIcon
--successBackground--successText
--warningBackground--errorText
--errorBackground--errorIcon
+
+
+

Progress

+
+
+
Determinate — 56%
+
+
+
+
Indeterminate
+
+
+
+

Spinner

+
+
xSmall
+
small
+
default
+
large
+
+

Shimmer

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+

Panels & Dialogs

+

Overlay surfaces for detail editing, confirmation dialogs, and modal workflows. Click the buttons to preview.

+
+
+

Panel Sizes

+
+ + + +
+

Panel Tokens

+ + + + +
--overlayBackground
--cardStandoutBackground
--bodyFrameDivider
+
+
+

Dialogs

+
+ + +
+
+
+ + +
+
+

Small Panel

+

Panel body content. Width: small (340px).

+ +
+
+
+

Medium Panel

+

Panel body content. Width: medium (592px).

+ +
+
+
+

Large Panel

+

Panel body content. Width: large (940px).

+ +
+ + +
+
+

Confirm Action

+

Are you sure you want to delete this catalog? This action cannot be undone.

+ +
+
+
+
+

New Catalog Wizard

+

This dialog uses the flm-dialog--large-header variant for a taller title area. Ideal for multi-step wizards.

+ +
+
+
+ + +
+

Personas & Cards

+

Identity components and content cards with preview areas and activity indicators.

+
+
+

Persona Sizes

+
+
A
+
AB
Alice B.
+
JD
Jane Doe
Analyst
+
MS
Max Smith
Engineer
Online
+
+

Presence

+
+
A
Available
+
B
Busy
+
C
Away
+
D
Offline
+
E
DND
+
+

Facepile

+
+
AB
+
CD
+
EF
+
GH
+
IJ
+
KL
+
+
+
+

DocumentCard

+
+
+ +
+
+
10-K_ACME_2025.pdf
+
+ sec-filings + Feb 22, 2026 +
+
+
+

Card Tokens

+ + + + + +
--cardStandoutBackground
--cardShadowshadow value
--variantBorder
--variantBorderHovered
+
+
+
+ + +
+

Icons

+

All registered icons from the base library and Awarity extension. Use <i class="flm-icon" data-icon="Name"> or register custom SVG paths via FluentIcons.register().

+
+
+ +
+ + + + + + + + + diff --git a/default-themes/nebula-dawn.v3.css b/default-themes/nebula-dawn.v3.css new file mode 100644 index 0000000..29e4958 --- /dev/null +++ b/default-themes/nebula-dawn.v3.css @@ -0,0 +1,199 @@ +/* ========================================================================== + Nebula Dawn — Light Theme (v3) + Palette overrides + semantic tokens for FluentLM. + Rebrands from default blue to Nebula purple. + ========================================================================== */ + +:root, +.nebula-dawn { + /* ----------------------------------------------------------------------- + PALETTE — Brand / Theme Colors (Nebula purple ramp) + ----------------------------------------------------------------------- */ + --themePrimary: #667eea; + --themeDark: #764ba2; + --themeDarkAlt: #5639a0; + --themeDarker: #432d80; + --themeSecondary: #c054d4; + --themeTertiary: #8b9ef0; + --themeLight: #c5cff7; + --themeLighter: #e0e5fb; + --themeLighterAlt: #f3f5fe; + + /* ----------------------------------------------------------------------- + PALETTE — Neutrals (warm purple-tinted) + ----------------------------------------------------------------------- */ + --neutralDark: #1e1830; + --neutralPrimary: #2d2640; + --neutralPrimaryAlt: #3d2e56; + --neutralSecondary: #6b4f8a; + --neutralSecondaryAlt: #5e4579; + --neutralTertiary: #8a7a9d; + --neutralTertiaryAlt: #a898b5; + --neutralQuaternary: #cbc0d6; + --neutralQuaternaryAlt: #d5cae0; + --neutralLight: #e8dff0; + --neutralLighter: #f0e6f6; + --neutralLighterAlt: #f7f2fa; + --white: #ffffff; + + /* ----------------------------------------------------------------------- + BODY / BACKGROUND + ----------------------------------------------------------------------- */ + --bodyBackground: var(--white); + --bodyBackgroundHovered: var(--neutralLighter); + --bodyBackgroundChecked: var(--neutralLight); + --bodyStandoutBackground: var(--neutralLighterAlt); + --bodyFrameBackground: var(--white); + --bodyFrameDivider: var(--neutralLight); + --bodyDivider: var(--neutralLight); + + /* ----------------------------------------------------------------------- + TEXT + ----------------------------------------------------------------------- */ + --bodyText: var(--neutralPrimary); + --bodyTextChecked: var(--neutralDark); + --bodySubtext: var(--neutralSecondary); + --disabledText: var(--neutralTertiary); + --disabledBodyText: var(--neutralTertiary); + --disabledSubtext: var(--neutralQuaternary); + --disabledBodySubtext: var(--neutralTertiaryAlt); + + /* ----------------------------------------------------------------------- + LINKS + ----------------------------------------------------------------------- */ + --link: var(--themePrimary); + --linkHovered: var(--themeDarker); + --actionLink: var(--neutralPrimary); + --actionLinkHovered: var(--neutralDark); + + /* ----------------------------------------------------------------------- + BUTTONS — Default + ----------------------------------------------------------------------- */ + --buttonBackground: var(--white); + --buttonBackgroundHovered: var(--neutralLighter); + --buttonBackgroundChecked: var(--neutralLight); + --buttonBackgroundCheckedHovered: var(--neutralQuaternaryAlt); + --buttonBackgroundPressed: var(--neutralLight); + --buttonBackgroundDisabled: var(--neutralLighter); + --buttonBorder: var(--neutralSecondaryAlt); + --buttonBorderDisabled: var(--neutralLighter); + --buttonText: var(--neutralPrimary); + --buttonTextHovered: var(--neutralDark); + --buttonTextChecked: var(--neutralDark); + --buttonTextCheckedHovered: var(--black); + --buttonTextPressed: var(--neutralDark); + --buttonTextDisabled: var(--neutralTertiary); + + /* ----------------------------------------------------------------------- + BUTTONS — Primary + ----------------------------------------------------------------------- */ + --primaryButtonBackground: var(--themePrimary); + --primaryButtonBackgroundHovered: var(--themeDarkAlt); + --primaryButtonBackgroundPressed: var(--themeDark); + --primaryButtonBackgroundDisabled: var(--neutralLighter); + --primaryButtonBorder: transparent; + --primaryButtonText: var(--white); + --primaryButtonTextHovered: var(--white); + --primaryButtonTextPressed: var(--white); + --primaryButtonTextDisabled: var(--neutralQuaternary); + + /* ----------------------------------------------------------------------- + BUTTONS — Accent + ----------------------------------------------------------------------- */ + --accentButtonBackground: var(--themePrimary); + --accentButtonText: var(--white); + + /* ----------------------------------------------------------------------- + INPUTS + ----------------------------------------------------------------------- */ + --inputBackground: var(--white); + --inputBackgroundChecked: var(--themePrimary); + --inputBackgroundCheckedHovered: var(--themeDarkAlt); + --inputPlaceholderBackgroundChecked: var(--themeLighter); + --inputForegroundChecked: var(--white); + --inputBorder: var(--neutralSecondary); + --smallInputBorder: var(--neutralSecondary); + --inputBorderHovered: var(--neutralPrimary); + --inputFocusBorderAlt: var(--themePrimary); + --inputText: var(--neutralPrimary); + --inputTextHovered: var(--neutralDark); + --inputPlaceholderText: var(--neutralSecondary); + --inputIcon: var(--themePrimary); + --inputIconHovered: var(--themeDark); + --inputIconDisabled: var(--neutralTertiary); + + /* ----------------------------------------------------------------------- + LISTS + ----------------------------------------------------------------------- */ + --listBackground: var(--white); + --listText: var(--neutralPrimary); + --listItemBackgroundHovered: var(--neutralLighter); + --listItemBackgroundChecked: var(--neutralLight); + --listItemBackgroundCheckedHovered: var(--neutralQuaternaryAlt); + --listHeaderBackgroundHovered: var(--neutralLighter); + --listHeaderBackgroundPressed: var(--neutralLight); + + /* ----------------------------------------------------------------------- + MENUS + ----------------------------------------------------------------------- */ + --menuBackground: var(--white); + --menuDivider: var(--neutralTertiaryAlt); + --menuIcon: var(--themePrimary); + --menuHeader: var(--themePrimary); + --menuItemBackgroundHovered: var(--neutralLighter); + --menuItemBackgroundPressed: var(--neutralLight); + --menuItemText: var(--neutralPrimary); + --menuItemTextHovered: var(--neutralDark); + + /* ----------------------------------------------------------------------- + CARDS + ----------------------------------------------------------------------- */ + --cardStandoutBackground: var(--white); + --cardShadow: var(--elevation4); + --cardShadowHovered: var(--elevation8); + --variantBorder: var(--neutralLight); + --variantBorderHovered: var(--neutralTertiaryAlt); + + /* ----------------------------------------------------------------------- + DISABLED / FOCUS + ----------------------------------------------------------------------- */ + --disabledBackground: var(--neutralLighter); + --disabledBorder: var(--neutralLighter); + --focusBorder: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + STATUS — Backgrounds + ----------------------------------------------------------------------- */ + --infoBackground: var(--neutralLighter); + --errorBackground: #FDE7E9; + --blockingBackground: #FDE7E9; + --warningBackground: #FFF4CE; + --severeWarningBackground: #FED9CC; + --successBackground: #DFF6DD; + + /* ----------------------------------------------------------------------- + STATUS — Icons + ----------------------------------------------------------------------- */ + --infoIcon: var(--neutralSecondary); + --errorIcon: #A80000; + --blockingIcon: #FDE7E9; + --warningIcon: #797775; + --severeWarningIcon: var(--orange); + --successIcon: var(--green); + + /* ----------------------------------------------------------------------- + STATUS — Text + ----------------------------------------------------------------------- */ + --errorText: var(--redDark); + --messageText: var(--neutralPrimary); + --messageLink: var(--themeDark); + --messageLinkHovered: var(--themeDarker); + --warningText: var(--neutralPrimary); + --successText: var(--green); + + /* ----------------------------------------------------------------------- + MISC + ----------------------------------------------------------------------- */ + --defaultStateBackground: var(--neutralLighterAlt); + --overlayBackground: var(--blackTranslucent40); +} diff --git a/default-themes/nebula-dusk.v3.css b/default-themes/nebula-dusk.v3.css new file mode 100644 index 0000000..9ab7ef8 --- /dev/null +++ b/default-themes/nebula-dusk.v3.css @@ -0,0 +1,201 @@ +/* ========================================================================== + Nebula Dusk — Dark Theme (v3) + Palette overrides + semantic tokens for FluentLM. + Rebrands from default blue to Nebula purple, dark mode. + ========================================================================== */ + +.nebula-dusk { + /* ----------------------------------------------------------------------- + PALETTE — Brand / Theme Colors (Nebula purple ramp) + ----------------------------------------------------------------------- */ + --themePrimary: #667eea; + --themeDark: #764ba2; + --themeDarkAlt: #5639a0; + --themeDarker: #432d80; + --themeSecondary: #f093fb; + --themeTertiary: #8b9ef0; + --themeLight: #c5cff7; + --themeLighter: #e0e5fb; + --themeLighterAlt: #f3f5fe; + + /* ----------------------------------------------------------------------- + PALETTE — Neutrals (inverted for dark mode) + ----------------------------------------------------------------------- */ + --black: #000000; + --neutralDarker: #0f0f23; + --neutralDark: #16213e; + --neutralPrimary: #1a1a2e; + --neutralPrimaryAlt: #222244; + --neutralSecondary: #2e2e52; + --neutralSecondaryAlt: #3a3a60; + --neutralTertiary: #5a5a80; + --neutralTertiaryAlt: #b794f6; + --neutralQuaternary: #7a7a9a; + --neutralQuaternaryAlt: #8a8aaa; + --neutralLight: #a0a0c0; + --neutralLighter: #e0e0e0; + --neutralLighterAlt: #f0f0f0; + --white: #ffffff; + + /* ----------------------------------------------------------------------- + BODY / BACKGROUND + ----------------------------------------------------------------------- */ + --bodyBackground: var(--neutralPrimary); + --bodyBackgroundHovered: var(--neutralPrimaryAlt); + --bodyBackgroundChecked: var(--neutralSecondary); + --bodyStandoutBackground: var(--neutralDark); + --bodyFrameBackground: var(--neutralPrimary); + --bodyFrameDivider: var(--neutralSecondary); + --bodyDivider: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + TEXT + ----------------------------------------------------------------------- */ + --bodyText: var(--neutralLighter); + --bodyTextChecked: var(--white); + --bodySubtext: var(--neutralTertiaryAlt); + --disabledText: var(--neutralSecondary); + --disabledBodyText: var(--neutralSecondary); + --disabledSubtext: var(--neutralSecondaryAlt); + --disabledBodySubtext: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + LINKS (Nebula purple instead of default blue) + ----------------------------------------------------------------------- */ + --link: #b794f6; + --linkHovered: #d4b8ff; + --actionLink: var(--neutralLighter); + --actionLinkHovered: var(--white); + + /* ----------------------------------------------------------------------- + BUTTONS — Default + ----------------------------------------------------------------------- */ + --buttonBackground: var(--neutralPrimary); + --buttonBackgroundHovered: var(--neutralPrimaryAlt); + --buttonBackgroundChecked: var(--neutralSecondary); + --buttonBackgroundCheckedHovered: var(--neutralSecondaryAlt); + --buttonBackgroundPressed: var(--neutralSecondary); + --buttonBackgroundDisabled: var(--neutralDark); + --buttonBorder: var(--neutralSecondaryAlt); + --buttonBorderDisabled: var(--neutralDark); + --buttonText: var(--neutralLighter); + --buttonTextHovered: var(--white); + --buttonTextChecked: var(--white); + --buttonTextCheckedHovered: var(--white); + --buttonTextPressed: var(--white); + --buttonTextDisabled: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + BUTTONS — Primary + ----------------------------------------------------------------------- */ + --primaryButtonBackground: var(--themePrimary); + --primaryButtonBackgroundHovered: var(--themeDarkAlt); + --primaryButtonBackgroundPressed: var(--themeDark); + --primaryButtonBackgroundDisabled: var(--neutralDark); + --primaryButtonBorder: transparent; + --primaryButtonText: var(--white); + --primaryButtonTextHovered: var(--white); + --primaryButtonTextPressed: var(--white); + --primaryButtonTextDisabled: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + BUTTONS — Accent + ----------------------------------------------------------------------- */ + --accentButtonBackground: var(--themePrimary); + --accentButtonText: var(--white); + + /* ----------------------------------------------------------------------- + INPUTS + ----------------------------------------------------------------------- */ + --inputBackground: var(--neutralPrimary); + --inputBackgroundChecked: var(--themePrimary); + --inputBackgroundCheckedHovered: var(--themeDarkAlt); + --inputPlaceholderBackgroundChecked: var(--themeDark); + --inputForegroundChecked: var(--white); + --inputBorder: var(--neutralTertiary); + --smallInputBorder: var(--neutralTertiary); + --inputBorderHovered: var(--neutralLighter); + --inputFocusBorderAlt: var(--themeSecondary); + --inputText: var(--neutralLighter); + --inputTextHovered: var(--white); + --inputPlaceholderText: var(--neutralTertiaryAlt); + --inputIcon: var(--themeSecondary); + --inputIconHovered: var(--themePrimary); + --inputIconDisabled: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + LISTS + ----------------------------------------------------------------------- */ + --listBackground: var(--neutralPrimary); + --listText: var(--neutralLighter); + --listItemBackgroundHovered: var(--neutralPrimaryAlt); + --listItemBackgroundChecked: var(--neutralSecondary); + --listItemBackgroundCheckedHovered: var(--neutralSecondaryAlt); + --listHeaderBackgroundHovered: var(--neutralPrimaryAlt); + --listHeaderBackgroundPressed: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + MENUS + ----------------------------------------------------------------------- */ + --menuBackground: var(--neutralPrimary); + --menuDivider: var(--neutralSecondary); + --menuIcon: var(--themeSecondary); + --menuHeader: var(--themeSecondary); + --menuItemBackgroundHovered: var(--neutralPrimaryAlt); + --menuItemBackgroundPressed: var(--neutralSecondary); + --menuItemText: var(--neutralLighter); + --menuItemTextHovered: var(--white); + + /* ----------------------------------------------------------------------- + CARDS + In dark themes Fluent v8 drops shadows and uses borders instead. + ----------------------------------------------------------------------- */ + --cardStandoutBackground: var(--neutralDark); + --cardShadow: none; + --cardShadowHovered: none; + --variantBorder: var(--neutralSecondary); + --variantBorderHovered: var(--neutralTertiary); + + /* ----------------------------------------------------------------------- + DISABLED / FOCUS + ----------------------------------------------------------------------- */ + --disabledBackground: var(--neutralDark); + --disabledBorder: var(--neutralDark); + --focusBorder: var(--neutralTertiaryAlt); + + /* ----------------------------------------------------------------------- + STATUS — Backgrounds + ----------------------------------------------------------------------- */ + --infoBackground: var(--neutralPrimary); + --errorBackground: #442726; + --blockingBackground: #442726; + --warningBackground: #433519; + --severeWarningBackground: #4F2A0F; + --successBackground: #393D1B; + + /* ----------------------------------------------------------------------- + STATUS — Icons + ----------------------------------------------------------------------- */ + --infoIcon: var(--neutralTertiaryAlt); + --errorIcon: #F1707B; + --blockingIcon: #442726; + --warningIcon: var(--neutralTertiaryAlt); + --severeWarningIcon: #FCE100; + --successIcon: #92C353; + + /* ----------------------------------------------------------------------- + STATUS — Text + ----------------------------------------------------------------------- */ + --errorText: #F1707B; + --messageText: var(--neutralLighter); + --messageLink: #b794f6; + --messageLinkHovered: #d4b8ff; + --warningText: var(--neutralLighter); + --successText: #92C353; + + /* ----------------------------------------------------------------------- + MISC + ----------------------------------------------------------------------- */ + --defaultStateBackground: var(--neutralDark); + --overlayBackground: rgba(0, 0, 0, 0.6); +} diff --git a/migration-rules/v2-to-v3.md b/migration-rules/v2-to-v3.md new file mode 100644 index 0000000..7a7aade --- /dev/null +++ b/migration-rules/v2-to-v3.md @@ -0,0 +1,111 @@ +# SynthOS v2 → v3 Migration Rules + +This document covers the migration from SynthOS v2 format to v3 (FluentLM-based). + +--- + +## 1. Theme Migration + +### Overview + +In v2, theme files (~800 lines each) define **both** CSS custom properties and all shell component styles (chat panel, modals, forms, scrollbars, etc.). + +In v3, component styles live in `fluentlm.css`. Theme files **only** override FluentLM palette variables and set semantic color tokens. This brings each theme down to ~170 lines. + +### Structure of a v3 Theme File + +``` +:root, .theme-class { + /* 1. Palette overrides — rebrand FluentLM's default blue to your theme ramp */ + /* 2. Semantic tokens — map palette vars to UI roles (same sections as theme-light/dark.css) */ +} +``` + +Zero component CSS. No selectors for `.chat-panel`, `.modal-*`, `.brainstorm-*`, scrollbars, etc. + +### Palette Variable Mapping (v2 → v3) + +#### Brand / Theme Colors + +| v2 variable | v3 palette variable | Notes | +|---|---|---| +| `--accent-primary` | `--themePrimary` | Primary brand color | +| `--accent-secondary` | `--themeDark` | Darker accent | +| `--accent-tertiary` | `--themeSecondary` | Secondary accent | +| — | `--themeDarkAlt` | Interpolated from brand ramp | +| — | `--themeDarker` | Interpolated from brand ramp | +| — | `--themeTertiary` | Interpolated from brand ramp | +| — | `--themeLight` | Interpolated from brand ramp | +| — | `--themeLighter` | Interpolated from brand ramp | +| — | `--themeLighterAlt` | Interpolated from brand ramp | + +#### Neutral Colors — Light Theme + +| v2 variable | v3 palette variable | Notes | +|---|---|---| +| `--bg-tertiary` | `--neutralLighterAlt` | Lightest background | +| `--bg-primary` | `--neutralLighter` | Primary background | +| `--bg-secondary` | `--neutralLight` | Secondary background | +| `--text-secondary` | `--neutralSecondary` | Secondary text | +| `--text-primary` | `--neutralPrimary` | Primary text | +| `--white` | `--white` | White | + +#### Neutral Colors — Dark Theme + +| v2 variable | v3 palette variable | Notes | +|---|---|---| +| `--bg-primary` | `--neutralPrimary` | Dark primary background | +| `--bg-secondary` | `--neutralDark` | Dark secondary background | +| `--bg-tertiary` | `--neutralDarker` (custom) | Darkest background | +| `--text-primary` | `--neutralLighter` | Light text on dark | +| `--text-secondary` | `--neutralTertiaryAlt` | Secondary text on dark | + +### Variables Without FluentLM Equivalents + +The following v2 variables have **no** FluentLM palette equivalent. If a page uses them, the page author must define them in a page-level ` + + + + +
+
+
FluentLM
+
Theme Gallery
+
+
+ + + + + + + + + + +
+
+ + +
+ + +
+

Nebula Dusk

+ Purple + Dark +
+ + +

Brand / Accent Ramp

+
+ + +

Neutral Ramp

+
+ + +

Component Preview

+
+
+

Buttons

+
+ + + + +
+
+ + +
+
+
+

Text Fields

+
+
+ + +
+
+ + + Invalid value. +
+
+
+
+

Card Example

+
+
Document Title
+
A card rendered with the current theme's surface and border tokens.
+
+ + +
+
+
+
+

Message Bars

+
+
Informational message.
+
Operation succeeded.
+
Warning: check inputs.
+
Severe warning issued.
+
An error occurred.
+
Access blocked.
+
+
+
+ +
+ + + + + + diff --git a/default-themes/aurora-dawn.json b/default-themes/aurora-dawn.json new file mode 100644 index 0000000..819cf9d --- /dev/null +++ b/default-themes/aurora-dawn.json @@ -0,0 +1,19 @@ +{ + "mode": "light", + "colors": { + "bg-primary": "#e9f4f6", + "bg-secondary": "#dceef0", + "bg-tertiary": "#f2f9fa", + "accent-primary": "#00b4d8", + "accent-secondary": "#008299", + "accent-tertiary": "#66d3e8", + "accent-glow": "rgba(0,180,216,0.15)", + "text-primary": "#18363c", + "text-secondary": "#40788a", + "border-color": "rgba(0,180,216,0.25)", + "header-min-height": "58px", + "header-padding-vertical": "14px", + "header-padding-horizontal": "20px", + "header-line-height": "1.25" + } +} diff --git a/default-themes/aurora-dawn.v3.css b/default-themes/aurora-dawn.v3.css new file mode 100644 index 0000000..34af549 --- /dev/null +++ b/default-themes/aurora-dawn.v3.css @@ -0,0 +1,198 @@ +/* ========================================================================== + Aurora Dawn — Light Theme (v3) + Palette overrides + semantic tokens for FluentLM. + Rebrands from default blue to Aurora teal/cyan. + ========================================================================== */ + +.aurora-dawn { + /* ----------------------------------------------------------------------- + PALETTE — Brand / Theme Colors (Aurora teal ramp) + ----------------------------------------------------------------------- */ + --themePrimary: #00b4d8; + --themeDark: #008299; + --themeDarkAlt: #009ab8; + --themeDarker: #004d5e; + --themeSecondary: #33c3e0; + --themeTertiary: #66d3e8; + --themeLight: #b3e8f3; + --themeLighter: #d9f3f9; + --themeLighterAlt: #ecf9fc; + + /* ----------------------------------------------------------------------- + PALETTE — Neutrals (cool teal-tinted) + ----------------------------------------------------------------------- */ + --neutralDark: #0e2a2e; + --neutralPrimary: #18363c; + --neutralPrimaryAlt: #1e4a52; + --neutralSecondary: #40788a; + --neutralSecondaryAlt: #366a79; + --neutralTertiary: #709da8; + --neutralTertiaryAlt: #8eb5be; + --neutralQuaternary: #bbd6db; + --neutralQuaternaryAlt: #cae0e4; + --neutralLight: #dceef0; + --neutralLighter: #e9f4f6; + --neutralLighterAlt: #f2f9fa; + --white: #ffffff; + + /* ----------------------------------------------------------------------- + BODY / BACKGROUND + ----------------------------------------------------------------------- */ + --bodyBackground: var(--white); + --bodyBackgroundHovered: var(--neutralLighter); + --bodyBackgroundChecked: var(--neutralLight); + --bodyStandoutBackground: var(--neutralLighterAlt); + --bodyFrameBackground: var(--white); + --bodyFrameDivider: var(--neutralLight); + --bodyDivider: var(--neutralLight); + + /* ----------------------------------------------------------------------- + TEXT + ----------------------------------------------------------------------- */ + --bodyText: var(--neutralPrimary); + --bodyTextChecked: var(--neutralDark); + --bodySubtext: var(--neutralSecondary); + --disabledText: var(--neutralTertiary); + --disabledBodyText: var(--neutralTertiary); + --disabledSubtext: var(--neutralQuaternary); + --disabledBodySubtext: var(--neutralTertiaryAlt); + + /* ----------------------------------------------------------------------- + LINKS + ----------------------------------------------------------------------- */ + --link: var(--themePrimary); + --linkHovered: var(--themeDarker); + --actionLink: var(--neutralPrimary); + --actionLinkHovered: var(--neutralDark); + + /* ----------------------------------------------------------------------- + BUTTONS — Default + ----------------------------------------------------------------------- */ + --buttonBackground: var(--white); + --buttonBackgroundHovered: var(--neutralLighter); + --buttonBackgroundChecked: var(--neutralLight); + --buttonBackgroundCheckedHovered: var(--neutralQuaternaryAlt); + --buttonBackgroundPressed: var(--neutralLight); + --buttonBackgroundDisabled: var(--neutralLighter); + --buttonBorder: var(--neutralSecondaryAlt); + --buttonBorderDisabled: var(--neutralLighter); + --buttonText: var(--neutralPrimary); + --buttonTextHovered: var(--neutralDark); + --buttonTextChecked: var(--neutralDark); + --buttonTextCheckedHovered: var(--black); + --buttonTextPressed: var(--neutralDark); + --buttonTextDisabled: var(--neutralTertiary); + + /* ----------------------------------------------------------------------- + BUTTONS — Primary + ----------------------------------------------------------------------- */ + --primaryButtonBackground: var(--themePrimary); + --primaryButtonBackgroundHovered: var(--themeDarkAlt); + --primaryButtonBackgroundPressed: var(--themeDark); + --primaryButtonBackgroundDisabled: var(--neutralLighter); + --primaryButtonBorder: transparent; + --primaryButtonText: var(--white); + --primaryButtonTextHovered: var(--white); + --primaryButtonTextPressed: var(--white); + --primaryButtonTextDisabled: var(--neutralQuaternary); + + /* ----------------------------------------------------------------------- + BUTTONS — Accent + ----------------------------------------------------------------------- */ + --accentButtonBackground: var(--themePrimary); + --accentButtonText: var(--white); + + /* ----------------------------------------------------------------------- + INPUTS + ----------------------------------------------------------------------- */ + --inputBackground: var(--white); + --inputBackgroundChecked: var(--themePrimary); + --inputBackgroundCheckedHovered: var(--themeDarkAlt); + --inputPlaceholderBackgroundChecked: var(--themeLighter); + --inputForegroundChecked: var(--white); + --inputBorder: var(--neutralSecondary); + --smallInputBorder: var(--neutralSecondary); + --inputBorderHovered: var(--neutralPrimary); + --inputFocusBorderAlt: var(--themePrimary); + --inputText: var(--neutralPrimary); + --inputTextHovered: var(--neutralDark); + --inputPlaceholderText: var(--neutralSecondary); + --inputIcon: var(--themePrimary); + --inputIconHovered: var(--themeDark); + --inputIconDisabled: var(--neutralTertiary); + + /* ----------------------------------------------------------------------- + LISTS + ----------------------------------------------------------------------- */ + --listBackground: var(--white); + --listText: var(--neutralPrimary); + --listItemBackgroundHovered: var(--neutralLighter); + --listItemBackgroundChecked: var(--neutralLight); + --listItemBackgroundCheckedHovered: var(--neutralQuaternaryAlt); + --listHeaderBackgroundHovered: var(--neutralLighter); + --listHeaderBackgroundPressed: var(--neutralLight); + + /* ----------------------------------------------------------------------- + MENUS + ----------------------------------------------------------------------- */ + --menuBackground: var(--white); + --menuDivider: var(--neutralTertiaryAlt); + --menuIcon: var(--themePrimary); + --menuHeader: var(--themePrimary); + --menuItemBackgroundHovered: var(--neutralLighter); + --menuItemBackgroundPressed: var(--neutralLight); + --menuItemText: var(--neutralPrimary); + --menuItemTextHovered: var(--neutralDark); + + /* ----------------------------------------------------------------------- + CARDS + ----------------------------------------------------------------------- */ + --cardStandoutBackground: var(--white); + --cardShadow: var(--elevation4); + --cardShadowHovered: var(--elevation8); + --variantBorder: var(--neutralLight); + --variantBorderHovered: var(--neutralTertiaryAlt); + + /* ----------------------------------------------------------------------- + DISABLED / FOCUS + ----------------------------------------------------------------------- */ + --disabledBackground: var(--neutralLighter); + --disabledBorder: var(--neutralLighter); + --focusBorder: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + STATUS — Backgrounds + ----------------------------------------------------------------------- */ + --infoBackground: var(--neutralLighter); + --errorBackground: #FDE7E9; + --blockingBackground: #FDE7E9; + --warningBackground: #FFF4CE; + --severeWarningBackground: #FED9CC; + --successBackground: #DFF6DD; + + /* ----------------------------------------------------------------------- + STATUS — Icons + ----------------------------------------------------------------------- */ + --infoIcon: var(--neutralSecondary); + --errorIcon: #A80000; + --blockingIcon: #FDE7E9; + --warningIcon: #797775; + --severeWarningIcon: var(--orange); + --successIcon: var(--green); + + /* ----------------------------------------------------------------------- + STATUS — Text + ----------------------------------------------------------------------- */ + --errorText: var(--redDark); + --messageText: var(--neutralPrimary); + --messageLink: var(--themeDark); + --messageLinkHovered: var(--themeDarker); + --warningText: var(--neutralPrimary); + --successText: var(--green); + + /* ----------------------------------------------------------------------- + MISC + ----------------------------------------------------------------------- */ + --defaultStateBackground: var(--neutralLighterAlt); + --overlayBackground: var(--blackTranslucent40); +} diff --git a/default-themes/aurora-dusk.json b/default-themes/aurora-dusk.json new file mode 100644 index 0000000..4988757 --- /dev/null +++ b/default-themes/aurora-dusk.json @@ -0,0 +1,19 @@ +{ + "mode": "dark", + "colors": { + "bg-primary": "#0e2a2e", + "bg-secondary": "#0c2028", + "bg-tertiary": "#061518", + "accent-primary": "#00b4d8", + "accent-secondary": "#008299", + "accent-tertiary": "#66d3e8", + "accent-glow": "rgba(0,180,216,0.3)", + "text-primary": "#d4e6ea", + "text-secondary": "#66d3e8", + "border-color": "rgba(0,180,216,0.3)", + "header-min-height": "58px", + "header-padding-vertical": "14px", + "header-padding-horizontal": "20px", + "header-line-height": "1.25" + } +} diff --git a/default-themes/aurora-dusk.v3.css b/default-themes/aurora-dusk.v3.css new file mode 100644 index 0000000..acb2765 --- /dev/null +++ b/default-themes/aurora-dusk.v3.css @@ -0,0 +1,200 @@ +/* ========================================================================== + Aurora Dusk — Dark Theme (v3) + Palette overrides + semantic tokens for FluentLM. + Rebrands from default blue to Aurora teal/cyan, dark mode. + ========================================================================== */ + +.aurora-dusk { + /* ----------------------------------------------------------------------- + PALETTE — Brand / Theme Colors (Aurora teal ramp) + ----------------------------------------------------------------------- */ + --themePrimary: #00b4d8; + --themeDark: #008299; + --themeDarkAlt: #009ab8; + --themeDarker: #004d5e; + --themeSecondary: #33c3e0; + --themeTertiary: #66d3e8; + --themeLight: #b3e8f3; + --themeLighter: #d9f3f9; + --themeLighterAlt: #ecf9fc; + + /* ----------------------------------------------------------------------- + PALETTE — Neutrals (inverted for dark mode, cool teal-tinted) + ----------------------------------------------------------------------- */ + --black: #000000; + --neutralDarker: #061518; + --neutralDark: #0c2028; + --neutralPrimary: #0e2a2e; + --neutralPrimaryAlt: #163a42; + --neutralSecondary: #1e4a52; + --neutralSecondaryAlt: #2a5e68; + --neutralTertiary: #40788a; + --neutralTertiaryAlt: #66d3e8; + --neutralQuaternary: #709da8; + --neutralQuaternaryAlt: #8eb5be; + --neutralLight: #a8c8ce; + --neutralLighter: #d4e6ea; + --neutralLighterAlt: #e9f4f6; + --white: #ffffff; + + /* ----------------------------------------------------------------------- + BODY / BACKGROUND + ----------------------------------------------------------------------- */ + --bodyBackground: var(--neutralPrimary); + --bodyBackgroundHovered: var(--neutralPrimaryAlt); + --bodyBackgroundChecked: var(--neutralSecondary); + --bodyStandoutBackground: var(--neutralDark); + --bodyFrameBackground: var(--neutralPrimary); + --bodyFrameDivider: var(--neutralSecondary); + --bodyDivider: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + TEXT + ----------------------------------------------------------------------- */ + --bodyText: var(--neutralLighter); + --bodyTextChecked: var(--white); + --bodySubtext: var(--neutralTertiaryAlt); + --disabledText: var(--neutralSecondary); + --disabledBodyText: var(--neutralSecondary); + --disabledSubtext: var(--neutralSecondaryAlt); + --disabledBodySubtext: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + LINKS + ----------------------------------------------------------------------- */ + --link: #66d3e8; + --linkHovered: #b3e8f3; + --actionLink: var(--neutralLighter); + --actionLinkHovered: var(--white); + + /* ----------------------------------------------------------------------- + BUTTONS — Default + ----------------------------------------------------------------------- */ + --buttonBackground: var(--neutralPrimary); + --buttonBackgroundHovered: var(--neutralPrimaryAlt); + --buttonBackgroundChecked: var(--neutralSecondary); + --buttonBackgroundCheckedHovered: var(--neutralSecondaryAlt); + --buttonBackgroundPressed: var(--neutralSecondary); + --buttonBackgroundDisabled: var(--neutralDark); + --buttonBorder: var(--neutralSecondaryAlt); + --buttonBorderDisabled: var(--neutralDark); + --buttonText: var(--neutralLighter); + --buttonTextHovered: var(--white); + --buttonTextChecked: var(--white); + --buttonTextCheckedHovered: var(--white); + --buttonTextPressed: var(--white); + --buttonTextDisabled: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + BUTTONS — Primary + ----------------------------------------------------------------------- */ + --primaryButtonBackground: var(--themePrimary); + --primaryButtonBackgroundHovered: var(--themeDarkAlt); + --primaryButtonBackgroundPressed: var(--themeDark); + --primaryButtonBackgroundDisabled: var(--neutralDark); + --primaryButtonBorder: transparent; + --primaryButtonText: var(--white); + --primaryButtonTextHovered: var(--white); + --primaryButtonTextPressed: var(--white); + --primaryButtonTextDisabled: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + BUTTONS — Accent + ----------------------------------------------------------------------- */ + --accentButtonBackground: var(--themePrimary); + --accentButtonText: var(--white); + + /* ----------------------------------------------------------------------- + INPUTS + ----------------------------------------------------------------------- */ + --inputBackground: var(--neutralPrimary); + --inputBackgroundChecked: var(--themePrimary); + --inputBackgroundCheckedHovered: var(--themeDarkAlt); + --inputPlaceholderBackgroundChecked: var(--themeDark); + --inputForegroundChecked: var(--white); + --inputBorder: var(--neutralTertiary); + --smallInputBorder: var(--neutralTertiary); + --inputBorderHovered: var(--neutralLighter); + --inputFocusBorderAlt: var(--themeSecondary); + --inputText: var(--neutralLighter); + --inputTextHovered: var(--white); + --inputPlaceholderText: var(--neutralTertiaryAlt); + --inputIcon: var(--themeSecondary); + --inputIconHovered: var(--themePrimary); + --inputIconDisabled: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + LISTS + ----------------------------------------------------------------------- */ + --listBackground: var(--neutralPrimary); + --listText: var(--neutralLighter); + --listItemBackgroundHovered: var(--neutralPrimaryAlt); + --listItemBackgroundChecked: var(--neutralSecondary); + --listItemBackgroundCheckedHovered: var(--neutralSecondaryAlt); + --listHeaderBackgroundHovered: var(--neutralPrimaryAlt); + --listHeaderBackgroundPressed: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + MENUS + ----------------------------------------------------------------------- */ + --menuBackground: var(--neutralPrimary); + --menuDivider: var(--neutralSecondary); + --menuIcon: var(--themeSecondary); + --menuHeader: var(--themeSecondary); + --menuItemBackgroundHovered: var(--neutralPrimaryAlt); + --menuItemBackgroundPressed: var(--neutralSecondary); + --menuItemText: var(--neutralLighter); + --menuItemTextHovered: var(--white); + + /* ----------------------------------------------------------------------- + CARDS + ----------------------------------------------------------------------- */ + --cardStandoutBackground: var(--neutralDark); + --cardShadow: none; + --cardShadowHovered: none; + --variantBorder: var(--neutralSecondary); + --variantBorderHovered: var(--neutralTertiary); + + /* ----------------------------------------------------------------------- + DISABLED / FOCUS + ----------------------------------------------------------------------- */ + --disabledBackground: var(--neutralDark); + --disabledBorder: var(--neutralDark); + --focusBorder: var(--neutralTertiaryAlt); + + /* ----------------------------------------------------------------------- + STATUS — Backgrounds + ----------------------------------------------------------------------- */ + --infoBackground: var(--neutralPrimary); + --errorBackground: #442726; + --blockingBackground: #442726; + --warningBackground: #433519; + --severeWarningBackground: #4F2A0F; + --successBackground: #393D1B; + + /* ----------------------------------------------------------------------- + STATUS — Icons + ----------------------------------------------------------------------- */ + --infoIcon: var(--neutralTertiaryAlt); + --errorIcon: #F1707B; + --blockingIcon: #442726; + --warningIcon: var(--neutralTertiaryAlt); + --severeWarningIcon: #FCE100; + --successIcon: #92C353; + + /* ----------------------------------------------------------------------- + STATUS — Text + ----------------------------------------------------------------------- */ + --errorText: #F1707B; + --messageText: var(--neutralLighter); + --messageLink: #66d3e8; + --messageLinkHovered: #b3e8f3; + --warningText: var(--neutralLighter); + --successText: #92C353; + + /* ----------------------------------------------------------------------- + MISC + ----------------------------------------------------------------------- */ + --defaultStateBackground: var(--neutralDark); + --overlayBackground: rgba(0, 0, 0, 0.6); +} diff --git a/default-themes/cosmos-dawn.json b/default-themes/cosmos-dawn.json new file mode 100644 index 0000000..e2b1d0c --- /dev/null +++ b/default-themes/cosmos-dawn.json @@ -0,0 +1,19 @@ +{ + "mode": "light", + "colors": { + "bg-primary": "#eeedf6", + "bg-secondary": "#e4e1f0", + "bg-tertiary": "#f5f4fa", + "accent-primary": "#3f37c9", + "accent-secondary": "#2e28a0", + "accent-tertiary": "#908cdf", + "accent-glow": "rgba(63,55,201,0.15)", + "text-primary": "#1e1a40", + "text-secondary": "#4e488a", + "border-color": "rgba(63,55,201,0.25)", + "header-min-height": "58px", + "header-padding-vertical": "14px", + "header-padding-horizontal": "20px", + "header-line-height": "1.25" + } +} diff --git a/default-themes/cosmos-dawn.v3.css b/default-themes/cosmos-dawn.v3.css new file mode 100644 index 0000000..457d5d3 --- /dev/null +++ b/default-themes/cosmos-dawn.v3.css @@ -0,0 +1,198 @@ +/* ========================================================================== + Cosmos Dawn — Light Theme (v3) + Palette overrides + semantic tokens for FluentLM. + Rebrands from default blue to Cosmos deep indigo. + ========================================================================== */ + +.cosmos-dawn { + /* ----------------------------------------------------------------------- + PALETTE — Brand / Theme Colors (Cosmos indigo ramp) + ----------------------------------------------------------------------- */ + --themePrimary: #3f37c9; + --themeDark: #2e28a0; + --themeDarkAlt: #3630b5; + --themeDarker: #1a1654; + --themeSecondary: #6560d4; + --themeTertiary: #908cdf; + --themeLight: #c3c1ed; + --themeLighter: #e0dff6; + --themeLighterAlt: #f0f0fb; + + /* ----------------------------------------------------------------------- + PALETTE — Neutrals (cool indigo-tinted) + ----------------------------------------------------------------------- */ + --neutralDark: #121030; + --neutralPrimary: #1e1a40; + --neutralPrimaryAlt: #2a2556; + --neutralSecondary: #4e488a; + --neutralSecondaryAlt: #423c79; + --neutralTertiary: #7a74a0; + --neutralTertiaryAlt: #9994b8; + --neutralQuaternary: #c4c0d8; + --neutralQuaternaryAlt: #d2cee2; + --neutralLight: #e4e1f0; + --neutralLighter: #eeedf6; + --neutralLighterAlt: #f5f4fa; + --white: #ffffff; + + /* ----------------------------------------------------------------------- + BODY / BACKGROUND + ----------------------------------------------------------------------- */ + --bodyBackground: var(--white); + --bodyBackgroundHovered: var(--neutralLighter); + --bodyBackgroundChecked: var(--neutralLight); + --bodyStandoutBackground: var(--neutralLighterAlt); + --bodyFrameBackground: var(--white); + --bodyFrameDivider: var(--neutralLight); + --bodyDivider: var(--neutralLight); + + /* ----------------------------------------------------------------------- + TEXT + ----------------------------------------------------------------------- */ + --bodyText: var(--neutralPrimary); + --bodyTextChecked: var(--neutralDark); + --bodySubtext: var(--neutralSecondary); + --disabledText: var(--neutralTertiary); + --disabledBodyText: var(--neutralTertiary); + --disabledSubtext: var(--neutralQuaternary); + --disabledBodySubtext: var(--neutralTertiaryAlt); + + /* ----------------------------------------------------------------------- + LINKS + ----------------------------------------------------------------------- */ + --link: var(--themePrimary); + --linkHovered: var(--themeDarker); + --actionLink: var(--neutralPrimary); + --actionLinkHovered: var(--neutralDark); + + /* ----------------------------------------------------------------------- + BUTTONS — Default + ----------------------------------------------------------------------- */ + --buttonBackground: var(--white); + --buttonBackgroundHovered: var(--neutralLighter); + --buttonBackgroundChecked: var(--neutralLight); + --buttonBackgroundCheckedHovered: var(--neutralQuaternaryAlt); + --buttonBackgroundPressed: var(--neutralLight); + --buttonBackgroundDisabled: var(--neutralLighter); + --buttonBorder: var(--neutralSecondaryAlt); + --buttonBorderDisabled: var(--neutralLighter); + --buttonText: var(--neutralPrimary); + --buttonTextHovered: var(--neutralDark); + --buttonTextChecked: var(--neutralDark); + --buttonTextCheckedHovered: var(--black); + --buttonTextPressed: var(--neutralDark); + --buttonTextDisabled: var(--neutralTertiary); + + /* ----------------------------------------------------------------------- + BUTTONS — Primary + ----------------------------------------------------------------------- */ + --primaryButtonBackground: var(--themePrimary); + --primaryButtonBackgroundHovered: var(--themeDarkAlt); + --primaryButtonBackgroundPressed: var(--themeDark); + --primaryButtonBackgroundDisabled: var(--neutralLighter); + --primaryButtonBorder: transparent; + --primaryButtonText: var(--white); + --primaryButtonTextHovered: var(--white); + --primaryButtonTextPressed: var(--white); + --primaryButtonTextDisabled: var(--neutralQuaternary); + + /* ----------------------------------------------------------------------- + BUTTONS — Accent + ----------------------------------------------------------------------- */ + --accentButtonBackground: var(--themePrimary); + --accentButtonText: var(--white); + + /* ----------------------------------------------------------------------- + INPUTS + ----------------------------------------------------------------------- */ + --inputBackground: var(--white); + --inputBackgroundChecked: var(--themePrimary); + --inputBackgroundCheckedHovered: var(--themeDarkAlt); + --inputPlaceholderBackgroundChecked: var(--themeLighter); + --inputForegroundChecked: var(--white); + --inputBorder: var(--neutralSecondary); + --smallInputBorder: var(--neutralSecondary); + --inputBorderHovered: var(--neutralPrimary); + --inputFocusBorderAlt: var(--themePrimary); + --inputText: var(--neutralPrimary); + --inputTextHovered: var(--neutralDark); + --inputPlaceholderText: var(--neutralSecondary); + --inputIcon: var(--themePrimary); + --inputIconHovered: var(--themeDark); + --inputIconDisabled: var(--neutralTertiary); + + /* ----------------------------------------------------------------------- + LISTS + ----------------------------------------------------------------------- */ + --listBackground: var(--white); + --listText: var(--neutralPrimary); + --listItemBackgroundHovered: var(--neutralLighter); + --listItemBackgroundChecked: var(--neutralLight); + --listItemBackgroundCheckedHovered: var(--neutralQuaternaryAlt); + --listHeaderBackgroundHovered: var(--neutralLighter); + --listHeaderBackgroundPressed: var(--neutralLight); + + /* ----------------------------------------------------------------------- + MENUS + ----------------------------------------------------------------------- */ + --menuBackground: var(--white); + --menuDivider: var(--neutralTertiaryAlt); + --menuIcon: var(--themePrimary); + --menuHeader: var(--themePrimary); + --menuItemBackgroundHovered: var(--neutralLighter); + --menuItemBackgroundPressed: var(--neutralLight); + --menuItemText: var(--neutralPrimary); + --menuItemTextHovered: var(--neutralDark); + + /* ----------------------------------------------------------------------- + CARDS + ----------------------------------------------------------------------- */ + --cardStandoutBackground: var(--white); + --cardShadow: var(--elevation4); + --cardShadowHovered: var(--elevation8); + --variantBorder: var(--neutralLight); + --variantBorderHovered: var(--neutralTertiaryAlt); + + /* ----------------------------------------------------------------------- + DISABLED / FOCUS + ----------------------------------------------------------------------- */ + --disabledBackground: var(--neutralLighter); + --disabledBorder: var(--neutralLighter); + --focusBorder: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + STATUS — Backgrounds + ----------------------------------------------------------------------- */ + --infoBackground: var(--neutralLighter); + --errorBackground: #FDE7E9; + --blockingBackground: #FDE7E9; + --warningBackground: #FFF4CE; + --severeWarningBackground: #FED9CC; + --successBackground: #DFF6DD; + + /* ----------------------------------------------------------------------- + STATUS — Icons + ----------------------------------------------------------------------- */ + --infoIcon: var(--neutralSecondary); + --errorIcon: #A80000; + --blockingIcon: #FDE7E9; + --warningIcon: #797775; + --severeWarningIcon: var(--orange); + --successIcon: var(--green); + + /* ----------------------------------------------------------------------- + STATUS — Text + ----------------------------------------------------------------------- */ + --errorText: var(--redDark); + --messageText: var(--neutralPrimary); + --messageLink: var(--themeDark); + --messageLinkHovered: var(--themeDarker); + --warningText: var(--neutralPrimary); + --successText: var(--green); + + /* ----------------------------------------------------------------------- + MISC + ----------------------------------------------------------------------- */ + --defaultStateBackground: var(--neutralLighterAlt); + --overlayBackground: var(--blackTranslucent40); +} diff --git a/default-themes/cosmos-dusk.json b/default-themes/cosmos-dusk.json new file mode 100644 index 0000000..ed00311 --- /dev/null +++ b/default-themes/cosmos-dusk.json @@ -0,0 +1,19 @@ +{ + "mode": "dark", + "colors": { + "bg-primary": "#121030", + "bg-secondary": "#0f0d28", + "bg-tertiary": "#08071a", + "accent-primary": "#3f37c9", + "accent-secondary": "#2e28a0", + "accent-tertiary": "#908cdf", + "accent-glow": "rgba(63,55,201,0.3)", + "text-primary": "#d4d0e2", + "text-secondary": "#908cdf", + "border-color": "rgba(63,55,201,0.3)", + "header-min-height": "58px", + "header-padding-vertical": "14px", + "header-padding-horizontal": "20px", + "header-line-height": "1.25" + } +} diff --git a/default-themes/cosmos-dusk.v3.css b/default-themes/cosmos-dusk.v3.css new file mode 100644 index 0000000..40d44c8 --- /dev/null +++ b/default-themes/cosmos-dusk.v3.css @@ -0,0 +1,200 @@ +/* ========================================================================== + Cosmos Dusk — Dark Theme (v3) + Palette overrides + semantic tokens for FluentLM. + Rebrands from default blue to Cosmos deep indigo, dark mode. + ========================================================================== */ + +.cosmos-dusk { + /* ----------------------------------------------------------------------- + PALETTE — Brand / Theme Colors (Cosmos indigo ramp) + ----------------------------------------------------------------------- */ + --themePrimary: #3f37c9; + --themeDark: #2e28a0; + --themeDarkAlt: #3630b5; + --themeDarker: #1a1654; + --themeSecondary: #6560d4; + --themeTertiary: #908cdf; + --themeLight: #c3c1ed; + --themeLighter: #e0dff6; + --themeLighterAlt: #f0f0fb; + + /* ----------------------------------------------------------------------- + PALETTE — Neutrals (inverted for dark mode, cool indigo-tinted) + ----------------------------------------------------------------------- */ + --black: #000000; + --neutralDarker: #08071a; + --neutralDark: #0f0d28; + --neutralPrimary: #121030; + --neutralPrimaryAlt: #1e1a44; + --neutralSecondary: #2a2556; + --neutralSecondaryAlt: #3a3468; + --neutralTertiary: #5a5480; + --neutralTertiaryAlt: #908cdf; + --neutralQuaternary: #7a74a0; + --neutralQuaternaryAlt: #9994b8; + --neutralLight: #b0aac8; + --neutralLighter: #d4d0e2; + --neutralLighterAlt: #eeedf6; + --white: #ffffff; + + /* ----------------------------------------------------------------------- + BODY / BACKGROUND + ----------------------------------------------------------------------- */ + --bodyBackground: var(--neutralPrimary); + --bodyBackgroundHovered: var(--neutralPrimaryAlt); + --bodyBackgroundChecked: var(--neutralSecondary); + --bodyStandoutBackground: var(--neutralDark); + --bodyFrameBackground: var(--neutralPrimary); + --bodyFrameDivider: var(--neutralSecondary); + --bodyDivider: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + TEXT + ----------------------------------------------------------------------- */ + --bodyText: var(--neutralLighter); + --bodyTextChecked: var(--white); + --bodySubtext: var(--neutralTertiaryAlt); + --disabledText: var(--neutralSecondary); + --disabledBodyText: var(--neutralSecondary); + --disabledSubtext: var(--neutralSecondaryAlt); + --disabledBodySubtext: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + LINKS + ----------------------------------------------------------------------- */ + --link: #908cdf; + --linkHovered: #c3c1ed; + --actionLink: var(--neutralLighter); + --actionLinkHovered: var(--white); + + /* ----------------------------------------------------------------------- + BUTTONS — Default + ----------------------------------------------------------------------- */ + --buttonBackground: var(--neutralPrimary); + --buttonBackgroundHovered: var(--neutralPrimaryAlt); + --buttonBackgroundChecked: var(--neutralSecondary); + --buttonBackgroundCheckedHovered: var(--neutralSecondaryAlt); + --buttonBackgroundPressed: var(--neutralSecondary); + --buttonBackgroundDisabled: var(--neutralDark); + --buttonBorder: var(--neutralSecondaryAlt); + --buttonBorderDisabled: var(--neutralDark); + --buttonText: var(--neutralLighter); + --buttonTextHovered: var(--white); + --buttonTextChecked: var(--white); + --buttonTextCheckedHovered: var(--white); + --buttonTextPressed: var(--white); + --buttonTextDisabled: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + BUTTONS — Primary + ----------------------------------------------------------------------- */ + --primaryButtonBackground: var(--themePrimary); + --primaryButtonBackgroundHovered: var(--themeDarkAlt); + --primaryButtonBackgroundPressed: var(--themeDark); + --primaryButtonBackgroundDisabled: var(--neutralDark); + --primaryButtonBorder: transparent; + --primaryButtonText: var(--white); + --primaryButtonTextHovered: var(--white); + --primaryButtonTextPressed: var(--white); + --primaryButtonTextDisabled: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + BUTTONS — Accent + ----------------------------------------------------------------------- */ + --accentButtonBackground: var(--themePrimary); + --accentButtonText: var(--white); + + /* ----------------------------------------------------------------------- + INPUTS + ----------------------------------------------------------------------- */ + --inputBackground: var(--neutralPrimary); + --inputBackgroundChecked: var(--themePrimary); + --inputBackgroundCheckedHovered: var(--themeDarkAlt); + --inputPlaceholderBackgroundChecked: var(--themeDark); + --inputForegroundChecked: var(--white); + --inputBorder: var(--neutralTertiary); + --smallInputBorder: var(--neutralTertiary); + --inputBorderHovered: var(--neutralLighter); + --inputFocusBorderAlt: var(--themeSecondary); + --inputText: var(--neutralLighter); + --inputTextHovered: var(--white); + --inputPlaceholderText: var(--neutralTertiaryAlt); + --inputIcon: var(--themeSecondary); + --inputIconHovered: var(--themePrimary); + --inputIconDisabled: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + LISTS + ----------------------------------------------------------------------- */ + --listBackground: var(--neutralPrimary); + --listText: var(--neutralLighter); + --listItemBackgroundHovered: var(--neutralPrimaryAlt); + --listItemBackgroundChecked: var(--neutralSecondary); + --listItemBackgroundCheckedHovered: var(--neutralSecondaryAlt); + --listHeaderBackgroundHovered: var(--neutralPrimaryAlt); + --listHeaderBackgroundPressed: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + MENUS + ----------------------------------------------------------------------- */ + --menuBackground: var(--neutralPrimary); + --menuDivider: var(--neutralSecondary); + --menuIcon: var(--themeSecondary); + --menuHeader: var(--themeSecondary); + --menuItemBackgroundHovered: var(--neutralPrimaryAlt); + --menuItemBackgroundPressed: var(--neutralSecondary); + --menuItemText: var(--neutralLighter); + --menuItemTextHovered: var(--white); + + /* ----------------------------------------------------------------------- + CARDS + ----------------------------------------------------------------------- */ + --cardStandoutBackground: var(--neutralDark); + --cardShadow: none; + --cardShadowHovered: none; + --variantBorder: var(--neutralSecondary); + --variantBorderHovered: var(--neutralTertiary); + + /* ----------------------------------------------------------------------- + DISABLED / FOCUS + ----------------------------------------------------------------------- */ + --disabledBackground: var(--neutralDark); + --disabledBorder: var(--neutralDark); + --focusBorder: var(--neutralTertiaryAlt); + + /* ----------------------------------------------------------------------- + STATUS — Backgrounds + ----------------------------------------------------------------------- */ + --infoBackground: var(--neutralPrimary); + --errorBackground: #442726; + --blockingBackground: #442726; + --warningBackground: #433519; + --severeWarningBackground: #4F2A0F; + --successBackground: #393D1B; + + /* ----------------------------------------------------------------------- + STATUS — Icons + ----------------------------------------------------------------------- */ + --infoIcon: var(--neutralTertiaryAlt); + --errorIcon: #F1707B; + --blockingIcon: #442726; + --warningIcon: var(--neutralTertiaryAlt); + --severeWarningIcon: #FCE100; + --successIcon: #92C353; + + /* ----------------------------------------------------------------------- + STATUS — Text + ----------------------------------------------------------------------- */ + --errorText: #F1707B; + --messageText: var(--neutralLighter); + --messageLink: #908cdf; + --messageLinkHovered: #c3c1ed; + --warningText: var(--neutralLighter); + --successText: #92C353; + + /* ----------------------------------------------------------------------- + MISC + ----------------------------------------------------------------------- */ + --defaultStateBackground: var(--neutralDark); + --overlayBackground: rgba(0, 0, 0, 0.6); +} diff --git a/default-themes/high-contrast-dark.json b/default-themes/high-contrast-dark.json new file mode 100644 index 0000000..3eb54a6 --- /dev/null +++ b/default-themes/high-contrast-dark.json @@ -0,0 +1,19 @@ +{ + "mode": "dark", + "colors": { + "bg-primary": "#000000", + "bg-secondary": "#0a0a0a", + "bg-tertiary": "#000000", + "accent-primary": "#ffffff", + "accent-secondary": "#ffffff", + "accent-tertiary": "#999999", + "accent-glow": "rgba(255,255,255,0.15)", + "text-primary": "#ffffff", + "text-secondary": "#e0e0e0", + "border-color": "#ffffff", + "header-min-height": "58px", + "header-padding-vertical": "14px", + "header-padding-horizontal": "20px", + "header-line-height": "1.25" + } +} diff --git a/default-themes/high-contrast-dark.v3.css b/default-themes/high-contrast-dark.v3.css new file mode 100644 index 0000000..388d923 --- /dev/null +++ b/default-themes/high-contrast-dark.v3.css @@ -0,0 +1,200 @@ +/* ========================================================================== + High Contrast Dark — Dark Theme (v3) + Maximum contrast for accessibility (WCAG AAA 7:1+). + White primary on black body, yellow links. + ========================================================================== */ + +.high-contrast-dark { + /* ----------------------------------------------------------------------- + PALETTE — Brand / Theme Colors (pure white ramp) + ----------------------------------------------------------------------- */ + --themePrimary: #ffffff; + --themeDark: #ffffff; + --themeDarkAlt: #ffffff; + --themeDarker: #ffffff; + --themeSecondary: #cccccc; + --themeTertiary: #999999; + --themeLight: #333333; + --themeLighter: #1a1a1a; + --themeLighterAlt: #0d0d0d; + + /* ----------------------------------------------------------------------- + PALETTE — Neutrals (inverted for dark mode, pure grayscale) + ----------------------------------------------------------------------- */ + --black: #000000; + --neutralDarker: #000000; + --neutralDark: #0a0a0a; + --neutralPrimary: #000000; + --neutralPrimaryAlt: #141414; + --neutralSecondary: #1e1e1e; + --neutralSecondaryAlt: #2e2e2e; + --neutralTertiary: #555555; + --neutralTertiaryAlt: #ffffff; + --neutralQuaternary: #767676; + --neutralQuaternaryAlt: #999999; + --neutralLight: #c8c8c8; + --neutralLighter: #e0e0e0; + --neutralLighterAlt: #f0f0f0; + --white: #ffffff; + + /* ----------------------------------------------------------------------- + BODY / BACKGROUND + ----------------------------------------------------------------------- */ + --bodyBackground: #000000; + --bodyBackgroundHovered: var(--neutralPrimaryAlt); + --bodyBackgroundChecked: var(--neutralSecondary); + --bodyStandoutBackground: #000000; + --bodyFrameBackground: #000000; + --bodyFrameDivider: #ffffff; + --bodyDivider: #ffffff; + + /* ----------------------------------------------------------------------- + TEXT + ----------------------------------------------------------------------- */ + --bodyText: #ffffff; + --bodyTextChecked: #ffffff; + --bodySubtext: #e0e0e0; + --disabledText: #767676; + --disabledBodyText: #767676; + --disabledSubtext: #555555; + --disabledBodySubtext: #555555; + + /* ----------------------------------------------------------------------- + LINKS + ----------------------------------------------------------------------- */ + --link: #ffff00; + --linkHovered: #ffff99; + --actionLink: #ffffff; + --actionLinkHovered: #ffffff; + + /* ----------------------------------------------------------------------- + BUTTONS — Default + ----------------------------------------------------------------------- */ + --buttonBackground: #000000; + --buttonBackgroundHovered: var(--neutralPrimaryAlt); + --buttonBackgroundChecked: var(--neutralSecondary); + --buttonBackgroundCheckedHovered: var(--neutralSecondaryAlt); + --buttonBackgroundPressed: var(--neutralSecondary); + --buttonBackgroundDisabled: #000000; + --buttonBorder: #ffffff; + --buttonBorderDisabled: #767676; + --buttonText: #ffffff; + --buttonTextHovered: #ffffff; + --buttonTextChecked: #ffffff; + --buttonTextCheckedHovered: #ffffff; + --buttonTextPressed: #ffffff; + --buttonTextDisabled: #767676; + + /* ----------------------------------------------------------------------- + BUTTONS — Primary + ----------------------------------------------------------------------- */ + --primaryButtonBackground: #ffffff; + --primaryButtonBackgroundHovered: #e0e0e0; + --primaryButtonBackgroundPressed: #c8c8c8; + --primaryButtonBackgroundDisabled: #555555; + --primaryButtonBorder: transparent; + --primaryButtonText: #000000; + --primaryButtonTextHovered: #000000; + --primaryButtonTextPressed: #000000; + --primaryButtonTextDisabled: #767676; + + /* ----------------------------------------------------------------------- + BUTTONS — Accent + ----------------------------------------------------------------------- */ + --accentButtonBackground: #ffffff; + --accentButtonText: #000000; + + /* ----------------------------------------------------------------------- + INPUTS + ----------------------------------------------------------------------- */ + --inputBackground: #000000; + --inputBackgroundChecked: #ffffff; + --inputBackgroundCheckedHovered: #e0e0e0; + --inputPlaceholderBackgroundChecked: #1a1a1a; + --inputForegroundChecked: #000000; + --inputBorder: #ffffff; + --smallInputBorder: #ffffff; + --inputBorderHovered: #ffffff; + --inputFocusBorderAlt: #ffff00; + --inputText: #ffffff; + --inputTextHovered: #ffffff; + --inputPlaceholderText: #c8c8c8; + --inputIcon: #ffffff; + --inputIconHovered: #ffff00; + --inputIconDisabled: #767676; + + /* ----------------------------------------------------------------------- + LISTS + ----------------------------------------------------------------------- */ + --listBackground: #000000; + --listText: #ffffff; + --listItemBackgroundHovered: var(--neutralPrimaryAlt); + --listItemBackgroundChecked: var(--neutralSecondary); + --listItemBackgroundCheckedHovered: var(--neutralSecondaryAlt); + --listHeaderBackgroundHovered: var(--neutralPrimaryAlt); + --listHeaderBackgroundPressed: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + MENUS + ----------------------------------------------------------------------- */ + --menuBackground: #000000; + --menuDivider: #ffffff; + --menuIcon: #ffffff; + --menuHeader: #ffffff; + --menuItemBackgroundHovered: var(--neutralPrimaryAlt); + --menuItemBackgroundPressed: var(--neutralSecondary); + --menuItemText: #ffffff; + --menuItemTextHovered: #ffffff; + + /* ----------------------------------------------------------------------- + CARDS + ----------------------------------------------------------------------- */ + --cardStandoutBackground: #000000; + --cardShadow: none; + --cardShadowHovered: none; + --variantBorder: #ffffff; + --variantBorderHovered: #ffffff; + + /* ----------------------------------------------------------------------- + DISABLED / FOCUS + ----------------------------------------------------------------------- */ + --disabledBackground: #1a1a1a; + --disabledBorder: #767676; + --focusBorder: #ffffff; + + /* ----------------------------------------------------------------------- + STATUS — Backgrounds + ----------------------------------------------------------------------- */ + --infoBackground: #1a1a1a; + --errorBackground: #440000; + --blockingBackground: #440000; + --warningBackground: #443300; + --severeWarningBackground: #442200; + --successBackground: #003300; + + /* ----------------------------------------------------------------------- + STATUS — Icons + ----------------------------------------------------------------------- */ + --infoIcon: #ffffff; + --errorIcon: #ff6666; + --blockingIcon: #ff6666; + --warningIcon: #ffff00; + --severeWarningIcon: #ff6600; + --successIcon: #66ff66; + + /* ----------------------------------------------------------------------- + STATUS — Text + ----------------------------------------------------------------------- */ + --errorText: #ff6666; + --messageText: #ffffff; + --messageLink: #ffff00; + --messageLinkHovered: #ffff99; + --warningText: #ffffff; + --successText: #66ff66; + + /* ----------------------------------------------------------------------- + MISC + ----------------------------------------------------------------------- */ + --defaultStateBackground: #0a0a0a; + --overlayBackground: rgba(0, 0, 0, 0.8); +} diff --git a/default-themes/high-contrast-light.json b/default-themes/high-contrast-light.json new file mode 100644 index 0000000..a3a9795 --- /dev/null +++ b/default-themes/high-contrast-light.json @@ -0,0 +1,19 @@ +{ + "mode": "light", + "colors": { + "bg-primary": "#f0f0f0", + "bg-secondary": "#e0e0e0", + "bg-tertiary": "#f8f8f8", + "accent-primary": "#000000", + "accent-secondary": "#000000", + "accent-tertiary": "#666666", + "accent-glow": "rgba(0,0,0,0.15)", + "text-primary": "#000000", + "text-secondary": "#1a1a1a", + "border-color": "#000000", + "header-min-height": "58px", + "header-padding-vertical": "14px", + "header-padding-horizontal": "20px", + "header-line-height": "1.25" + } +} diff --git a/default-themes/high-contrast-light.v3.css b/default-themes/high-contrast-light.v3.css new file mode 100644 index 0000000..3e5226d --- /dev/null +++ b/default-themes/high-contrast-light.v3.css @@ -0,0 +1,198 @@ +/* ========================================================================== + High Contrast Light — Light Theme (v3) + Maximum contrast for accessibility (WCAG AAA 7:1+). + Pure grayscale neutrals, black primary. + ========================================================================== */ + +.high-contrast-light { + /* ----------------------------------------------------------------------- + PALETTE — Brand / Theme Colors (pure black ramp) + ----------------------------------------------------------------------- */ + --themePrimary: #000000; + --themeDark: #000000; + --themeDarkAlt: #000000; + --themeDarker: #000000; + --themeSecondary: #333333; + --themeTertiary: #666666; + --themeLight: #cccccc; + --themeLighter: #e6e6e6; + --themeLighterAlt: #f5f5f5; + + /* ----------------------------------------------------------------------- + PALETTE — Neutrals (pure untinted grayscale) + ----------------------------------------------------------------------- */ + --neutralDark: #000000; + --neutralPrimary: #1a1a1a; + --neutralPrimaryAlt: #2e2e2e; + --neutralSecondary: #555555; + --neutralSecondaryAlt: #444444; + --neutralTertiary: #767676; + --neutralTertiaryAlt: #999999; + --neutralQuaternary: #c8c8c8; + --neutralQuaternaryAlt: #d0d0d0; + --neutralLight: #e0e0e0; + --neutralLighter: #f0f0f0; + --neutralLighterAlt: #f8f8f8; + --white: #ffffff; + + /* ----------------------------------------------------------------------- + BODY / BACKGROUND + ----------------------------------------------------------------------- */ + --bodyBackground: var(--white); + --bodyBackgroundHovered: var(--neutralLighter); + --bodyBackgroundChecked: var(--neutralLight); + --bodyStandoutBackground: var(--neutralLighterAlt); + --bodyFrameBackground: var(--white); + --bodyFrameDivider: #000000; + --bodyDivider: #000000; + + /* ----------------------------------------------------------------------- + TEXT + ----------------------------------------------------------------------- */ + --bodyText: #000000; + --bodyTextChecked: #000000; + --bodySubtext: #1a1a1a; + --disabledText: #767676; + --disabledBodyText: #767676; + --disabledSubtext: #999999; + --disabledBodySubtext: #999999; + + /* ----------------------------------------------------------------------- + LINKS + ----------------------------------------------------------------------- */ + --link: #0000cc; + --linkHovered: #000066; + --actionLink: #000000; + --actionLinkHovered: #000000; + + /* ----------------------------------------------------------------------- + BUTTONS — Default + ----------------------------------------------------------------------- */ + --buttonBackground: var(--white); + --buttonBackgroundHovered: var(--neutralLighter); + --buttonBackgroundChecked: var(--neutralLight); + --buttonBackgroundCheckedHovered: var(--neutralQuaternaryAlt); + --buttonBackgroundPressed: var(--neutralLight); + --buttonBackgroundDisabled: var(--neutralLighter); + --buttonBorder: #000000; + --buttonBorderDisabled: #767676; + --buttonText: #000000; + --buttonTextHovered: #000000; + --buttonTextChecked: #000000; + --buttonTextCheckedHovered: #000000; + --buttonTextPressed: #000000; + --buttonTextDisabled: #767676; + + /* ----------------------------------------------------------------------- + BUTTONS — Primary + ----------------------------------------------------------------------- */ + --primaryButtonBackground: #000000; + --primaryButtonBackgroundHovered: #1a1a1a; + --primaryButtonBackgroundPressed: #333333; + --primaryButtonBackgroundDisabled: #767676; + --primaryButtonBorder: transparent; + --primaryButtonText: var(--white); + --primaryButtonTextHovered: var(--white); + --primaryButtonTextPressed: var(--white); + --primaryButtonTextDisabled: var(--neutralQuaternary); + + /* ----------------------------------------------------------------------- + BUTTONS — Accent + ----------------------------------------------------------------------- */ + --accentButtonBackground: #000000; + --accentButtonText: var(--white); + + /* ----------------------------------------------------------------------- + INPUTS + ----------------------------------------------------------------------- */ + --inputBackground: var(--white); + --inputBackgroundChecked: #000000; + --inputBackgroundCheckedHovered: #1a1a1a; + --inputPlaceholderBackgroundChecked: var(--neutralLighter); + --inputForegroundChecked: var(--white); + --inputBorder: #000000; + --smallInputBorder: #000000; + --inputBorderHovered: #000000; + --inputFocusBorderAlt: #000000; + --inputText: #000000; + --inputTextHovered: #000000; + --inputPlaceholderText: #555555; + --inputIcon: #000000; + --inputIconHovered: #000000; + --inputIconDisabled: #767676; + + /* ----------------------------------------------------------------------- + LISTS + ----------------------------------------------------------------------- */ + --listBackground: var(--white); + --listText: #000000; + --listItemBackgroundHovered: var(--neutralLighter); + --listItemBackgroundChecked: var(--neutralLight); + --listItemBackgroundCheckedHovered: var(--neutralQuaternaryAlt); + --listHeaderBackgroundHovered: var(--neutralLighter); + --listHeaderBackgroundPressed: var(--neutralLight); + + /* ----------------------------------------------------------------------- + MENUS + ----------------------------------------------------------------------- */ + --menuBackground: var(--white); + --menuDivider: #000000; + --menuIcon: #000000; + --menuHeader: #000000; + --menuItemBackgroundHovered: var(--neutralLighter); + --menuItemBackgroundPressed: var(--neutralLight); + --menuItemText: #000000; + --menuItemTextHovered: #000000; + + /* ----------------------------------------------------------------------- + CARDS + ----------------------------------------------------------------------- */ + --cardStandoutBackground: var(--white); + --cardShadow: none; + --cardShadowHovered: none; + --variantBorder: #000000; + --variantBorderHovered: #000000; + + /* ----------------------------------------------------------------------- + DISABLED / FOCUS + ----------------------------------------------------------------------- */ + --disabledBackground: var(--neutralLighter); + --disabledBorder: #767676; + --focusBorder: #000000; + + /* ----------------------------------------------------------------------- + STATUS — Backgrounds + ----------------------------------------------------------------------- */ + --infoBackground: var(--neutralLighter); + --errorBackground: #FDE7E9; + --blockingBackground: #FDE7E9; + --warningBackground: #FFF4CE; + --severeWarningBackground: #FED9CC; + --successBackground: #DFF6DD; + + /* ----------------------------------------------------------------------- + STATUS — Icons + ----------------------------------------------------------------------- */ + --infoIcon: #000000; + --errorIcon: #A80000; + --blockingIcon: #A80000; + --warningIcon: #333333; + --severeWarningIcon: #A80000; + --successIcon: #107C10; + + /* ----------------------------------------------------------------------- + STATUS — Text + ----------------------------------------------------------------------- */ + --errorText: #A80000; + --messageText: #000000; + --messageLink: #0000cc; + --messageLinkHovered: #000066; + --warningText: #000000; + --successText: #107C10; + + /* ----------------------------------------------------------------------- + MISC + ----------------------------------------------------------------------- */ + --defaultStateBackground: var(--neutralLighterAlt); + --overlayBackground: rgba(0, 0, 0, 0.5); +} diff --git a/default-themes/nebula-dawn.v3.css b/default-themes/nebula-dawn.v3.css index 29e4958..a4004bd 100644 --- a/default-themes/nebula-dawn.v3.css +++ b/default-themes/nebula-dawn.v3.css @@ -9,15 +9,15 @@ /* ----------------------------------------------------------------------- PALETTE — Brand / Theme Colors (Nebula purple ramp) ----------------------------------------------------------------------- */ - --themePrimary: #667eea; - --themeDark: #764ba2; - --themeDarkAlt: #5639a0; - --themeDarker: #432d80; - --themeSecondary: #c054d4; - --themeTertiary: #8b9ef0; - --themeLight: #c5cff7; - --themeLighter: #e0e5fb; - --themeLighterAlt: #f3f5fe; + --themePrimary: #764ba2; + --themeDark: #5a3580; + --themeDarkAlt: #663d91; + --themeDarker: #3b2054; + --themeSecondary: #9670b8; + --themeTertiary: #b49bce; + --themeLight: #d3c3e3; + --themeLighter: #e8ddf1; + --themeLighterAlt: #f5f1fa; /* ----------------------------------------------------------------------- PALETTE — Neutrals (warm purple-tinted) diff --git a/default-themes/nebula-dusk.v3.css b/default-themes/nebula-dusk.v3.css index 9ab7ef8..939e985 100644 --- a/default-themes/nebula-dusk.v3.css +++ b/default-themes/nebula-dusk.v3.css @@ -8,15 +8,15 @@ /* ----------------------------------------------------------------------- PALETTE — Brand / Theme Colors (Nebula purple ramp) ----------------------------------------------------------------------- */ - --themePrimary: #667eea; - --themeDark: #764ba2; - --themeDarkAlt: #5639a0; - --themeDarker: #432d80; - --themeSecondary: #f093fb; - --themeTertiary: #8b9ef0; - --themeLight: #c5cff7; - --themeLighter: #e0e5fb; - --themeLighterAlt: #f3f5fe; + --themePrimary: #764ba2; + --themeDark: #5a3580; + --themeDarkAlt: #663d91; + --themeDarker: #3b2054; + --themeSecondary: #9670b8; + --themeTertiary: #b49bce; + --themeLight: #d3c3e3; + --themeLighter: #e8ddf1; + --themeLighterAlt: #f5f1fa; /* ----------------------------------------------------------------------- PALETTE — Neutrals (inverted for dark mode) diff --git a/default-themes/solar-flare-dawn.json b/default-themes/solar-flare-dawn.json new file mode 100644 index 0000000..6b23132 --- /dev/null +++ b/default-themes/solar-flare-dawn.json @@ -0,0 +1,19 @@ +{ + "mode": "light", + "colors": { + "bg-primary": "#f6efe4", + "bg-secondary": "#f0e6d8", + "bg-tertiary": "#faf7f2", + "accent-primary": "#e67e22", + "accent-secondary": "#b8621a", + "accent-tertiary": "#f0b67e", + "accent-glow": "rgba(230,126,34,0.15)", + "text-primary": "#402d18", + "text-secondary": "#8a6840", + "border-color": "rgba(230,126,34,0.25)", + "header-min-height": "58px", + "header-padding-vertical": "14px", + "header-padding-horizontal": "20px", + "header-line-height": "1.25" + } +} diff --git a/default-themes/solar-flare-dawn.v3.css b/default-themes/solar-flare-dawn.v3.css new file mode 100644 index 0000000..95ccd88 --- /dev/null +++ b/default-themes/solar-flare-dawn.v3.css @@ -0,0 +1,198 @@ +/* ========================================================================== + Solar Flare Dawn — Light Theme (v3) + Palette overrides + semantic tokens for FluentLM. + Rebrands from default blue to Solar Flare amber/orange. + ========================================================================== */ + +.solar-flare-dawn { + /* ----------------------------------------------------------------------- + PALETTE — Brand / Theme Colors (Solar Flare amber ramp) + ----------------------------------------------------------------------- */ + --themePrimary: #e67e22; + --themeDark: #b8621a; + --themeDarkAlt: #cf701e; + --themeDarker: #7a4310; + --themeSecondary: #eb9950; + --themeTertiary: #f0b67e; + --themeLight: #f7d6b3; + --themeLighter: #fbe9d5; + --themeLighterAlt: #fdf5ec; + + /* ----------------------------------------------------------------------- + PALETTE — Neutrals (warm amber-tinted) + ----------------------------------------------------------------------- */ + --neutralDark: #2e1e0e; + --neutralPrimary: #402d18; + --neutralPrimaryAlt: #563a1e; + --neutralSecondary: #8a6840; + --neutralSecondaryAlt: #795a36; + --neutralTertiary: #9d8a70; + --neutralTertiaryAlt: #b5a48e; + --neutralQuaternary: #d6cabb; + --neutralQuaternaryAlt: #e0d5c6; + --neutralLight: #f0e6d8; + --neutralLighter: #f6efe4; + --neutralLighterAlt: #faf7f2; + --white: #ffffff; + + /* ----------------------------------------------------------------------- + BODY / BACKGROUND + ----------------------------------------------------------------------- */ + --bodyBackground: var(--white); + --bodyBackgroundHovered: var(--neutralLighter); + --bodyBackgroundChecked: var(--neutralLight); + --bodyStandoutBackground: var(--neutralLighterAlt); + --bodyFrameBackground: var(--white); + --bodyFrameDivider: var(--neutralLight); + --bodyDivider: var(--neutralLight); + + /* ----------------------------------------------------------------------- + TEXT + ----------------------------------------------------------------------- */ + --bodyText: var(--neutralPrimary); + --bodyTextChecked: var(--neutralDark); + --bodySubtext: var(--neutralSecondary); + --disabledText: var(--neutralTertiary); + --disabledBodyText: var(--neutralTertiary); + --disabledSubtext: var(--neutralQuaternary); + --disabledBodySubtext: var(--neutralTertiaryAlt); + + /* ----------------------------------------------------------------------- + LINKS + ----------------------------------------------------------------------- */ + --link: var(--themePrimary); + --linkHovered: var(--themeDarker); + --actionLink: var(--neutralPrimary); + --actionLinkHovered: var(--neutralDark); + + /* ----------------------------------------------------------------------- + BUTTONS — Default + ----------------------------------------------------------------------- */ + --buttonBackground: var(--white); + --buttonBackgroundHovered: var(--neutralLighter); + --buttonBackgroundChecked: var(--neutralLight); + --buttonBackgroundCheckedHovered: var(--neutralQuaternaryAlt); + --buttonBackgroundPressed: var(--neutralLight); + --buttonBackgroundDisabled: var(--neutralLighter); + --buttonBorder: var(--neutralSecondaryAlt); + --buttonBorderDisabled: var(--neutralLighter); + --buttonText: var(--neutralPrimary); + --buttonTextHovered: var(--neutralDark); + --buttonTextChecked: var(--neutralDark); + --buttonTextCheckedHovered: var(--black); + --buttonTextPressed: var(--neutralDark); + --buttonTextDisabled: var(--neutralTertiary); + + /* ----------------------------------------------------------------------- + BUTTONS — Primary + ----------------------------------------------------------------------- */ + --primaryButtonBackground: var(--themePrimary); + --primaryButtonBackgroundHovered: var(--themeDarkAlt); + --primaryButtonBackgroundPressed: var(--themeDark); + --primaryButtonBackgroundDisabled: var(--neutralLighter); + --primaryButtonBorder: transparent; + --primaryButtonText: var(--white); + --primaryButtonTextHovered: var(--white); + --primaryButtonTextPressed: var(--white); + --primaryButtonTextDisabled: var(--neutralQuaternary); + + /* ----------------------------------------------------------------------- + BUTTONS — Accent + ----------------------------------------------------------------------- */ + --accentButtonBackground: var(--themePrimary); + --accentButtonText: var(--white); + + /* ----------------------------------------------------------------------- + INPUTS + ----------------------------------------------------------------------- */ + --inputBackground: var(--white); + --inputBackgroundChecked: var(--themePrimary); + --inputBackgroundCheckedHovered: var(--themeDarkAlt); + --inputPlaceholderBackgroundChecked: var(--themeLighter); + --inputForegroundChecked: var(--white); + --inputBorder: var(--neutralSecondary); + --smallInputBorder: var(--neutralSecondary); + --inputBorderHovered: var(--neutralPrimary); + --inputFocusBorderAlt: var(--themePrimary); + --inputText: var(--neutralPrimary); + --inputTextHovered: var(--neutralDark); + --inputPlaceholderText: var(--neutralSecondary); + --inputIcon: var(--themePrimary); + --inputIconHovered: var(--themeDark); + --inputIconDisabled: var(--neutralTertiary); + + /* ----------------------------------------------------------------------- + LISTS + ----------------------------------------------------------------------- */ + --listBackground: var(--white); + --listText: var(--neutralPrimary); + --listItemBackgroundHovered: var(--neutralLighter); + --listItemBackgroundChecked: var(--neutralLight); + --listItemBackgroundCheckedHovered: var(--neutralQuaternaryAlt); + --listHeaderBackgroundHovered: var(--neutralLighter); + --listHeaderBackgroundPressed: var(--neutralLight); + + /* ----------------------------------------------------------------------- + MENUS + ----------------------------------------------------------------------- */ + --menuBackground: var(--white); + --menuDivider: var(--neutralTertiaryAlt); + --menuIcon: var(--themePrimary); + --menuHeader: var(--themePrimary); + --menuItemBackgroundHovered: var(--neutralLighter); + --menuItemBackgroundPressed: var(--neutralLight); + --menuItemText: var(--neutralPrimary); + --menuItemTextHovered: var(--neutralDark); + + /* ----------------------------------------------------------------------- + CARDS + ----------------------------------------------------------------------- */ + --cardStandoutBackground: var(--white); + --cardShadow: var(--elevation4); + --cardShadowHovered: var(--elevation8); + --variantBorder: var(--neutralLight); + --variantBorderHovered: var(--neutralTertiaryAlt); + + /* ----------------------------------------------------------------------- + DISABLED / FOCUS + ----------------------------------------------------------------------- */ + --disabledBackground: var(--neutralLighter); + --disabledBorder: var(--neutralLighter); + --focusBorder: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + STATUS — Backgrounds + ----------------------------------------------------------------------- */ + --infoBackground: var(--neutralLighter); + --errorBackground: #FDE7E9; + --blockingBackground: #FDE7E9; + --warningBackground: #FFF4CE; + --severeWarningBackground: #FED9CC; + --successBackground: #DFF6DD; + + /* ----------------------------------------------------------------------- + STATUS — Icons + ----------------------------------------------------------------------- */ + --infoIcon: var(--neutralSecondary); + --errorIcon: #A80000; + --blockingIcon: #FDE7E9; + --warningIcon: #797775; + --severeWarningIcon: var(--orange); + --successIcon: var(--green); + + /* ----------------------------------------------------------------------- + STATUS — Text + ----------------------------------------------------------------------- */ + --errorText: var(--redDark); + --messageText: var(--neutralPrimary); + --messageLink: var(--themeDark); + --messageLinkHovered: var(--themeDarker); + --warningText: var(--neutralPrimary); + --successText: var(--green); + + /* ----------------------------------------------------------------------- + MISC + ----------------------------------------------------------------------- */ + --defaultStateBackground: var(--neutralLighterAlt); + --overlayBackground: var(--blackTranslucent40); +} diff --git a/default-themes/solar-flare-dusk.json b/default-themes/solar-flare-dusk.json new file mode 100644 index 0000000..2ace126 --- /dev/null +++ b/default-themes/solar-flare-dusk.json @@ -0,0 +1,19 @@ +{ + "mode": "dark", + "colors": { + "bg-primary": "#2e1e0e", + "bg-secondary": "#241a10", + "bg-tertiary": "#1a120a", + "accent-primary": "#e67e22", + "accent-secondary": "#b8621a", + "accent-tertiary": "#f0b67e", + "accent-glow": "rgba(230,126,34,0.3)", + "text-primary": "#e0d5c6", + "text-secondary": "#f0b67e", + "border-color": "rgba(230,126,34,0.3)", + "header-min-height": "58px", + "header-padding-vertical": "14px", + "header-padding-horizontal": "20px", + "header-line-height": "1.25" + } +} diff --git a/default-themes/solar-flare-dusk.v3.css b/default-themes/solar-flare-dusk.v3.css new file mode 100644 index 0000000..cab43e1 --- /dev/null +++ b/default-themes/solar-flare-dusk.v3.css @@ -0,0 +1,200 @@ +/* ========================================================================== + Solar Flare Dusk — Dark Theme (v3) + Palette overrides + semantic tokens for FluentLM. + Rebrands from default blue to Solar Flare amber/orange, dark mode. + ========================================================================== */ + +.solar-flare-dusk { + /* ----------------------------------------------------------------------- + PALETTE — Brand / Theme Colors (Solar Flare amber ramp) + ----------------------------------------------------------------------- */ + --themePrimary: #e67e22; + --themeDark: #b8621a; + --themeDarkAlt: #cf701e; + --themeDarker: #7a4310; + --themeSecondary: #eb9950; + --themeTertiary: #f0b67e; + --themeLight: #f7d6b3; + --themeLighter: #fbe9d5; + --themeLighterAlt: #fdf5ec; + + /* ----------------------------------------------------------------------- + PALETTE — Neutrals (inverted for dark mode, warm amber-tinted) + ----------------------------------------------------------------------- */ + --black: #000000; + --neutralDarker: #1a120a; + --neutralDark: #241a10; + --neutralPrimary: #2e1e0e; + --neutralPrimaryAlt: #3c2a16; + --neutralSecondary: #52381e; + --neutralSecondaryAlt: #6a4a28; + --neutralTertiary: #8a6840; + --neutralTertiaryAlt: #f0b67e; + --neutralQuaternary: #9d8a70; + --neutralQuaternaryAlt: #b5a48e; + --neutralLight: #c8b898; + --neutralLighter: #e0d5c6; + --neutralLighterAlt: #f0e6d8; + --white: #ffffff; + + /* ----------------------------------------------------------------------- + BODY / BACKGROUND + ----------------------------------------------------------------------- */ + --bodyBackground: var(--neutralPrimary); + --bodyBackgroundHovered: var(--neutralPrimaryAlt); + --bodyBackgroundChecked: var(--neutralSecondary); + --bodyStandoutBackground: var(--neutralDark); + --bodyFrameBackground: var(--neutralPrimary); + --bodyFrameDivider: var(--neutralSecondary); + --bodyDivider: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + TEXT + ----------------------------------------------------------------------- */ + --bodyText: var(--neutralLighter); + --bodyTextChecked: var(--white); + --bodySubtext: var(--neutralTertiaryAlt); + --disabledText: var(--neutralSecondary); + --disabledBodyText: var(--neutralSecondary); + --disabledSubtext: var(--neutralSecondaryAlt); + --disabledBodySubtext: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + LINKS + ----------------------------------------------------------------------- */ + --link: #f0b67e; + --linkHovered: #f7d6b3; + --actionLink: var(--neutralLighter); + --actionLinkHovered: var(--white); + + /* ----------------------------------------------------------------------- + BUTTONS — Default + ----------------------------------------------------------------------- */ + --buttonBackground: var(--neutralPrimary); + --buttonBackgroundHovered: var(--neutralPrimaryAlt); + --buttonBackgroundChecked: var(--neutralSecondary); + --buttonBackgroundCheckedHovered: var(--neutralSecondaryAlt); + --buttonBackgroundPressed: var(--neutralSecondary); + --buttonBackgroundDisabled: var(--neutralDark); + --buttonBorder: var(--neutralSecondaryAlt); + --buttonBorderDisabled: var(--neutralDark); + --buttonText: var(--neutralLighter); + --buttonTextHovered: var(--white); + --buttonTextChecked: var(--white); + --buttonTextCheckedHovered: var(--white); + --buttonTextPressed: var(--white); + --buttonTextDisabled: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + BUTTONS — Primary + ----------------------------------------------------------------------- */ + --primaryButtonBackground: var(--themePrimary); + --primaryButtonBackgroundHovered: var(--themeDarkAlt); + --primaryButtonBackgroundPressed: var(--themeDark); + --primaryButtonBackgroundDisabled: var(--neutralDark); + --primaryButtonBorder: transparent; + --primaryButtonText: var(--white); + --primaryButtonTextHovered: var(--white); + --primaryButtonTextPressed: var(--white); + --primaryButtonTextDisabled: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + BUTTONS — Accent + ----------------------------------------------------------------------- */ + --accentButtonBackground: var(--themePrimary); + --accentButtonText: var(--white); + + /* ----------------------------------------------------------------------- + INPUTS + ----------------------------------------------------------------------- */ + --inputBackground: var(--neutralPrimary); + --inputBackgroundChecked: var(--themePrimary); + --inputBackgroundCheckedHovered: var(--themeDarkAlt); + --inputPlaceholderBackgroundChecked: var(--themeDark); + --inputForegroundChecked: var(--white); + --inputBorder: var(--neutralTertiary); + --smallInputBorder: var(--neutralTertiary); + --inputBorderHovered: var(--neutralLighter); + --inputFocusBorderAlt: var(--themeSecondary); + --inputText: var(--neutralLighter); + --inputTextHovered: var(--white); + --inputPlaceholderText: var(--neutralTertiaryAlt); + --inputIcon: var(--themeSecondary); + --inputIconHovered: var(--themePrimary); + --inputIconDisabled: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + LISTS + ----------------------------------------------------------------------- */ + --listBackground: var(--neutralPrimary); + --listText: var(--neutralLighter); + --listItemBackgroundHovered: var(--neutralPrimaryAlt); + --listItemBackgroundChecked: var(--neutralSecondary); + --listItemBackgroundCheckedHovered: var(--neutralSecondaryAlt); + --listHeaderBackgroundHovered: var(--neutralPrimaryAlt); + --listHeaderBackgroundPressed: var(--neutralSecondary); + + /* ----------------------------------------------------------------------- + MENUS + ----------------------------------------------------------------------- */ + --menuBackground: var(--neutralPrimary); + --menuDivider: var(--neutralSecondary); + --menuIcon: var(--themeSecondary); + --menuHeader: var(--themeSecondary); + --menuItemBackgroundHovered: var(--neutralPrimaryAlt); + --menuItemBackgroundPressed: var(--neutralSecondary); + --menuItemText: var(--neutralLighter); + --menuItemTextHovered: var(--white); + + /* ----------------------------------------------------------------------- + CARDS + ----------------------------------------------------------------------- */ + --cardStandoutBackground: var(--neutralDark); + --cardShadow: none; + --cardShadowHovered: none; + --variantBorder: var(--neutralSecondary); + --variantBorderHovered: var(--neutralTertiary); + + /* ----------------------------------------------------------------------- + DISABLED / FOCUS + ----------------------------------------------------------------------- */ + --disabledBackground: var(--neutralDark); + --disabledBorder: var(--neutralDark); + --focusBorder: var(--neutralTertiaryAlt); + + /* ----------------------------------------------------------------------- + STATUS — Backgrounds + ----------------------------------------------------------------------- */ + --infoBackground: var(--neutralPrimary); + --errorBackground: #442726; + --blockingBackground: #442726; + --warningBackground: #433519; + --severeWarningBackground: #4F2A0F; + --successBackground: #393D1B; + + /* ----------------------------------------------------------------------- + STATUS — Icons + ----------------------------------------------------------------------- */ + --infoIcon: var(--neutralTertiaryAlt); + --errorIcon: #F1707B; + --blockingIcon: #442726; + --warningIcon: var(--neutralTertiaryAlt); + --severeWarningIcon: #FCE100; + --successIcon: #92C353; + + /* ----------------------------------------------------------------------- + STATUS — Text + ----------------------------------------------------------------------- */ + --errorText: #F1707B; + --messageText: var(--neutralLighter); + --messageLink: #f0b67e; + --messageLinkHovered: #f7d6b3; + --warningText: var(--neutralLighter); + --successText: #92C353; + + /* ----------------------------------------------------------------------- + MISC + ----------------------------------------------------------------------- */ + --defaultStateBackground: var(--neutralDark); + --overlayBackground: rgba(0, 0, 0, 0.6); +} diff --git a/migration-rules/v2-to-v3.md b/migration-rules/v2-to-v3.md index 7a7aade..1155708 100644 --- a/migration-rules/v2-to-v3.md +++ b/migration-rules/v2-to-v3.md @@ -102,10 +102,169 @@ All CSS rules targeting component selectors. The full list from v2 themes that a ## 2. Page Migration -*(To be added — covers HTML structure changes, script injection, and component class updates.)* +### Overview + +v2 pages rely on the v2 theme CSS for **all** styling — shell chrome, viewer layout, and page content. In v3, the theme CSS only provides FluentLM palette tokens. When migrating a v2 page to run under a v3 theme, we must **inline** the v2 styles that the page content depends on so it keeps its look. + +### What the framework handles (NOT inlined) + +The shell chrome CSS (chat panel, modals, toggle, brainstorm, attachments, etc.) will be provided by a framework-level shell stylesheet. Pages do NOT need to carry these rules: + +- `.chat-panel`, `.chat-header`, `.chat-messages`, `.chat-message`, `.chat-message p`, `.chat-message p strong`, `.chat-message p code` +- `.link-group`, `.link-group a` +- `form` (chat form layout), `.chat-input`, `.chat-submit` +- `.chat-toggle`, `.chat-toggle-dots`, `.chat-toggle-dot`, `body.chat-collapsed *` +- `.chat-input-wrapper`, `.brainstorm-icon-btn`, `.brainstorm-*` +- `.modal-overlay`, `.modal-content`, `.modal-header`, `.modal-body`, `.modal-footer`, `.modal-footer-right` +- `.modal-btn`, `.modal-btn-primary`, `.modal-btn-secondary`, `.modal-btn-danger` +- `.form-group`, `.form-label`, `.form-input`, `.checkbox-label` +- `.loading-overlay`, `.spinner`, `#loadingOverlay` +- `.save-line`, `.save-line-label` +- `.attach-btn`, `.attach-menu`, `.attach-menu-item`, `.attachment-pill*` +- `.screenshot-overlay`, `.screenshot-rect` + +### What must be inlined into each migrated page + +The following ` +``` + +### Migration rules for the LLM + +When migrating a v2 page to v3: + +1. **Inject the ` -
-
SynthOS
+ +
+ + + +
+ +
+
+
Page Builder

SynthOS: Welcome to My Notes — a simple note-taking app! Your notes are listed in the sidebar on the left. Click any note to view or edit it, or tap "+ New Note" to create a fresh one. Each note has a rich text editor with formatting tools, and you can save or delete notes using the buttons below the editor. Use the search box to filter notes by title or content. What would you like to do?