From 1259645706c12f6f3734e5b80ff58ed81a2d2c4d Mon Sep 17 00:00:00 2001 From: Myx Date: Mon, 25 May 2026 16:51:44 +0200 Subject: [PATCH] style: Update default theme --- agents-docs/LESSONS.md | 7 + project-files/themes/toju-website-dark.json | 165 +++++ toju-app/src/app/domains/theme/README.md | 8 +- .../services/theme.service.spec.ts | 194 +++-- .../application/services/theme.service.ts | 22 + .../domain/logic/theme-defaults.logic.ts | 162 +++- .../theme-settings.component.html | 35 + .../theme-settings.component.ts | 9 + tools/deploy-web-apps.ps1 | 2 +- website/CONTEXT.md | 7 +- website/README.md | 6 +- website/angular.json | 10 +- website/package.json | 2 + website/public/web.config | 16 +- .../components/header/header.component.html | 8 +- .../src/app/pages/home/home.component.html | 694 +++++------------- .../src/app/pages/home/home.component.scss | 341 +++++++++ website/src/app/pages/home/home.component.ts | 7 +- website/src/styles.scss | 67 +- website/tailwind.config.js | 4 +- website/tools/copy-iis-web-config.mjs | 10 + website/tools/iis-release-config.test.mjs | 22 + website/tools/website-design.test.mjs | 38 + 23 files changed, 1206 insertions(+), 630 deletions(-) create mode 100644 project-files/themes/toju-website-dark.json create mode 100644 website/src/app/pages/home/home.component.scss create mode 100644 website/tools/copy-iis-web-config.mjs create mode 100644 website/tools/iis-release-config.test.mjs create mode 100644 website/tools/website-design.test.mjs diff --git a/agents-docs/LESSONS.md b/agents-docs/LESSONS.md index 9869084..03a50f8 100644 --- a/agents-docs/LESSONS.md +++ b/agents-docs/LESSONS.md @@ -25,6 +25,13 @@ Durable rules for AI agents working on this project. Read this file at session s ## Lessons +### Do not add fake chrome around screenshots [website] [design] + +- **Trigger:** wrapping a real product screenshot in decorative titlebar/window chrome or placing oversized marketing headings beside copy without checking overlap. +- **Rule:** use the screenshot's existing frame when it already includes app chrome, and top-align large heading/copy columns with explicit readable widths. +- **Why:** duplicated chrome makes CTA/product previews look broken, and bottom-aligned large headings can cover accompanying text on the marketing site. +- **Example:** `website/src/app/pages/home/home.component.html` should render the screenshot directly; `host-section` should use top-aligned heading and `.host-section-copy` columns. + ### Verify lint exits 0 before claiming done [verification] - **Trigger:** about to report a task as complete after running tests but skipping ESLint. diff --git a/project-files/themes/toju-website-dark.json b/project-files/themes/toju-website-dark.json new file mode 100644 index 0000000..1ad1d3b --- /dev/null +++ b/project-files/themes/toju-website-dark.json @@ -0,0 +1,165 @@ +{ + "meta": { + "name": "Toju Website Dark", + "version": "1.0.0", + "description": "Website-inspired dark app theme using the charcoal, green, and amber palette from the public Toju site." + }, + "css": "/* Website-inspired app shell surfaces */\napp-chat-messages .chat-layout,\napp-dm-chat .chat-layout {\n background-image: linear-gradient(180deg, hsl(var(--workspace-background) / 0.96), hsl(var(--background)) 38rem) !important;\n}\n\napp-chat-message-list > div {\n background: transparent !important;\n}\n\napp-chat-message-item > div[data-message-id] {\n margin: 8px 0 !important;\n border: 1px solid hsl(var(--border) / 0.56) !important;\n border-radius: 0.7rem !important;\n background: hsl(var(--card) / 0.72) !important;\n box-shadow: 0 14px 34px rgb(0 0 0 / 0.2) !important;\n backdrop-filter: blur(12px) saturate(120%) !important;\n -webkit-backdrop-filter: blur(12px) saturate(120%) !important;\n}\n\napp-chat-message-item > div[data-message-id]:hover {\n border-color: hsl(var(--primary) / 0.42) !important;\n background: hsl(var(--card) / 0.84) !important;\n}\n\napp-chat-message-composer,\n.chat-bottom-bar {\n border-top: 0 !important;\n background: hsl(var(--background) / 0.84) !important;\n backdrop-filter: blur(16px) saturate(125%) !important;\n -webkit-backdrop-filter: blur(16px) saturate(125%) !important;\n}\n\napp-chat-message-composer textarea,\napp-chat-message-composer [contenteditable=\"true\"] {\n background: hsl(var(--panel-background-alt) / 0.8) !important;\n}\n", + "tokens": { + "colors": { + "background": "210 18% 7%", + "foreground": "42 33% 94%", + "card": "210 17% 10%", + "cardForeground": "42 33% 94%", + "popover": "210 17% 9%", + "popoverForeground": "42 33% 94%", + "primary": "154 49% 55%", + "primaryForeground": "210 18% 7%", + "secondary": "210 14% 15%", + "secondaryForeground": "42 33% 94%", + "muted": "210 14% 15%", + "mutedForeground": "42 13% 67%", + "accent": "38 64% 61%", + "accentForeground": "210 18% 7%", + "destructive": "0 72% 55%", + "destructiveForeground": "0 0% 100%", + "border": "210 13% 22%", + "input": "210 13% 22%", + "ring": "154 49% 55%", + "railBackground": "210 19% 6%", + "workspaceBackground": "210 18% 8%", + "panelBackground": "210 17% 10%", + "panelBackgroundAlt": "210 14% 13%", + "titleBarBackground": "210 19% 6%", + "surfaceHighlight": "154 49% 55%", + "surfaceHighlightAlt": "38 64% 61%" + }, + "spacing": {}, + "radii": { + "radius": "0.6rem", + "surface": "0.85rem", + "pill": "999px" + }, + "effects": { + "panelShadow": "0 28px 64px rgba(0, 0, 0, 0.34)", + "softShadow": "0 16px 36px rgba(0, 0, 0, 0.22)", + "glassBlur": "blur(16px) saturate(125%)" + } + }, + "layout": { + "serversRail": { + "container": "appShell", + "grid": { + "x": 0, + "y": 0, + "w": 1, + "h": 1 + } + }, + "appWorkspace": { + "container": "appShell", + "grid": { + "x": 1, + "y": 0, + "w": 19, + "h": 1 + } + }, + "chatRoomChannelsPanel": { + "container": "roomLayout", + "grid": { + "x": 0, + "y": 0, + "w": 4, + "h": 12 + } + }, + "chatRoomMainPanel": { + "container": "roomLayout", + "grid": { + "x": 4, + "y": 0, + "w": 12, + "h": 12 + } + }, + "chatRoomMembersPanel": { + "container": "roomLayout", + "grid": { + "x": 16, + "y": 0, + "w": 4, + "h": 12 + } + }, + "dmConversationsPanel": { + "container": "dmLayout", + "grid": { + "x": 0, + "y": 0, + "w": 4, + "h": 12 + } + }, + "dmChatPanel": { + "container": "dmLayout", + "grid": { + "x": 4, + "y": 0, + "w": 16, + "h": 12 + } + } + }, + "elements": { + "titleBar": { + "border": "0", + "backgroundColor": "hsl(var(--title-bar-background) / 0.88)", + "boxShadow": "0 12px 28px rgba(0, 0, 0, 0.22)", + "backdropFilter": "var(--theme-effect-glass-blur)" + }, + "serversRail": { + "border": "0", + "backgroundColor": "hsl(var(--rail-background) / 0.94)", + "boxShadow": "var(--theme-effect-panel-shadow)" + }, + "appWorkspace": { + "backgroundColor": "hsl(var(--workspace-background))" + }, + "chatRoomChannelsPanel": { + "border": "0", + "backgroundColor": "hsl(var(--panel-background) / 0.86)", + "backdropFilter": "var(--theme-effect-glass-blur)" + }, + "chatRoomMembersPanel": { + "border": "0", + "backgroundColor": "hsl(var(--panel-background) / 0.82)", + "backdropFilter": "var(--theme-effect-glass-blur)" + }, + "chatRoomMainPanel": { + "backgroundColor": "hsl(var(--workspace-background))" + }, + "chatSurface": { + "backgroundColor": "transparent" + }, + "chatMessageBubble": { + "border": "1px solid hsl(var(--border) / 0.56)", + "borderRadius": "0.7rem", + "backgroundColor": "hsl(var(--card) / 0.72)", + "boxShadow": "var(--theme-effect-soft-shadow)", + "backdropFilter": "blur(12px) saturate(120%)" + }, + "chatComposerBar": { + "border": "0", + "backgroundColor": "hsl(var(--background) / 0.84)", + "backdropFilter": "var(--theme-effect-glass-blur)" + }, + "chatComposerInput": { + "border": "1px solid hsl(var(--border) / 0.6)", + "borderRadius": "0.6rem", + "backgroundColor": "hsl(var(--panel-background-alt) / 0.8)", + "color": "hsl(var(--foreground))" + } + }, + "animations": {} +} \ No newline at end of file diff --git a/toju-app/src/app/domains/theme/README.md b/toju-app/src/app/domains/theme/README.md index 84dfdfa..9ab94ba 100644 --- a/toju-app/src/app/domains/theme/README.md +++ b/toju-app/src/app/domains/theme/README.md @@ -18,7 +18,7 @@ theme/ │ ├── constants/ │ │ └── theme-llm-guide.constants.ts LLM prompt context for AI-assisted theme editing │ ├── logic/ -│ │ ├── theme-defaults.logic.ts Default theme document, JSON template, legacy detection +│ │ ├── theme-defaults.logic.ts Built-in presets, default theme document, JSON template, legacy detection │ │ ├── theme-registry.logic.ts Static registry of themeable elements and layout containers │ │ ├── theme-schema.logic.ts Schema field definitions, animation starters, suggested defaults │ │ └── theme-validation.logic.ts Theme document validation against registry + schema @@ -42,6 +42,12 @@ theme/ └── index.ts Barrel exports ``` +## Built-in presets + +`theme-defaults.logic.ts` exports `BUILT_IN_THEME_PRESETS` for themes that ship with the app and do not depend on the Electron saved-theme library. The default preset is `Toju Website Dark`, which mirrors the website palette and removes the previous green radial chat background bubble. The previous app default remains available as `Toju Default Dark` and can be applied from Theme Studio. + +The importable artifact at `project-files/themes/toju-website-dark.json` is kept byte-for-byte aligned with `DEFAULT_THEME_JSON` by the ThemeService spec. + ## Layer composition ```mermaid diff --git a/toju-app/src/app/domains/theme/application/services/theme.service.spec.ts b/toju-app/src/app/domains/theme/application/services/theme.service.spec.ts index 16f83dc..afb89af 100644 --- a/toju-app/src/app/domains/theme/application/services/theme.service.spec.ts +++ b/toju-app/src/app/domains/theme/application/services/theme.service.spec.ts @@ -1,7 +1,13 @@ import { DOCUMENT } from '@angular/common'; import { EnvironmentInjector, createEnvironmentInjector } from '@angular/core'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; -import { DEFAULT_THEME_JSON, createDefaultThemeDocument } from '../../domain/logic/theme-defaults.logic'; +import { + BUILT_IN_THEME_PRESETS, + DEFAULT_THEME_JSON, + createDefaultThemeDocument +} from '../../domain/logic/theme-defaults.logic'; import { validateThemeDocument } from '../../domain/logic/theme-validation.logic'; import { ThemeService } from './theme.service'; @@ -30,55 +36,58 @@ describe('ThemeService theme application', () => { vi.unstubAllGlobals(); }); - it('uses the compact Toju dark theme as the built-in default JSON', () => { - expect(JSON.parse(DEFAULT_THEME_JSON) as unknown).toEqual({ + it('uses the website dark theme as the built-in default JSON', () => { + const defaultTheme = JSON.parse(DEFAULT_THEME_JSON) as Record; + + expect(defaultTheme['css']).toEqual(expect.not.stringContaining('radial-gradient')); + expect(defaultTheme).toEqual(expect.objectContaining({ meta: { - name: 'Toju Default Dark', - version: '2.0.0', - description: 'Built-in dark glass theme for the full Toju app shell.' + name: 'Toju Website Dark', + version: '1.0.0', + description: 'Website-inspired dark app theme using the charcoal, green, and amber palette from the public Toju site.' }, tokens: { colors: { - background: '224 28% 7%', - foreground: '210 40% 96%', - card: '224 25% 10%', - cardForeground: '210 40% 96%', - popover: '224 26% 9%', - popoverForeground: '210 40% 96%', - primary: '193 95% 68%', - primaryForeground: '222 47% 11%', - secondary: '223 19% 16%', - secondaryForeground: '210 40% 96%', - muted: '223 18% 14%', - mutedForeground: '215 20% 70%', - accent: '218 22% 18%', - accentForeground: '210 40% 98%', + background: '210 18% 7%', + foreground: '42 33% 94%', + card: '210 17% 10%', + cardForeground: '42 33% 94%', + popover: '210 17% 9%', + popoverForeground: '42 33% 94%', + primary: '154 49% 55%', + primaryForeground: '210 18% 7%', + secondary: '210 14% 15%', + secondaryForeground: '42 33% 94%', + muted: '210 14% 15%', + mutedForeground: '42 13% 67%', + accent: '38 64% 61%', + accentForeground: '210 18% 7%', destructive: '0 72% 55%', destructiveForeground: '0 0% 100%', - border: '222 18% 22%', - input: '222 18% 22%', - ring: '193 95% 68%', - railBackground: '226 33% 8%', - workspaceBackground: '224 30% 9%', - panelBackground: '224 24% 11%', - panelBackgroundAlt: '222 22% 13%', - titleBarBackground: '226 34% 7%', - surfaceHighlight: '193 95% 68%', - surfaceHighlightAlt: '261 82% 72%' + border: '210 13% 22%', + input: '210 13% 22%', + ring: '154 49% 55%', + railBackground: '210 19% 6%', + workspaceBackground: '210 18% 8%', + panelBackground: '210 17% 10%', + panelBackgroundAlt: '210 14% 13%', + titleBarBackground: '210 19% 6%', + surfaceHighlight: '154 49% 55%', + surfaceHighlightAlt: '38 64% 61%' }, spacing: {}, radii: { - radius: '0.875rem', - surface: '1.35rem', + radius: '0.6rem', + surface: '0.85rem', pill: '999px' }, effects: { - panelShadow: '0 24px 60px rgba(0, 0, 0, 0.42)', - softShadow: '0 14px 36px rgba(0, 0, 0, 0.28)', - glassBlur: 'blur(18px) saturate(135%)' + panelShadow: '0 28px 64px rgba(0, 0, 0, 0.34)', + softShadow: '0 16px 36px rgba(0, 0, 0, 0.22)', + glassBlur: 'blur(16px) saturate(125%)' } }, - layout: { + layout: expect.objectContaining({ serversRail: { container: 'appShell', grid: { x: 0, y: 0, w: 1, h: 1 } @@ -99,10 +108,76 @@ describe('ThemeService theme application', () => { container: 'roomLayout', grid: { x: 16, y: 0, w: 4, h: 12 } } - } + }), + elements: expect.objectContaining({ + titleBar: expect.objectContaining({ border: '0' }) + }), + animations: {} + })); + }); + + it('exposes both built-in theme presets and applies the legacy default preset', () => { + expect(BUILT_IN_THEME_PRESETS.map((preset) => preset.theme.meta.name)).toEqual(['Toju Website Dark', 'Toju Default Dark']); + expect(service.activeThemeName()).toBe('Toju Website Dark'); + + const applied = service.applyBuiltInPreset('Toju Default Dark'); + + expect(applied).toBe(true); + expect(service.activeThemeName()).toBe('Toju Default Dark'); + expect(service.getHostStyles('appRoot')).toMatchObject({ + '--background': '224 28% 7%', + '--primary': '193 95% 68%' }); }); + it('resets to the website dark preset as the new default', () => { + expect(service.applyBuiltInPreset('Toju Default Dark')).toBe(true); + + service.resetToDefault(); + + expect(service.activeThemeName()).toBe('Toju Website Dark'); + expect(service.getHostStyles('appRoot')).toMatchObject({ + '--background': '210 18% 7%', + '--primary': '154 49% 55%', + '--accent': '38 64% 61%' + }); + + expect(service.activeThemeText()).not.toContain('radial-gradient'); + }); + + it('keeps the importable website dark artifact aligned with the default preset', () => { + const artifact = JSON.parse(readFileSync(resolve('../project-files/themes/toju-website-dark.json'), 'utf8')) as unknown; + const defaultTheme = JSON.parse(DEFAULT_THEME_JSON) as unknown; + + expect(artifact).toEqual(defaultTheme); + }); + + it('keeps all built-in presets valid', () => { + for (const preset of BUILT_IN_THEME_PRESETS) { + const result = validateThemeDocument(preset.theme); + + expect(result.valid).toBe(true); + expect(result.errors).toEqual([]); + } + }); + + it('applies a CSS-only theme over the new default JSON theme', () => { + const applied = service.applyCssOnlyTheme('.css-only-theme { background: hsl(var(--background)); }'); + + expect(applied).toBe(true); + expect(service.activeThemeName()).toBe('Toju Website Dark'); + expect(service.getLayoutItemStyles('dmChatPanel')).toMatchObject({ + gridColumn: '5 / span 16', + gridRow: '1 / span 12' + }); + + expect(service.getHostStyles('appRoot')).toMatchObject({ + '--background': '210 18% 7%' + }); + + expect(styleElements.some((styleElement) => styleElement.textContent === '.css-only-theme { background: hsl(var(--background)); }')).toBe(true); + }); + it('applies a JSON theme with tokens, layout, backgrounds, effects, metadata, and animations', () => { const theme = createCompleteThemeDocument(); const loaded = service.loadThemeText(JSON.stringify(theme), 'apply', 'Theme applied.', 'complete JSON theme'); @@ -185,23 +260,6 @@ describe('ThemeService theme application', () => { expect(service.activeThemeText()).toContain('"css"'); }); - it('applies a CSS-only theme over the default JSON theme', () => { - const applied = service.applyCssOnlyTheme('.css-only-theme { background: hsl(var(--background)); }'); - - expect(applied).toBe(true); - expect(service.activeThemeName()).toBe('Toju Default Dark'); - expect(service.getLayoutItemStyles('dmChatPanel')).toMatchObject({ - gridColumn: '5 / span 16', - gridRow: '1 / span 12' - }); - - expect(service.getHostStyles('appRoot')).toMatchObject({ - '--background': '224 28% 7%' - }); - - expect(styleElements.some((styleElement) => styleElement.textContent === '.css-only-theme { background: hsl(var(--background)); }')).toBe(true); - }); - it('applies CSS over the current JSON draft theme', () => { const draftTheme = createDefaultThemeDocument(); @@ -261,7 +319,7 @@ describe('ThemeService theme application', () => { } }); - expect(service.activeThemeName()).toBe('Toju Default Dark'); + expect(service.activeThemeName()).toBe('Toju Website Dark'); }); it('validates the dedicated DM workspace layout container', () => { @@ -378,6 +436,32 @@ describe('ThemeService theme application', () => { expect(service.getHostStyles('titleBar')).toEqual({}); expect(service.activeThemeText()).not.toContain('"elements"'); }); + + it('loads the website dark saved-theme artifact', () => { + const themeText = readFileSync(resolve('../project-files/themes/toju-website-dark.json'), 'utf8'); + const loaded = service.loadThemeText(themeText, 'apply', 'Theme applied.', 'website dark saved theme'); + + expect(loaded).toBe(true); + expect(service.activeThemeName()).toBe('Toju Website Dark'); + expect(service.getHostStyles('appRoot')).toMatchObject({ + '--background': '210 18% 7%', + '--primary': '154 49% 55%', + '--accent': '38 64% 61%' + }); + + expect(service.getHostStyles('titleBar')).toMatchObject({ + border: '0', + backgroundColor: 'hsl(var(--title-bar-background) / 0.88)', + backdropFilter: 'var(--theme-effect-glass-blur)' + }); + + expect(service.getHostStyles('serversRail')).toMatchObject({ border: '0' }); + expect(service.getHostStyles('chatRoomChannelsPanel')).toMatchObject({ border: '0' }); + expect(service.getHostStyles('chatRoomMembersPanel')).toMatchObject({ border: '0' }); + expect(service.getHostStyles('chatComposerBar')).toMatchObject({ border: '0' }); + + expect(service.activeThemeText()).toContain('Website-inspired app shell surfaces'); + }); }); interface TestStyleElement { diff --git a/toju-app/src/app/domains/theme/application/services/theme.service.ts b/toju-app/src/app/domains/theme/application/services/theme.service.ts index f1ff4a5..db42ee3 100644 --- a/toju-app/src/app/domains/theme/application/services/theme.service.ts +++ b/toju-app/src/app/domains/theme/application/services/theme.service.ts @@ -15,6 +15,7 @@ import { ThemeElementStyles } from '../../domain/models/theme.model'; import { + BUILT_IN_THEME_PRESETS, DEFAULT_THEME_JSON, createDefaultThemeDocument, createDefaultThemeLayout, @@ -137,6 +138,7 @@ export class ThemeService { readonly activeThemeName: Signal; readonly knownAnimationClasses: Signal; readonly isDraftDirty: Signal; + readonly builtInPresets = BUILT_IN_THEME_PRESETS; private readonly documentRef = inject(DOCUMENT); @@ -325,6 +327,26 @@ export class ThemeService { this.setStatusMessage(reason === 'shortcut' ? 'Theme reset to the default preset by shortcut.' : 'Theme reset to the default preset.'); } + applyBuiltInPreset(name: string): boolean { + const preset = this.builtInPresets.find((builtInPreset) => builtInPreset.theme.meta.name === name || builtInPreset.key === name); + + if (!preset) { + this.setStatusMessage('Built-in theme preset not found.'); + return false; + } + + const theme = structuredClone(preset.theme); + const result = validateThemeDocument(theme); + + if (!result.valid || !result.value) { + this.setStatusMessage(result.errors[0] ?? 'Built-in theme preset could not be applied.'); + return false; + } + + this.commitTheme(result.value, stringifyTheme(result.value), `${result.value.meta.name} preset applied.`); + return true; + } + handleGlobalShortcut(event: KeyboardEvent): boolean { const usesModifier = event.ctrlKey || event.metaKey; diff --git a/toju-app/src/app/domains/theme/domain/logic/theme-defaults.logic.ts b/toju-app/src/app/domains/theme/domain/logic/theme-defaults.logic.ts index 65a1689..2fb0c28 100644 --- a/toju-app/src/app/domains/theme/domain/logic/theme-defaults.logic.ts +++ b/toju-app/src/app/domains/theme/domain/logic/theme-defaults.logic.ts @@ -111,7 +111,12 @@ function allElementsEmpty(elements: Record): boolean return Object.values(elements).every((elementStyles) => Object.keys(elementStyles).length === 0); } -export function createDefaultThemeDocument(): ThemeDocument { +export interface BuiltInThemePreset { + key: string; + theme: ThemeDocument; +} + +function createLegacyDefaultDarkThemeDocument(): ThemeDocument { return { css: '', meta: { @@ -166,6 +171,147 @@ export function createDefaultThemeDocument(): ThemeDocument { }; } +export function createDefaultThemeDocument(): ThemeDocument { + return { + css: `/* Website-inspired app shell surfaces */ +app-chat-messages .chat-layout, +app-dm-chat .chat-layout { + background-image: linear-gradient(180deg, hsl(var(--workspace-background) / 0.96), hsl(var(--background)) 38rem) !important; +} + +app-chat-message-list > div { + background: transparent !important; +} + +app-chat-message-item > div[data-message-id] { + margin: 8px 0 !important; + border: 1px solid hsl(var(--border) / 0.56) !important; + border-radius: 0.7rem !important; + background: hsl(var(--card) / 0.72) !important; + box-shadow: 0 14px 34px rgb(0 0 0 / 0.2) !important; + backdrop-filter: blur(12px) saturate(120%) !important; + -webkit-backdrop-filter: blur(12px) saturate(120%) !important; +} + +app-chat-message-item > div[data-message-id]:hover { + border-color: hsl(var(--primary) / 0.42) !important; + background: hsl(var(--card) / 0.84) !important; +} + +app-chat-message-composer, +.chat-bottom-bar { + border-top: 0 !important; + background: hsl(var(--background) / 0.84) !important; + backdrop-filter: blur(16px) saturate(125%) !important; + -webkit-backdrop-filter: blur(16px) saturate(125%) !important; +} + +app-chat-message-composer textarea, +app-chat-message-composer [contenteditable="true"] { + background: hsl(var(--panel-background-alt) / 0.8) !important; +} +`, + meta: { + name: 'Toju Website Dark', + version: '1.0.0', + description: 'Website-inspired dark app theme using the charcoal, green, and amber palette from the public Toju site.' + }, + tokens: { + colors: { + background: '210 18% 7%', + foreground: '42 33% 94%', + card: '210 17% 10%', + cardForeground: '42 33% 94%', + popover: '210 17% 9%', + popoverForeground: '42 33% 94%', + primary: '154 49% 55%', + primaryForeground: '210 18% 7%', + secondary: '210 14% 15%', + secondaryForeground: '42 33% 94%', + muted: '210 14% 15%', + mutedForeground: '42 13% 67%', + accent: '38 64% 61%', + accentForeground: '210 18% 7%', + destructive: '0 72% 55%', + destructiveForeground: '0 0% 100%', + border: '210 13% 22%', + input: '210 13% 22%', + ring: '154 49% 55%', + railBackground: '210 19% 6%', + workspaceBackground: '210 18% 8%', + panelBackground: '210 17% 10%', + panelBackgroundAlt: '210 14% 13%', + titleBarBackground: '210 19% 6%', + surfaceHighlight: '154 49% 55%', + surfaceHighlightAlt: '38 64% 61%' + }, + spacing: {}, + radii: { + radius: '0.6rem', + surface: '0.85rem', + pill: '999px' + }, + effects: { + panelShadow: '0 28px 64px rgba(0, 0, 0, 0.34)', + softShadow: '0 16px 36px rgba(0, 0, 0, 0.22)', + glassBlur: 'blur(16px) saturate(125%)' + } + }, + layout: createDefaultThemeLayout(), + elements: { + titleBar: { + border: '0', + backgroundColor: 'hsl(var(--title-bar-background) / 0.88)', + boxShadow: '0 12px 28px rgba(0, 0, 0, 0.22)', + backdropFilter: 'var(--theme-effect-glass-blur)' + }, + serversRail: { + border: '0', + backgroundColor: 'hsl(var(--rail-background) / 0.94)', + boxShadow: 'var(--theme-effect-panel-shadow)' + }, + appWorkspace: { + backgroundColor: 'hsl(var(--workspace-background))' + }, + chatRoomChannelsPanel: { + border: '0', + backgroundColor: 'hsl(var(--panel-background) / 0.86)', + backdropFilter: 'var(--theme-effect-glass-blur)' + }, + chatRoomMembersPanel: { + border: '0', + backgroundColor: 'hsl(var(--panel-background) / 0.82)', + backdropFilter: 'var(--theme-effect-glass-blur)' + }, + chatRoomMainPanel: { + backgroundColor: 'hsl(var(--workspace-background))' + }, + chatSurface: { + backgroundColor: 'transparent' + }, + chatMessageBubble: { + border: '1px solid hsl(var(--border) / 0.56)', + borderRadius: '0.7rem', + backgroundColor: 'hsl(var(--card) / 0.72)', + boxShadow: 'var(--theme-effect-soft-shadow)', + backdropFilter: 'blur(12px) saturate(120%)' + }, + chatComposerBar: { + border: '0', + backgroundColor: 'hsl(var(--background) / 0.84)', + backdropFilter: 'var(--theme-effect-glass-blur)' + }, + chatComposerInput: { + border: '1px solid hsl(var(--border) / 0.6)', + borderRadius: '0.6rem', + backgroundColor: 'hsl(var(--panel-background-alt) / 0.8)', + color: 'hsl(var(--foreground))' + } + }, + animations: {} + }; +} + export function isLegacyDefaultThemeDocument(document: ThemeDocument): boolean { return ( document.meta.name === 'Toju Default Theme' && @@ -182,12 +328,18 @@ export function isLegacyDefaultThemeDocument(document: ThemeDocument): boolean { } export const DEFAULT_THEME_DOCUMENT: ThemeDocument = createDefaultThemeDocument(); -export const DEFAULT_THEME_JSON = JSON.stringify( +export const BUILT_IN_THEME_PRESETS: readonly BuiltInThemePreset[] = [ { - meta: DEFAULT_THEME_DOCUMENT.meta, - tokens: DEFAULT_THEME_DOCUMENT.tokens, - layout: DEFAULT_THEME_DOCUMENT.layout + key: 'toju-website-dark', + theme: DEFAULT_THEME_DOCUMENT }, + { + key: 'toju-default-dark', + theme: createLegacyDefaultDarkThemeDocument() + } +]; +export const DEFAULT_THEME_JSON = JSON.stringify( + DEFAULT_THEME_DOCUMENT, null, 2 ); diff --git a/toju-app/src/app/domains/theme/feature/settings/theme-settings/theme-settings.component.html b/toju-app/src/app/domains/theme/feature/settings/theme-settings/theme-settings.component.html index d12fda8..f6752d5 100644 --- a/toju-app/src/app/domains/theme/feature/settings/theme-settings/theme-settings.component.html +++ b/toju-app/src/app/domains/theme/feature/settings/theme-settings/theme-settings.component.html @@ -164,6 +164,41 @@