From 0c6447a6f522e81de290bfbbfc8553e87b6d8984 Mon Sep 17 00:00:00 2001 From: Cody Seibert Date: Sat, 20 Dec 2025 01:52:25 -0500 Subject: [PATCH] Implement settings service and routes for file-based settings management - Add SettingsService to handle reading/writing global and project settings. - Introduce API routes for managing settings, including global settings, credentials, and project-specific settings. - Implement migration functionality to transfer settings from localStorage to file-based storage. - Create common utilities for settings routes and integrate logging for error handling. - Update server entry point to include new settings routes. --- apps/server/src/index.ts | 4 + apps/server/src/lib/automaker-paths.ts | 35 + apps/server/src/routes/settings/common.ts | 15 + apps/server/src/routes/settings/index.ts | 38 + .../routes/settings/routes/get-credentials.ts | 23 + .../src/routes/settings/routes/get-global.ts | 23 + .../src/routes/settings/routes/get-project.ts | 34 + .../src/routes/settings/routes/migrate.ts | 54 + .../src/routes/settings/routes/status.ts | 28 + .../settings/routes/update-credentials.ts | 39 + .../routes/settings/routes/update-global.ts | 34 + .../routes/settings/routes/update-project.ts | 48 + apps/server/src/services/settings-service.ts | 542 +++++ apps/server/src/types/settings.ts | 269 +++ apps/ui/src/App.tsx | 30 +- apps/ui/src/components/splash-screen.tsx | 309 +++ apps/ui/src/components/views/setup-view.tsx | 82 +- .../views/setup-view/steps/index.ts | 1 + .../views/setup-view/steps/theme-step.tsx | 90 + apps/ui/src/hooks/use-settings-migration.ts | 261 +++ apps/ui/src/lib/http-api-client.ts | 129 ++ apps/ui/src/routes/__root.tsx | 34 + apps/ui/src/store/app-store.ts | 202 ++ apps/ui/src/store/setup-store.ts | 1 + apps/ui/src/styles/global.css | 1949 ----------------- apps/ui/src/styles/theme-imports.ts | 22 + apps/ui/src/styles/themes/catppuccin.css | 144 ++ apps/ui/src/styles/themes/cream.css | 116 + apps/ui/src/styles/themes/dark.css | 166 ++ apps/ui/src/styles/themes/dracula.css | 144 ++ apps/ui/src/styles/themes/gray.css | 110 + apps/ui/src/styles/themes/gruvbox.css | 144 ++ apps/ui/src/styles/themes/light.css | 103 + apps/ui/src/styles/themes/monokai.css | 144 ++ apps/ui/src/styles/themes/nord.css | 144 ++ apps/ui/src/styles/themes/onedark.css | 144 ++ apps/ui/src/styles/themes/red.css | 70 + apps/ui/src/styles/themes/retro.css | 227 ++ apps/ui/src/styles/themes/solarized.css | 144 ++ apps/ui/src/styles/themes/sunset.css | 111 + apps/ui/src/styles/themes/synthwave.css | 149 ++ apps/ui/src/styles/themes/tokyonight.css | 144 ++ 42 files changed, 4516 insertions(+), 1984 deletions(-) create mode 100644 apps/server/src/routes/settings/common.ts create mode 100644 apps/server/src/routes/settings/index.ts create mode 100644 apps/server/src/routes/settings/routes/get-credentials.ts create mode 100644 apps/server/src/routes/settings/routes/get-global.ts create mode 100644 apps/server/src/routes/settings/routes/get-project.ts create mode 100644 apps/server/src/routes/settings/routes/migrate.ts create mode 100644 apps/server/src/routes/settings/routes/status.ts create mode 100644 apps/server/src/routes/settings/routes/update-credentials.ts create mode 100644 apps/server/src/routes/settings/routes/update-global.ts create mode 100644 apps/server/src/routes/settings/routes/update-project.ts create mode 100644 apps/server/src/services/settings-service.ts create mode 100644 apps/server/src/types/settings.ts create mode 100644 apps/ui/src/components/splash-screen.tsx create mode 100644 apps/ui/src/components/views/setup-view/steps/theme-step.tsx create mode 100644 apps/ui/src/hooks/use-settings-migration.ts create mode 100644 apps/ui/src/styles/theme-imports.ts create mode 100644 apps/ui/src/styles/themes/catppuccin.css create mode 100644 apps/ui/src/styles/themes/cream.css create mode 100644 apps/ui/src/styles/themes/dark.css create mode 100644 apps/ui/src/styles/themes/dracula.css create mode 100644 apps/ui/src/styles/themes/gray.css create mode 100644 apps/ui/src/styles/themes/gruvbox.css create mode 100644 apps/ui/src/styles/themes/light.css create mode 100644 apps/ui/src/styles/themes/monokai.css create mode 100644 apps/ui/src/styles/themes/nord.css create mode 100644 apps/ui/src/styles/themes/onedark.css create mode 100644 apps/ui/src/styles/themes/red.css create mode 100644 apps/ui/src/styles/themes/retro.css create mode 100644 apps/ui/src/styles/themes/solarized.css create mode 100644 apps/ui/src/styles/themes/sunset.css create mode 100644 apps/ui/src/styles/themes/synthwave.css create mode 100644 apps/ui/src/styles/themes/tokyonight.css diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index a4b32872..0cbece15 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -37,10 +37,12 @@ import { isTerminalEnabled, isTerminalPasswordRequired, } from "./routes/terminal/index.js"; +import { createSettingsRoutes } from "./routes/settings/index.js"; import { AgentService } from "./services/agent-service.js"; import { FeatureLoader } from "./services/feature-loader.js"; import { AutoModeService } from "./services/auto-mode-service.js"; import { getTerminalService } from "./services/terminal-service.js"; +import { SettingsService } from "./services/settings-service.js"; import { createSpecRegenerationRoutes } from "./routes/app-spec/index.js"; // Load environment variables @@ -108,6 +110,7 @@ const events: EventEmitter = createEventEmitter(); const agentService = new AgentService(DATA_DIR, events); const featureLoader = new FeatureLoader(); const autoModeService = new AutoModeService(events); +const settingsService = new SettingsService(DATA_DIR); // Initialize services (async () => { @@ -137,6 +140,7 @@ app.use("/api/running-agents", createRunningAgentsRoutes(autoModeService)); app.use("/api/workspace", createWorkspaceRoutes()); app.use("/api/templates", createTemplatesRoutes()); app.use("/api/terminal", createTerminalRoutes()); +app.use("/api/settings", createSettingsRoutes(settingsService)); // Create HTTP server const server = createServer(app); diff --git a/apps/server/src/lib/automaker-paths.ts b/apps/server/src/lib/automaker-paths.ts index e11c6d7b..7aad73a7 100644 --- a/apps/server/src/lib/automaker-paths.ts +++ b/apps/server/src/lib/automaker-paths.ts @@ -89,3 +89,38 @@ export async function ensureAutomakerDir(projectPath: string): Promise { await fs.mkdir(automakerDir, { recursive: true }); return automakerDir; } + +// ============================================================================ +// Global Settings Paths (stored in DATA_DIR from app.getPath('userData')) +// ============================================================================ + +/** + * Get the global settings file path + * DATA_DIR is typically ~/Library/Application Support/automaker (macOS) + * or %APPDATA%\automaker (Windows) or ~/.config/automaker (Linux) + */ +export function getGlobalSettingsPath(dataDir: string): string { + return path.join(dataDir, "settings.json"); +} + +/** + * Get the credentials file path (separate from settings for security) + */ +export function getCredentialsPath(dataDir: string): string { + return path.join(dataDir, "credentials.json"); +} + +/** + * Get the project settings file path within a project's .automaker directory + */ +export function getProjectSettingsPath(projectPath: string): string { + return path.join(getAutomakerDir(projectPath), "settings.json"); +} + +/** + * Ensure the global data directory exists + */ +export async function ensureDataDir(dataDir: string): Promise { + await fs.mkdir(dataDir, { recursive: true }); + return dataDir; +} diff --git a/apps/server/src/routes/settings/common.ts b/apps/server/src/routes/settings/common.ts new file mode 100644 index 00000000..bbadf18d --- /dev/null +++ b/apps/server/src/routes/settings/common.ts @@ -0,0 +1,15 @@ +/** + * Common utilities for settings routes + */ + +import { createLogger } from "../../lib/logger.js"; +import { + getErrorMessage as getErrorMessageShared, + createLogError, +} from "../common.js"; + +export const logger = createLogger("Settings"); + +// Re-export shared utilities +export { getErrorMessageShared as getErrorMessage }; +export const logError = createLogError(logger); diff --git a/apps/server/src/routes/settings/index.ts b/apps/server/src/routes/settings/index.ts new file mode 100644 index 00000000..180876b9 --- /dev/null +++ b/apps/server/src/routes/settings/index.ts @@ -0,0 +1,38 @@ +/** + * Settings routes - HTTP API for persistent file-based settings + */ + +import { Router } from "express"; +import type { SettingsService } from "../../services/settings-service.js"; +import { createGetGlobalHandler } from "./routes/get-global.js"; +import { createUpdateGlobalHandler } from "./routes/update-global.js"; +import { createGetCredentialsHandler } from "./routes/get-credentials.js"; +import { createUpdateCredentialsHandler } from "./routes/update-credentials.js"; +import { createGetProjectHandler } from "./routes/get-project.js"; +import { createUpdateProjectHandler } from "./routes/update-project.js"; +import { createMigrateHandler } from "./routes/migrate.js"; +import { createStatusHandler } from "./routes/status.js"; + +export function createSettingsRoutes(settingsService: SettingsService): Router { + const router = Router(); + + // Status endpoint (check if migration needed) + router.get("/status", createStatusHandler(settingsService)); + + // Global settings + router.get("/global", createGetGlobalHandler(settingsService)); + router.put("/global", createUpdateGlobalHandler(settingsService)); + + // Credentials (separate for security) + router.get("/credentials", createGetCredentialsHandler(settingsService)); + router.put("/credentials", createUpdateCredentialsHandler(settingsService)); + + // Project settings + router.post("/project", createGetProjectHandler(settingsService)); + router.put("/project", createUpdateProjectHandler(settingsService)); + + // Migration from localStorage + router.post("/migrate", createMigrateHandler(settingsService)); + + return router; +} diff --git a/apps/server/src/routes/settings/routes/get-credentials.ts b/apps/server/src/routes/settings/routes/get-credentials.ts new file mode 100644 index 00000000..63f93a99 --- /dev/null +++ b/apps/server/src/routes/settings/routes/get-credentials.ts @@ -0,0 +1,23 @@ +/** + * GET /api/settings/credentials - Get credentials (masked for security) + */ + +import type { Request, Response } from "express"; +import type { SettingsService } from "../../../services/settings-service.js"; +import { getErrorMessage, logError } from "../common.js"; + +export function createGetCredentialsHandler(settingsService: SettingsService) { + return async (_req: Request, res: Response): Promise => { + try { + const credentials = await settingsService.getMaskedCredentials(); + + res.json({ + success: true, + credentials, + }); + } catch (error) { + logError(error, "Get credentials failed"); + res.status(500).json({ success: false, error: getErrorMessage(error) }); + } + }; +} diff --git a/apps/server/src/routes/settings/routes/get-global.ts b/apps/server/src/routes/settings/routes/get-global.ts new file mode 100644 index 00000000..4a2c28ca --- /dev/null +++ b/apps/server/src/routes/settings/routes/get-global.ts @@ -0,0 +1,23 @@ +/** + * GET /api/settings/global - Get global settings + */ + +import type { Request, Response } from "express"; +import type { SettingsService } from "../../../services/settings-service.js"; +import { getErrorMessage, logError } from "../common.js"; + +export function createGetGlobalHandler(settingsService: SettingsService) { + return async (_req: Request, res: Response): Promise => { + try { + const settings = await settingsService.getGlobalSettings(); + + res.json({ + success: true, + settings, + }); + } catch (error) { + logError(error, "Get global settings failed"); + res.status(500).json({ success: false, error: getErrorMessage(error) }); + } + }; +} diff --git a/apps/server/src/routes/settings/routes/get-project.ts b/apps/server/src/routes/settings/routes/get-project.ts new file mode 100644 index 00000000..1a380a23 --- /dev/null +++ b/apps/server/src/routes/settings/routes/get-project.ts @@ -0,0 +1,34 @@ +/** + * POST /api/settings/project - Get project settings + * Uses POST because projectPath may contain special characters + */ + +import type { Request, Response } from "express"; +import type { SettingsService } from "../../../services/settings-service.js"; +import { getErrorMessage, logError } from "../common.js"; + +export function createGetProjectHandler(settingsService: SettingsService) { + return async (req: Request, res: Response): Promise => { + try { + const { projectPath } = req.body as { projectPath?: string }; + + if (!projectPath || typeof projectPath !== "string") { + res.status(400).json({ + success: false, + error: "projectPath is required", + }); + return; + } + + const settings = await settingsService.getProjectSettings(projectPath); + + res.json({ + success: true, + settings, + }); + } catch (error) { + logError(error, "Get project settings failed"); + res.status(500).json({ success: false, error: getErrorMessage(error) }); + } + }; +} diff --git a/apps/server/src/routes/settings/routes/migrate.ts b/apps/server/src/routes/settings/routes/migrate.ts new file mode 100644 index 00000000..ce056a60 --- /dev/null +++ b/apps/server/src/routes/settings/routes/migrate.ts @@ -0,0 +1,54 @@ +/** + * POST /api/settings/migrate - Migrate settings from localStorage + */ + +import type { Request, Response } from "express"; +import type { SettingsService } from "../../../services/settings-service.js"; +import { getErrorMessage, logError, logger } from "../common.js"; + +export function createMigrateHandler(settingsService: SettingsService) { + return async (req: Request, res: Response): Promise => { + try { + const { data } = req.body as { + data?: { + "automaker-storage"?: string; + "automaker-setup"?: string; + "worktree-panel-collapsed"?: string; + "file-browser-recent-folders"?: string; + "automaker:lastProjectDir"?: string; + }; + }; + + if (!data || typeof data !== "object") { + res.status(400).json({ + success: false, + error: "data object is required containing localStorage data", + }); + return; + } + + logger.info("Starting settings migration from localStorage"); + + const result = await settingsService.migrateFromLocalStorage(data); + + if (result.success) { + logger.info( + `Migration successful: ${result.migratedProjectCount} projects migrated` + ); + } else { + logger.warn(`Migration completed with errors: ${result.errors.join(", ")}`); + } + + res.json({ + success: result.success, + migratedGlobalSettings: result.migratedGlobalSettings, + migratedCredentials: result.migratedCredentials, + migratedProjectCount: result.migratedProjectCount, + errors: result.errors, + }); + } catch (error) { + logError(error, "Migration failed"); + res.status(500).json({ success: false, error: getErrorMessage(error) }); + } + }; +} diff --git a/apps/server/src/routes/settings/routes/status.ts b/apps/server/src/routes/settings/routes/status.ts new file mode 100644 index 00000000..ee7dff58 --- /dev/null +++ b/apps/server/src/routes/settings/routes/status.ts @@ -0,0 +1,28 @@ +/** + * GET /api/settings/status - Get settings migration status + * Returns whether settings files exist (to determine if migration is needed) + */ + +import type { Request, Response } from "express"; +import type { SettingsService } from "../../../services/settings-service.js"; +import { getErrorMessage, logError } from "../common.js"; + +export function createStatusHandler(settingsService: SettingsService) { + return async (_req: Request, res: Response): Promise => { + try { + const hasGlobalSettings = await settingsService.hasGlobalSettings(); + const hasCredentials = await settingsService.hasCredentials(); + + res.json({ + success: true, + hasGlobalSettings, + hasCredentials, + dataDir: settingsService.getDataDir(), + needsMigration: !hasGlobalSettings, + }); + } catch (error) { + logError(error, "Get settings status failed"); + res.status(500).json({ success: false, error: getErrorMessage(error) }); + } + }; +} diff --git a/apps/server/src/routes/settings/routes/update-credentials.ts b/apps/server/src/routes/settings/routes/update-credentials.ts new file mode 100644 index 00000000..82d544f0 --- /dev/null +++ b/apps/server/src/routes/settings/routes/update-credentials.ts @@ -0,0 +1,39 @@ +/** + * PUT /api/settings/credentials - Update credentials + */ + +import type { Request, Response } from "express"; +import type { SettingsService } from "../../../services/settings-service.js"; +import type { Credentials } from "../../../types/settings.js"; +import { getErrorMessage, logError } from "../common.js"; + +export function createUpdateCredentialsHandler( + settingsService: SettingsService +) { + return async (req: Request, res: Response): Promise => { + try { + const updates = req.body as Partial; + + if (!updates || typeof updates !== "object") { + res.status(400).json({ + success: false, + error: "Invalid request body - expected credentials object", + }); + return; + } + + await settingsService.updateCredentials(updates); + + // Return masked credentials for confirmation + const masked = await settingsService.getMaskedCredentials(); + + res.json({ + success: true, + credentials: masked, + }); + } catch (error) { + logError(error, "Update credentials failed"); + res.status(500).json({ success: false, error: getErrorMessage(error) }); + } + }; +} diff --git a/apps/server/src/routes/settings/routes/update-global.ts b/apps/server/src/routes/settings/routes/update-global.ts new file mode 100644 index 00000000..973efd74 --- /dev/null +++ b/apps/server/src/routes/settings/routes/update-global.ts @@ -0,0 +1,34 @@ +/** + * PUT /api/settings/global - Update global settings + */ + +import type { Request, Response } from "express"; +import type { SettingsService } from "../../../services/settings-service.js"; +import type { GlobalSettings } from "../../../types/settings.js"; +import { getErrorMessage, logError } from "../common.js"; + +export function createUpdateGlobalHandler(settingsService: SettingsService) { + return async (req: Request, res: Response): Promise => { + try { + const updates = req.body as Partial; + + if (!updates || typeof updates !== "object") { + res.status(400).json({ + success: false, + error: "Invalid request body - expected settings object", + }); + return; + } + + const settings = await settingsService.updateGlobalSettings(updates); + + res.json({ + success: true, + settings, + }); + } catch (error) { + logError(error, "Update global settings failed"); + res.status(500).json({ success: false, error: getErrorMessage(error) }); + } + }; +} diff --git a/apps/server/src/routes/settings/routes/update-project.ts b/apps/server/src/routes/settings/routes/update-project.ts new file mode 100644 index 00000000..4b48e52e --- /dev/null +++ b/apps/server/src/routes/settings/routes/update-project.ts @@ -0,0 +1,48 @@ +/** + * PUT /api/settings/project - Update project settings + */ + +import type { Request, Response } from "express"; +import type { SettingsService } from "../../../services/settings-service.js"; +import type { ProjectSettings } from "../../../types/settings.js"; +import { getErrorMessage, logError } from "../common.js"; + +export function createUpdateProjectHandler(settingsService: SettingsService) { + return async (req: Request, res: Response): Promise => { + try { + const { projectPath, updates } = req.body as { + projectPath?: string; + updates?: Partial; + }; + + if (!projectPath || typeof projectPath !== "string") { + res.status(400).json({ + success: false, + error: "projectPath is required", + }); + return; + } + + if (!updates || typeof updates !== "object") { + res.status(400).json({ + success: false, + error: "updates object is required", + }); + return; + } + + const settings = await settingsService.updateProjectSettings( + projectPath, + updates + ); + + res.json({ + success: true, + settings, + }); + } catch (error) { + logError(error, "Update project settings failed"); + res.status(500).json({ success: false, error: getErrorMessage(error) }); + } + }; +} diff --git a/apps/server/src/services/settings-service.ts b/apps/server/src/services/settings-service.ts new file mode 100644 index 00000000..0682854f --- /dev/null +++ b/apps/server/src/services/settings-service.ts @@ -0,0 +1,542 @@ +/** + * Settings Service - Handles reading/writing settings to JSON files + * + * Provides persistent storage for: + * - Global settings (DATA_DIR/settings.json) + * - Credentials (DATA_DIR/credentials.json) + * - Per-project settings ({projectPath}/.automaker/settings.json) + */ + +import fs from "fs/promises"; +import path from "path"; +import { createLogger } from "../lib/logger.js"; +import { + getGlobalSettingsPath, + getCredentialsPath, + getProjectSettingsPath, + ensureDataDir, + ensureAutomakerDir, +} from "../lib/automaker-paths.js"; +import type { + GlobalSettings, + Credentials, + ProjectSettings, + KeyboardShortcuts, + AIProfile, + ProjectRef, + TrashedProjectRef, + BoardBackgroundSettings, + WorktreeInfo, +} from "../types/settings.js"; +import { + DEFAULT_GLOBAL_SETTINGS, + DEFAULT_CREDENTIALS, + DEFAULT_PROJECT_SETTINGS, + SETTINGS_VERSION, + CREDENTIALS_VERSION, + PROJECT_SETTINGS_VERSION, +} from "../types/settings.js"; + +const logger = createLogger("SettingsService"); + +/** + * Atomic file write - write to temp file then rename + */ +async function atomicWriteJson(filePath: string, data: unknown): Promise { + const tempPath = `${filePath}.tmp.${Date.now()}`; + const content = JSON.stringify(data, null, 2); + + try { + await fs.writeFile(tempPath, content, "utf-8"); + await fs.rename(tempPath, filePath); + } catch (error) { + // Clean up temp file if it exists + try { + await fs.unlink(tempPath); + } catch { + // Ignore cleanup errors + } + throw error; + } +} + +/** + * Safely read JSON file with fallback to default + */ +async function readJsonFile(filePath: string, defaultValue: T): Promise { + try { + const content = await fs.readFile(filePath, "utf-8"); + return JSON.parse(content) as T; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + return defaultValue; + } + logger.error(`Error reading ${filePath}:`, error); + return defaultValue; + } +} + +/** + * Check if a file exists + */ +async function fileExists(filePath: string): Promise { + try { + await fs.access(filePath); + return true; + } catch { + return false; + } +} + +export class SettingsService { + private dataDir: string; + + constructor(dataDir: string) { + this.dataDir = dataDir; + } + + // ============================================================================ + // Global Settings + // ============================================================================ + + /** + * Get global settings + */ + async getGlobalSettings(): Promise { + const settingsPath = getGlobalSettingsPath(this.dataDir); + const settings = await readJsonFile( + settingsPath, + DEFAULT_GLOBAL_SETTINGS + ); + + // Apply any missing defaults (for backwards compatibility) + return { + ...DEFAULT_GLOBAL_SETTINGS, + ...settings, + keyboardShortcuts: { + ...DEFAULT_GLOBAL_SETTINGS.keyboardShortcuts, + ...settings.keyboardShortcuts, + }, + }; + } + + /** + * Update global settings (partial update) + */ + async updateGlobalSettings( + updates: Partial + ): Promise { + await ensureDataDir(this.dataDir); + const settingsPath = getGlobalSettingsPath(this.dataDir); + + const current = await this.getGlobalSettings(); + const updated: GlobalSettings = { + ...current, + ...updates, + version: SETTINGS_VERSION, + }; + + // Deep merge keyboard shortcuts if provided + if (updates.keyboardShortcuts) { + updated.keyboardShortcuts = { + ...current.keyboardShortcuts, + ...updates.keyboardShortcuts, + }; + } + + await atomicWriteJson(settingsPath, updated); + logger.info("Global settings updated"); + + return updated; + } + + /** + * Check if global settings file exists + */ + async hasGlobalSettings(): Promise { + const settingsPath = getGlobalSettingsPath(this.dataDir); + return fileExists(settingsPath); + } + + // ============================================================================ + // Credentials + // ============================================================================ + + /** + * Get credentials + */ + async getCredentials(): Promise { + const credentialsPath = getCredentialsPath(this.dataDir); + const credentials = await readJsonFile( + credentialsPath, + DEFAULT_CREDENTIALS + ); + + return { + ...DEFAULT_CREDENTIALS, + ...credentials, + apiKeys: { + ...DEFAULT_CREDENTIALS.apiKeys, + ...credentials.apiKeys, + }, + }; + } + + /** + * Update credentials (partial update) + */ + async updateCredentials( + updates: Partial + ): Promise { + await ensureDataDir(this.dataDir); + const credentialsPath = getCredentialsPath(this.dataDir); + + const current = await this.getCredentials(); + const updated: Credentials = { + ...current, + ...updates, + version: CREDENTIALS_VERSION, + }; + + // Deep merge api keys if provided + if (updates.apiKeys) { + updated.apiKeys = { + ...current.apiKeys, + ...updates.apiKeys, + }; + } + + await atomicWriteJson(credentialsPath, updated); + logger.info("Credentials updated"); + + return updated; + } + + /** + * Get masked credentials (for UI display - don't expose full keys) + */ + async getMaskedCredentials(): Promise<{ + anthropic: { configured: boolean; masked: string }; + google: { configured: boolean; masked: string }; + openai: { configured: boolean; masked: string }; + }> { + const credentials = await this.getCredentials(); + + const maskKey = (key: string): string => { + if (!key || key.length < 8) return ""; + return `${key.substring(0, 4)}...${key.substring(key.length - 4)}`; + }; + + return { + anthropic: { + configured: !!credentials.apiKeys.anthropic, + masked: maskKey(credentials.apiKeys.anthropic), + }, + google: { + configured: !!credentials.apiKeys.google, + masked: maskKey(credentials.apiKeys.google), + }, + openai: { + configured: !!credentials.apiKeys.openai, + masked: maskKey(credentials.apiKeys.openai), + }, + }; + } + + /** + * Check if credentials file exists + */ + async hasCredentials(): Promise { + const credentialsPath = getCredentialsPath(this.dataDir); + return fileExists(credentialsPath); + } + + // ============================================================================ + // Project Settings + // ============================================================================ + + /** + * Get project settings + */ + async getProjectSettings(projectPath: string): Promise { + const settingsPath = getProjectSettingsPath(projectPath); + const settings = await readJsonFile( + settingsPath, + DEFAULT_PROJECT_SETTINGS + ); + + return { + ...DEFAULT_PROJECT_SETTINGS, + ...settings, + }; + } + + /** + * Update project settings (partial update) + */ + async updateProjectSettings( + projectPath: string, + updates: Partial + ): Promise { + await ensureAutomakerDir(projectPath); + const settingsPath = getProjectSettingsPath(projectPath); + + const current = await this.getProjectSettings(projectPath); + const updated: ProjectSettings = { + ...current, + ...updates, + version: PROJECT_SETTINGS_VERSION, + }; + + // Deep merge board background if provided + if (updates.boardBackground) { + updated.boardBackground = { + ...current.boardBackground, + ...updates.boardBackground, + }; + } + + await atomicWriteJson(settingsPath, updated); + logger.info(`Project settings updated for ${projectPath}`); + + return updated; + } + + /** + * Check if project settings file exists + */ + async hasProjectSettings(projectPath: string): Promise { + const settingsPath = getProjectSettingsPath(projectPath); + return fileExists(settingsPath); + } + + // ============================================================================ + // Migration + // ============================================================================ + + /** + * Migrate settings from localStorage data + * This is called when the UI detects it has localStorage data but no settings files + */ + async migrateFromLocalStorage(localStorageData: { + "automaker-storage"?: string; + "automaker-setup"?: string; + "worktree-panel-collapsed"?: string; + "file-browser-recent-folders"?: string; + "automaker:lastProjectDir"?: string; + }): Promise<{ + success: boolean; + migratedGlobalSettings: boolean; + migratedCredentials: boolean; + migratedProjectCount: number; + errors: string[]; + }> { + const errors: string[] = []; + let migratedGlobalSettings = false; + let migratedCredentials = false; + let migratedProjectCount = 0; + + try { + // Parse the main automaker-storage + let appState: Record = {}; + if (localStorageData["automaker-storage"]) { + try { + const parsed = JSON.parse(localStorageData["automaker-storage"]); + appState = parsed.state || parsed; + } catch (e) { + errors.push(`Failed to parse automaker-storage: ${e}`); + } + } + + // Extract global settings + const globalSettings: Partial = { + theme: (appState.theme as GlobalSettings["theme"]) || "dark", + sidebarOpen: + appState.sidebarOpen !== undefined + ? (appState.sidebarOpen as boolean) + : true, + chatHistoryOpen: (appState.chatHistoryOpen as boolean) || false, + kanbanCardDetailLevel: + (appState.kanbanCardDetailLevel as GlobalSettings["kanbanCardDetailLevel"]) || + "standard", + maxConcurrency: (appState.maxConcurrency as number) || 3, + defaultSkipTests: + appState.defaultSkipTests !== undefined + ? (appState.defaultSkipTests as boolean) + : true, + enableDependencyBlocking: + appState.enableDependencyBlocking !== undefined + ? (appState.enableDependencyBlocking as boolean) + : true, + useWorktrees: (appState.useWorktrees as boolean) || false, + showProfilesOnly: (appState.showProfilesOnly as boolean) || false, + defaultPlanningMode: + (appState.defaultPlanningMode as GlobalSettings["defaultPlanningMode"]) || + "skip", + defaultRequirePlanApproval: + (appState.defaultRequirePlanApproval as boolean) || false, + defaultAIProfileId: + (appState.defaultAIProfileId as string | null) || null, + muteDoneSound: (appState.muteDoneSound as boolean) || false, + enhancementModel: + (appState.enhancementModel as GlobalSettings["enhancementModel"]) || + "sonnet", + keyboardShortcuts: + (appState.keyboardShortcuts as KeyboardShortcuts) || + DEFAULT_GLOBAL_SETTINGS.keyboardShortcuts, + aiProfiles: (appState.aiProfiles as AIProfile[]) || [], + projects: (appState.projects as ProjectRef[]) || [], + trashedProjects: + (appState.trashedProjects as TrashedProjectRef[]) || [], + projectHistory: (appState.projectHistory as string[]) || [], + projectHistoryIndex: (appState.projectHistoryIndex as number) || -1, + lastSelectedSessionByProject: + (appState.lastSelectedSessionByProject as Record) || + {}, + }; + + // Add direct localStorage values + if (localStorageData["automaker:lastProjectDir"]) { + globalSettings.lastProjectDir = + localStorageData["automaker:lastProjectDir"]; + } + + if (localStorageData["file-browser-recent-folders"]) { + try { + globalSettings.recentFolders = JSON.parse( + localStorageData["file-browser-recent-folders"] + ); + } catch { + globalSettings.recentFolders = []; + } + } + + if (localStorageData["worktree-panel-collapsed"]) { + globalSettings.worktreePanelCollapsed = + localStorageData["worktree-panel-collapsed"] === "true"; + } + + // Save global settings + await this.updateGlobalSettings(globalSettings); + migratedGlobalSettings = true; + logger.info("Migrated global settings from localStorage"); + + // Extract and save credentials + if (appState.apiKeys) { + const apiKeys = appState.apiKeys as { + anthropic?: string; + google?: string; + openai?: string; + }; + await this.updateCredentials({ + apiKeys: { + anthropic: apiKeys.anthropic || "", + google: apiKeys.google || "", + openai: apiKeys.openai || "", + }, + }); + migratedCredentials = true; + logger.info("Migrated credentials from localStorage"); + } + + // Migrate per-project settings + const boardBackgroundByProject = appState.boardBackgroundByProject as + | Record + | undefined; + const currentWorktreeByProject = appState.currentWorktreeByProject as + | Record + | undefined; + const worktreesByProject = appState.worktreesByProject as + | Record + | undefined; + + // Get unique project paths that have per-project settings + const projectPaths = new Set(); + if (boardBackgroundByProject) { + Object.keys(boardBackgroundByProject).forEach((p) => + projectPaths.add(p) + ); + } + if (currentWorktreeByProject) { + Object.keys(currentWorktreeByProject).forEach((p) => + projectPaths.add(p) + ); + } + if (worktreesByProject) { + Object.keys(worktreesByProject).forEach((p) => projectPaths.add(p)); + } + + // Also check projects list for theme settings + const projects = (appState.projects as ProjectRef[]) || []; + for (const project of projects) { + if (project.theme) { + projectPaths.add(project.path); + } + } + + // Migrate each project's settings + for (const projectPath of projectPaths) { + try { + const projectSettings: Partial = {}; + + // Get theme from project object + const project = projects.find((p) => p.path === projectPath); + if (project?.theme) { + projectSettings.theme = + project.theme as ProjectSettings["theme"]; + } + + if (boardBackgroundByProject?.[projectPath]) { + projectSettings.boardBackground = + boardBackgroundByProject[projectPath]; + } + + if (currentWorktreeByProject?.[projectPath]) { + projectSettings.currentWorktree = + currentWorktreeByProject[projectPath]; + } + + if (worktreesByProject?.[projectPath]) { + projectSettings.worktrees = worktreesByProject[projectPath]; + } + + if (Object.keys(projectSettings).length > 0) { + await this.updateProjectSettings(projectPath, projectSettings); + migratedProjectCount++; + } + } catch (e) { + errors.push(`Failed to migrate project settings for ${projectPath}: ${e}`); + } + } + + logger.info( + `Migration complete: ${migratedProjectCount} projects migrated` + ); + + return { + success: errors.length === 0, + migratedGlobalSettings, + migratedCredentials, + migratedProjectCount, + errors, + }; + } catch (error) { + logger.error("Migration failed:", error); + errors.push(`Migration failed: ${error}`); + return { + success: false, + migratedGlobalSettings, + migratedCredentials, + migratedProjectCount, + errors, + }; + } + } + + /** + * Get the DATA_DIR path (for debugging/info) + */ + getDataDir(): string { + return this.dataDir; + } +} diff --git a/apps/server/src/types/settings.ts b/apps/server/src/types/settings.ts new file mode 100644 index 00000000..d0fc2cfc --- /dev/null +++ b/apps/server/src/types/settings.ts @@ -0,0 +1,269 @@ +/** + * Settings Types - Shared types for file-based settings storage + */ + +// Theme modes (matching UI ThemeMode type) +export type ThemeMode = + | "light" + | "dark" + | "system" + | "retro" + | "dracula" + | "nord" + | "monokai" + | "tokyonight" + | "solarized" + | "gruvbox" + | "catppuccin" + | "onedark" + | "synthwave" + | "red" + | "cream" + | "sunset" + | "gray"; + +export type KanbanCardDetailLevel = "minimal" | "standard" | "detailed"; +export type AgentModel = "opus" | "sonnet" | "haiku"; +export type PlanningMode = "skip" | "lite" | "spec" | "full"; +export type ThinkingLevel = "none" | "low" | "medium" | "high" | "ultrathink"; +export type ModelProvider = "claude"; + +// Keyboard Shortcuts +export interface KeyboardShortcuts { + board: string; + agent: string; + spec: string; + context: string; + settings: string; + profiles: string; + terminal: string; + toggleSidebar: string; + addFeature: string; + addContextFile: string; + startNext: string; + newSession: string; + openProject: string; + projectPicker: string; + cyclePrevProject: string; + cycleNextProject: string; + addProfile: string; + splitTerminalRight: string; + splitTerminalDown: string; + closeTerminal: string; +} + +// AI Profile +export interface AIProfile { + id: string; + name: string; + description: string; + model: AgentModel; + thinkingLevel: ThinkingLevel; + provider: ModelProvider; + isBuiltIn: boolean; + icon?: string; +} + +// Project reference (minimal info stored in global settings) +export interface ProjectRef { + id: string; + name: string; + path: string; + lastOpened?: string; + theme?: string; +} + +// Trashed project reference +export interface TrashedProjectRef extends ProjectRef { + trashedAt: string; + deletedFromDisk?: boolean; +} + +// Chat session (minimal info, full content can be loaded separately) +export interface ChatSessionRef { + id: string; + title: string; + projectId: string; + createdAt: string; + updatedAt: string; + archived: boolean; +} + +/** + * Global Settings - stored in {DATA_DIR}/settings.json + */ +export interface GlobalSettings { + version: number; + + // Theme + theme: ThemeMode; + + // UI State + sidebarOpen: boolean; + chatHistoryOpen: boolean; + kanbanCardDetailLevel: KanbanCardDetailLevel; + + // Feature Defaults + maxConcurrency: number; + defaultSkipTests: boolean; + enableDependencyBlocking: boolean; + useWorktrees: boolean; + showProfilesOnly: boolean; + defaultPlanningMode: PlanningMode; + defaultRequirePlanApproval: boolean; + defaultAIProfileId: string | null; + + // Audio + muteDoneSound: boolean; + + // Enhancement + enhancementModel: AgentModel; + + // Keyboard Shortcuts + keyboardShortcuts: KeyboardShortcuts; + + // AI Profiles + aiProfiles: AIProfile[]; + + // Projects + projects: ProjectRef[]; + trashedProjects: TrashedProjectRef[]; + projectHistory: string[]; + projectHistoryIndex: number; + + // UI Preferences (previously in direct localStorage) + lastProjectDir?: string; + recentFolders: string[]; + worktreePanelCollapsed: boolean; + + // Session tracking (per-project, keyed by project path) + lastSelectedSessionByProject: Record; +} + +/** + * Credentials - stored in {DATA_DIR}/credentials.json + */ +export interface Credentials { + version: number; + apiKeys: { + anthropic: string; + google: string; + openai: string; + }; +} + +/** + * Board Background Settings + */ +export interface BoardBackgroundSettings { + imagePath: string | null; + imageVersion?: number; + cardOpacity: number; + columnOpacity: number; + columnBorderEnabled: boolean; + cardGlassmorphism: boolean; + cardBorderEnabled: boolean; + cardBorderOpacity: number; + hideScrollbar: boolean; +} + +/** + * Worktree Info + */ +export interface WorktreeInfo { + path: string; + branch: string; + isMain: boolean; + hasChanges?: boolean; + changedFilesCount?: number; +} + +/** + * Per-Project Settings - stored in {projectPath}/.automaker/settings.json + */ +export interface ProjectSettings { + version: number; + + // Theme override (null = use global) + theme?: ThemeMode; + + // Worktree settings + useWorktrees?: boolean; + currentWorktree?: { path: string | null; branch: string }; + worktrees?: WorktreeInfo[]; + + // Board background + boardBackground?: BoardBackgroundSettings; + + // Last selected session + lastSelectedSessionId?: string; +} + +// Default values +export const DEFAULT_KEYBOARD_SHORTCUTS: KeyboardShortcuts = { + board: "K", + agent: "A", + spec: "D", + context: "C", + settings: "S", + profiles: "M", + terminal: "T", + toggleSidebar: "`", + addFeature: "N", + addContextFile: "N", + startNext: "G", + newSession: "N", + openProject: "O", + projectPicker: "P", + cyclePrevProject: "Q", + cycleNextProject: "E", + addProfile: "N", + splitTerminalRight: "Alt+D", + splitTerminalDown: "Alt+S", + closeTerminal: "Alt+W", +}; + +export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = { + version: 1, + theme: "dark", + sidebarOpen: true, + chatHistoryOpen: false, + kanbanCardDetailLevel: "standard", + maxConcurrency: 3, + defaultSkipTests: true, + enableDependencyBlocking: true, + useWorktrees: false, + showProfilesOnly: false, + defaultPlanningMode: "skip", + defaultRequirePlanApproval: false, + defaultAIProfileId: null, + muteDoneSound: false, + enhancementModel: "sonnet", + keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS, + aiProfiles: [], + projects: [], + trashedProjects: [], + projectHistory: [], + projectHistoryIndex: -1, + lastProjectDir: undefined, + recentFolders: [], + worktreePanelCollapsed: false, + lastSelectedSessionByProject: {}, +}; + +export const DEFAULT_CREDENTIALS: Credentials = { + version: 1, + apiKeys: { + anthropic: "", + google: "", + openai: "", + }, +}; + +export const DEFAULT_PROJECT_SETTINGS: ProjectSettings = { + version: 1, +}; + +export const SETTINGS_VERSION = 1; +export const CREDENTIALS_VERSION = 1; +export const PROJECT_SETTINGS_VERSION = 1; diff --git a/apps/ui/src/App.tsx b/apps/ui/src/App.tsx index a38bfb42..a38de6b2 100644 --- a/apps/ui/src/App.tsx +++ b/apps/ui/src/App.tsx @@ -1,7 +1,35 @@ +import { useState, useCallback } from "react"; import { RouterProvider } from "@tanstack/react-router"; import { router } from "./utils/router"; +import { SplashScreen } from "./components/splash-screen"; +import { useSettingsMigration } from "./hooks/use-settings-migration"; import "./styles/global.css"; +import "./styles/theme-imports"; export default function App() { - return ; + const [showSplash, setShowSplash] = useState(() => { + // Only show splash once per session + if (sessionStorage.getItem("automaker-splash-shown")) { + return false; + } + return true; + }); + + // Run settings migration on startup (localStorage -> file storage) + const migrationState = useSettingsMigration(); + if (migrationState.migrated) { + console.log("[App] Settings migrated to file storage"); + } + + const handleSplashComplete = useCallback(() => { + sessionStorage.setItem("automaker-splash-shown", "true"); + setShowSplash(false); + }, []); + + return ( + <> + + {showSplash && } + + ); } diff --git a/apps/ui/src/components/splash-screen.tsx b/apps/ui/src/components/splash-screen.tsx new file mode 100644 index 00000000..83af7f86 --- /dev/null +++ b/apps/ui/src/components/splash-screen.tsx @@ -0,0 +1,309 @@ +import { useEffect, useState, useMemo } from "react"; + +const TOTAL_DURATION = 2300; // Total animation duration in ms (tightened from 4000) +const LOGO_ENTER_DURATION = 500; // Tightened from 1200 +const PARTICLES_ENTER_DELAY = 100; // Tightened from 400 +const EXIT_START = 1800; // Adjusted for shorter duration + +interface Particle { + id: number; + x: number; + y: number; + size: number; + delay: number; + angle: number; + distance: number; + opacity: number; + floatDuration: number; +} + +function generateParticles(count: number): Particle[] { + return Array.from({ length: count }, (_, i) => { + const angle = (i / count) * 360 + Math.random() * 30; + const distance = 60 + Math.random() * 80; // Increased spread + return { + id: i, + x: Math.cos((angle * Math.PI) / 180) * distance, + y: Math.sin((angle * Math.PI) / 180) * distance, + size: 3 + Math.random() * 6, // Slightly smaller range for more subtle look + delay: Math.random() * 400, + angle, + distance: 300 + Math.random() * 200, + opacity: 0.4 + Math.random() * 0.6, + floatDuration: 3000 + Math.random() * 4000, + }; + }); +} + +export function SplashScreen({ onComplete }: { onComplete: () => void }) { + const [phase, setPhase] = useState<"enter" | "hold" | "exit" | "done">( + "enter" + ); + + const particles = useMemo(() => generateParticles(50), []); + + useEffect(() => { + const timers: NodeJS.Timeout[] = []; + + // Phase transitions + timers.push(setTimeout(() => setPhase("hold"), LOGO_ENTER_DURATION)); + timers.push(setTimeout(() => setPhase("exit"), EXIT_START)); + timers.push( + setTimeout(() => { + setPhase("done"); + onComplete(); + }, TOTAL_DURATION) + ); + + return () => timers.forEach(clearTimeout); + }, [onComplete]); + + if (phase === "done") return null; + + return ( +
+ + + {/* Subtle gradient background */} +
+ + {/* Particle container 1 - Clockwise */} +
+ {particles.slice(0, 25).map((particle) => ( +
+
+
+ ))} +
+ + {/* Particle container 2 - Counter-Clockwise */} +
+ {particles.slice(25).map((particle) => ( +
+
+
+ ))} +
+ + {/* Logo container */} +
+ {/* Glow effect behind logo */} +
+ + {/* The logo */} + + + + + + + + + + + + + + + + + +
+ + {/* Automaker text that fades in below the logo */} +
+ + automaker. + +
+
+ ); +} diff --git a/apps/ui/src/components/views/setup-view.tsx b/apps/ui/src/components/views/setup-view.tsx index d31862ad..f0546839 100644 --- a/apps/ui/src/components/views/setup-view.tsx +++ b/apps/ui/src/components/views/setup-view.tsx @@ -3,6 +3,7 @@ import { useSetupStore } from "@/store/setup-store"; import { StepIndicator } from "./setup-view/components"; import { WelcomeStep, + ThemeStep, CompleteStep, ClaudeSetupStep, GitHubSetupStep, @@ -19,12 +20,13 @@ export function SetupView() { } = useSetupStore(); const navigate = useNavigate(); - const steps = ["welcome", "claude", "github", "complete"] as const; + const steps = ["welcome", "theme", "claude", "github", "complete"] as const; type StepName = (typeof steps)[number]; const getStepName = (): StepName => { if (currentStep === "claude_detect" || currentStep === "claude_auth") return "claude"; if (currentStep === "welcome") return "welcome"; + if (currentStep === "theme") return "theme"; if (currentStep === "github") return "github"; return "complete"; }; @@ -39,6 +41,10 @@ export function SetupView() { ); switch (from) { case "welcome": + console.log("[Setup Flow] Moving to theme step"); + setCurrentStep("theme"); + break; + case "theme": console.log("[Setup Flow] Moving to claude_detect step"); setCurrentStep("claude_detect"); break; @@ -56,9 +62,12 @@ export function SetupView() { const handleBack = (from: string) => { console.log("[Setup Flow] handleBack called from:", from); switch (from) { - case "claude": + case "theme": setCurrentStep("welcome"); break; + case "claude": + setCurrentStep("theme"); + break; case "github": setCurrentStep("claude_detect"); break; @@ -98,42 +107,47 @@ export function SetupView() {
{/* Content */} -
-
-
-
- +
+
+ +
+ +
+ {currentStep === "welcome" && ( + handleNext("welcome")} /> + )} + + {currentStep === "theme" && ( + handleNext("theme")} + onBack={() => handleBack("theme")} /> -
+ )} -
- {currentStep === "welcome" && ( - handleNext("welcome")} /> - )} + {(currentStep === "claude_detect" || + currentStep === "claude_auth") && ( + handleNext("claude")} + onBack={() => handleBack("claude")} + onSkip={handleSkipClaude} + /> + )} - {(currentStep === "claude_detect" || - currentStep === "claude_auth") && ( - handleNext("claude")} - onBack={() => handleBack("claude")} - onSkip={handleSkipClaude} - /> - )} + {currentStep === "github" && ( + handleNext("github")} + onBack={() => handleBack("github")} + onSkip={handleSkipGithub} + /> + )} - {currentStep === "github" && ( - handleNext("github")} - onBack={() => handleBack("github")} - onSkip={handleSkipGithub} - /> - )} - - {currentStep === "complete" && ( - - )} -
+ {currentStep === "complete" && ( + + )}
diff --git a/apps/ui/src/components/views/setup-view/steps/index.ts b/apps/ui/src/components/views/setup-view/steps/index.ts index 7299a070..fef2d09d 100644 --- a/apps/ui/src/components/views/setup-view/steps/index.ts +++ b/apps/ui/src/components/views/setup-view/steps/index.ts @@ -1,5 +1,6 @@ // Re-export all setup step components for easier imports export { WelcomeStep } from "./welcome-step"; +export { ThemeStep } from "./theme-step"; export { CompleteStep } from "./complete-step"; export { ClaudeSetupStep } from "./claude-setup-step"; export { GitHubSetupStep } from "./github-setup-step"; diff --git a/apps/ui/src/components/views/setup-view/steps/theme-step.tsx b/apps/ui/src/components/views/setup-view/steps/theme-step.tsx new file mode 100644 index 00000000..850340b1 --- /dev/null +++ b/apps/ui/src/components/views/setup-view/steps/theme-step.tsx @@ -0,0 +1,90 @@ +import { Button } from "@/components/ui/button"; +import { ArrowRight, ArrowLeft, Check } from "lucide-react"; +import { themeOptions } from "@/config/theme-options"; +import { useAppStore } from "@/store/app-store"; +import { cn } from "@/lib/utils"; + +interface ThemeStepProps { + onNext: () => void; + onBack: () => void; +} + +export function ThemeStep({ onNext, onBack }: ThemeStepProps) { + const { theme, setTheme, setPreviewTheme } = useAppStore(); + + const handleThemeHover = (themeValue: string) => { + setPreviewTheme(themeValue as typeof theme); + }; + + const handleThemeLeave = () => { + setPreviewTheme(null); + }; + + const handleThemeClick = (themeValue: string) => { + setTheme(themeValue as typeof theme); + setPreviewTheme(null); + }; + + return ( +
+
+

+ Choose Your Theme +

+

+ Pick a theme that suits your style. Hover to preview, click to select. +

+
+ +
+ {themeOptions.map((option) => { + const Icon = option.Icon; + const isSelected = theme === option.value; + + return ( + + ); + })} +
+ +
+ + +
+
+ ); +} diff --git a/apps/ui/src/hooks/use-settings-migration.ts b/apps/ui/src/hooks/use-settings-migration.ts new file mode 100644 index 00000000..9a941605 --- /dev/null +++ b/apps/ui/src/hooks/use-settings-migration.ts @@ -0,0 +1,261 @@ +/** + * Settings Migration Hook + * + * This hook handles migrating settings from localStorage to file-based storage. + * It runs on app startup and: + * 1. Checks if server has settings files + * 2. If not, migrates localStorage data to server + * 3. Clears old localStorage keys after successful migration + * + * This approach keeps localStorage as a fast cache while ensuring + * settings are persisted to files that survive app updates. + */ + +import { useEffect, useState, useRef } from "react"; +import { getHttpApiClient } from "@/lib/http-api-client"; +import { isElectron } from "@/lib/electron"; + +interface MigrationState { + checked: boolean; + migrated: boolean; + error: string | null; +} + +// localStorage keys to migrate +const LOCALSTORAGE_KEYS = [ + "automaker-storage", + "automaker-setup", + "worktree-panel-collapsed", + "file-browser-recent-folders", + "automaker:lastProjectDir", +] as const; + +// Keys to clear after migration (not automaker-storage as it's still used by Zustand) +const KEYS_TO_CLEAR_AFTER_MIGRATION = [ + "worktree-panel-collapsed", + "file-browser-recent-folders", + "automaker:lastProjectDir", + // Legacy keys + "automaker_projects", + "automaker_current_project", + "automaker_trashed_projects", +] as const; + +/** + * Hook to handle settings migration from localStorage to file-based storage + */ +export function useSettingsMigration(): MigrationState { + const [state, setState] = useState({ + checked: false, + migrated: false, + error: null, + }); + const migrationAttempted = useRef(false); + + useEffect(() => { + // Only run once + if (migrationAttempted.current) return; + migrationAttempted.current = true; + + async function checkAndMigrate() { + // Only run migration in Electron mode (web mode uses different storage) + if (!isElectron()) { + setState({ checked: true, migrated: false, error: null }); + return; + } + + try { + const api = getHttpApiClient(); + + // Check if server has settings files + const status = await api.settings.getStatus(); + + if (!status.success) { + console.error("[Settings Migration] Failed to get status:", status); + setState({ + checked: true, + migrated: false, + error: "Failed to check settings status", + }); + return; + } + + // If settings files already exist, no migration needed + if (!status.needsMigration) { + console.log( + "[Settings Migration] Settings files exist, no migration needed" + ); + setState({ checked: true, migrated: false, error: null }); + return; + } + + // Check if we have localStorage data to migrate + const automakerStorage = localStorage.getItem("automaker-storage"); + if (!automakerStorage) { + console.log( + "[Settings Migration] No localStorage data to migrate" + ); + setState({ checked: true, migrated: false, error: null }); + return; + } + + console.log("[Settings Migration] Starting migration..."); + + // Collect all localStorage data + const localStorageData: Record = {}; + for (const key of LOCALSTORAGE_KEYS) { + const value = localStorage.getItem(key); + if (value) { + localStorageData[key] = value; + } + } + + // Send to server for migration + const result = await api.settings.migrate(localStorageData); + + if (result.success) { + console.log("[Settings Migration] Migration successful:", { + globalSettings: result.migratedGlobalSettings, + credentials: result.migratedCredentials, + projects: result.migratedProjectCount, + }); + + // Clear old localStorage keys (but keep automaker-storage for Zustand) + for (const key of KEYS_TO_CLEAR_AFTER_MIGRATION) { + localStorage.removeItem(key); + } + + setState({ checked: true, migrated: true, error: null }); + } else { + console.warn( + "[Settings Migration] Migration had errors:", + result.errors + ); + setState({ + checked: true, + migrated: false, + error: result.errors.join(", "), + }); + } + } catch (error) { + console.error("[Settings Migration] Migration failed:", error); + setState({ + checked: true, + migrated: false, + error: error instanceof Error ? error.message : "Unknown error", + }); + } + } + + checkAndMigrate(); + }, []); + + return state; +} + +/** + * Sync current settings to the server + * Call this when important settings change + */ +export async function syncSettingsToServer(): Promise { + if (!isElectron()) return false; + + try { + const api = getHttpApiClient(); + const automakerStorage = localStorage.getItem("automaker-storage"); + + if (!automakerStorage) { + return false; + } + + const parsed = JSON.parse(automakerStorage); + const state = parsed.state || parsed; + + // Extract settings to sync + const updates = { + theme: state.theme, + sidebarOpen: state.sidebarOpen, + chatHistoryOpen: state.chatHistoryOpen, + kanbanCardDetailLevel: state.kanbanCardDetailLevel, + maxConcurrency: state.maxConcurrency, + defaultSkipTests: state.defaultSkipTests, + enableDependencyBlocking: state.enableDependencyBlocking, + useWorktrees: state.useWorktrees, + showProfilesOnly: state.showProfilesOnly, + defaultPlanningMode: state.defaultPlanningMode, + defaultRequirePlanApproval: state.defaultRequirePlanApproval, + defaultAIProfileId: state.defaultAIProfileId, + muteDoneSound: state.muteDoneSound, + enhancementModel: state.enhancementModel, + keyboardShortcuts: state.keyboardShortcuts, + aiProfiles: state.aiProfiles, + projects: state.projects, + trashedProjects: state.trashedProjects, + projectHistory: state.projectHistory, + projectHistoryIndex: state.projectHistoryIndex, + lastSelectedSessionByProject: state.lastSelectedSessionByProject, + }; + + const result = await api.settings.updateGlobal(updates); + return result.success; + } catch (error) { + console.error("[Settings Sync] Failed to sync settings:", error); + return false; + } +} + +/** + * Sync credentials to the server + * Call this when API keys change + */ +export async function syncCredentialsToServer(apiKeys: { + anthropic?: string; + google?: string; + openai?: string; +}): Promise { + if (!isElectron()) return false; + + try { + const api = getHttpApiClient(); + const result = await api.settings.updateCredentials({ apiKeys }); + return result.success; + } catch (error) { + console.error("[Settings Sync] Failed to sync credentials:", error); + return false; + } +} + +/** + * Sync project settings to the server + * Call this when project-specific settings change + */ +export async function syncProjectSettingsToServer( + projectPath: string, + updates: { + theme?: string; + useWorktrees?: boolean; + boardBackground?: Record; + currentWorktree?: { path: string | null; branch: string }; + worktrees?: Array<{ + path: string; + branch: string; + isMain: boolean; + hasChanges?: boolean; + changedFilesCount?: number; + }>; + } +): Promise { + if (!isElectron()) return false; + + try { + const api = getHttpApiClient(); + const result = await api.settings.updateProject(projectPath, updates); + return result.success; + } catch (error) { + console.error( + "[Settings Sync] Failed to sync project settings:", + error + ); + return false; + } +} diff --git a/apps/ui/src/lib/http-api-client.ts b/apps/ui/src/lib/http-api-client.ts index 5814fa08..5b863f2d 100644 --- a/apps/ui/src/lib/http-api-client.ts +++ b/apps/ui/src/lib/http-api-client.ts @@ -837,6 +837,135 @@ export class HttpApiClient implements ElectronAPI { this.post("/api/templates/clone", { repoUrl, projectName, parentDir }), }; + // Settings API - persistent file-based settings + settings = { + // Get settings status (check if migration needed) + getStatus: (): Promise<{ + success: boolean; + hasGlobalSettings: boolean; + hasCredentials: boolean; + dataDir: string; + needsMigration: boolean; + }> => this.get("/api/settings/status"), + + // Global settings + getGlobal: (): Promise<{ + success: boolean; + settings?: { + version: number; + theme: string; + sidebarOpen: boolean; + chatHistoryOpen: boolean; + kanbanCardDetailLevel: string; + maxConcurrency: number; + defaultSkipTests: boolean; + enableDependencyBlocking: boolean; + useWorktrees: boolean; + showProfilesOnly: boolean; + defaultPlanningMode: string; + defaultRequirePlanApproval: boolean; + defaultAIProfileId: string | null; + muteDoneSound: boolean; + enhancementModel: string; + keyboardShortcuts: Record; + aiProfiles: unknown[]; + projects: unknown[]; + trashedProjects: unknown[]; + projectHistory: string[]; + projectHistoryIndex: number; + lastProjectDir?: string; + recentFolders: string[]; + worktreePanelCollapsed: boolean; + lastSelectedSessionByProject: Record; + }; + error?: string; + }> => this.get("/api/settings/global"), + + updateGlobal: (updates: Record): Promise<{ + success: boolean; + settings?: Record; + error?: string; + }> => this.put("/api/settings/global", updates), + + // Credentials (masked for security) + getCredentials: (): Promise<{ + success: boolean; + credentials?: { + anthropic: { configured: boolean; masked: string }; + google: { configured: boolean; masked: string }; + openai: { configured: boolean; masked: string }; + }; + error?: string; + }> => this.get("/api/settings/credentials"), + + updateCredentials: (updates: { + apiKeys?: { anthropic?: string; google?: string; openai?: string }; + }): Promise<{ + success: boolean; + credentials?: { + anthropic: { configured: boolean; masked: string }; + google: { configured: boolean; masked: string }; + openai: { configured: boolean; masked: string }; + }; + error?: string; + }> => this.put("/api/settings/credentials", updates), + + // Project settings + getProject: (projectPath: string): Promise<{ + success: boolean; + settings?: { + version: number; + theme?: string; + useWorktrees?: boolean; + currentWorktree?: { path: string | null; branch: string }; + worktrees?: Array<{ + path: string; + branch: string; + isMain: boolean; + hasChanges?: boolean; + changedFilesCount?: number; + }>; + boardBackground?: { + imagePath: string | null; + imageVersion?: number; + cardOpacity: number; + columnOpacity: number; + columnBorderEnabled: boolean; + cardGlassmorphism: boolean; + cardBorderEnabled: boolean; + cardBorderOpacity: number; + hideScrollbar: boolean; + }; + lastSelectedSessionId?: string; + }; + error?: string; + }> => this.post("/api/settings/project", { projectPath }), + + updateProject: ( + projectPath: string, + updates: Record + ): Promise<{ + success: boolean; + settings?: Record; + error?: string; + }> => this.put("/api/settings/project", { projectPath, updates }), + + // Migration from localStorage + migrate: (data: { + "automaker-storage"?: string; + "automaker-setup"?: string; + "worktree-panel-collapsed"?: string; + "file-browser-recent-folders"?: string; + "automaker:lastProjectDir"?: string; + }): Promise<{ + success: boolean; + migratedGlobalSettings: boolean; + migratedCredentials: boolean; + migratedProjectCount: number; + errors: string[]; + }> => this.post("/api/settings/migrate", { data }), + }; + // Sessions API sessions = { list: ( diff --git a/apps/ui/src/routes/__root.tsx b/apps/ui/src/routes/__root.tsx index c144dd78..ed06c601 100644 --- a/apps/ui/src/routes/__root.tsx +++ b/apps/ui/src/routes/__root.tsx @@ -3,6 +3,7 @@ import { useEffect, useState, useCallback } from "react"; import { Sidebar } from "@/components/layout/sidebar"; import { FileBrowserProvider, useFileBrowser, setGlobalFileBrowser } from "@/contexts/file-browser-context"; import { useAppStore } from "@/store/app-store"; +import { useSetupStore } from "@/store/setup-store"; import { getElectronAPI } from "@/lib/electron"; import { Toaster } from "sonner"; import { ThemeOption, themeOptions } from "@/config/theme-options"; @@ -16,9 +17,13 @@ function RootLayoutContent() { previewTheme, getEffectiveTheme, } = useAppStore(); + const { setupComplete } = useSetupStore(); const navigate = useNavigate(); const [isMounted, setIsMounted] = useState(false); const [streamerPanelOpen, setStreamerPanelOpen] = useState(false); + const [setupHydrated, setSetupHydrated] = useState(() => + useSetupStore.persist?.hasHydrated?.() ?? false + ); const { openFileBrowser } = useFileBrowser(); // Hidden streamer panel - opens with "\" key @@ -61,6 +66,35 @@ function RootLayoutContent() { setIsMounted(true); }, []); + // Wait for setup store hydration before enforcing routing rules + useEffect(() => { + if (useSetupStore.persist?.hasHydrated?.()) { + setSetupHydrated(true); + return; + } + + const unsubscribe = useSetupStore.persist?.onFinishHydration?.(() => { + setSetupHydrated(true); + }); + + return () => { + if (typeof unsubscribe === "function") { + unsubscribe(); + } + }; + }, []); + + // Redirect first-run users (or anyone who reopened the wizard) to /setup + useEffect(() => { + if (!setupHydrated) return; + + if (!setupComplete && location.pathname !== "/setup") { + navigate({ to: "/setup" }); + } else if (setupComplete && location.pathname === "/setup") { + navigate({ to: "/" }); + } + }, [setupComplete, setupHydrated, location.pathname, navigate]); + useEffect(() => { setGlobalFileBrowser(openFileBrowser); }, [openFileBrowser]); diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts index ee128598..f433578a 100644 --- a/apps/ui/src/store/app-store.ts +++ b/apps/ui/src/store/app-store.ts @@ -2356,3 +2356,205 @@ export const useAppStore = create()( } ) ); + +// ============================================================================ +// Settings Sync to Server (file-based storage) +// ============================================================================ + +// Debounced sync function to avoid excessive server calls +let syncTimeoutId: NodeJS.Timeout | null = null; +const SYNC_DEBOUNCE_MS = 2000; // Wait 2 seconds after last change before syncing + +/** + * Schedule a sync of current settings to the server + * This is debounced to avoid excessive API calls + */ +function scheduleSyncToServer() { + // Only sync in Electron mode + if (typeof window === "undefined") return; + + // Clear any pending sync + if (syncTimeoutId) { + clearTimeout(syncTimeoutId); + } + + // Schedule new sync + syncTimeoutId = setTimeout(async () => { + try { + // Dynamic import to avoid circular dependencies + const { syncSettingsToServer } = await import( + "@/hooks/use-settings-migration" + ); + await syncSettingsToServer(); + } catch (error) { + console.error("[AppStore] Failed to sync settings to server:", error); + } + }, SYNC_DEBOUNCE_MS); +} + +// Subscribe to store changes and sync to server +// Only sync when important settings change (not every state change) +let previousState: Partial | null = null; +let previousProjectSettings: Record< + string, + { + theme?: string; + boardBackground?: typeof initialState.boardBackgroundByProject[string]; + currentWorktree?: typeof initialState.currentWorktreeByProject[string]; + worktrees?: typeof initialState.worktreesByProject[string]; + } +> = {}; + +// Track pending project syncs (debounced per project) +const projectSyncTimeouts: Record = {}; +const PROJECT_SYNC_DEBOUNCE_MS = 2000; + +/** + * Schedule sync of project settings to server + */ +function scheduleProjectSettingsSync( + projectPath: string, + updates: Record +) { + // Only sync in Electron mode + if (typeof window === "undefined") return; + + // Clear any pending sync for this project + if (projectSyncTimeouts[projectPath]) { + clearTimeout(projectSyncTimeouts[projectPath]); + } + + // Schedule new sync + projectSyncTimeouts[projectPath] = setTimeout(async () => { + try { + const { syncProjectSettingsToServer } = await import( + "@/hooks/use-settings-migration" + ); + await syncProjectSettingsToServer(projectPath, updates); + } catch (error) { + console.error( + `[AppStore] Failed to sync project settings for ${projectPath}:`, + error + ); + } + delete projectSyncTimeouts[projectPath]; + }, PROJECT_SYNC_DEBOUNCE_MS); +} + +useAppStore.subscribe((state) => { + // Skip if this is the initial load + if (!previousState) { + previousState = { + theme: state.theme, + projects: state.projects, + trashedProjects: state.trashedProjects, + keyboardShortcuts: state.keyboardShortcuts, + aiProfiles: state.aiProfiles, + maxConcurrency: state.maxConcurrency, + defaultSkipTests: state.defaultSkipTests, + enableDependencyBlocking: state.enableDependencyBlocking, + useWorktrees: state.useWorktrees, + showProfilesOnly: state.showProfilesOnly, + muteDoneSound: state.muteDoneSound, + enhancementModel: state.enhancementModel, + defaultPlanningMode: state.defaultPlanningMode, + defaultRequirePlanApproval: state.defaultRequirePlanApproval, + defaultAIProfileId: state.defaultAIProfileId, + }; + // Initialize project settings tracking + for (const project of state.projects) { + previousProjectSettings[project.path] = { + theme: project.theme, + boardBackground: state.boardBackgroundByProject[project.path], + currentWorktree: state.currentWorktreeByProject[project.path], + worktrees: state.worktreesByProject[project.path], + }; + } + return; + } + + // Check if any important global settings changed + const importantSettingsChanged = + state.theme !== previousState.theme || + state.projects !== previousState.projects || + state.trashedProjects !== previousState.trashedProjects || + state.keyboardShortcuts !== previousState.keyboardShortcuts || + state.aiProfiles !== previousState.aiProfiles || + state.maxConcurrency !== previousState.maxConcurrency || + state.defaultSkipTests !== previousState.defaultSkipTests || + state.enableDependencyBlocking !== previousState.enableDependencyBlocking || + state.useWorktrees !== previousState.useWorktrees || + state.showProfilesOnly !== previousState.showProfilesOnly || + state.muteDoneSound !== previousState.muteDoneSound || + state.enhancementModel !== previousState.enhancementModel || + state.defaultPlanningMode !== previousState.defaultPlanningMode || + state.defaultRequirePlanApproval !== previousState.defaultRequirePlanApproval || + state.defaultAIProfileId !== previousState.defaultAIProfileId; + + if (importantSettingsChanged) { + // Update previous state + previousState = { + theme: state.theme, + projects: state.projects, + trashedProjects: state.trashedProjects, + keyboardShortcuts: state.keyboardShortcuts, + aiProfiles: state.aiProfiles, + maxConcurrency: state.maxConcurrency, + defaultSkipTests: state.defaultSkipTests, + enableDependencyBlocking: state.enableDependencyBlocking, + useWorktrees: state.useWorktrees, + showProfilesOnly: state.showProfilesOnly, + muteDoneSound: state.muteDoneSound, + enhancementModel: state.enhancementModel, + defaultPlanningMode: state.defaultPlanningMode, + defaultRequirePlanApproval: state.defaultRequirePlanApproval, + defaultAIProfileId: state.defaultAIProfileId, + }; + + // Schedule sync to server + scheduleSyncToServer(); + } + + // Check for per-project settings changes + for (const project of state.projects) { + const projectPath = project.path; + const prev = previousProjectSettings[projectPath] || {}; + const updates: Record = {}; + + // Check if project theme changed + if (project.theme !== prev.theme) { + updates.theme = project.theme; + } + + // Check if board background changed + const currentBg = state.boardBackgroundByProject[projectPath]; + if (currentBg !== prev.boardBackground) { + updates.boardBackground = currentBg; + } + + // Check if current worktree changed + const currentWt = state.currentWorktreeByProject[projectPath]; + if (currentWt !== prev.currentWorktree) { + updates.currentWorktree = currentWt; + } + + // Check if worktrees list changed + const worktrees = state.worktreesByProject[projectPath]; + if (worktrees !== prev.worktrees) { + updates.worktrees = worktrees; + } + + // If any project settings changed, sync them + if (Object.keys(updates).length > 0) { + scheduleProjectSettingsSync(projectPath, updates); + + // Update tracking + previousProjectSettings[projectPath] = { + theme: project.theme, + boardBackground: currentBg, + currentWorktree: currentWt, + worktrees: worktrees, + }; + } + } +}); diff --git a/apps/ui/src/store/setup-store.ts b/apps/ui/src/store/setup-store.ts index 4d2ac6f7..6e2fa907 100644 --- a/apps/ui/src/store/setup-store.ts +++ b/apps/ui/src/store/setup-store.ts @@ -53,6 +53,7 @@ export interface InstallProgress { export type SetupStep = | "welcome" + | "theme" | "claude_detect" | "claude_auth" | "github" diff --git a/apps/ui/src/styles/global.css b/apps/ui/src/styles/global.css index 04e15212..59dce140 100644 --- a/apps/ui/src/styles/global.css +++ b/apps/ui/src/styles/global.css @@ -336,1138 +336,6 @@ --running-indicator-text: oklch(0.6 0.22 265); } -.dark { - /* Deep dark backgrounds - zinc-950 family */ - --background: oklch(0.04 0 0); /* zinc-950 */ - --background-50: oklch(0.04 0 0 / 0.5); /* zinc-950/50 */ - --background-80: oklch(0.04 0 0 / 0.8); /* zinc-950/80 */ - - /* Text colors following hierarchy */ - --foreground: oklch(1 0 0); /* text-white */ - --foreground-secondary: oklch(0.588 0 0); /* text-zinc-400 */ - --foreground-muted: oklch(0.525 0 0); /* text-zinc-500 */ - - /* Card and popover backgrounds */ - --card: oklch(0.14 0 0); /* slightly lighter than background for contrast */ - --card-foreground: oklch(1 0 0); - --popover: oklch(0.10 0 0); /* slightly lighter than background */ - --popover-foreground: oklch(1 0 0); - - /* Brand colors - purple/violet theme */ - --primary: oklch(0.55 0.25 265); /* brand-500 */ - --primary-foreground: oklch(1 0 0); - --brand-400: oklch(0.6 0.22 265); - --brand-500: oklch(0.55 0.25 265); - --brand-600: oklch(0.5 0.28 270); /* purple-600 for gradients */ - - /* Glass morphism borders and accents */ - --secondary: oklch(1 0 0 / 0.05); /* bg-white/5 */ - --secondary-foreground: oklch(1 0 0); - --muted: oklch(0.176 0 0); /* zinc-800 */ - --muted-foreground: oklch(0.588 0 0); /* text-zinc-400 */ - --accent: oklch(1 0 0 / 0.1); /* bg-white/10 for hover */ - --accent-foreground: oklch(1 0 0); - - /* Borders with transparency for glass effect */ - --border: oklch(0.176 0 0); /* zinc-800 */ - --border-glass: oklch(1 0 0 / 0.1); /* white/10 for glass morphism */ - --destructive: oklch(0.6 0.25 25); - --input: oklch(0.04 0 0 / 0.8); /* Semi-transparent dark */ - --ring: oklch(0.55 0.25 265); - - /* Chart colors with brand theme */ - --chart-1: oklch(0.55 0.25 265); - --chart-2: oklch(0.65 0.2 160); - --chart-3: oklch(0.75 0.2 70); - --chart-4: oklch(0.6 0.25 300); - --chart-5: oklch(0.6 0.25 20); - - /* Sidebar with glass morphism */ - --sidebar: oklch(0.04 0 0 / 0.5); /* zinc-950/50 with backdrop blur */ - --sidebar-foreground: oklch(1 0 0); - --sidebar-primary: oklch(0.55 0.25 265); - --sidebar-primary-foreground: oklch(1 0 0); - --sidebar-accent: oklch(1 0 0 / 0.05); /* bg-white/5 */ - --sidebar-accent-foreground: oklch(1 0 0); - --sidebar-border: oklch(1 0 0 / 0.1); /* white/10 for glass borders */ - --sidebar-ring: oklch(0.55 0.25 265); - - /* Action button colors */ - --action-view: oklch(0.6 0.25 265); /* Purple */ - --action-view-hover: oklch(0.55 0.27 270); - --action-followup: oklch(0.6 0.2 230); /* Blue */ - --action-followup-hover: oklch(0.55 0.22 230); - --action-commit: oklch(0.55 0.2 140); /* Green */ - --action-commit-hover: oklch(0.5 0.22 140); - --action-verify: oklch(0.55 0.2 140); /* Green */ - --action-verify-hover: oklch(0.5 0.22 140); - - /* Running indicator - Purple */ - --running-indicator: oklch(0.6 0.25 265); - --running-indicator-text: oklch(0.65 0.22 265); - - /* Status colors - Dark mode */ - --status-success: oklch(0.65 0.2 140); - --status-success-bg: oklch(0.65 0.2 140 / 0.2); - --status-warning: oklch(0.75 0.15 70); - --status-warning-bg: oklch(0.75 0.15 70 / 0.2); - --status-error: oklch(0.65 0.22 25); - --status-error-bg: oklch(0.65 0.22 25 / 0.2); - --status-info: oklch(0.65 0.2 230); - --status-info-bg: oklch(0.65 0.2 230 / 0.2); - --status-backlog: oklch(0.6 0 0); - --status-in-progress: oklch(0.75 0.15 70); - --status-waiting: oklch(0.7 0.18 50); - - /* Shadow tokens - darker for dark mode */ - --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3); - --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.2); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.3); -} - -.retro { - /* Retro / Cyberpunk Theme */ - --background: oklch(0 0 0); /* Pure Black */ - --background-50: oklch(0 0 0 / 0.5); - --background-80: oklch(0 0 0 / 0.8); - - /* Neon Green Text */ - --foreground: oklch(0.85 0.25 145); /* Neon Green */ - --foreground-secondary: oklch(0.7 0.2 145); - --foreground-muted: oklch(0.5 0.15 145); - - /* Hard Edges */ - --radius: 0px; - - /* UI Elements */ - --card: oklch(0 0 0); /* Black card */ - --card-foreground: oklch(0.85 0.25 145); - --popover: oklch(0.05 0.05 145); - --popover-foreground: oklch(0.85 0.25 145); - - --primary: oklch(0.85 0.25 145); /* Neon Green */ - --primary-foreground: oklch(0 0 0); /* Black text on green */ - - --brand-400: oklch(0.85 0.25 145); - --brand-500: oklch(0.85 0.25 145); - --brand-600: oklch(0.75 0.25 145); - - --secondary: oklch(0.1 0.1 145); /* Dark Green bg */ - --secondary-foreground: oklch(0.85 0.25 145); - - --muted: oklch(0.1 0.05 145); - --muted-foreground: oklch(0.5 0.15 145); - - --accent: oklch(0.2 0.2 145); /* Brighter green accent */ - --accent-foreground: oklch(0.85 0.25 145); - - --destructive: oklch(0.6 0.25 25); /* Keep red for destructive */ - - --border: oklch(0.3 0.15 145); /* Visible Green Border */ - --border-glass: oklch(0.85 0.25 145 / 0.3); - - --input: oklch(0.1 0.1 145); - --ring: oklch(0.85 0.25 145); - - /* Charts - various neons */ - --chart-1: oklch(0.85 0.25 145); /* Green */ - --chart-2: oklch(0.8 0.25 300); /* Purple Neon */ - --chart-3: oklch(0.8 0.25 200); /* Cyan Neon */ - --chart-4: oklch(0.8 0.25 60); /* Yellow Neon */ - --chart-5: oklch(0.8 0.25 20); /* Red Neon */ - - /* Sidebar */ - --sidebar: oklch(0 0 0); - --sidebar-foreground: oklch(0.85 0.25 145); - --sidebar-primary: oklch(0.85 0.25 145); - --sidebar-primary-foreground: oklch(0 0 0); - --sidebar-accent: oklch(0.1 0.1 145); - --sidebar-accent-foreground: oklch(0.85 0.25 145); - --sidebar-border: oklch(0.3 0.15 145); - --sidebar-ring: oklch(0.85 0.25 145); - - /* Fonts */ - --font-sans: var(--font-geist-mono); /* Force Mono everywhere */ - - /* Action button colors - All green neon for retro theme */ - --action-view: oklch(0.85 0.25 145); /* Neon Green */ - --action-view-hover: oklch(0.9 0.25 145); - --action-followup: oklch(0.85 0.25 145); /* Neon Green */ - --action-followup-hover: oklch(0.9 0.25 145); - --action-commit: oklch(0.85 0.25 145); /* Neon Green */ - --action-commit-hover: oklch(0.9 0.25 145); - --action-verify: oklch(0.85 0.25 145); /* Neon Green */ - --action-verify-hover: oklch(0.9 0.25 145); - - /* Running indicator - Neon Green for retro */ - --running-indicator: oklch(0.85 0.25 145); - --running-indicator-text: oklch(0.85 0.25 145); -} - -/* ======================================== - DRACULA THEME - Inspired by the popular Dracula VS Code theme - ======================================== */ -.dracula { - --background: oklch(0.18 0.02 280); /* #282a36 */ - --background-50: oklch(0.18 0.02 280 / 0.5); - --background-80: oklch(0.18 0.02 280 / 0.8); - - --foreground: oklch(0.95 0.01 280); /* #f8f8f2 */ - --foreground-secondary: oklch(0.7 0.05 280); - --foreground-muted: oklch(0.55 0.08 280); /* #6272a4 */ - - --card: oklch(0.22 0.02 280); /* #44475a */ - --card-foreground: oklch(0.95 0.01 280); - --popover: oklch(0.2 0.02 280); - --popover-foreground: oklch(0.95 0.01 280); - - --primary: oklch(0.7 0.2 320); /* #bd93f9 purple */ - --primary-foreground: oklch(0.18 0.02 280); - - --brand-400: oklch(0.75 0.2 320); - --brand-500: oklch(0.7 0.2 320); /* #bd93f9 */ - --brand-600: oklch(0.65 0.22 320); - - --secondary: oklch(0.28 0.03 280); /* #44475a */ - --secondary-foreground: oklch(0.95 0.01 280); - - --muted: oklch(0.28 0.03 280); - --muted-foreground: oklch(0.55 0.08 280); /* #6272a4 */ - - --accent: oklch(0.32 0.04 280); - --accent-foreground: oklch(0.95 0.01 280); - - --destructive: oklch(0.65 0.25 15); /* #ff5555 */ - - --border: oklch(0.35 0.05 280); - --border-glass: oklch(0.7 0.2 320 / 0.3); - - --input: oklch(0.22 0.02 280); - --ring: oklch(0.7 0.2 320); - - --chart-1: oklch(0.7 0.2 320); /* Purple */ - --chart-2: oklch(0.75 0.2 180); /* Cyan #8be9fd */ - --chart-3: oklch(0.8 0.2 130); /* Green #50fa7b */ - --chart-4: oklch(0.7 0.25 350); /* Pink #ff79c6 */ - --chart-5: oklch(0.85 0.2 90); /* Yellow #f1fa8c */ - - --sidebar: oklch(0.16 0.02 280); - --sidebar-foreground: oklch(0.95 0.01 280); - --sidebar-primary: oklch(0.7 0.2 320); - --sidebar-primary-foreground: oklch(0.18 0.02 280); - --sidebar-accent: oklch(0.28 0.03 280); - --sidebar-accent-foreground: oklch(0.95 0.01 280); - --sidebar-border: oklch(0.35 0.05 280); - --sidebar-ring: oklch(0.7 0.2 320); - - /* Action button colors - Dracula purple/pink theme */ - --action-view: oklch(0.7 0.2 320); /* Purple */ - --action-view-hover: oklch(0.65 0.22 320); - --action-followup: oklch(0.65 0.25 350); /* Pink */ - --action-followup-hover: oklch(0.6 0.27 350); - --action-commit: oklch(0.75 0.2 130); /* Green */ - --action-commit-hover: oklch(0.7 0.22 130); - --action-verify: oklch(0.75 0.2 130); /* Green */ - --action-verify-hover: oklch(0.7 0.22 130); - - /* Running indicator - Purple */ - --running-indicator: oklch(0.7 0.2 320); - --running-indicator-text: oklch(0.75 0.18 320); -} - -/* ======================================== - NORD THEME - Inspired by the Arctic, north-bluish color palette - ======================================== */ -.nord { - --background: oklch(0.23 0.02 240); /* #2e3440 */ - --background-50: oklch(0.23 0.02 240 / 0.5); - --background-80: oklch(0.23 0.02 240 / 0.8); - - --foreground: oklch(0.9 0.01 230); /* #eceff4 */ - --foreground-secondary: oklch(0.75 0.02 230); /* #d8dee9 */ - --foreground-muted: oklch(0.6 0.03 230); /* #4c566a */ - - --card: oklch(0.27 0.02 240); /* #3b4252 */ - --card-foreground: oklch(0.9 0.01 230); - --popover: oklch(0.25 0.02 240); - --popover-foreground: oklch(0.9 0.01 230); - - --primary: oklch(0.7 0.12 220); /* #88c0d0 frost */ - --primary-foreground: oklch(0.23 0.02 240); - - --brand-400: oklch(0.75 0.12 220); - --brand-500: oklch(0.7 0.12 220); /* #88c0d0 */ - --brand-600: oklch(0.65 0.14 220); /* #81a1c1 */ - - --secondary: oklch(0.31 0.02 240); /* #434c5e */ - --secondary-foreground: oklch(0.9 0.01 230); - - --muted: oklch(0.31 0.02 240); - --muted-foreground: oklch(0.55 0.03 230); - - --accent: oklch(0.35 0.03 240); /* #4c566a */ - --accent-foreground: oklch(0.9 0.01 230); - - --destructive: oklch(0.65 0.2 15); /* #bf616a */ - - --border: oklch(0.35 0.03 240); - --border-glass: oklch(0.7 0.12 220 / 0.3); - - --input: oklch(0.27 0.02 240); - --ring: oklch(0.7 0.12 220); - - --chart-1: oklch(0.7 0.12 220); /* Frost blue */ - --chart-2: oklch(0.65 0.14 220); /* #81a1c1 */ - --chart-3: oklch(0.7 0.15 140); /* #a3be8c green */ - --chart-4: oklch(0.7 0.2 320); /* #b48ead purple */ - --chart-5: oklch(0.75 0.15 70); /* #ebcb8b yellow */ - - --sidebar: oklch(0.21 0.02 240); - --sidebar-foreground: oklch(0.9 0.01 230); - --sidebar-primary: oklch(0.7 0.12 220); - --sidebar-primary-foreground: oklch(0.23 0.02 240); - --sidebar-accent: oklch(0.31 0.02 240); - --sidebar-accent-foreground: oklch(0.9 0.01 230); - --sidebar-border: oklch(0.35 0.03 240); - --sidebar-ring: oklch(0.7 0.12 220); - - /* Action button colors - Nord frost blue theme */ - --action-view: oklch(0.7 0.12 220); /* Frost blue */ - --action-view-hover: oklch(0.65 0.14 220); - --action-followup: oklch(0.65 0.14 220); /* Darker frost */ - --action-followup-hover: oklch(0.6 0.16 220); - --action-commit: oklch(0.7 0.15 140); /* Green */ - --action-commit-hover: oklch(0.65 0.17 140); - --action-verify: oklch(0.7 0.15 140); /* Green */ - --action-verify-hover: oklch(0.65 0.17 140); - - /* Running indicator - Frost blue */ - --running-indicator: oklch(0.7 0.12 220); - --running-indicator-text: oklch(0.75 0.1 220); -} - -/* ======================================== - MONOKAI THEME - The classic Monokai color scheme - ======================================== */ -.monokai { - --background: oklch(0.17 0.01 90); /* #272822 */ - --background-50: oklch(0.17 0.01 90 / 0.5); - --background-80: oklch(0.17 0.01 90 / 0.8); - - --foreground: oklch(0.95 0.02 100); /* #f8f8f2 */ - --foreground-secondary: oklch(0.8 0.02 100); - --foreground-muted: oklch(0.55 0.04 100); /* #75715e */ - - --card: oklch(0.22 0.01 90); /* #3e3d32 */ - --card-foreground: oklch(0.95 0.02 100); - --popover: oklch(0.2 0.01 90); - --popover-foreground: oklch(0.95 0.02 100); - - --primary: oklch(0.8 0.2 350); /* #f92672 pink */ - --primary-foreground: oklch(0.17 0.01 90); - - --brand-400: oklch(0.85 0.2 350); - --brand-500: oklch(0.8 0.2 350); /* #f92672 */ - --brand-600: oklch(0.75 0.22 350); - - --secondary: oklch(0.25 0.02 90); - --secondary-foreground: oklch(0.95 0.02 100); - - --muted: oklch(0.25 0.02 90); - --muted-foreground: oklch(0.55 0.04 100); - - --accent: oklch(0.3 0.02 90); - --accent-foreground: oklch(0.95 0.02 100); - - --destructive: oklch(0.65 0.25 15); /* red */ - - --border: oklch(0.35 0.03 90); - --border-glass: oklch(0.8 0.2 350 / 0.3); - - --input: oklch(0.22 0.01 90); - --ring: oklch(0.8 0.2 350); - - --chart-1: oklch(0.8 0.2 350); /* Pink #f92672 */ - --chart-2: oklch(0.85 0.2 90); /* Yellow #e6db74 */ - --chart-3: oklch(0.8 0.2 140); /* Green #a6e22e */ - --chart-4: oklch(0.75 0.2 200); /* Cyan #66d9ef */ - --chart-5: oklch(0.75 0.2 30); /* Orange #fd971f */ - - --sidebar: oklch(0.15 0.01 90); - --sidebar-foreground: oklch(0.95 0.02 100); - --sidebar-primary: oklch(0.8 0.2 350); - --sidebar-primary-foreground: oklch(0.17 0.01 90); - --sidebar-accent: oklch(0.25 0.02 90); - --sidebar-accent-foreground: oklch(0.95 0.02 100); - --sidebar-border: oklch(0.35 0.03 90); - --sidebar-ring: oklch(0.8 0.2 350); - - /* Action button colors - Monokai pink/yellow theme */ - --action-view: oklch(0.8 0.2 350); /* Pink */ - --action-view-hover: oklch(0.75 0.22 350); - --action-followup: oklch(0.75 0.2 200); /* Cyan */ - --action-followup-hover: oklch(0.7 0.22 200); - --action-commit: oklch(0.8 0.2 140); /* Green */ - --action-commit-hover: oklch(0.75 0.22 140); - --action-verify: oklch(0.8 0.2 140); /* Green */ - --action-verify-hover: oklch(0.75 0.22 140); - - /* Running indicator - Pink */ - --running-indicator: oklch(0.8 0.2 350); - --running-indicator-text: oklch(0.85 0.18 350); -} - -/* ======================================== - TOKYO NIGHT THEME - A clean dark theme celebrating Tokyo at night - ======================================== */ -.tokyonight { - --background: oklch(0.16 0.03 260); /* #1a1b26 */ - --background-50: oklch(0.16 0.03 260 / 0.5); - --background-80: oklch(0.16 0.03 260 / 0.8); - - --foreground: oklch(0.85 0.02 250); /* #a9b1d6 */ - --foreground-secondary: oklch(0.7 0.03 250); - --foreground-muted: oklch(0.5 0.04 250); /* #565f89 */ - - --card: oklch(0.2 0.03 260); /* #24283b */ - --card-foreground: oklch(0.85 0.02 250); - --popover: oklch(0.18 0.03 260); - --popover-foreground: oklch(0.85 0.02 250); - - --primary: oklch(0.7 0.18 280); /* #7aa2f7 blue */ - --primary-foreground: oklch(0.16 0.03 260); - - --brand-400: oklch(0.75 0.18 280); - --brand-500: oklch(0.7 0.18 280); /* #7aa2f7 */ - --brand-600: oklch(0.65 0.2 280); /* #7dcfff */ - - --secondary: oklch(0.24 0.03 260); /* #292e42 */ - --secondary-foreground: oklch(0.85 0.02 250); - - --muted: oklch(0.24 0.03 260); - --muted-foreground: oklch(0.5 0.04 250); - - --accent: oklch(0.28 0.04 260); - --accent-foreground: oklch(0.85 0.02 250); - - --destructive: oklch(0.65 0.2 15); /* #f7768e */ - - --border: oklch(0.32 0.04 260); - --border-glass: oklch(0.7 0.18 280 / 0.3); - - --input: oklch(0.2 0.03 260); - --ring: oklch(0.7 0.18 280); - - --chart-1: oklch(0.7 0.18 280); /* Blue #7aa2f7 */ - --chart-2: oklch(0.75 0.18 200); /* Cyan #7dcfff */ - --chart-3: oklch(0.75 0.18 140); /* Green #9ece6a */ - --chart-4: oklch(0.7 0.2 320); /* Magenta #bb9af7 */ - --chart-5: oklch(0.8 0.18 70); /* Yellow #e0af68 */ - - --sidebar: oklch(0.14 0.03 260); - --sidebar-foreground: oklch(0.85 0.02 250); - --sidebar-primary: oklch(0.7 0.18 280); - --sidebar-primary-foreground: oklch(0.16 0.03 260); - --sidebar-accent: oklch(0.24 0.03 260); - --sidebar-accent-foreground: oklch(0.85 0.02 250); - --sidebar-border: oklch(0.32 0.04 260); - --sidebar-ring: oklch(0.7 0.18 280); - - /* Action button colors - Tokyo Night blue/magenta theme */ - --action-view: oklch(0.7 0.18 280); /* Blue */ - --action-view-hover: oklch(0.65 0.2 280); - --action-followup: oklch(0.75 0.18 200); /* Cyan */ - --action-followup-hover: oklch(0.7 0.2 200); - --action-commit: oklch(0.75 0.18 140); /* Green */ - --action-commit-hover: oklch(0.7 0.2 140); - --action-verify: oklch(0.75 0.18 140); /* Green */ - --action-verify-hover: oklch(0.7 0.2 140); - - /* Running indicator - Blue */ - --running-indicator: oklch(0.7 0.18 280); - --running-indicator-text: oklch(0.75 0.16 280); -} - -/* ======================================== - SOLARIZED DARK THEME - The classic color scheme by Ethan Schoonover - ======================================== */ -.solarized { - --background: oklch(0.2 0.02 230); /* #002b36 base03 */ - --background-50: oklch(0.2 0.02 230 / 0.5); - --background-80: oklch(0.2 0.02 230 / 0.8); - - --foreground: oklch(0.75 0.02 90); /* #839496 base0 */ - --foreground-secondary: oklch(0.6 0.03 200); /* #657b83 base00 */ - --foreground-muted: oklch(0.5 0.04 200); /* #586e75 base01 */ - - --card: oklch(0.23 0.02 230); /* #073642 base02 */ - --card-foreground: oklch(0.75 0.02 90); - --popover: oklch(0.22 0.02 230); - --popover-foreground: oklch(0.75 0.02 90); - - --primary: oklch(0.65 0.15 220); /* #268bd2 blue */ - --primary-foreground: oklch(0.2 0.02 230); - - --brand-400: oklch(0.7 0.15 220); - --brand-500: oklch(0.65 0.15 220); /* #268bd2 */ - --brand-600: oklch(0.6 0.17 220); - - --secondary: oklch(0.25 0.02 230); - --secondary-foreground: oklch(0.75 0.02 90); - - --muted: oklch(0.25 0.02 230); - --muted-foreground: oklch(0.5 0.04 200); - - --accent: oklch(0.28 0.03 230); - --accent-foreground: oklch(0.75 0.02 90); - - --destructive: oklch(0.55 0.2 25); /* #dc322f red */ - - --border: oklch(0.35 0.03 230); - --border-glass: oklch(0.65 0.15 220 / 0.3); - - --input: oklch(0.23 0.02 230); - --ring: oklch(0.65 0.15 220); - - --chart-1: oklch(0.65 0.15 220); /* Blue */ - --chart-2: oklch(0.6 0.18 180); /* Cyan #2aa198 */ - --chart-3: oklch(0.65 0.2 140); /* Green #859900 */ - --chart-4: oklch(0.7 0.18 55); /* Yellow #b58900 */ - --chart-5: oklch(0.6 0.2 30); /* Orange #cb4b16 */ - - --sidebar: oklch(0.18 0.02 230); - --sidebar-foreground: oklch(0.75 0.02 90); - --sidebar-primary: oklch(0.65 0.15 220); - --sidebar-primary-foreground: oklch(0.2 0.02 230); - --sidebar-accent: oklch(0.25 0.02 230); - --sidebar-accent-foreground: oklch(0.75 0.02 90); - --sidebar-border: oklch(0.35 0.03 230); - --sidebar-ring: oklch(0.65 0.15 220); - - /* Action button colors - Solarized blue/cyan theme */ - --action-view: oklch(0.65 0.15 220); /* Blue */ - --action-view-hover: oklch(0.6 0.17 220); - --action-followup: oklch(0.6 0.18 180); /* Cyan */ - --action-followup-hover: oklch(0.55 0.2 180); - --action-commit: oklch(0.65 0.2 140); /* Green */ - --action-commit-hover: oklch(0.6 0.22 140); - --action-verify: oklch(0.65 0.2 140); /* Green */ - --action-verify-hover: oklch(0.6 0.22 140); - - /* Running indicator - Blue */ - --running-indicator: oklch(0.65 0.15 220); - --running-indicator-text: oklch(0.7 0.13 220); -} - -/* ======================================== - GRUVBOX THEME - Retro groove color scheme - ======================================== */ -.gruvbox { - --background: oklch(0.18 0.02 60); /* #282828 bg */ - --background-50: oklch(0.18 0.02 60 / 0.5); - --background-80: oklch(0.18 0.02 60 / 0.8); - - --foreground: oklch(0.85 0.05 85); /* #ebdbb2 fg */ - --foreground-secondary: oklch(0.7 0.04 85); /* #d5c4a1 */ - --foreground-muted: oklch(0.55 0.04 85); /* #928374 */ - - --card: oklch(0.22 0.02 60); /* #3c3836 bg1 */ - --card-foreground: oklch(0.85 0.05 85); - --popover: oklch(0.2 0.02 60); - --popover-foreground: oklch(0.85 0.05 85); - - --primary: oklch(0.7 0.18 55); /* #fabd2f yellow */ - --primary-foreground: oklch(0.18 0.02 60); - - --brand-400: oklch(0.75 0.18 55); - --brand-500: oklch(0.7 0.18 55); /* Yellow */ - --brand-600: oklch(0.65 0.2 55); - - --secondary: oklch(0.26 0.02 60); /* #504945 bg2 */ - --secondary-foreground: oklch(0.85 0.05 85); - - --muted: oklch(0.26 0.02 60); - --muted-foreground: oklch(0.55 0.04 85); - - --accent: oklch(0.3 0.03 60); - --accent-foreground: oklch(0.85 0.05 85); - - --destructive: oklch(0.55 0.22 25); /* #fb4934 red */ - - --border: oklch(0.35 0.03 60); - --border-glass: oklch(0.7 0.18 55 / 0.3); - - --input: oklch(0.22 0.02 60); - --ring: oklch(0.7 0.18 55); - - --chart-1: oklch(0.7 0.18 55); /* Yellow */ - --chart-2: oklch(0.65 0.2 140); /* Green #b8bb26 */ - --chart-3: oklch(0.7 0.15 200); /* Aqua #8ec07c */ - --chart-4: oklch(0.6 0.2 30); /* Orange #fe8019 */ - --chart-5: oklch(0.6 0.2 320); /* Purple #d3869b */ - - --sidebar: oklch(0.16 0.02 60); - --sidebar-foreground: oklch(0.85 0.05 85); - --sidebar-primary: oklch(0.7 0.18 55); - --sidebar-primary-foreground: oklch(0.18 0.02 60); - --sidebar-accent: oklch(0.26 0.02 60); - --sidebar-accent-foreground: oklch(0.85 0.05 85); - --sidebar-border: oklch(0.35 0.03 60); - --sidebar-ring: oklch(0.7 0.18 55); - - /* Action button colors - Gruvbox yellow/orange theme */ - --action-view: oklch(0.7 0.18 55); /* Yellow */ - --action-view-hover: oklch(0.65 0.2 55); - --action-followup: oklch(0.7 0.15 200); /* Aqua */ - --action-followup-hover: oklch(0.65 0.17 200); - --action-commit: oklch(0.65 0.2 140); /* Green */ - --action-commit-hover: oklch(0.6 0.22 140); - --action-verify: oklch(0.65 0.2 140); /* Green */ - --action-verify-hover: oklch(0.6 0.22 140); - - /* Running indicator - Yellow */ - --running-indicator: oklch(0.7 0.18 55); - --running-indicator-text: oklch(0.75 0.16 55); -} - -/* ======================================== - CATPPUCCIN MOCHA THEME - Soothing pastel theme for the high-spirited - ======================================== */ -.catppuccin { - --background: oklch(0.18 0.02 260); /* #1e1e2e base */ - --background-50: oklch(0.18 0.02 260 / 0.5); - --background-80: oklch(0.18 0.02 260 / 0.8); - - --foreground: oklch(0.9 0.01 280); /* #cdd6f4 text */ - --foreground-secondary: oklch(0.75 0.02 280); /* #bac2de subtext1 */ - --foreground-muted: oklch(0.6 0.03 280); /* #a6adc8 subtext0 */ - - --card: oklch(0.22 0.02 260); /* #313244 surface0 */ - --card-foreground: oklch(0.9 0.01 280); - --popover: oklch(0.2 0.02 260); - --popover-foreground: oklch(0.9 0.01 280); - - --primary: oklch(0.75 0.15 280); /* #cba6f7 mauve */ - --primary-foreground: oklch(0.18 0.02 260); - - --brand-400: oklch(0.8 0.15 280); - --brand-500: oklch(0.75 0.15 280); /* Mauve */ - --brand-600: oklch(0.7 0.17 280); - - --secondary: oklch(0.26 0.02 260); /* #45475a surface1 */ - --secondary-foreground: oklch(0.9 0.01 280); - - --muted: oklch(0.26 0.02 260); - --muted-foreground: oklch(0.6 0.03 280); - - --accent: oklch(0.3 0.03 260); /* #585b70 surface2 */ - --accent-foreground: oklch(0.9 0.01 280); - - --destructive: oklch(0.65 0.2 15); /* #f38ba8 red */ - - --border: oklch(0.35 0.03 260); - --border-glass: oklch(0.75 0.15 280 / 0.3); - - --input: oklch(0.22 0.02 260); - --ring: oklch(0.75 0.15 280); - - --chart-1: oklch(0.75 0.15 280); /* Mauve */ - --chart-2: oklch(0.75 0.15 220); /* Blue #89b4fa */ - --chart-3: oklch(0.8 0.15 160); /* Green #a6e3a1 */ - --chart-4: oklch(0.8 0.15 350); /* Pink #f5c2e7 */ - --chart-5: oklch(0.85 0.12 90); /* Yellow #f9e2af */ - - --sidebar: oklch(0.16 0.02 260); /* #181825 mantle */ - --sidebar-foreground: oklch(0.9 0.01 280); - --sidebar-primary: oklch(0.75 0.15 280); - --sidebar-primary-foreground: oklch(0.18 0.02 260); - --sidebar-accent: oklch(0.26 0.02 260); - --sidebar-accent-foreground: oklch(0.9 0.01 280); - --sidebar-border: oklch(0.35 0.03 260); - --sidebar-ring: oklch(0.75 0.15 280); - - /* Action button colors - Catppuccin mauve/pink theme */ - --action-view: oklch(0.75 0.15 280); /* Mauve */ - --action-view-hover: oklch(0.7 0.17 280); - --action-followup: oklch(0.75 0.15 220); /* Blue */ - --action-followup-hover: oklch(0.7 0.17 220); - --action-commit: oklch(0.8 0.15 160); /* Green */ - --action-commit-hover: oklch(0.75 0.17 160); - --action-verify: oklch(0.8 0.15 160); /* Green */ - --action-verify-hover: oklch(0.75 0.17 160); - - /* Running indicator - Mauve */ - --running-indicator: oklch(0.75 0.15 280); - --running-indicator-text: oklch(0.8 0.13 280); -} - -/* ======================================== - ONE DARK THEME - Atom's iconic One Dark theme - ======================================== */ -.onedark { - --background: oklch(0.19 0.01 250); /* #282c34 */ - --background-50: oklch(0.19 0.01 250 / 0.5); - --background-80: oklch(0.19 0.01 250 / 0.8); - - --foreground: oklch(0.85 0.02 240); /* #abb2bf */ - --foreground-secondary: oklch(0.7 0.02 240); - --foreground-muted: oklch(0.5 0.03 240); /* #5c6370 */ - - --card: oklch(0.23 0.01 250); /* #21252b */ - --card-foreground: oklch(0.85 0.02 240); - --popover: oklch(0.21 0.01 250); - --popover-foreground: oklch(0.85 0.02 240); - - --primary: oklch(0.7 0.18 230); /* #61afef blue */ - --primary-foreground: oklch(0.19 0.01 250); - - --brand-400: oklch(0.75 0.18 230); - --brand-500: oklch(0.7 0.18 230); /* Blue */ - --brand-600: oklch(0.65 0.2 230); - - --secondary: oklch(0.25 0.01 250); - --secondary-foreground: oklch(0.85 0.02 240); - - --muted: oklch(0.25 0.01 250); - --muted-foreground: oklch(0.5 0.03 240); - - --accent: oklch(0.28 0.02 250); - --accent-foreground: oklch(0.85 0.02 240); - - --destructive: oklch(0.6 0.2 20); /* #e06c75 red */ - - --border: oklch(0.35 0.02 250); - --border-glass: oklch(0.7 0.18 230 / 0.3); - - --input: oklch(0.23 0.01 250); - --ring: oklch(0.7 0.18 230); - - --chart-1: oklch(0.7 0.18 230); /* Blue */ - --chart-2: oklch(0.75 0.15 320); /* Magenta #c678dd */ - --chart-3: oklch(0.75 0.18 150); /* Green #98c379 */ - --chart-4: oklch(0.8 0.15 80); /* Yellow #e5c07b */ - --chart-5: oklch(0.7 0.15 180); /* Cyan #56b6c2 */ - - --sidebar: oklch(0.17 0.01 250); - --sidebar-foreground: oklch(0.85 0.02 240); - --sidebar-primary: oklch(0.7 0.18 230); - --sidebar-primary-foreground: oklch(0.19 0.01 250); - --sidebar-accent: oklch(0.25 0.01 250); - --sidebar-accent-foreground: oklch(0.85 0.02 240); - --sidebar-border: oklch(0.35 0.02 250); - --sidebar-ring: oklch(0.7 0.18 230); - - /* Action button colors - One Dark blue/magenta theme */ - --action-view: oklch(0.7 0.18 230); /* Blue */ - --action-view-hover: oklch(0.65 0.2 230); - --action-followup: oklch(0.75 0.15 320); /* Magenta */ - --action-followup-hover: oklch(0.7 0.17 320); - --action-commit: oklch(0.75 0.18 150); /* Green */ - --action-commit-hover: oklch(0.7 0.2 150); - --action-verify: oklch(0.75 0.18 150); /* Green */ - --action-verify-hover: oklch(0.7 0.2 150); - - /* Running indicator - Blue */ - --running-indicator: oklch(0.7 0.18 230); - --running-indicator-text: oklch(0.75 0.16 230); -} - -/* ======================================== - SYNTHWAVE '84 THEME - Neon dreams of the 80s - ======================================== */ -.synthwave { - --background: oklch(0.15 0.05 290); /* #262335 */ - --background-50: oklch(0.15 0.05 290 / 0.5); - --background-80: oklch(0.15 0.05 290 / 0.8); - - --foreground: oklch(0.95 0.02 320); /* #ffffff with warm tint */ - --foreground-secondary: oklch(0.75 0.05 320); - --foreground-muted: oklch(0.55 0.08 290); - - --card: oklch(0.2 0.06 290); /* #34294f */ - --card-foreground: oklch(0.95 0.02 320); - --popover: oklch(0.18 0.05 290); - --popover-foreground: oklch(0.95 0.02 320); - - --primary: oklch(0.7 0.28 350); /* #f97e72 hot pink */ - --primary-foreground: oklch(0.15 0.05 290); - - --brand-400: oklch(0.75 0.28 350); - --brand-500: oklch(0.7 0.28 350); /* Hot pink */ - --brand-600: oklch(0.65 0.3 350); - - --secondary: oklch(0.25 0.07 290); - --secondary-foreground: oklch(0.95 0.02 320); - - --muted: oklch(0.25 0.07 290); - --muted-foreground: oklch(0.55 0.08 290); - - --accent: oklch(0.3 0.08 290); - --accent-foreground: oklch(0.95 0.02 320); - - --destructive: oklch(0.6 0.25 15); - - --border: oklch(0.4 0.1 290); - --border-glass: oklch(0.7 0.28 350 / 0.3); - - --input: oklch(0.2 0.06 290); - --ring: oklch(0.7 0.28 350); - - --chart-1: oklch(0.7 0.28 350); /* Hot pink */ - --chart-2: oklch(0.8 0.25 200); /* Cyan #72f1b8 */ - --chart-3: oklch(0.85 0.2 60); /* Yellow #fede5d */ - --chart-4: oklch(0.7 0.25 280); /* Purple #ff7edb */ - --chart-5: oklch(0.7 0.2 30); /* Orange #f97e72 */ - - --sidebar: oklch(0.13 0.05 290); - --sidebar-foreground: oklch(0.95 0.02 320); - --sidebar-primary: oklch(0.7 0.28 350); - --sidebar-primary-foreground: oklch(0.15 0.05 290); - --sidebar-accent: oklch(0.25 0.07 290); - --sidebar-accent-foreground: oklch(0.95 0.02 320); - --sidebar-border: oklch(0.4 0.1 290); - --sidebar-ring: oklch(0.7 0.28 350); - - /* Action button colors - Synthwave hot pink/cyan theme */ - --action-view: oklch(0.7 0.28 350); /* Hot pink */ - --action-view-hover: oklch(0.65 0.3 350); - --action-followup: oklch(0.8 0.25 200); /* Cyan */ - --action-followup-hover: oklch(0.75 0.27 200); - --action-commit: oklch(0.85 0.2 60); /* Yellow */ - --action-commit-hover: oklch(0.8 0.22 60); - --action-verify: oklch(0.85 0.2 60); /* Yellow */ - --action-verify-hover: oklch(0.8 0.22 60); - - /* Running indicator - Hot pink */ - --running-indicator: oklch(0.7 0.28 350); - --running-indicator-text: oklch(0.75 0.26 350); -} - -/* Red Theme - Bold crimson/red aesthetic */ -.red { - --background: oklch(0.12 0.03 15); /* Deep dark red-tinted black */ - --background-50: oklch(0.12 0.03 15 / 0.5); - --background-80: oklch(0.12 0.03 15 / 0.8); - - --foreground: oklch(0.95 0.01 15); /* Off-white with warm tint */ - --foreground-secondary: oklch(0.7 0.02 15); - --foreground-muted: oklch(0.5 0.03 15); - - --card: oklch(0.18 0.04 15); /* Slightly lighter dark red */ - --card-foreground: oklch(0.95 0.01 15); - --popover: oklch(0.15 0.035 15); - --popover-foreground: oklch(0.95 0.01 15); - - --primary: oklch(0.55 0.25 25); /* Vibrant crimson red */ - --primary-foreground: oklch(0.98 0 0); - - --brand-400: oklch(0.6 0.23 25); - --brand-500: oklch(0.55 0.25 25); /* Crimson */ - --brand-600: oklch(0.5 0.27 25); - - --secondary: oklch(0.22 0.05 15); - --secondary-foreground: oklch(0.95 0.01 15); - - --muted: oklch(0.22 0.05 15); - --muted-foreground: oklch(0.5 0.03 15); - - --accent: oklch(0.28 0.06 15); - --accent-foreground: oklch(0.95 0.01 15); - - --destructive: oklch(0.6 0.28 30); /* Bright orange-red for destructive */ - - --border: oklch(0.35 0.08 15); - --border-glass: oklch(0.55 0.25 25 / 0.3); - - --input: oklch(0.18 0.04 15); - --ring: oklch(0.55 0.25 25); - - --chart-1: oklch(0.55 0.25 25); /* Crimson */ - --chart-2: oklch(0.7 0.2 50); /* Orange */ - --chart-3: oklch(0.8 0.18 80); /* Gold */ - --chart-4: oklch(0.6 0.22 0); /* Pure red */ - --chart-5: oklch(0.65 0.2 350); /* Pink-red */ - - --sidebar: oklch(0.1 0.025 15); - --sidebar-foreground: oklch(0.95 0.01 15); - --sidebar-primary: oklch(0.55 0.25 25); - --sidebar-primary-foreground: oklch(0.98 0 0); - --sidebar-accent: oklch(0.22 0.05 15); - --sidebar-accent-foreground: oklch(0.95 0.01 15); - --sidebar-border: oklch(0.35 0.08 15); - --sidebar-ring: oklch(0.55 0.25 25); - - /* Action button colors - Red theme */ - --action-view: oklch(0.55 0.25 25); /* Crimson */ - --action-view-hover: oklch(0.5 0.27 25); - --action-followup: oklch(0.7 0.2 50); /* Orange */ - --action-followup-hover: oklch(0.65 0.22 50); - --action-commit: oklch(0.6 0.2 140); /* Green for positive actions */ - --action-commit-hover: oklch(0.55 0.22 140); - --action-verify: oklch(0.6 0.2 140); /* Green */ - --action-verify-hover: oklch(0.55 0.22 140); - - /* Running indicator - Crimson */ - --running-indicator: oklch(0.55 0.25 25); - --running-indicator-text: oklch(0.6 0.23 25); -} - -.cream { - /* Cream Theme - Warm, soft, easy on the eyes */ - --background: oklch(0.95 0.01 70); /* Warm cream background */ - --background-50: oklch(0.95 0.01 70 / 0.5); - --background-80: oklch(0.95 0.01 70 / 0.8); - - --foreground: oklch(0.25 0.02 60); /* Dark warm brown */ - --foreground-secondary: oklch(0.45 0.02 60); /* Medium brown */ - --foreground-muted: oklch(0.55 0.02 60); /* Light brown */ - - --card: oklch(0.98 0.005 70); /* Slightly lighter cream */ - --card-foreground: oklch(0.25 0.02 60); - --popover: oklch(0.97 0.008 70); - --popover-foreground: oklch(0.25 0.02 60); - - --primary: oklch(0.5 0.12 45); /* Warm terracotta/rust */ - --primary-foreground: oklch(0.98 0.005 70); - - --brand-400: oklch(0.55 0.12 45); - --brand-500: oklch(0.5 0.12 45); /* Terracotta */ - --brand-600: oklch(0.45 0.13 45); - - --secondary: oklch(0.88 0.02 70); - --secondary-foreground: oklch(0.25 0.02 60); - - --muted: oklch(0.9 0.015 70); - --muted-foreground: oklch(0.45 0.02 60); - - --accent: oklch(0.85 0.025 70); - --accent-foreground: oklch(0.25 0.02 60); - - --destructive: oklch(0.55 0.22 25); /* Warm red */ - - --border: oklch(0.85 0.015 70); - --border-glass: oklch(0.5 0.12 45 / 0.2); - - --input: oklch(0.98 0.005 70); - --ring: oklch(0.5 0.12 45); - - --chart-1: oklch(0.5 0.12 45); /* Terracotta */ - --chart-2: oklch(0.55 0.15 35); /* Burnt orange */ - --chart-3: oklch(0.6 0.12 100); /* Olive */ - --chart-4: oklch(0.5 0.15 20); /* Deep rust */ - --chart-5: oklch(0.65 0.1 80); /* Golden */ - - --sidebar: oklch(0.93 0.012 70); - --sidebar-foreground: oklch(0.25 0.02 60); - --sidebar-primary: oklch(0.5 0.12 45); - --sidebar-primary-foreground: oklch(0.98 0.005 70); - --sidebar-accent: oklch(0.88 0.02 70); - --sidebar-accent-foreground: oklch(0.25 0.02 60); - --sidebar-border: oklch(0.85 0.015 70); - --sidebar-ring: oklch(0.5 0.12 45); - - /* Action button colors - Warm earth tones */ - --action-view: oklch(0.5 0.12 45); /* Terracotta */ - --action-view-hover: oklch(0.45 0.13 45); - --action-followup: oklch(0.55 0.15 35); /* Burnt orange */ - --action-followup-hover: oklch(0.5 0.16 35); - --action-commit: oklch(0.55 0.12 130); /* Sage green */ - --action-commit-hover: oklch(0.5 0.13 130); - --action-verify: oklch(0.55 0.12 130); /* Sage green */ - --action-verify-hover: oklch(0.5 0.13 130); - - /* Running indicator - Terracotta */ - --running-indicator: oklch(0.5 0.12 45); - --running-indicator-text: oklch(0.55 0.12 45); - - /* Status colors - Cream theme */ - --status-success: oklch(0.55 0.15 130); - --status-success-bg: oklch(0.55 0.15 130 / 0.15); - --status-warning: oklch(0.6 0.15 70); - --status-warning-bg: oklch(0.6 0.15 70 / 0.15); - --status-error: oklch(0.55 0.22 25); - --status-error-bg: oklch(0.55 0.22 25 / 0.15); - --status-info: oklch(0.5 0.15 230); - --status-info-bg: oklch(0.5 0.15 230 / 0.15); - --status-backlog: oklch(0.6 0.02 60); - --status-in-progress: oklch(0.6 0.15 70); - --status-waiting: oklch(0.58 0.13 50); -} - -.sunset { - /* Sunset Theme - Mellow oranges and soft purples */ - --background: oklch(0.15 0.02 280); /* Deep twilight blue-purple */ - --background-50: oklch(0.15 0.02 280 / 0.5); - --background-80: oklch(0.15 0.02 280 / 0.8); - - --foreground: oklch(0.95 0.01 80); /* Warm white */ - --foreground-secondary: oklch(0.75 0.02 60); - --foreground-muted: oklch(0.6 0.02 60); - - --card: oklch(0.2 0.025 280); - --card-foreground: oklch(0.95 0.01 80); - --popover: oklch(0.18 0.02 280); - --popover-foreground: oklch(0.95 0.01 80); - - --primary: oklch(0.68 0.18 45); /* Mellow sunset orange */ - --primary-foreground: oklch(0.15 0.02 280); - - --brand-400: oklch(0.72 0.17 45); - --brand-500: oklch(0.68 0.18 45); /* Soft sunset orange */ - --brand-600: oklch(0.64 0.19 42); - - --secondary: oklch(0.25 0.03 280); - --secondary-foreground: oklch(0.95 0.01 80); - - --muted: oklch(0.27 0.03 280); - --muted-foreground: oklch(0.6 0.02 60); - - --accent: oklch(0.35 0.04 310); - --accent-foreground: oklch(0.95 0.01 80); - - --destructive: oklch(0.6 0.2 25); /* Muted red */ - - --border: oklch(0.32 0.04 280); - --border-glass: oklch(0.68 0.18 45 / 0.3); - - --input: oklch(0.2 0.025 280); - --ring: oklch(0.68 0.18 45); - - --chart-1: oklch(0.68 0.18 45); /* Mellow orange */ - --chart-2: oklch(0.75 0.16 340); /* Soft pink sunset */ - --chart-3: oklch(0.78 0.18 70); /* Soft golden */ - --chart-4: oklch(0.66 0.19 42); /* Subtle coral */ - --chart-5: oklch(0.72 0.14 310); /* Pastel purple */ - - --sidebar: oklch(0.13 0.015 280); - --sidebar-foreground: oklch(0.95 0.01 80); - --sidebar-primary: oklch(0.68 0.18 45); - --sidebar-primary-foreground: oklch(0.15 0.02 280); - --sidebar-accent: oklch(0.25 0.03 280); - --sidebar-accent-foreground: oklch(0.95 0.01 80); - --sidebar-border: oklch(0.32 0.04 280); - --sidebar-ring: oklch(0.68 0.18 45); - - /* Action button colors - Mellow sunset palette */ - --action-view: oklch(0.68 0.18 45); /* Mellow orange */ - --action-view-hover: oklch(0.64 0.19 42); - --action-followup: oklch(0.75 0.16 340); /* Soft pink */ - --action-followup-hover: oklch(0.7 0.17 340); - --action-commit: oklch(0.65 0.16 140); /* Soft green */ - --action-commit-hover: oklch(0.6 0.17 140); - --action-verify: oklch(0.65 0.16 140); /* Soft green */ - --action-verify-hover: oklch(0.6 0.17 140); - - /* Running indicator - Mellow orange */ - --running-indicator: oklch(0.68 0.18 45); - --running-indicator-text: oklch(0.72 0.17 45); - - /* Status colors - Sunset theme */ - --status-success: oklch(0.65 0.16 140); - --status-success-bg: oklch(0.65 0.16 140 / 0.2); - --status-warning: oklch(0.78 0.18 70); - --status-warning-bg: oklch(0.78 0.18 70 / 0.2); - --status-error: oklch(0.65 0.2 25); - --status-error-bg: oklch(0.65 0.2 25 / 0.2); - --status-info: oklch(0.75 0.16 340); - --status-info-bg: oklch(0.75 0.16 340 / 0.2); - --status-backlog: oklch(0.65 0.02 280); - --status-in-progress: oklch(0.78 0.18 70); - --status-waiting: oklch(0.72 0.17 60); -} - -.gray { - /* Gray Theme - Modern, minimal gray scheme inspired by Cursor */ - --background: oklch(0.2 0.005 250); /* Medium-dark neutral gray */ - --background-50: oklch(0.2 0.005 250 / 0.5); - --background-80: oklch(0.2 0.005 250 / 0.8); - - --foreground: oklch(0.9 0.005 250); /* Light gray */ - --foreground-secondary: oklch(0.65 0.005 250); - --foreground-muted: oklch(0.5 0.005 250); - - --card: oklch(0.24 0.005 250); - --card-foreground: oklch(0.9 0.005 250); - --popover: oklch(0.22 0.005 250); - --popover-foreground: oklch(0.9 0.005 250); - - --primary: oklch(0.6 0.08 250); /* Subtle blue-gray */ - --primary-foreground: oklch(0.95 0.005 250); - - --brand-400: oklch(0.65 0.08 250); - --brand-500: oklch(0.6 0.08 250); /* Blue-gray */ - --brand-600: oklch(0.55 0.09 250); - - --secondary: oklch(0.28 0.005 250); - --secondary-foreground: oklch(0.9 0.005 250); - - --muted: oklch(0.3 0.005 250); - --muted-foreground: oklch(0.6 0.005 250); - - --accent: oklch(0.35 0.01 250); - --accent-foreground: oklch(0.9 0.005 250); - - --destructive: oklch(0.6 0.2 25); /* Muted red */ - - --border: oklch(0.32 0.005 250); - --border-glass: oklch(0.6 0.08 250 / 0.2); - - --input: oklch(0.24 0.005 250); - --ring: oklch(0.6 0.08 250); - - --chart-1: oklch(0.6 0.08 250); /* Blue-gray */ - --chart-2: oklch(0.65 0.1 210); /* Cyan */ - --chart-3: oklch(0.7 0.12 160); /* Teal */ - --chart-4: oklch(0.65 0.1 280); /* Purple */ - --chart-5: oklch(0.7 0.08 300); /* Violet */ - - --sidebar: oklch(0.18 0.005 250); - --sidebar-foreground: oklch(0.9 0.005 250); - --sidebar-primary: oklch(0.6 0.08 250); - --sidebar-primary-foreground: oklch(0.95 0.005 250); - --sidebar-accent: oklch(0.28 0.005 250); - --sidebar-accent-foreground: oklch(0.9 0.005 250); - --sidebar-border: oklch(0.32 0.005 250); - --sidebar-ring: oklch(0.6 0.08 250); - - /* Action button colors - Subtle modern colors */ - --action-view: oklch(0.6 0.08 250); /* Blue-gray */ - --action-view-hover: oklch(0.55 0.09 250); - --action-followup: oklch(0.65 0.1 210); /* Cyan */ - --action-followup-hover: oklch(0.6 0.11 210); - --action-commit: oklch(0.65 0.12 150); /* Teal-green */ - --action-commit-hover: oklch(0.6 0.13 150); - --action-verify: oklch(0.65 0.12 150); /* Teal-green */ - --action-verify-hover: oklch(0.6 0.13 150); - - /* Running indicator - Blue-gray */ - --running-indicator: oklch(0.6 0.08 250); - --running-indicator-text: oklch(0.65 0.08 250); - - /* Status colors - Gray theme */ - --status-success: oklch(0.65 0.12 150); - --status-success-bg: oklch(0.65 0.12 150 / 0.2); - --status-warning: oklch(0.7 0.15 70); - --status-warning-bg: oklch(0.7 0.15 70 / 0.2); - --status-error: oklch(0.6 0.2 25); - --status-error-bg: oklch(0.6 0.2 25 / 0.2); - --status-info: oklch(0.65 0.1 210); - --status-info-bg: oklch(0.65 0.1 210 / 0.2); - --status-backlog: oklch(0.6 0.005 250); - --status-in-progress: oklch(0.7 0.15 70); - --status-waiting: oklch(0.68 0.1 220); -} @layer base { * { @@ -1545,62 +413,6 @@ background: oklch(0.15 0.05 25); } -/* Cream theme scrollbar */ -.cream ::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -.cream ::-webkit-scrollbar-thumb, -.cream .scrollbar-visible::-webkit-scrollbar-thumb { - background: oklch(0.7 0.03 60); - border-radius: 4px; -} - -.cream ::-webkit-scrollbar-thumb:hover, -.cream .scrollbar-visible::-webkit-scrollbar-thumb:hover { - background: oklch(0.6 0.04 60); -} - -.cream ::-webkit-scrollbar-track, -.cream .scrollbar-visible::-webkit-scrollbar-track { - background: oklch(0.9 0.015 70); -} - -/* Sunset theme scrollbar */ -.sunset ::-webkit-scrollbar-thumb, -.sunset .scrollbar-visible::-webkit-scrollbar-thumb { - background: oklch(0.5 0.14 45); - border-radius: 4px; -} - -.sunset ::-webkit-scrollbar-thumb:hover, -.sunset .scrollbar-visible::-webkit-scrollbar-thumb:hover { - background: oklch(0.58 0.16 45); -} - -.sunset ::-webkit-scrollbar-track, -.sunset .scrollbar-visible::-webkit-scrollbar-track { - background: oklch(0.18 0.03 280); -} - -/* Gray theme scrollbar */ -.gray ::-webkit-scrollbar-thumb, -.gray .scrollbar-visible::-webkit-scrollbar-thumb { - background: oklch(0.4 0.01 250); - border-radius: 4px; -} - -.gray ::-webkit-scrollbar-thumb:hover, -.gray .scrollbar-visible::-webkit-scrollbar-thumb:hover { - background: oklch(0.5 0.02 250); -} - -.gray ::-webkit-scrollbar-track, -.gray .scrollbar-visible::-webkit-scrollbar-track { - background: oklch(0.25 0.005 250); -} - /* Always visible scrollbar for file diffs and code blocks */ .scrollbar-visible { overflow-y: auto !important; @@ -1633,30 +445,6 @@ visibility: visible; } -/* Light mode scrollbar-visible adjustments */ -.light .scrollbar-visible::-webkit-scrollbar-track { - background: oklch(0.95 0 0); -} - -.light .scrollbar-visible::-webkit-scrollbar-thumb { - background: oklch(0.7 0 0); -} - -.light .scrollbar-visible::-webkit-scrollbar-thumb:hover { - background: oklch(0.6 0 0); -} - -/* Retro mode scrollbar-visible adjustments */ -.retro .scrollbar-visible::-webkit-scrollbar-thumb { - background: var(--primary); - border-radius: 0; -} - -.retro .scrollbar-visible::-webkit-scrollbar-track { - background: var(--background); - border-radius: 0; -} - /* Styled scrollbar for code blocks and log entries (horizontal/vertical) */ .scrollbar-styled { scrollbar-width: thin; @@ -1682,53 +470,6 @@ background: oklch(0.45 0 0); } -/* Light mode scrollbar-styled adjustments */ -.light .scrollbar-styled::-webkit-scrollbar-thumb { - background: oklch(0.75 0 0); -} - -.light .scrollbar-styled::-webkit-scrollbar-thumb:hover { - background: oklch(0.65 0 0); -} - -/* Cream theme scrollbar-styled */ -.cream .scrollbar-styled::-webkit-scrollbar-thumb { - background: oklch(0.7 0.03 60); -} - -.cream .scrollbar-styled::-webkit-scrollbar-thumb:hover { - background: oklch(0.6 0.04 60); -} - -/* Retro theme scrollbar-styled */ -.retro .scrollbar-styled::-webkit-scrollbar-thumb { - background: var(--primary); - border-radius: 0; -} - -.retro .scrollbar-styled::-webkit-scrollbar-track { - background: var(--background); - border-radius: 0; -} - -/* Sunset theme scrollbar-styled */ -.sunset .scrollbar-styled::-webkit-scrollbar-thumb { - background: oklch(0.5 0.14 45); -} - -.sunset .scrollbar-styled::-webkit-scrollbar-thumb:hover { - background: oklch(0.58 0.16 45); -} - -/* Gray theme scrollbar-styled */ -.gray .scrollbar-styled::-webkit-scrollbar-thumb { - background: oklch(0.4 0.01 250); -} - -.gray .scrollbar-styled::-webkit-scrollbar-thumb:hover { - background: oklch(0.5 0.02 250); -} - /* Glass morphism utilities */ @layer utilities { .glass { @@ -1778,13 +519,7 @@ -webkit-backdrop-filter: blur(12px); } - .light .bg-glass { - background: oklch(1 0 0 / 0.8); - } - .light .bg-glass-80 { - background: oklch(1 0 0 / 0.95); - } /* Hover state utilities */ .hover-glass { @@ -1808,13 +543,7 @@ background: var(--background); } - .light .content-bg { - background: linear-gradient(135deg, oklch(0.99 0 0), oklch(0.98 0 0), oklch(0.99 0 0)); - } - .dark .content-bg { - background: linear-gradient(135deg, oklch(0.04 0 0), oklch(0.08 0 0), oklch(0.04 0 0)); - } /* Action button utilities */ .bg-action-view { @@ -1902,28 +631,8 @@ } /* Retro Overrides for Utilities */ -.retro .glass, -.retro .glass-subtle, -.retro .glass-strong, -.retro .bg-glass, -.retro .bg-glass-80 { - backdrop-filter: none; - background: var(--background); - border: 1px solid var(--border); -} -.retro .gradient-brand { - background: var(--primary); - color: var(--primary-foreground); -} -.retro .content-bg { - background: - linear-gradient(rgba(0, 255, 65, 0.03) 1px, transparent 1px), - linear-gradient(90deg, rgba(0, 255, 65, 0.03) 1px, transparent 1px), - var(--background); - background-size: 20px 20px; -} .retro * { border-radius: 0 !important; @@ -1936,41 +645,14 @@ } /* Light mode - deeper purple to blue gradient for better visibility */ -.light .animated-outline-gradient { - background: conic-gradient(from 90deg at 50% 50%, #7c3aed 0%, #2563eb 50%, #7c3aed 100%); -} -.light .animated-outline-inner { - background: oklch(100% 0 0) !important; - color: #7c3aed !important; - border: 1px solid oklch(92% 0 0); -} -.light [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { - background: oklch(97% 0.02 270) !important; - color: #5b21b6 !important; -} /* Dark mode - purple to blue gradient */ -.dark .animated-outline-gradient { - background: conic-gradient(from 90deg at 50% 50%, #a855f7 0%, #3b82f6 50%, #a855f7 100%); -} -.dark .animated-outline-inner { - background: oklch(0.15 0 0) !important; - color: #c084fc !important; -} -.dark [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { - background: oklch(0.2 0.02 270) !important; - color: #e9d5ff !important; -} /* Retro mode - unique scanline + neon effect */ -.retro .animated-outline-gradient { - background: conic-gradient(from 90deg at 50% 50%, #00ff41 0%, #00ffff 25%, #ff00ff 50%, #00ffff 75%, #00ff41 100%); - animation: spin 2s linear infinite, retro-glow 1s ease-in-out infinite alternate; -} @keyframes retro-glow { from { @@ -1981,155 +663,42 @@ } } -.retro [data-slot="button"][class*="animated-outline"] { - border-radius: 0 !important; -} -.retro .animated-outline-inner { - background: oklch(0 0 0) !important; - color: #00ff41 !important; - border-radius: 0 !important; - text-shadow: 0 0 5px #00ff41; - font-family: var(--font-geist-mono), monospace; - text-transform: uppercase; - letter-spacing: 0.1em; -} -.retro [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { - background: oklch(0.1 0.1 145) !important; - color: #00ff41 !important; - box-shadow: - 0 0 10px #00ff41, - 0 0 20px #00ff41, - inset 0 0 10px rgba(0, 255, 65, 0.1); - text-shadow: 0 0 10px #00ff41, 0 0 20px #00ff41; -} /* Dracula animated-outline - purple/pink */ -.dracula .animated-outline-gradient { - background: conic-gradient(from 90deg at 50% 50%, #bd93f9 0%, #ff79c6 50%, #bd93f9 100%); -} -.dracula .animated-outline-inner { - background: oklch(0.18 0.02 280) !important; - color: #bd93f9 !important; -} -.dracula [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { - background: oklch(0.24 0.03 280) !important; - color: #ff79c6 !important; -} /* Nord animated-outline - frost blue */ -.nord .animated-outline-gradient { - background: conic-gradient(from 90deg at 50% 50%, #88c0d0 0%, #81a1c1 50%, #88c0d0 100%); -} -.nord .animated-outline-inner { - background: oklch(0.23 0.02 240) !important; - color: #88c0d0 !important; -} -.nord [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { - background: oklch(0.28 0.03 240) !important; - color: #8fbcbb !important; -} /* Monokai animated-outline - pink/yellow */ -.monokai .animated-outline-gradient { - background: conic-gradient(from 90deg at 50% 50%, #f92672 0%, #e6db74 50%, #f92672 100%); -} -.monokai .animated-outline-inner { - background: oklch(0.17 0.01 90) !important; - color: #f92672 !important; -} -.monokai [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { - background: oklch(0.22 0.02 90) !important; - color: #e6db74 !important; -} /* Tokyo Night animated-outline - blue/magenta */ -.tokyonight .animated-outline-gradient { - background: conic-gradient(from 90deg at 50% 50%, #7aa2f7 0%, #bb9af7 50%, #7aa2f7 100%); -} -.tokyonight .animated-outline-inner { - background: oklch(0.16 0.03 260) !important; - color: #7aa2f7 !important; -} -.tokyonight [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { - background: oklch(0.22 0.04 260) !important; - color: #bb9af7 !important; -} /* Solarized animated-outline - blue/cyan */ -.solarized .animated-outline-gradient { - background: conic-gradient(from 90deg at 50% 50%, #268bd2 0%, #2aa198 50%, #268bd2 100%); -} -.solarized .animated-outline-inner { - background: oklch(0.2 0.02 230) !important; - color: #268bd2 !important; -} -.solarized [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { - background: oklch(0.25 0.03 230) !important; - color: #2aa198 !important; -} /* Gruvbox animated-outline - yellow/orange */ -.gruvbox .animated-outline-gradient { - background: conic-gradient(from 90deg at 50% 50%, #fabd2f 0%, #fe8019 50%, #fabd2f 100%); -} -.gruvbox .animated-outline-inner { - background: oklch(0.18 0.02 60) !important; - color: #fabd2f !important; -} -.gruvbox [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { - background: oklch(0.24 0.03 60) !important; - color: #fe8019 !important; -} /* Catppuccin animated-outline - mauve/pink */ -.catppuccin .animated-outline-gradient { - background: conic-gradient(from 90deg at 50% 50%, #cba6f7 0%, #f5c2e7 50%, #cba6f7 100%); -} -.catppuccin .animated-outline-inner { - background: oklch(0.18 0.02 260) !important; - color: #cba6f7 !important; -} -.catppuccin [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { - background: oklch(0.24 0.03 260) !important; - color: #f5c2e7 !important; -} /* One Dark animated-outline - blue/magenta */ -.onedark .animated-outline-gradient { - background: conic-gradient(from 90deg at 50% 50%, #61afef 0%, #c678dd 50%, #61afef 100%); -} -.onedark .animated-outline-inner { - background: oklch(0.19 0.01 250) !important; - color: #61afef !important; -} -.onedark [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { - background: oklch(0.25 0.02 250) !important; - color: #c678dd !important; -} /* Synthwave animated-outline - hot pink/cyan with glow */ -.synthwave .animated-outline-gradient { - background: conic-gradient(from 90deg at 50% 50%, #f97e72 0%, #72f1b8 25%, #ff7edb 50%, #72f1b8 75%, #f97e72 100%); - animation: spin 2s linear infinite, synthwave-glow 1.5s ease-in-out infinite alternate; -} @keyframes synthwave-glow { from { @@ -2140,197 +709,54 @@ } } -.synthwave .animated-outline-inner { - background: oklch(0.15 0.05 290) !important; - color: #f97e72 !important; - text-shadow: 0 0 8px #f97e72; -} -.synthwave [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { - background: oklch(0.22 0.07 290) !important; - color: #72f1b8 !important; - text-shadow: 0 0 12px #72f1b8; - box-shadow: 0 0 15px rgba(114, 241, 184, 0.3); -} /* Slider Theme Styles */ -.light .slider-track { - background: oklch(90% 0 0); -} -.light .slider-range { - background: linear-gradient(to right, #7c3aed, #2563eb); -} -.light .slider-thumb { - background: oklch(100% 0 0); - border-color: oklch(80% 0 0); -} -.dark .slider-track { - background: oklch(0.2 0 0); -} -.dark .slider-range { - background: linear-gradient(to right, #a855f7, #3b82f6); -} -.dark .slider-thumb { - background: oklch(0.25 0 0); - border-color: oklch(0.4 0 0); -} -.retro .slider-track { - background: oklch(0.15 0.05 145); - border: 1px solid #00ff41; - border-radius: 0 !important; -} -.retro .slider-range { - background: #00ff41; - box-shadow: 0 0 10px #00ff41, 0 0 5px #00ff41; - border-radius: 0 !important; -} -.retro .slider-thumb { - background: oklch(0 0 0); - border: 2px solid #00ff41; - border-radius: 0 !important; - box-shadow: 0 0 8px #00ff41; -} -.retro .slider-thumb:hover { - background: oklch(0.1 0.1 145); - box-shadow: 0 0 12px #00ff41, 0 0 20px #00ff41; -} /* Dracula slider */ -.dracula .slider-track { - background: oklch(0.28 0.03 280); -} -.dracula .slider-range { - background: linear-gradient(to right, #bd93f9, #ff79c6); -} -.dracula .slider-thumb { - background: oklch(0.22 0.02 280); - border-color: #bd93f9; -} /* Nord slider */ -.nord .slider-track { - background: oklch(0.31 0.02 240); -} -.nord .slider-range { - background: linear-gradient(to right, #88c0d0, #81a1c1); -} -.nord .slider-thumb { - background: oklch(0.27 0.02 240); - border-color: #88c0d0; -} /* Monokai slider */ -.monokai .slider-track { - background: oklch(0.25 0.02 90); -} -.monokai .slider-range { - background: linear-gradient(to right, #f92672, #fd971f); -} -.monokai .slider-thumb { - background: oklch(0.22 0.01 90); - border-color: #f92672; -} /* Tokyo Night slider */ -.tokyonight .slider-track { - background: oklch(0.24 0.03 260); -} -.tokyonight .slider-range { - background: linear-gradient(to right, #7aa2f7, #bb9af7); -} -.tokyonight .slider-thumb { - background: oklch(0.2 0.03 260); - border-color: #7aa2f7; -} /* Solarized slider */ -.solarized .slider-track { - background: oklch(0.25 0.02 230); -} -.solarized .slider-range { - background: linear-gradient(to right, #268bd2, #2aa198); -} -.solarized .slider-thumb { - background: oklch(0.23 0.02 230); - border-color: #268bd2; -} /* Gruvbox slider */ -.gruvbox .slider-track { - background: oklch(0.26 0.02 60); -} -.gruvbox .slider-range { - background: linear-gradient(to right, #fabd2f, #fe8019); -} -.gruvbox .slider-thumb { - background: oklch(0.22 0.02 60); - border-color: #fabd2f; -} /* Catppuccin slider */ -.catppuccin .slider-track { - background: oklch(0.26 0.02 260); -} -.catppuccin .slider-range { - background: linear-gradient(to right, #cba6f7, #89b4fa); -} -.catppuccin .slider-thumb { - background: oklch(0.22 0.02 260); - border-color: #cba6f7; -} /* One Dark slider */ -.onedark .slider-track { - background: oklch(0.25 0.01 250); -} -.onedark .slider-range { - background: linear-gradient(to right, #61afef, #c678dd); -} -.onedark .slider-thumb { - background: oklch(0.23 0.01 250); - border-color: #61afef; -} /* Synthwave slider */ -.synthwave .slider-track { - background: oklch(0.25 0.07 290); -} -.synthwave .slider-range { - background: linear-gradient(to right, #f97e72, #ff7edb); - box-shadow: 0 0 10px #f97e72, 0 0 5px #ff7edb; -} -.synthwave .slider-thumb { - background: oklch(0.2 0.06 290); - border-color: #f97e72; - box-shadow: 0 0 8px #f97e72; -} /* Line clamp utilities for text overflow prevention */ .line-clamp-2 { @@ -2380,511 +806,136 @@ ======================================== */ /* Light theme - professional and readable */ -.light .xml-highlight { - color: oklch(0.3 0 0); /* Default text */ -} -.light .xml-tag-bracket { - color: oklch(0.45 0.15 250); /* Blue-gray for < > */ -} -.light .xml-tag-name { - color: oklch(0.45 0.22 25); /* Red/maroon for tag names */ -} -.light .xml-attribute-name { - color: oklch(0.45 0.18 280); /* Purple for attributes */ -} -.light .xml-attribute-equals { - color: oklch(0.4 0 0); /* Dark gray for = */ -} -.light .xml-attribute-value { - color: oklch(0.45 0.18 145); /* Green for string values */ -} -.light .xml-comment { - color: oklch(0.55 0.05 100); /* Muted olive for comments */ - font-style: italic; -} -.light .xml-cdata { - color: oklch(0.5 0.1 200); /* Teal for CDATA */ -} -.light .xml-doctype { - color: oklch(0.5 0.15 280); /* Purple for DOCTYPE */ -} -.light .xml-text { - color: oklch(0.25 0 0); /* Near-black for text content */ -} /* Dark theme - high contrast */ -.dark .xml-highlight { - color: oklch(0.9 0 0); /* Default light text */ -} -.dark .xml-tag-bracket { - color: oklch(0.7 0.12 220); /* Soft blue for < > */ -} -.dark .xml-tag-name { - color: oklch(0.75 0.2 25); /* Coral/salmon for tag names */ -} -.dark .xml-attribute-name { - color: oklch(0.8 0.15 280); /* Light purple for attributes */ -} -.dark .xml-attribute-equals { - color: oklch(0.6 0 0); /* Gray for = */ -} -.dark .xml-attribute-value { - color: oklch(0.8 0.18 145); /* Bright green for strings */ -} -.dark .xml-comment { - color: oklch(0.55 0.05 100); /* Muted for comments */ - font-style: italic; -} -.dark .xml-cdata { - color: oklch(0.7 0.12 200); /* Teal for CDATA */ -} -.dark .xml-doctype { - color: oklch(0.7 0.15 280); /* Purple for DOCTYPE */ -} -.dark .xml-text { - color: oklch(0.85 0 0); /* Off-white for text */ -} /* Retro theme - neon green on black */ -.retro .xml-highlight { - color: oklch(0.85 0.25 145); /* Neon green default */ -} -.retro .xml-tag-bracket { - color: oklch(0.8 0.25 200); /* Cyan for brackets */ -} -.retro .xml-tag-name { - color: oklch(0.85 0.25 145); /* Bright green for tags */ - text-shadow: 0 0 5px oklch(0.85 0.25 145 / 0.5); -} -.retro .xml-attribute-name { - color: oklch(0.8 0.25 300); /* Purple neon for attrs */ -} -.retro .xml-attribute-equals { - color: oklch(0.6 0.15 145); /* Dim green for = */ -} -.retro .xml-attribute-value { - color: oklch(0.8 0.25 60); /* Yellow neon for strings */ -} -.retro .xml-comment { - color: oklch(0.5 0.15 145); /* Dim green for comments */ - font-style: italic; -} -.retro .xml-cdata { - color: oklch(0.75 0.2 200); /* Cyan for CDATA */ -} -.retro .xml-doctype { - color: oklch(0.75 0.2 300); /* Purple for DOCTYPE */ -} -.retro .xml-text { - color: oklch(0.7 0.2 145); /* Green text */ -} /* Dracula theme */ -.dracula .xml-highlight { - color: oklch(0.95 0.01 280); /* #f8f8f2 */ -} -.dracula .xml-tag-bracket { - color: oklch(0.7 0.25 350); /* Pink #ff79c6 */ -} -.dracula .xml-tag-name { - color: oklch(0.7 0.25 350); /* Pink for tags */ -} -.dracula .xml-attribute-name { - color: oklch(0.8 0.2 130); /* Green #50fa7b */ -} -.dracula .xml-attribute-equals { - color: oklch(0.95 0.01 280); /* White */ -} -.dracula .xml-attribute-value { - color: oklch(0.85 0.2 90); /* Yellow #f1fa8c */ -} -.dracula .xml-comment { - color: oklch(0.55 0.08 280); /* #6272a4 */ - font-style: italic; -} -.dracula .xml-cdata { - color: oklch(0.75 0.2 180); /* Cyan */ -} -.dracula .xml-doctype { - color: oklch(0.7 0.2 320); /* Purple #bd93f9 */ -} -.dracula .xml-text { - color: oklch(0.95 0.01 280); /* White */ -} /* Nord theme */ -.nord .xml-highlight { - color: oklch(0.9 0.01 230); /* #eceff4 */ -} -.nord .xml-tag-bracket { - color: oklch(0.65 0.14 220); /* #81a1c1 */ -} -.nord .xml-tag-name { - color: oklch(0.65 0.14 220); /* Frost blue for tags */ -} -.nord .xml-attribute-name { - color: oklch(0.7 0.12 220); /* #88c0d0 */ -} -.nord .xml-attribute-equals { - color: oklch(0.75 0.02 230); /* Dim white */ -} -.nord .xml-attribute-value { - color: oklch(0.7 0.15 140); /* #a3be8c green */ -} -.nord .xml-comment { - color: oklch(0.5 0.04 230); /* Dim text */ - font-style: italic; -} -.nord .xml-cdata { - color: oklch(0.7 0.12 220); /* Frost blue */ -} -.nord .xml-doctype { - color: oklch(0.7 0.2 320); /* #b48ead purple */ -} -.nord .xml-text { - color: oklch(0.9 0.01 230); /* Snow white */ -} /* Monokai theme */ -.monokai .xml-highlight { - color: oklch(0.95 0.02 100); /* #f8f8f2 */ -} -.monokai .xml-tag-bracket { - color: oklch(0.95 0.02 100); /* White */ -} -.monokai .xml-tag-name { - color: oklch(0.8 0.2 350); /* #f92672 pink */ -} -.monokai .xml-attribute-name { - color: oklch(0.8 0.2 140); /* #a6e22e green */ -} -.monokai .xml-attribute-equals { - color: oklch(0.95 0.02 100); /* White */ -} -.monokai .xml-attribute-value { - color: oklch(0.85 0.2 90); /* #e6db74 yellow */ -} -.monokai .xml-comment { - color: oklch(0.55 0.04 100); /* #75715e */ - font-style: italic; -} -.monokai .xml-cdata { - color: oklch(0.75 0.2 200); /* Cyan #66d9ef */ -} -.monokai .xml-doctype { - color: oklch(0.75 0.2 200); /* Cyan */ -} -.monokai .xml-text { - color: oklch(0.95 0.02 100); /* White */ -} /* Tokyo Night theme */ -.tokyonight .xml-highlight { - color: oklch(0.85 0.02 250); /* #a9b1d6 */ -} -.tokyonight .xml-tag-bracket { - color: oklch(0.65 0.2 15); /* #f7768e red */ -} -.tokyonight .xml-tag-name { - color: oklch(0.65 0.2 15); /* Red for tags */ -} -.tokyonight .xml-attribute-name { - color: oklch(0.7 0.2 320); /* #bb9af7 purple */ -} -.tokyonight .xml-attribute-equals { - color: oklch(0.75 0.02 250); /* Dim text */ -} -.tokyonight .xml-attribute-value { - color: oklch(0.75 0.18 140); /* #9ece6a green */ -} -.tokyonight .xml-comment { - color: oklch(0.5 0.04 250); /* #565f89 */ - font-style: italic; -} -.tokyonight .xml-cdata { - color: oklch(0.75 0.18 200); /* #7dcfff cyan */ -} -.tokyonight .xml-doctype { - color: oklch(0.7 0.18 280); /* #7aa2f7 blue */ -} -.tokyonight .xml-text { - color: oklch(0.85 0.02 250); /* Text color */ -} /* Solarized theme */ -.solarized .xml-highlight { - color: oklch(0.75 0.02 90); /* #839496 */ -} -.solarized .xml-tag-bracket { - color: oklch(0.65 0.15 220); /* #268bd2 blue */ -} -.solarized .xml-tag-name { - color: oklch(0.65 0.15 220); /* Blue for tags */ -} -.solarized .xml-attribute-name { - color: oklch(0.6 0.18 180); /* #2aa198 cyan */ -} -.solarized .xml-attribute-equals { - color: oklch(0.75 0.02 90); /* Base text */ -} -.solarized .xml-attribute-value { - color: oklch(0.65 0.2 140); /* #859900 green */ -} -.solarized .xml-comment { - color: oklch(0.5 0.04 200); /* #586e75 */ - font-style: italic; -} -.solarized .xml-cdata { - color: oklch(0.6 0.18 180); /* Cyan */ -} -.solarized .xml-doctype { - color: oklch(0.6 0.2 290); /* #6c71c4 violet */ -} -.solarized .xml-text { - color: oklch(0.75 0.02 90); /* Base text */ -} /* Gruvbox theme */ -.gruvbox .xml-highlight { - color: oklch(0.85 0.05 85); /* #ebdbb2 */ -} -.gruvbox .xml-tag-bracket { - color: oklch(0.55 0.22 25); /* #fb4934 red */ -} -.gruvbox .xml-tag-name { - color: oklch(0.55 0.22 25); /* Red for tags */ -} -.gruvbox .xml-attribute-name { - color: oklch(0.7 0.15 200); /* #8ec07c aqua */ -} -.gruvbox .xml-attribute-equals { - color: oklch(0.7 0.04 85); /* Dim text */ -} -.gruvbox .xml-attribute-value { - color: oklch(0.65 0.2 140); /* #b8bb26 green */ -} -.gruvbox .xml-comment { - color: oklch(0.55 0.04 85); /* #928374 gray */ - font-style: italic; -} -.gruvbox .xml-cdata { - color: oklch(0.7 0.15 200); /* Aqua */ -} -.gruvbox .xml-doctype { - color: oklch(0.6 0.2 320); /* #d3869b purple */ -} -.gruvbox .xml-text { - color: oklch(0.85 0.05 85); /* Foreground */ -} /* Catppuccin theme */ -.catppuccin .xml-highlight { - color: oklch(0.9 0.01 280); /* #cdd6f4 */ -} -.catppuccin .xml-tag-bracket { - color: oklch(0.65 0.2 15); /* #f38ba8 red */ -} -.catppuccin .xml-tag-name { - color: oklch(0.65 0.2 15); /* Red for tags */ -} -.catppuccin .xml-attribute-name { - color: oklch(0.75 0.15 280); /* #cba6f7 mauve */ -} -.catppuccin .xml-attribute-equals { - color: oklch(0.75 0.02 280); /* Subtext */ -} -.catppuccin .xml-attribute-value { - color: oklch(0.8 0.15 160); /* #a6e3a1 green */ -} -.catppuccin .xml-comment { - color: oklch(0.5 0.04 280); /* Overlay */ - font-style: italic; -} -.catppuccin .xml-cdata { - color: oklch(0.75 0.15 220); /* #89b4fa blue */ -} -.catppuccin .xml-doctype { - color: oklch(0.8 0.15 350); /* #f5c2e7 pink */ -} -.catppuccin .xml-text { - color: oklch(0.9 0.01 280); /* Text */ -} /* One Dark theme */ -.onedark .xml-highlight { - color: oklch(0.85 0.02 240); /* #abb2bf */ -} -.onedark .xml-tag-bracket { - color: oklch(0.6 0.2 20); /* #e06c75 red */ -} -.onedark .xml-tag-name { - color: oklch(0.6 0.2 20); /* Red for tags */ -} -.onedark .xml-attribute-name { - color: oklch(0.8 0.15 80); /* #e5c07b yellow */ -} -.onedark .xml-attribute-equals { - color: oklch(0.7 0.02 240); /* Dim text */ -} -.onedark .xml-attribute-value { - color: oklch(0.75 0.18 150); /* #98c379 green */ -} -.onedark .xml-comment { - color: oklch(0.5 0.03 240); /* #5c6370 */ - font-style: italic; -} -.onedark .xml-cdata { - color: oklch(0.7 0.15 180); /* #56b6c2 cyan */ -} -.onedark .xml-doctype { - color: oklch(0.75 0.15 320); /* #c678dd magenta */ -} -.onedark .xml-text { - color: oklch(0.85 0.02 240); /* Text */ -} /* Synthwave theme */ -.synthwave .xml-highlight { - color: oklch(0.95 0.02 320); /* Warm white */ -} -.synthwave .xml-tag-bracket { - color: oklch(0.7 0.28 350); /* #f97e72 hot pink */ -} -.synthwave .xml-tag-name { - color: oklch(0.7 0.28 350); /* Hot pink */ - text-shadow: 0 0 8px oklch(0.7 0.28 350 / 0.5); -} -.synthwave .xml-attribute-name { - color: oklch(0.7 0.25 280); /* #ff7edb purple */ -} -.synthwave .xml-attribute-equals { - color: oklch(0.8 0.02 320); /* White-ish */ -} -.synthwave .xml-attribute-value { - color: oklch(0.85 0.2 60); /* #fede5d yellow */ - text-shadow: 0 0 5px oklch(0.85 0.2 60 / 0.3); -} -.synthwave .xml-comment { - color: oklch(0.55 0.08 290); /* Dim purple */ - font-style: italic; -} -.synthwave .xml-cdata { - color: oklch(0.8 0.25 200); /* #72f1b8 cyan */ -} -.synthwave .xml-doctype { - color: oklch(0.8 0.25 200); /* Cyan */ -} -.synthwave .xml-text { - color: oklch(0.95 0.02 320); /* White */ -} /* XML Editor container styles */ .xml-editor { diff --git a/apps/ui/src/styles/theme-imports.ts b/apps/ui/src/styles/theme-imports.ts new file mode 100644 index 00000000..c662342e --- /dev/null +++ b/apps/ui/src/styles/theme-imports.ts @@ -0,0 +1,22 @@ +/** + * Bundles all individual theme styles so the build pipeline + * doesn't tree-shake their CSS when imported dynamically. + */ +import "./themes/dark.css"; +import "./themes/light.css"; +import "./themes/retro.css"; +import "./themes/dracula.css"; +import "./themes/nord.css"; +import "./themes/monokai.css"; +import "./themes/tokyonight.css"; +import "./themes/solarized.css"; +import "./themes/gruvbox.css"; +import "./themes/catppuccin.css"; +import "./themes/onedark.css"; +import "./themes/synthwave.css"; +import "./themes/red.css"; +import "./themes/cream.css"; +import "./themes/sunset.css"; +import "./themes/gray.css"; + + diff --git a/apps/ui/src/styles/themes/catppuccin.css b/apps/ui/src/styles/themes/catppuccin.css new file mode 100644 index 00000000..422b6e52 --- /dev/null +++ b/apps/ui/src/styles/themes/catppuccin.css @@ -0,0 +1,144 @@ +/* Catppuccin Theme */ + +.catppuccin { + --background: oklch(0.18 0.02 260); /* #1e1e2e base */ + --background-50: oklch(0.18 0.02 260 / 0.5); + --background-80: oklch(0.18 0.02 260 / 0.8); + + --foreground: oklch(0.9 0.01 280); /* #cdd6f4 text */ + --foreground-secondary: oklch(0.75 0.02 280); /* #bac2de subtext1 */ + --foreground-muted: oklch(0.6 0.03 280); /* #a6adc8 subtext0 */ + + --card: oklch(0.22 0.02 260); /* #313244 surface0 */ + --card-foreground: oklch(0.9 0.01 280); + --popover: oklch(0.2 0.02 260); + --popover-foreground: oklch(0.9 0.01 280); + + --primary: oklch(0.75 0.15 280); /* #cba6f7 mauve */ + --primary-foreground: oklch(0.18 0.02 260); + + --brand-400: oklch(0.8 0.15 280); + --brand-500: oklch(0.75 0.15 280); /* Mauve */ + --brand-600: oklch(0.7 0.17 280); + + --secondary: oklch(0.26 0.02 260); /* #45475a surface1 */ + --secondary-foreground: oklch(0.9 0.01 280); + + --muted: oklch(0.26 0.02 260); + --muted-foreground: oklch(0.6 0.03 280); + + --accent: oklch(0.3 0.03 260); /* #585b70 surface2 */ + --accent-foreground: oklch(0.9 0.01 280); + + --destructive: oklch(0.65 0.2 15); /* #f38ba8 red */ + + --border: oklch(0.35 0.03 260); + --border-glass: oklch(0.75 0.15 280 / 0.3); + + --input: oklch(0.22 0.02 260); + --ring: oklch(0.75 0.15 280); + + --chart-1: oklch(0.75 0.15 280); /* Mauve */ + --chart-2: oklch(0.75 0.15 220); /* Blue #89b4fa */ + --chart-3: oklch(0.8 0.15 160); /* Green #a6e3a1 */ + --chart-4: oklch(0.8 0.15 350); /* Pink #f5c2e7 */ + --chart-5: oklch(0.85 0.12 90); /* Yellow #f9e2af */ + + --sidebar: oklch(0.16 0.02 260); /* #181825 mantle */ + --sidebar-foreground: oklch(0.9 0.01 280); + --sidebar-primary: oklch(0.75 0.15 280); + --sidebar-primary-foreground: oklch(0.18 0.02 260); + --sidebar-accent: oklch(0.26 0.02 260); + --sidebar-accent-foreground: oklch(0.9 0.01 280); + --sidebar-border: oklch(0.35 0.03 260); + --sidebar-ring: oklch(0.75 0.15 280); + + /* Action button colors - Catppuccin mauve/pink theme */ + --action-view: oklch(0.75 0.15 280); /* Mauve */ + --action-view-hover: oklch(0.7 0.17 280); + --action-followup: oklch(0.75 0.15 220); /* Blue */ + --action-followup-hover: oklch(0.7 0.17 220); + --action-commit: oklch(0.8 0.15 160); /* Green */ + --action-commit-hover: oklch(0.75 0.17 160); + --action-verify: oklch(0.8 0.15 160); /* Green */ + --action-verify-hover: oklch(0.75 0.17 160); + + /* Running indicator - Mauve */ + --running-indicator: oklch(0.75 0.15 280); + --running-indicator-text: oklch(0.8 0.13 280); +} + +/* ======================================== + ONE DARK THEME + Atom's iconic One Dark theme + ======================================== */ + +/* Theme-specific overrides */ + +.catppuccin .animated-outline-gradient { + background: conic-gradient(from 90deg at 50% 50%, #cba6f7 0%, #f5c2e7 50%, #cba6f7 100%); +} + +.catppuccin .animated-outline-inner { + background: oklch(0.18 0.02 260) !important; + color: #cba6f7 !important; +} + +.catppuccin [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { + background: oklch(0.24 0.03 260) !important; + color: #f5c2e7 !important; +} + +.catppuccin .slider-track { + background: oklch(0.26 0.02 260); +} + +.catppuccin .slider-range { + background: linear-gradient(to right, #cba6f7, #89b4fa); +} + +.catppuccin .slider-thumb { + background: oklch(0.22 0.02 260); + border-color: #cba6f7; +} + +.catppuccin .xml-highlight { + color: oklch(0.9 0.01 280); /* #cdd6f4 */ +} + +.catppuccin .xml-tag-bracket { + color: oklch(0.65 0.2 15); /* #f38ba8 red */ +} + +.catppuccin .xml-tag-name { + color: oklch(0.65 0.2 15); /* Red for tags */ +} + +.catppuccin .xml-attribute-name { + color: oklch(0.75 0.15 280); /* #cba6f7 mauve */ +} + +.catppuccin .xml-attribute-equals { + color: oklch(0.75 0.02 280); /* Subtext */ +} + +.catppuccin .xml-attribute-value { + color: oklch(0.8 0.15 160); /* #a6e3a1 green */ +} + +.catppuccin .xml-comment { + color: oklch(0.5 0.04 280); /* Overlay */ + font-style: italic; +} + +.catppuccin .xml-cdata { + color: oklch(0.75 0.15 220); /* #89b4fa blue */ +} + +.catppuccin .xml-doctype { + color: oklch(0.8 0.15 350); /* #f5c2e7 pink */ +} + +.catppuccin .xml-text { + color: oklch(0.9 0.01 280); /* Text */ +} diff --git a/apps/ui/src/styles/themes/cream.css b/apps/ui/src/styles/themes/cream.css new file mode 100644 index 00000000..95fb349b --- /dev/null +++ b/apps/ui/src/styles/themes/cream.css @@ -0,0 +1,116 @@ +/* Cream Theme */ + +.cream { + /* Cream Theme - Warm, soft, easy on the eyes */ + --background: oklch(0.95 0.01 70); /* Warm cream background */ + --background-50: oklch(0.95 0.01 70 / 0.5); + --background-80: oklch(0.95 0.01 70 / 0.8); + + --foreground: oklch(0.25 0.02 60); /* Dark warm brown */ + --foreground-secondary: oklch(0.45 0.02 60); /* Medium brown */ + --foreground-muted: oklch(0.55 0.02 60); /* Light brown */ + + --card: oklch(0.98 0.005 70); /* Slightly lighter cream */ + --card-foreground: oklch(0.25 0.02 60); + --popover: oklch(0.97 0.008 70); + --popover-foreground: oklch(0.25 0.02 60); + + --primary: oklch(0.5 0.12 45); /* Warm terracotta/rust */ + --primary-foreground: oklch(0.98 0.005 70); + + --brand-400: oklch(0.55 0.12 45); + --brand-500: oklch(0.5 0.12 45); /* Terracotta */ + --brand-600: oklch(0.45 0.13 45); + + --secondary: oklch(0.88 0.02 70); + --secondary-foreground: oklch(0.25 0.02 60); + + --muted: oklch(0.9 0.015 70); + --muted-foreground: oklch(0.45 0.02 60); + + --accent: oklch(0.85 0.025 70); + --accent-foreground: oklch(0.25 0.02 60); + + --destructive: oklch(0.55 0.22 25); /* Warm red */ + + --border: oklch(0.85 0.015 70); + --border-glass: oklch(0.5 0.12 45 / 0.2); + + --input: oklch(0.98 0.005 70); + --ring: oklch(0.5 0.12 45); + + --chart-1: oklch(0.5 0.12 45); /* Terracotta */ + --chart-2: oklch(0.55 0.15 35); /* Burnt orange */ + --chart-3: oklch(0.6 0.12 100); /* Olive */ + --chart-4: oklch(0.5 0.15 20); /* Deep rust */ + --chart-5: oklch(0.65 0.1 80); /* Golden */ + + --sidebar: oklch(0.93 0.012 70); + --sidebar-foreground: oklch(0.25 0.02 60); + --sidebar-primary: oklch(0.5 0.12 45); + --sidebar-primary-foreground: oklch(0.98 0.005 70); + --sidebar-accent: oklch(0.88 0.02 70); + --sidebar-accent-foreground: oklch(0.25 0.02 60); + --sidebar-border: oklch(0.85 0.015 70); + --sidebar-ring: oklch(0.5 0.12 45); + + /* Action button colors - Warm earth tones */ + --action-view: oklch(0.5 0.12 45); /* Terracotta */ + --action-view-hover: oklch(0.45 0.13 45); + --action-followup: oklch(0.55 0.15 35); /* Burnt orange */ + --action-followup-hover: oklch(0.5 0.16 35); + --action-commit: oklch(0.55 0.12 130); /* Sage green */ + --action-commit-hover: oklch(0.5 0.13 130); + --action-verify: oklch(0.55 0.12 130); /* Sage green */ + --action-verify-hover: oklch(0.5 0.13 130); + + /* Running indicator - Terracotta */ + --running-indicator: oklch(0.5 0.12 45); + --running-indicator-text: oklch(0.55 0.12 45); + + /* Status colors - Cream theme */ + --status-success: oklch(0.55 0.15 130); + --status-success-bg: oklch(0.55 0.15 130 / 0.15); + --status-warning: oklch(0.6 0.15 70); + --status-warning-bg: oklch(0.6 0.15 70 / 0.15); + --status-error: oklch(0.55 0.22 25); + --status-error-bg: oklch(0.55 0.22 25 / 0.15); + --status-info: oklch(0.5 0.15 230); + --status-info-bg: oklch(0.5 0.15 230 / 0.15); + --status-backlog: oklch(0.6 0.02 60); + --status-in-progress: oklch(0.6 0.15 70); + --status-waiting: oklch(0.58 0.13 50); +} + + +/* Theme-specific overrides */ + +/* Cream theme scrollbar */ +.cream ::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.cream ::-webkit-scrollbar-thumb, +.cream .scrollbar-visible::-webkit-scrollbar-thumb { + background: oklch(0.7 0.03 60); + border-radius: 4px; +} + +.cream ::-webkit-scrollbar-thumb:hover, +.cream .scrollbar-visible::-webkit-scrollbar-thumb:hover { + background: oklch(0.6 0.04 60); +} + +.cream ::-webkit-scrollbar-track, +.cream .scrollbar-visible::-webkit-scrollbar-track { + background: oklch(0.9 0.015 70); +} + +.cream .scrollbar-styled::-webkit-scrollbar-thumb { + background: oklch(0.7 0.03 60); +} + +.cream .scrollbar-styled::-webkit-scrollbar-thumb:hover { + background: oklch(0.6 0.04 60); +} diff --git a/apps/ui/src/styles/themes/dark.css b/apps/ui/src/styles/themes/dark.css new file mode 100644 index 00000000..81aeb244 --- /dev/null +++ b/apps/ui/src/styles/themes/dark.css @@ -0,0 +1,166 @@ +/* Dark Theme */ + +.dark { + /* Deep dark backgrounds - zinc-950 family */ + --background: oklch(0.04 0 0); /* zinc-950 */ + --background-50: oklch(0.04 0 0 / 0.5); /* zinc-950/50 */ + --background-80: oklch(0.04 0 0 / 0.8); /* zinc-950/80 */ + + /* Text colors following hierarchy */ + --foreground: oklch(1 0 0); /* text-white */ + --foreground-secondary: oklch(0.588 0 0); /* text-zinc-400 */ + --foreground-muted: oklch(0.525 0 0); /* text-zinc-500 */ + + /* Card and popover backgrounds */ + --card: oklch(0.14 0 0); /* slightly lighter than background for contrast */ + --card-foreground: oklch(1 0 0); + --popover: oklch(0.10 0 0); /* slightly lighter than background */ + --popover-foreground: oklch(1 0 0); + + /* Brand colors - purple/violet theme */ + --primary: oklch(0.55 0.25 265); /* brand-500 */ + --primary-foreground: oklch(1 0 0); + --brand-400: oklch(0.6 0.22 265); + --brand-500: oklch(0.55 0.25 265); + --brand-600: oklch(0.5 0.28 270); /* purple-600 for gradients */ + + /* Glass morphism borders and accents */ + --secondary: oklch(1 0 0 / 0.05); /* bg-white/5 */ + --secondary-foreground: oklch(1 0 0); + --muted: oklch(0.176 0 0); /* zinc-800 */ + --muted-foreground: oklch(0.588 0 0); /* text-zinc-400 */ + --accent: oklch(1 0 0 / 0.1); /* bg-white/10 for hover */ + --accent-foreground: oklch(1 0 0); + + /* Borders with transparency for glass effect */ + --border: oklch(0.176 0 0); /* zinc-800 */ + --border-glass: oklch(1 0 0 / 0.1); /* white/10 for glass morphism */ + --destructive: oklch(0.6 0.25 25); + --input: oklch(0.04 0 0 / 0.8); /* Semi-transparent dark */ + --ring: oklch(0.55 0.25 265); + + /* Chart colors with brand theme */ + --chart-1: oklch(0.55 0.25 265); + --chart-2: oklch(0.65 0.2 160); + --chart-3: oklch(0.75 0.2 70); + --chart-4: oklch(0.6 0.25 300); + --chart-5: oklch(0.6 0.25 20); + + /* Sidebar with glass morphism */ + --sidebar: oklch(0.04 0 0 / 0.5); /* zinc-950/50 with backdrop blur */ + --sidebar-foreground: oklch(1 0 0); + --sidebar-primary: oklch(0.55 0.25 265); + --sidebar-primary-foreground: oklch(1 0 0); + --sidebar-accent: oklch(1 0 0 / 0.05); /* bg-white/5 */ + --sidebar-accent-foreground: oklch(1 0 0); + --sidebar-border: oklch(1 0 0 / 0.1); /* white/10 for glass borders */ + --sidebar-ring: oklch(0.55 0.25 265); + + /* Action button colors */ + --action-view: oklch(0.6 0.25 265); /* Purple */ + --action-view-hover: oklch(0.55 0.27 270); + --action-followup: oklch(0.6 0.2 230); /* Blue */ + --action-followup-hover: oklch(0.55 0.22 230); + --action-commit: oklch(0.55 0.2 140); /* Green */ + --action-commit-hover: oklch(0.5 0.22 140); + --action-verify: oklch(0.55 0.2 140); /* Green */ + --action-verify-hover: oklch(0.5 0.22 140); + + /* Running indicator - Purple */ + --running-indicator: oklch(0.6 0.25 265); + --running-indicator-text: oklch(0.65 0.22 265); + + /* Status colors - Dark mode */ + --status-success: oklch(0.65 0.2 140); + --status-success-bg: oklch(0.65 0.2 140 / 0.2); + --status-warning: oklch(0.75 0.15 70); + --status-warning-bg: oklch(0.75 0.15 70 / 0.2); + --status-error: oklch(0.65 0.22 25); + --status-error-bg: oklch(0.65 0.22 25 / 0.2); + --status-info: oklch(0.65 0.2 230); + --status-info-bg: oklch(0.65 0.2 230 / 0.2); + --status-backlog: oklch(0.6 0 0); + --status-in-progress: oklch(0.75 0.15 70); + --status-waiting: oklch(0.7 0.18 50); + + /* Shadow tokens - darker for dark mode */ + --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.2); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.3); +} + +/* Theme-specific overrides */ + + .dark .content-bg { + background: linear-gradient(135deg, oklch(0.04 0 0), oklch(0.08 0 0), oklch(0.04 0 0)); + } + +.dark .animated-outline-gradient { + background: conic-gradient(from 90deg at 50% 50%, #a855f7 0%, #3b82f6 50%, #a855f7 100%); +} + +.dark .animated-outline-inner { + background: oklch(0.15 0 0) !important; + color: #c084fc !important; +} + +.dark [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { + background: oklch(0.2 0.02 270) !important; + color: #e9d5ff !important; +} + +.dark .slider-track { + background: oklch(0.2 0 0); +} + +.dark .slider-range { + background: linear-gradient(to right, #a855f7, #3b82f6); +} + +.dark .slider-thumb { + background: oklch(0.25 0 0); + border-color: oklch(0.4 0 0); +} + +.dark .xml-highlight { + color: oklch(0.9 0 0); /* Default light text */ +} + +.dark .xml-tag-bracket { + color: oklch(0.7 0.12 220); /* Soft blue for < > */ +} + +.dark .xml-tag-name { + color: oklch(0.75 0.2 25); /* Coral/salmon for tag names */ +} + +.dark .xml-attribute-name { + color: oklch(0.8 0.15 280); /* Light purple for attributes */ +} + +.dark .xml-attribute-equals { + color: oklch(0.6 0 0); /* Gray for = */ +} + +.dark .xml-attribute-value { + color: oklch(0.8 0.18 145); /* Bright green for strings */ +} + +.dark .xml-comment { + color: oklch(0.55 0.05 100); /* Muted for comments */ + font-style: italic; +} + +.dark .xml-cdata { + color: oklch(0.7 0.12 200); /* Teal for CDATA */ +} + +.dark .xml-doctype { + color: oklch(0.7 0.15 280); /* Purple for DOCTYPE */ +} + +.dark .xml-text { + color: oklch(0.85 0 0); /* Off-white for text */ +} diff --git a/apps/ui/src/styles/themes/dracula.css b/apps/ui/src/styles/themes/dracula.css new file mode 100644 index 00000000..d7f569b3 --- /dev/null +++ b/apps/ui/src/styles/themes/dracula.css @@ -0,0 +1,144 @@ +/* Dracula Theme */ + +.dracula { + --background: oklch(0.18 0.02 280); /* #282a36 */ + --background-50: oklch(0.18 0.02 280 / 0.5); + --background-80: oklch(0.18 0.02 280 / 0.8); + + --foreground: oklch(0.95 0.01 280); /* #f8f8f2 */ + --foreground-secondary: oklch(0.7 0.05 280); + --foreground-muted: oklch(0.55 0.08 280); /* #6272a4 */ + + --card: oklch(0.22 0.02 280); /* #44475a */ + --card-foreground: oklch(0.95 0.01 280); + --popover: oklch(0.2 0.02 280); + --popover-foreground: oklch(0.95 0.01 280); + + --primary: oklch(0.7 0.2 320); /* #bd93f9 purple */ + --primary-foreground: oklch(0.18 0.02 280); + + --brand-400: oklch(0.75 0.2 320); + --brand-500: oklch(0.7 0.2 320); /* #bd93f9 */ + --brand-600: oklch(0.65 0.22 320); + + --secondary: oklch(0.28 0.03 280); /* #44475a */ + --secondary-foreground: oklch(0.95 0.01 280); + + --muted: oklch(0.28 0.03 280); + --muted-foreground: oklch(0.55 0.08 280); /* #6272a4 */ + + --accent: oklch(0.32 0.04 280); + --accent-foreground: oklch(0.95 0.01 280); + + --destructive: oklch(0.65 0.25 15); /* #ff5555 */ + + --border: oklch(0.35 0.05 280); + --border-glass: oklch(0.7 0.2 320 / 0.3); + + --input: oklch(0.22 0.02 280); + --ring: oklch(0.7 0.2 320); + + --chart-1: oklch(0.7 0.2 320); /* Purple */ + --chart-2: oklch(0.75 0.2 180); /* Cyan #8be9fd */ + --chart-3: oklch(0.8 0.2 130); /* Green #50fa7b */ + --chart-4: oklch(0.7 0.25 350); /* Pink #ff79c6 */ + --chart-5: oklch(0.85 0.2 90); /* Yellow #f1fa8c */ + + --sidebar: oklch(0.16 0.02 280); + --sidebar-foreground: oklch(0.95 0.01 280); + --sidebar-primary: oklch(0.7 0.2 320); + --sidebar-primary-foreground: oklch(0.18 0.02 280); + --sidebar-accent: oklch(0.28 0.03 280); + --sidebar-accent-foreground: oklch(0.95 0.01 280); + --sidebar-border: oklch(0.35 0.05 280); + --sidebar-ring: oklch(0.7 0.2 320); + + /* Action button colors - Dracula purple/pink theme */ + --action-view: oklch(0.7 0.2 320); /* Purple */ + --action-view-hover: oklch(0.65 0.22 320); + --action-followup: oklch(0.65 0.25 350); /* Pink */ + --action-followup-hover: oklch(0.6 0.27 350); + --action-commit: oklch(0.75 0.2 130); /* Green */ + --action-commit-hover: oklch(0.7 0.22 130); + --action-verify: oklch(0.75 0.2 130); /* Green */ + --action-verify-hover: oklch(0.7 0.22 130); + + /* Running indicator - Purple */ + --running-indicator: oklch(0.7 0.2 320); + --running-indicator-text: oklch(0.75 0.18 320); +} + +/* ======================================== + NORD THEME + Inspired by the Arctic, north-bluish color palette + ======================================== */ + +/* Theme-specific overrides */ + +.dracula .animated-outline-gradient { + background: conic-gradient(from 90deg at 50% 50%, #bd93f9 0%, #ff79c6 50%, #bd93f9 100%); +} + +.dracula .animated-outline-inner { + background: oklch(0.18 0.02 280) !important; + color: #bd93f9 !important; +} + +.dracula [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { + background: oklch(0.24 0.03 280) !important; + color: #ff79c6 !important; +} + +.dracula .slider-track { + background: oklch(0.28 0.03 280); +} + +.dracula .slider-range { + background: linear-gradient(to right, #bd93f9, #ff79c6); +} + +.dracula .slider-thumb { + background: oklch(0.22 0.02 280); + border-color: #bd93f9; +} + +.dracula .xml-highlight { + color: oklch(0.95 0.01 280); /* #f8f8f2 */ +} + +.dracula .xml-tag-bracket { + color: oklch(0.7 0.25 350); /* Pink #ff79c6 */ +} + +.dracula .xml-tag-name { + color: oklch(0.7 0.25 350); /* Pink for tags */ +} + +.dracula .xml-attribute-name { + color: oklch(0.8 0.2 130); /* Green #50fa7b */ +} + +.dracula .xml-attribute-equals { + color: oklch(0.95 0.01 280); /* White */ +} + +.dracula .xml-attribute-value { + color: oklch(0.85 0.2 90); /* Yellow #f1fa8c */ +} + +.dracula .xml-comment { + color: oklch(0.55 0.08 280); /* #6272a4 */ + font-style: italic; +} + +.dracula .xml-cdata { + color: oklch(0.75 0.2 180); /* Cyan */ +} + +.dracula .xml-doctype { + color: oklch(0.7 0.2 320); /* Purple #bd93f9 */ +} + +.dracula .xml-text { + color: oklch(0.95 0.01 280); /* White */ +} diff --git a/apps/ui/src/styles/themes/gray.css b/apps/ui/src/styles/themes/gray.css new file mode 100644 index 00000000..3ee72483 --- /dev/null +++ b/apps/ui/src/styles/themes/gray.css @@ -0,0 +1,110 @@ +/* Gray Theme */ + +.gray { + /* Gray Theme - Modern, minimal gray scheme inspired by Cursor */ + --background: oklch(0.2 0.005 250); /* Medium-dark neutral gray */ + --background-50: oklch(0.2 0.005 250 / 0.5); + --background-80: oklch(0.2 0.005 250 / 0.8); + + --foreground: oklch(0.9 0.005 250); /* Light gray */ + --foreground-secondary: oklch(0.65 0.005 250); + --foreground-muted: oklch(0.5 0.005 250); + + --card: oklch(0.24 0.005 250); + --card-foreground: oklch(0.9 0.005 250); + --popover: oklch(0.22 0.005 250); + --popover-foreground: oklch(0.9 0.005 250); + + --primary: oklch(0.6 0.08 250); /* Subtle blue-gray */ + --primary-foreground: oklch(0.95 0.005 250); + + --brand-400: oklch(0.65 0.08 250); + --brand-500: oklch(0.6 0.08 250); /* Blue-gray */ + --brand-600: oklch(0.55 0.09 250); + + --secondary: oklch(0.28 0.005 250); + --secondary-foreground: oklch(0.9 0.005 250); + + --muted: oklch(0.3 0.005 250); + --muted-foreground: oklch(0.6 0.005 250); + + --accent: oklch(0.35 0.01 250); + --accent-foreground: oklch(0.9 0.005 250); + + --destructive: oklch(0.6 0.2 25); /* Muted red */ + + --border: oklch(0.32 0.005 250); + --border-glass: oklch(0.6 0.08 250 / 0.2); + + --input: oklch(0.24 0.005 250); + --ring: oklch(0.6 0.08 250); + + --chart-1: oklch(0.6 0.08 250); /* Blue-gray */ + --chart-2: oklch(0.65 0.1 210); /* Cyan */ + --chart-3: oklch(0.7 0.12 160); /* Teal */ + --chart-4: oklch(0.65 0.1 280); /* Purple */ + --chart-5: oklch(0.7 0.08 300); /* Violet */ + + --sidebar: oklch(0.18 0.005 250); + --sidebar-foreground: oklch(0.9 0.005 250); + --sidebar-primary: oklch(0.6 0.08 250); + --sidebar-primary-foreground: oklch(0.95 0.005 250); + --sidebar-accent: oklch(0.28 0.005 250); + --sidebar-accent-foreground: oklch(0.9 0.005 250); + --sidebar-border: oklch(0.32 0.005 250); + --sidebar-ring: oklch(0.6 0.08 250); + + /* Action button colors - Subtle modern colors */ + --action-view: oklch(0.6 0.08 250); /* Blue-gray */ + --action-view-hover: oklch(0.55 0.09 250); + --action-followup: oklch(0.65 0.1 210); /* Cyan */ + --action-followup-hover: oklch(0.6 0.11 210); + --action-commit: oklch(0.65 0.12 150); /* Teal-green */ + --action-commit-hover: oklch(0.6 0.13 150); + --action-verify: oklch(0.65 0.12 150); /* Teal-green */ + --action-verify-hover: oklch(0.6 0.13 150); + + /* Running indicator - Blue-gray */ + --running-indicator: oklch(0.6 0.08 250); + --running-indicator-text: oklch(0.65 0.08 250); + + /* Status colors - Gray theme */ + --status-success: oklch(0.65 0.12 150); + --status-success-bg: oklch(0.65 0.12 150 / 0.2); + --status-warning: oklch(0.7 0.15 70); + --status-warning-bg: oklch(0.7 0.15 70 / 0.2); + --status-error: oklch(0.6 0.2 25); + --status-error-bg: oklch(0.6 0.2 25 / 0.2); + --status-info: oklch(0.65 0.1 210); + --status-info-bg: oklch(0.65 0.1 210 / 0.2); + --status-backlog: oklch(0.6 0.005 250); + --status-in-progress: oklch(0.7 0.15 70); + --status-waiting: oklch(0.68 0.1 220); +} + +/* Theme-specific overrides */ + +/* Gray theme scrollbar */ +.gray ::-webkit-scrollbar-thumb, +.gray .scrollbar-visible::-webkit-scrollbar-thumb { + background: oklch(0.4 0.01 250); + border-radius: 4px; +} + +.gray ::-webkit-scrollbar-thumb:hover, +.gray .scrollbar-visible::-webkit-scrollbar-thumb:hover { + background: oklch(0.5 0.02 250); +} + +.gray ::-webkit-scrollbar-track, +.gray .scrollbar-visible::-webkit-scrollbar-track { + background: oklch(0.25 0.005 250); +} + +.gray .scrollbar-styled::-webkit-scrollbar-thumb { + background: oklch(0.4 0.01 250); +} + +.gray .scrollbar-styled::-webkit-scrollbar-thumb:hover { + background: oklch(0.5 0.02 250); +} diff --git a/apps/ui/src/styles/themes/gruvbox.css b/apps/ui/src/styles/themes/gruvbox.css new file mode 100644 index 00000000..074dddbd --- /dev/null +++ b/apps/ui/src/styles/themes/gruvbox.css @@ -0,0 +1,144 @@ +/* Gruvbox Theme */ + +.gruvbox { + --background: oklch(0.18 0.02 60); /* #282828 bg */ + --background-50: oklch(0.18 0.02 60 / 0.5); + --background-80: oklch(0.18 0.02 60 / 0.8); + + --foreground: oklch(0.85 0.05 85); /* #ebdbb2 fg */ + --foreground-secondary: oklch(0.7 0.04 85); /* #d5c4a1 */ + --foreground-muted: oklch(0.55 0.04 85); /* #928374 */ + + --card: oklch(0.22 0.02 60); /* #3c3836 bg1 */ + --card-foreground: oklch(0.85 0.05 85); + --popover: oklch(0.2 0.02 60); + --popover-foreground: oklch(0.85 0.05 85); + + --primary: oklch(0.7 0.18 55); /* #fabd2f yellow */ + --primary-foreground: oklch(0.18 0.02 60); + + --brand-400: oklch(0.75 0.18 55); + --brand-500: oklch(0.7 0.18 55); /* Yellow */ + --brand-600: oklch(0.65 0.2 55); + + --secondary: oklch(0.26 0.02 60); /* #504945 bg2 */ + --secondary-foreground: oklch(0.85 0.05 85); + + --muted: oklch(0.26 0.02 60); + --muted-foreground: oklch(0.55 0.04 85); + + --accent: oklch(0.3 0.03 60); + --accent-foreground: oklch(0.85 0.05 85); + + --destructive: oklch(0.55 0.22 25); /* #fb4934 red */ + + --border: oklch(0.35 0.03 60); + --border-glass: oklch(0.7 0.18 55 / 0.3); + + --input: oklch(0.22 0.02 60); + --ring: oklch(0.7 0.18 55); + + --chart-1: oklch(0.7 0.18 55); /* Yellow */ + --chart-2: oklch(0.65 0.2 140); /* Green #b8bb26 */ + --chart-3: oklch(0.7 0.15 200); /* Aqua #8ec07c */ + --chart-4: oklch(0.6 0.2 30); /* Orange #fe8019 */ + --chart-5: oklch(0.6 0.2 320); /* Purple #d3869b */ + + --sidebar: oklch(0.16 0.02 60); + --sidebar-foreground: oklch(0.85 0.05 85); + --sidebar-primary: oklch(0.7 0.18 55); + --sidebar-primary-foreground: oklch(0.18 0.02 60); + --sidebar-accent: oklch(0.26 0.02 60); + --sidebar-accent-foreground: oklch(0.85 0.05 85); + --sidebar-border: oklch(0.35 0.03 60); + --sidebar-ring: oklch(0.7 0.18 55); + + /* Action button colors - Gruvbox yellow/orange theme */ + --action-view: oklch(0.7 0.18 55); /* Yellow */ + --action-view-hover: oklch(0.65 0.2 55); + --action-followup: oklch(0.7 0.15 200); /* Aqua */ + --action-followup-hover: oklch(0.65 0.17 200); + --action-commit: oklch(0.65 0.2 140); /* Green */ + --action-commit-hover: oklch(0.6 0.22 140); + --action-verify: oklch(0.65 0.2 140); /* Green */ + --action-verify-hover: oklch(0.6 0.22 140); + + /* Running indicator - Yellow */ + --running-indicator: oklch(0.7 0.18 55); + --running-indicator-text: oklch(0.75 0.16 55); +} + +/* ======================================== + CATPPUCCIN MOCHA THEME + Soothing pastel theme for the high-spirited + ======================================== */ + +/* Theme-specific overrides */ + +.gruvbox .animated-outline-gradient { + background: conic-gradient(from 90deg at 50% 50%, #fabd2f 0%, #fe8019 50%, #fabd2f 100%); +} + +.gruvbox .animated-outline-inner { + background: oklch(0.18 0.02 60) !important; + color: #fabd2f !important; +} + +.gruvbox [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { + background: oklch(0.24 0.03 60) !important; + color: #fe8019 !important; +} + +.gruvbox .slider-track { + background: oklch(0.26 0.02 60); +} + +.gruvbox .slider-range { + background: linear-gradient(to right, #fabd2f, #fe8019); +} + +.gruvbox .slider-thumb { + background: oklch(0.22 0.02 60); + border-color: #fabd2f; +} + +.gruvbox .xml-highlight { + color: oklch(0.85 0.05 85); /* #ebdbb2 */ +} + +.gruvbox .xml-tag-bracket { + color: oklch(0.55 0.22 25); /* #fb4934 red */ +} + +.gruvbox .xml-tag-name { + color: oklch(0.55 0.22 25); /* Red for tags */ +} + +.gruvbox .xml-attribute-name { + color: oklch(0.7 0.15 200); /* #8ec07c aqua */ +} + +.gruvbox .xml-attribute-equals { + color: oklch(0.7 0.04 85); /* Dim text */ +} + +.gruvbox .xml-attribute-value { + color: oklch(0.65 0.2 140); /* #b8bb26 green */ +} + +.gruvbox .xml-comment { + color: oklch(0.55 0.04 85); /* #928374 gray */ + font-style: italic; +} + +.gruvbox .xml-cdata { + color: oklch(0.7 0.15 200); /* Aqua */ +} + +.gruvbox .xml-doctype { + color: oklch(0.6 0.2 320); /* #d3869b purple */ +} + +.gruvbox .xml-text { + color: oklch(0.85 0.05 85); /* Foreground */ +} diff --git a/apps/ui/src/styles/themes/light.css b/apps/ui/src/styles/themes/light.css new file mode 100644 index 00000000..2c8cdc4b --- /dev/null +++ b/apps/ui/src/styles/themes/light.css @@ -0,0 +1,103 @@ +/* Light Theme Overrides */ + +.light .scrollbar-visible::-webkit-scrollbar-track { + background: oklch(0.95 0 0); +} + +.light .scrollbar-visible::-webkit-scrollbar-thumb { + background: oklch(0.7 0 0); +} + +.light .scrollbar-visible::-webkit-scrollbar-thumb:hover { + background: oklch(0.6 0 0); +} + +.light .scrollbar-styled::-webkit-scrollbar-thumb { + background: oklch(0.75 0 0); +} + +.light .scrollbar-styled::-webkit-scrollbar-thumb:hover { + background: oklch(0.65 0 0); +} + + .light .bg-glass { + background: oklch(1 0 0 / 0.8); + } + + .light .bg-glass-80 { + background: oklch(1 0 0 / 0.95); + } + + .light .content-bg { + background: linear-gradient(135deg, oklch(0.99 0 0), oklch(0.98 0 0), oklch(0.99 0 0)); + } + +.light .animated-outline-gradient { + background: conic-gradient(from 90deg at 50% 50%, #7c3aed 0%, #2563eb 50%, #7c3aed 100%); +} + +.light .animated-outline-inner { + background: oklch(100% 0 0) !important; + color: #7c3aed !important; + border: 1px solid oklch(92% 0 0); +} + +.light [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { + background: oklch(97% 0.02 270) !important; + color: #5b21b6 !important; +} + +.light .slider-track { + background: oklch(90% 0 0); +} + +.light .slider-range { + background: linear-gradient(to right, #7c3aed, #2563eb); +} + +.light .slider-thumb { + background: oklch(100% 0 0); + border-color: oklch(80% 0 0); +} + +.light .xml-highlight { + color: oklch(0.3 0 0); /* Default text */ +} + +.light .xml-tag-bracket { + color: oklch(0.45 0.15 250); /* Blue-gray for < > */ +} + +.light .xml-tag-name { + color: oklch(0.45 0.22 25); /* Red/maroon for tag names */ +} + +.light .xml-attribute-name { + color: oklch(0.45 0.18 280); /* Purple for attributes */ +} + +.light .xml-attribute-equals { + color: oklch(0.4 0 0); /* Dark gray for = */ +} + +.light .xml-attribute-value { + color: oklch(0.45 0.18 145); /* Green for string values */ +} + +.light .xml-comment { + color: oklch(0.55 0.05 100); /* Muted olive for comments */ + font-style: italic; +} + +.light .xml-cdata { + color: oklch(0.5 0.1 200); /* Teal for CDATA */ +} + +.light .xml-doctype { + color: oklch(0.5 0.15 280); /* Purple for DOCTYPE */ +} + +.light .xml-text { + color: oklch(0.25 0 0); /* Near-black for text content */ +} + diff --git a/apps/ui/src/styles/themes/monokai.css b/apps/ui/src/styles/themes/monokai.css new file mode 100644 index 00000000..f25cf0e2 --- /dev/null +++ b/apps/ui/src/styles/themes/monokai.css @@ -0,0 +1,144 @@ +/* Monokai Theme */ + +.monokai { + --background: oklch(0.17 0.01 90); /* #272822 */ + --background-50: oklch(0.17 0.01 90 / 0.5); + --background-80: oklch(0.17 0.01 90 / 0.8); + + --foreground: oklch(0.95 0.02 100); /* #f8f8f2 */ + --foreground-secondary: oklch(0.8 0.02 100); + --foreground-muted: oklch(0.55 0.04 100); /* #75715e */ + + --card: oklch(0.22 0.01 90); /* #3e3d32 */ + --card-foreground: oklch(0.95 0.02 100); + --popover: oklch(0.2 0.01 90); + --popover-foreground: oklch(0.95 0.02 100); + + --primary: oklch(0.8 0.2 350); /* #f92672 pink */ + --primary-foreground: oklch(0.17 0.01 90); + + --brand-400: oklch(0.85 0.2 350); + --brand-500: oklch(0.8 0.2 350); /* #f92672 */ + --brand-600: oklch(0.75 0.22 350); + + --secondary: oklch(0.25 0.02 90); + --secondary-foreground: oklch(0.95 0.02 100); + + --muted: oklch(0.25 0.02 90); + --muted-foreground: oklch(0.55 0.04 100); + + --accent: oklch(0.3 0.02 90); + --accent-foreground: oklch(0.95 0.02 100); + + --destructive: oklch(0.65 0.25 15); /* red */ + + --border: oklch(0.35 0.03 90); + --border-glass: oklch(0.8 0.2 350 / 0.3); + + --input: oklch(0.22 0.01 90); + --ring: oklch(0.8 0.2 350); + + --chart-1: oklch(0.8 0.2 350); /* Pink #f92672 */ + --chart-2: oklch(0.85 0.2 90); /* Yellow #e6db74 */ + --chart-3: oklch(0.8 0.2 140); /* Green #a6e22e */ + --chart-4: oklch(0.75 0.2 200); /* Cyan #66d9ef */ + --chart-5: oklch(0.75 0.2 30); /* Orange #fd971f */ + + --sidebar: oklch(0.15 0.01 90); + --sidebar-foreground: oklch(0.95 0.02 100); + --sidebar-primary: oklch(0.8 0.2 350); + --sidebar-primary-foreground: oklch(0.17 0.01 90); + --sidebar-accent: oklch(0.25 0.02 90); + --sidebar-accent-foreground: oklch(0.95 0.02 100); + --sidebar-border: oklch(0.35 0.03 90); + --sidebar-ring: oklch(0.8 0.2 350); + + /* Action button colors - Monokai pink/yellow theme */ + --action-view: oklch(0.8 0.2 350); /* Pink */ + --action-view-hover: oklch(0.75 0.22 350); + --action-followup: oklch(0.75 0.2 200); /* Cyan */ + --action-followup-hover: oklch(0.7 0.22 200); + --action-commit: oklch(0.8 0.2 140); /* Green */ + --action-commit-hover: oklch(0.75 0.22 140); + --action-verify: oklch(0.8 0.2 140); /* Green */ + --action-verify-hover: oklch(0.75 0.22 140); + + /* Running indicator - Pink */ + --running-indicator: oklch(0.8 0.2 350); + --running-indicator-text: oklch(0.85 0.18 350); +} + +/* ======================================== + TOKYO NIGHT THEME + A clean dark theme celebrating Tokyo at night + ======================================== */ + +/* Theme-specific overrides */ + +.monokai .animated-outline-gradient { + background: conic-gradient(from 90deg at 50% 50%, #f92672 0%, #e6db74 50%, #f92672 100%); +} + +.monokai .animated-outline-inner { + background: oklch(0.17 0.01 90) !important; + color: #f92672 !important; +} + +.monokai [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { + background: oklch(0.22 0.02 90) !important; + color: #e6db74 !important; +} + +.monokai .slider-track { + background: oklch(0.25 0.02 90); +} + +.monokai .slider-range { + background: linear-gradient(to right, #f92672, #fd971f); +} + +.monokai .slider-thumb { + background: oklch(0.22 0.01 90); + border-color: #f92672; +} + +.monokai .xml-highlight { + color: oklch(0.95 0.02 100); /* #f8f8f2 */ +} + +.monokai .xml-tag-bracket { + color: oklch(0.95 0.02 100); /* White */ +} + +.monokai .xml-tag-name { + color: oklch(0.8 0.2 350); /* #f92672 pink */ +} + +.monokai .xml-attribute-name { + color: oklch(0.8 0.2 140); /* #a6e22e green */ +} + +.monokai .xml-attribute-equals { + color: oklch(0.95 0.02 100); /* White */ +} + +.monokai .xml-attribute-value { + color: oklch(0.85 0.2 90); /* #e6db74 yellow */ +} + +.monokai .xml-comment { + color: oklch(0.55 0.04 100); /* #75715e */ + font-style: italic; +} + +.monokai .xml-cdata { + color: oklch(0.75 0.2 200); /* Cyan #66d9ef */ +} + +.monokai .xml-doctype { + color: oklch(0.75 0.2 200); /* Cyan */ +} + +.monokai .xml-text { + color: oklch(0.95 0.02 100); /* White */ +} diff --git a/apps/ui/src/styles/themes/nord.css b/apps/ui/src/styles/themes/nord.css new file mode 100644 index 00000000..2cc98ec0 --- /dev/null +++ b/apps/ui/src/styles/themes/nord.css @@ -0,0 +1,144 @@ +/* Nord Theme */ + +.nord { + --background: oklch(0.23 0.02 240); /* #2e3440 */ + --background-50: oklch(0.23 0.02 240 / 0.5); + --background-80: oklch(0.23 0.02 240 / 0.8); + + --foreground: oklch(0.9 0.01 230); /* #eceff4 */ + --foreground-secondary: oklch(0.75 0.02 230); /* #d8dee9 */ + --foreground-muted: oklch(0.6 0.03 230); /* #4c566a */ + + --card: oklch(0.27 0.02 240); /* #3b4252 */ + --card-foreground: oklch(0.9 0.01 230); + --popover: oklch(0.25 0.02 240); + --popover-foreground: oklch(0.9 0.01 230); + + --primary: oklch(0.7 0.12 220); /* #88c0d0 frost */ + --primary-foreground: oklch(0.23 0.02 240); + + --brand-400: oklch(0.75 0.12 220); + --brand-500: oklch(0.7 0.12 220); /* #88c0d0 */ + --brand-600: oklch(0.65 0.14 220); /* #81a1c1 */ + + --secondary: oklch(0.31 0.02 240); /* #434c5e */ + --secondary-foreground: oklch(0.9 0.01 230); + + --muted: oklch(0.31 0.02 240); + --muted-foreground: oklch(0.55 0.03 230); + + --accent: oklch(0.35 0.03 240); /* #4c566a */ + --accent-foreground: oklch(0.9 0.01 230); + + --destructive: oklch(0.65 0.2 15); /* #bf616a */ + + --border: oklch(0.35 0.03 240); + --border-glass: oklch(0.7 0.12 220 / 0.3); + + --input: oklch(0.27 0.02 240); + --ring: oklch(0.7 0.12 220); + + --chart-1: oklch(0.7 0.12 220); /* Frost blue */ + --chart-2: oklch(0.65 0.14 220); /* #81a1c1 */ + --chart-3: oklch(0.7 0.15 140); /* #a3be8c green */ + --chart-4: oklch(0.7 0.2 320); /* #b48ead purple */ + --chart-5: oklch(0.75 0.15 70); /* #ebcb8b yellow */ + + --sidebar: oklch(0.21 0.02 240); + --sidebar-foreground: oklch(0.9 0.01 230); + --sidebar-primary: oklch(0.7 0.12 220); + --sidebar-primary-foreground: oklch(0.23 0.02 240); + --sidebar-accent: oklch(0.31 0.02 240); + --sidebar-accent-foreground: oklch(0.9 0.01 230); + --sidebar-border: oklch(0.35 0.03 240); + --sidebar-ring: oklch(0.7 0.12 220); + + /* Action button colors - Nord frost blue theme */ + --action-view: oklch(0.7 0.12 220); /* Frost blue */ + --action-view-hover: oklch(0.65 0.14 220); + --action-followup: oklch(0.65 0.14 220); /* Darker frost */ + --action-followup-hover: oklch(0.6 0.16 220); + --action-commit: oklch(0.7 0.15 140); /* Green */ + --action-commit-hover: oklch(0.65 0.17 140); + --action-verify: oklch(0.7 0.15 140); /* Green */ + --action-verify-hover: oklch(0.65 0.17 140); + + /* Running indicator - Frost blue */ + --running-indicator: oklch(0.7 0.12 220); + --running-indicator-text: oklch(0.75 0.1 220); +} + +/* ======================================== + MONOKAI THEME + The classic Monokai color scheme + ======================================== */ + +/* Theme-specific overrides */ + +.nord .animated-outline-gradient { + background: conic-gradient(from 90deg at 50% 50%, #88c0d0 0%, #81a1c1 50%, #88c0d0 100%); +} + +.nord .animated-outline-inner { + background: oklch(0.23 0.02 240) !important; + color: #88c0d0 !important; +} + +.nord [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { + background: oklch(0.28 0.03 240) !important; + color: #8fbcbb !important; +} + +.nord .slider-track { + background: oklch(0.31 0.02 240); +} + +.nord .slider-range { + background: linear-gradient(to right, #88c0d0, #81a1c1); +} + +.nord .slider-thumb { + background: oklch(0.27 0.02 240); + border-color: #88c0d0; +} + +.nord .xml-highlight { + color: oklch(0.9 0.01 230); /* #eceff4 */ +} + +.nord .xml-tag-bracket { + color: oklch(0.65 0.14 220); /* #81a1c1 */ +} + +.nord .xml-tag-name { + color: oklch(0.65 0.14 220); /* Frost blue for tags */ +} + +.nord .xml-attribute-name { + color: oklch(0.7 0.12 220); /* #88c0d0 */ +} + +.nord .xml-attribute-equals { + color: oklch(0.75 0.02 230); /* Dim white */ +} + +.nord .xml-attribute-value { + color: oklch(0.7 0.15 140); /* #a3be8c green */ +} + +.nord .xml-comment { + color: oklch(0.5 0.04 230); /* Dim text */ + font-style: italic; +} + +.nord .xml-cdata { + color: oklch(0.7 0.12 220); /* Frost blue */ +} + +.nord .xml-doctype { + color: oklch(0.7 0.2 320); /* #b48ead purple */ +} + +.nord .xml-text { + color: oklch(0.9 0.01 230); /* Snow white */ +} diff --git a/apps/ui/src/styles/themes/onedark.css b/apps/ui/src/styles/themes/onedark.css new file mode 100644 index 00000000..403dfd9e --- /dev/null +++ b/apps/ui/src/styles/themes/onedark.css @@ -0,0 +1,144 @@ +/* Onedark Theme */ + +.onedark { + --background: oklch(0.19 0.01 250); /* #282c34 */ + --background-50: oklch(0.19 0.01 250 / 0.5); + --background-80: oklch(0.19 0.01 250 / 0.8); + + --foreground: oklch(0.85 0.02 240); /* #abb2bf */ + --foreground-secondary: oklch(0.7 0.02 240); + --foreground-muted: oklch(0.5 0.03 240); /* #5c6370 */ + + --card: oklch(0.23 0.01 250); /* #21252b */ + --card-foreground: oklch(0.85 0.02 240); + --popover: oklch(0.21 0.01 250); + --popover-foreground: oklch(0.85 0.02 240); + + --primary: oklch(0.7 0.18 230); /* #61afef blue */ + --primary-foreground: oklch(0.19 0.01 250); + + --brand-400: oklch(0.75 0.18 230); + --brand-500: oklch(0.7 0.18 230); /* Blue */ + --brand-600: oklch(0.65 0.2 230); + + --secondary: oklch(0.25 0.01 250); + --secondary-foreground: oklch(0.85 0.02 240); + + --muted: oklch(0.25 0.01 250); + --muted-foreground: oklch(0.5 0.03 240); + + --accent: oklch(0.28 0.02 250); + --accent-foreground: oklch(0.85 0.02 240); + + --destructive: oklch(0.6 0.2 20); /* #e06c75 red */ + + --border: oklch(0.35 0.02 250); + --border-glass: oklch(0.7 0.18 230 / 0.3); + + --input: oklch(0.23 0.01 250); + --ring: oklch(0.7 0.18 230); + + --chart-1: oklch(0.7 0.18 230); /* Blue */ + --chart-2: oklch(0.75 0.15 320); /* Magenta #c678dd */ + --chart-3: oklch(0.75 0.18 150); /* Green #98c379 */ + --chart-4: oklch(0.8 0.15 80); /* Yellow #e5c07b */ + --chart-5: oklch(0.7 0.15 180); /* Cyan #56b6c2 */ + + --sidebar: oklch(0.17 0.01 250); + --sidebar-foreground: oklch(0.85 0.02 240); + --sidebar-primary: oklch(0.7 0.18 230); + --sidebar-primary-foreground: oklch(0.19 0.01 250); + --sidebar-accent: oklch(0.25 0.01 250); + --sidebar-accent-foreground: oklch(0.85 0.02 240); + --sidebar-border: oklch(0.35 0.02 250); + --sidebar-ring: oklch(0.7 0.18 230); + + /* Action button colors - One Dark blue/magenta theme */ + --action-view: oklch(0.7 0.18 230); /* Blue */ + --action-view-hover: oklch(0.65 0.2 230); + --action-followup: oklch(0.75 0.15 320); /* Magenta */ + --action-followup-hover: oklch(0.7 0.17 320); + --action-commit: oklch(0.75 0.18 150); /* Green */ + --action-commit-hover: oklch(0.7 0.2 150); + --action-verify: oklch(0.75 0.18 150); /* Green */ + --action-verify-hover: oklch(0.7 0.2 150); + + /* Running indicator - Blue */ + --running-indicator: oklch(0.7 0.18 230); + --running-indicator-text: oklch(0.75 0.16 230); +} + +/* ======================================== + SYNTHWAVE '84 THEME + Neon dreams of the 80s + ======================================== */ + +/* Theme-specific overrides */ + +.onedark .animated-outline-gradient { + background: conic-gradient(from 90deg at 50% 50%, #61afef 0%, #c678dd 50%, #61afef 100%); +} + +.onedark .animated-outline-inner { + background: oklch(0.19 0.01 250) !important; + color: #61afef !important; +} + +.onedark [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { + background: oklch(0.25 0.02 250) !important; + color: #c678dd !important; +} + +.onedark .slider-track { + background: oklch(0.25 0.01 250); +} + +.onedark .slider-range { + background: linear-gradient(to right, #61afef, #c678dd); +} + +.onedark .slider-thumb { + background: oklch(0.23 0.01 250); + border-color: #61afef; +} + +.onedark .xml-highlight { + color: oklch(0.85 0.02 240); /* #abb2bf */ +} + +.onedark .xml-tag-bracket { + color: oklch(0.6 0.2 20); /* #e06c75 red */ +} + +.onedark .xml-tag-name { + color: oklch(0.6 0.2 20); /* Red for tags */ +} + +.onedark .xml-attribute-name { + color: oklch(0.8 0.15 80); /* #e5c07b yellow */ +} + +.onedark .xml-attribute-equals { + color: oklch(0.7 0.02 240); /* Dim text */ +} + +.onedark .xml-attribute-value { + color: oklch(0.75 0.18 150); /* #98c379 green */ +} + +.onedark .xml-comment { + color: oklch(0.5 0.03 240); /* #5c6370 */ + font-style: italic; +} + +.onedark .xml-cdata { + color: oklch(0.7 0.15 180); /* #56b6c2 cyan */ +} + +.onedark .xml-doctype { + color: oklch(0.75 0.15 320); /* #c678dd magenta */ +} + +.onedark .xml-text { + color: oklch(0.85 0.02 240); /* Text */ +} diff --git a/apps/ui/src/styles/themes/red.css b/apps/ui/src/styles/themes/red.css new file mode 100644 index 00000000..5e746adb --- /dev/null +++ b/apps/ui/src/styles/themes/red.css @@ -0,0 +1,70 @@ +/* Red Theme */ + +.red { + --background: oklch(0.12 0.03 15); /* Deep dark red-tinted black */ + --background-50: oklch(0.12 0.03 15 / 0.5); + --background-80: oklch(0.12 0.03 15 / 0.8); + + --foreground: oklch(0.95 0.01 15); /* Off-white with warm tint */ + --foreground-secondary: oklch(0.7 0.02 15); + --foreground-muted: oklch(0.5 0.03 15); + + --card: oklch(0.18 0.04 15); /* Slightly lighter dark red */ + --card-foreground: oklch(0.95 0.01 15); + --popover: oklch(0.15 0.035 15); + --popover-foreground: oklch(0.95 0.01 15); + + --primary: oklch(0.55 0.25 25); /* Vibrant crimson red */ + --primary-foreground: oklch(0.98 0 0); + + --brand-400: oklch(0.6 0.23 25); + --brand-500: oklch(0.55 0.25 25); /* Crimson */ + --brand-600: oklch(0.5 0.27 25); + + --secondary: oklch(0.22 0.05 15); + --secondary-foreground: oklch(0.95 0.01 15); + + --muted: oklch(0.22 0.05 15); + --muted-foreground: oklch(0.5 0.03 15); + + --accent: oklch(0.28 0.06 15); + --accent-foreground: oklch(0.95 0.01 15); + + --destructive: oklch(0.6 0.28 30); /* Bright orange-red for destructive */ + + --border: oklch(0.35 0.08 15); + --border-glass: oklch(0.55 0.25 25 / 0.3); + + --input: oklch(0.18 0.04 15); + --ring: oklch(0.55 0.25 25); + + --chart-1: oklch(0.55 0.25 25); /* Crimson */ + --chart-2: oklch(0.7 0.2 50); /* Orange */ + --chart-3: oklch(0.8 0.18 80); /* Gold */ + --chart-4: oklch(0.6 0.22 0); /* Pure red */ + --chart-5: oklch(0.65 0.2 350); /* Pink-red */ + + --sidebar: oklch(0.1 0.025 15); + --sidebar-foreground: oklch(0.95 0.01 15); + --sidebar-primary: oklch(0.55 0.25 25); + --sidebar-primary-foreground: oklch(0.98 0 0); + --sidebar-accent: oklch(0.22 0.05 15); + --sidebar-accent-foreground: oklch(0.95 0.01 15); + --sidebar-border: oklch(0.35 0.08 15); + --sidebar-ring: oklch(0.55 0.25 25); + + /* Action button colors - Red theme */ + --action-view: oklch(0.55 0.25 25); /* Crimson */ + --action-view-hover: oklch(0.5 0.27 25); + --action-followup: oklch(0.7 0.2 50); /* Orange */ + --action-followup-hover: oklch(0.65 0.22 50); + --action-commit: oklch(0.6 0.2 140); /* Green for positive actions */ + --action-commit-hover: oklch(0.55 0.22 140); + --action-verify: oklch(0.6 0.2 140); /* Green */ + --action-verify-hover: oklch(0.55 0.22 140); + + /* Running indicator - Crimson */ + --running-indicator: oklch(0.55 0.25 25); + --running-indicator-text: oklch(0.6 0.23 25); +} + diff --git a/apps/ui/src/styles/themes/retro.css b/apps/ui/src/styles/themes/retro.css new file mode 100644 index 00000000..4c0c8a4c --- /dev/null +++ b/apps/ui/src/styles/themes/retro.css @@ -0,0 +1,227 @@ +/* Retro Theme */ + +.retro { + /* Retro / Cyberpunk Theme */ + --background: oklch(0 0 0); /* Pure Black */ + --background-50: oklch(0 0 0 / 0.5); + --background-80: oklch(0 0 0 / 0.8); + + /* Neon Green Text */ + --foreground: oklch(0.85 0.25 145); /* Neon Green */ + --foreground-secondary: oklch(0.7 0.2 145); + --foreground-muted: oklch(0.5 0.15 145); + + /* Hard Edges */ + --radius: 0px; + + /* UI Elements */ + --card: oklch(0 0 0); /* Black card */ + --card-foreground: oklch(0.85 0.25 145); + --popover: oklch(0.05 0.05 145); + --popover-foreground: oklch(0.85 0.25 145); + + --primary: oklch(0.85 0.25 145); /* Neon Green */ + --primary-foreground: oklch(0 0 0); /* Black text on green */ + + --brand-400: oklch(0.85 0.25 145); + --brand-500: oklch(0.85 0.25 145); + --brand-600: oklch(0.75 0.25 145); + + --secondary: oklch(0.1 0.1 145); /* Dark Green bg */ + --secondary-foreground: oklch(0.85 0.25 145); + + --muted: oklch(0.1 0.05 145); + --muted-foreground: oklch(0.5 0.15 145); + + --accent: oklch(0.2 0.2 145); /* Brighter green accent */ + --accent-foreground: oklch(0.85 0.25 145); + + --destructive: oklch(0.6 0.25 25); /* Keep red for destructive */ + + --border: oklch(0.3 0.15 145); /* Visible Green Border */ + --border-glass: oklch(0.85 0.25 145 / 0.3); + + --input: oklch(0.1 0.1 145); + --ring: oklch(0.85 0.25 145); + + /* Charts - various neons */ + --chart-1: oklch(0.85 0.25 145); /* Green */ + --chart-2: oklch(0.8 0.25 300); /* Purple Neon */ + --chart-3: oklch(0.8 0.25 200); /* Cyan Neon */ + --chart-4: oklch(0.8 0.25 60); /* Yellow Neon */ + --chart-5: oklch(0.8 0.25 20); /* Red Neon */ + + /* Sidebar */ + --sidebar: oklch(0 0 0); + --sidebar-foreground: oklch(0.85 0.25 145); + --sidebar-primary: oklch(0.85 0.25 145); + --sidebar-primary-foreground: oklch(0 0 0); + --sidebar-accent: oklch(0.1 0.1 145); + --sidebar-accent-foreground: oklch(0.85 0.25 145); + --sidebar-border: oklch(0.3 0.15 145); + --sidebar-ring: oklch(0.85 0.25 145); + + /* Fonts */ + --font-sans: var(--font-geist-mono); /* Force Mono everywhere */ + + /* Action button colors - All green neon for retro theme */ + --action-view: oklch(0.85 0.25 145); /* Neon Green */ + --action-view-hover: oklch(0.9 0.25 145); + --action-followup: oklch(0.85 0.25 145); /* Neon Green */ + --action-followup-hover: oklch(0.9 0.25 145); + --action-commit: oklch(0.85 0.25 145); /* Neon Green */ + --action-commit-hover: oklch(0.9 0.25 145); + --action-verify: oklch(0.85 0.25 145); /* Neon Green */ + --action-verify-hover: oklch(0.9 0.25 145); + + /* Running indicator - Neon Green for retro */ + --running-indicator: oklch(0.85 0.25 145); + --running-indicator-text: oklch(0.85 0.25 145); +} + +/* ======================================== + DRACULA THEME + Inspired by the popular Dracula VS Code theme + ======================================== */ + +/* Theme-specific overrides */ + +.retro .scrollbar-visible::-webkit-scrollbar-thumb { + background: var(--primary); + border-radius: 0; +} + +.retro .scrollbar-visible::-webkit-scrollbar-track { + background: var(--background); + border-radius: 0; +} + +.retro .scrollbar-styled::-webkit-scrollbar-thumb { + background: var(--primary); + border-radius: 0; +} + +.retro .scrollbar-styled::-webkit-scrollbar-track { + background: var(--background); + border-radius: 0; +} + +.retro .glass, +.retro .glass-subtle, + +.retro .glass-strong, +.retro .bg-glass, + +.retro .bg-glass-80 { + backdrop-filter: none; + background: var(--background); + border: 1px solid var(--border); +} + +.retro .gradient-brand { + background: var(--primary); + color: var(--primary-foreground); +} + +.retro .content-bg { + background: + linear-gradient(rgba(0, 255, 65, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 255, 65, 0.03) 1px, transparent 1px), + var(--background); + background-size: 20px 20px; +} + +.retro .animated-outline-gradient { + background: conic-gradient(from 90deg at 50% 50%, #00ff41 0%, #00ffff 25%, #ff00ff 50%, #00ffff 75%, #00ff41 100%); + animation: spin 2s linear infinite, retro-glow 1s ease-in-out infinite alternate; +} + +.retro [data-slot="button"][class*="animated-outline"] { + border-radius: 0 !important; +} + +.retro .animated-outline-inner { + background: oklch(0 0 0) !important; + color: #00ff41 !important; + border-radius: 0 !important; + text-shadow: 0 0 5px #00ff41; + font-family: var(--font-geist-mono), monospace; + text-transform: uppercase; + letter-spacing: 0.1em; +} + +.retro [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { + background: oklch(0.1 0.1 145) !important; + color: #00ff41 !important; + box-shadow: + 0 0 10px #00ff41, + 0 0 20px #00ff41, + inset 0 0 10px rgba(0, 255, 65, 0.1); + text-shadow: 0 0 10px #00ff41, 0 0 20px #00ff41; +} + +.retro .slider-track { + background: oklch(0.15 0.05 145); + border: 1px solid #00ff41; + border-radius: 0 !important; +} + +.retro .slider-range { + background: #00ff41; + box-shadow: 0 0 10px #00ff41, 0 0 5px #00ff41; + border-radius: 0 !important; +} + +.retro .slider-thumb { + background: oklch(0 0 0); + border: 2px solid #00ff41; + border-radius: 0 !important; + box-shadow: 0 0 8px #00ff41; +} + +.retro .slider-thumb:hover { + background: oklch(0.1 0.1 145); + box-shadow: 0 0 12px #00ff41, 0 0 20px #00ff41; +} + +.retro .xml-highlight { + color: oklch(0.85 0.25 145); /* Neon green default */ +} + +.retro .xml-tag-bracket { + color: oklch(0.8 0.25 200); /* Cyan for brackets */ +} + +.retro .xml-tag-name { + color: oklch(0.85 0.25 145); /* Bright green for tags */ + text-shadow: 0 0 5px oklch(0.85 0.25 145 / 0.5); +} + +.retro .xml-attribute-name { + color: oklch(0.8 0.25 300); /* Purple neon for attrs */ +} + +.retro .xml-attribute-equals { + color: oklch(0.6 0.15 145); /* Dim green for = */ +} + +.retro .xml-attribute-value { + color: oklch(0.8 0.25 60); /* Yellow neon for strings */ +} + +.retro .xml-comment { + color: oklch(0.5 0.15 145); /* Dim green for comments */ + font-style: italic; +} + +.retro .xml-cdata { + color: oklch(0.75 0.2 200); /* Cyan for CDATA */ +} + +.retro .xml-doctype { + color: oklch(0.75 0.2 300); /* Purple for DOCTYPE */ +} + +.retro .xml-text { + color: oklch(0.7 0.2 145); /* Green text */ +} diff --git a/apps/ui/src/styles/themes/solarized.css b/apps/ui/src/styles/themes/solarized.css new file mode 100644 index 00000000..eb0989ae --- /dev/null +++ b/apps/ui/src/styles/themes/solarized.css @@ -0,0 +1,144 @@ +/* Solarized Theme */ + +.solarized { + --background: oklch(0.2 0.02 230); /* #002b36 base03 */ + --background-50: oklch(0.2 0.02 230 / 0.5); + --background-80: oklch(0.2 0.02 230 / 0.8); + + --foreground: oklch(0.75 0.02 90); /* #839496 base0 */ + --foreground-secondary: oklch(0.6 0.03 200); /* #657b83 base00 */ + --foreground-muted: oklch(0.5 0.04 200); /* #586e75 base01 */ + + --card: oklch(0.23 0.02 230); /* #073642 base02 */ + --card-foreground: oklch(0.75 0.02 90); + --popover: oklch(0.22 0.02 230); + --popover-foreground: oklch(0.75 0.02 90); + + --primary: oklch(0.65 0.15 220); /* #268bd2 blue */ + --primary-foreground: oklch(0.2 0.02 230); + + --brand-400: oklch(0.7 0.15 220); + --brand-500: oklch(0.65 0.15 220); /* #268bd2 */ + --brand-600: oklch(0.6 0.17 220); + + --secondary: oklch(0.25 0.02 230); + --secondary-foreground: oklch(0.75 0.02 90); + + --muted: oklch(0.25 0.02 230); + --muted-foreground: oklch(0.5 0.04 200); + + --accent: oklch(0.28 0.03 230); + --accent-foreground: oklch(0.75 0.02 90); + + --destructive: oklch(0.55 0.2 25); /* #dc322f red */ + + --border: oklch(0.35 0.03 230); + --border-glass: oklch(0.65 0.15 220 / 0.3); + + --input: oklch(0.23 0.02 230); + --ring: oklch(0.65 0.15 220); + + --chart-1: oklch(0.65 0.15 220); /* Blue */ + --chart-2: oklch(0.6 0.18 180); /* Cyan #2aa198 */ + --chart-3: oklch(0.65 0.2 140); /* Green #859900 */ + --chart-4: oklch(0.7 0.18 55); /* Yellow #b58900 */ + --chart-5: oklch(0.6 0.2 30); /* Orange #cb4b16 */ + + --sidebar: oklch(0.18 0.02 230); + --sidebar-foreground: oklch(0.75 0.02 90); + --sidebar-primary: oklch(0.65 0.15 220); + --sidebar-primary-foreground: oklch(0.2 0.02 230); + --sidebar-accent: oklch(0.25 0.02 230); + --sidebar-accent-foreground: oklch(0.75 0.02 90); + --sidebar-border: oklch(0.35 0.03 230); + --sidebar-ring: oklch(0.65 0.15 220); + + /* Action button colors - Solarized blue/cyan theme */ + --action-view: oklch(0.65 0.15 220); /* Blue */ + --action-view-hover: oklch(0.6 0.17 220); + --action-followup: oklch(0.6 0.18 180); /* Cyan */ + --action-followup-hover: oklch(0.55 0.2 180); + --action-commit: oklch(0.65 0.2 140); /* Green */ + --action-commit-hover: oklch(0.6 0.22 140); + --action-verify: oklch(0.65 0.2 140); /* Green */ + --action-verify-hover: oklch(0.6 0.22 140); + + /* Running indicator - Blue */ + --running-indicator: oklch(0.65 0.15 220); + --running-indicator-text: oklch(0.7 0.13 220); +} + +/* ======================================== + GRUVBOX THEME + Retro groove color scheme + ======================================== */ + +/* Theme-specific overrides */ + +.solarized .animated-outline-gradient { + background: conic-gradient(from 90deg at 50% 50%, #268bd2 0%, #2aa198 50%, #268bd2 100%); +} + +.solarized .animated-outline-inner { + background: oklch(0.2 0.02 230) !important; + color: #268bd2 !important; +} + +.solarized [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { + background: oklch(0.25 0.03 230) !important; + color: #2aa198 !important; +} + +.solarized .slider-track { + background: oklch(0.25 0.02 230); +} + +.solarized .slider-range { + background: linear-gradient(to right, #268bd2, #2aa198); +} + +.solarized .slider-thumb { + background: oklch(0.23 0.02 230); + border-color: #268bd2; +} + +.solarized .xml-highlight { + color: oklch(0.75 0.02 90); /* #839496 */ +} + +.solarized .xml-tag-bracket { + color: oklch(0.65 0.15 220); /* #268bd2 blue */ +} + +.solarized .xml-tag-name { + color: oklch(0.65 0.15 220); /* Blue for tags */ +} + +.solarized .xml-attribute-name { + color: oklch(0.6 0.18 180); /* #2aa198 cyan */ +} + +.solarized .xml-attribute-equals { + color: oklch(0.75 0.02 90); /* Base text */ +} + +.solarized .xml-attribute-value { + color: oklch(0.65 0.2 140); /* #859900 green */ +} + +.solarized .xml-comment { + color: oklch(0.5 0.04 200); /* #586e75 */ + font-style: italic; +} + +.solarized .xml-cdata { + color: oklch(0.6 0.18 180); /* Cyan */ +} + +.solarized .xml-doctype { + color: oklch(0.6 0.2 290); /* #6c71c4 violet */ +} + +.solarized .xml-text { + color: oklch(0.75 0.02 90); /* Base text */ +} diff --git a/apps/ui/src/styles/themes/sunset.css b/apps/ui/src/styles/themes/sunset.css new file mode 100644 index 00000000..7f523f6e --- /dev/null +++ b/apps/ui/src/styles/themes/sunset.css @@ -0,0 +1,111 @@ +/* Sunset Theme */ + +.sunset { + /* Sunset Theme - Mellow oranges and soft purples */ + --background: oklch(0.15 0.02 280); /* Deep twilight blue-purple */ + --background-50: oklch(0.15 0.02 280 / 0.5); + --background-80: oklch(0.15 0.02 280 / 0.8); + + --foreground: oklch(0.95 0.01 80); /* Warm white */ + --foreground-secondary: oklch(0.75 0.02 60); + --foreground-muted: oklch(0.6 0.02 60); + + --card: oklch(0.2 0.025 280); + --card-foreground: oklch(0.95 0.01 80); + --popover: oklch(0.18 0.02 280); + --popover-foreground: oklch(0.95 0.01 80); + + --primary: oklch(0.68 0.18 45); /* Mellow sunset orange */ + --primary-foreground: oklch(0.15 0.02 280); + + --brand-400: oklch(0.72 0.17 45); + --brand-500: oklch(0.68 0.18 45); /* Soft sunset orange */ + --brand-600: oklch(0.64 0.19 42); + + --secondary: oklch(0.25 0.03 280); + --secondary-foreground: oklch(0.95 0.01 80); + + --muted: oklch(0.27 0.03 280); + --muted-foreground: oklch(0.6 0.02 60); + + --accent: oklch(0.35 0.04 310); + --accent-foreground: oklch(0.95 0.01 80); + + --destructive: oklch(0.6 0.2 25); /* Muted red */ + + --border: oklch(0.32 0.04 280); + --border-glass: oklch(0.68 0.18 45 / 0.3); + + --input: oklch(0.2 0.025 280); + --ring: oklch(0.68 0.18 45); + + --chart-1: oklch(0.68 0.18 45); /* Mellow orange */ + --chart-2: oklch(0.75 0.16 340); /* Soft pink sunset */ + --chart-3: oklch(0.78 0.18 70); /* Soft golden */ + --chart-4: oklch(0.66 0.19 42); /* Subtle coral */ + --chart-5: oklch(0.72 0.14 310); /* Pastel purple */ + + --sidebar: oklch(0.13 0.015 280); + --sidebar-foreground: oklch(0.95 0.01 80); + --sidebar-primary: oklch(0.68 0.18 45); + --sidebar-primary-foreground: oklch(0.15 0.02 280); + --sidebar-accent: oklch(0.25 0.03 280); + --sidebar-accent-foreground: oklch(0.95 0.01 80); + --sidebar-border: oklch(0.32 0.04 280); + --sidebar-ring: oklch(0.68 0.18 45); + + /* Action button colors - Mellow sunset palette */ + --action-view: oklch(0.68 0.18 45); /* Mellow orange */ + --action-view-hover: oklch(0.64 0.19 42); + --action-followup: oklch(0.75 0.16 340); /* Soft pink */ + --action-followup-hover: oklch(0.7 0.17 340); + --action-commit: oklch(0.65 0.16 140); /* Soft green */ + --action-commit-hover: oklch(0.6 0.17 140); + --action-verify: oklch(0.65 0.16 140); /* Soft green */ + --action-verify-hover: oklch(0.6 0.17 140); + + /* Running indicator - Mellow orange */ + --running-indicator: oklch(0.68 0.18 45); + --running-indicator-text: oklch(0.72 0.17 45); + + /* Status colors - Sunset theme */ + --status-success: oklch(0.65 0.16 140); + --status-success-bg: oklch(0.65 0.16 140 / 0.2); + --status-warning: oklch(0.78 0.18 70); + --status-warning-bg: oklch(0.78 0.18 70 / 0.2); + --status-error: oklch(0.65 0.2 25); + --status-error-bg: oklch(0.65 0.2 25 / 0.2); + --status-info: oklch(0.75 0.16 340); + --status-info-bg: oklch(0.75 0.16 340 / 0.2); + --status-backlog: oklch(0.65 0.02 280); + --status-in-progress: oklch(0.78 0.18 70); + --status-waiting: oklch(0.72 0.17 60); +} + + +/* Theme-specific overrides */ + +/* Sunset theme scrollbar */ +.sunset ::-webkit-scrollbar-thumb, +.sunset .scrollbar-visible::-webkit-scrollbar-thumb { + background: oklch(0.5 0.14 45); + border-radius: 4px; +} + +.sunset ::-webkit-scrollbar-thumb:hover, +.sunset .scrollbar-visible::-webkit-scrollbar-thumb:hover { + background: oklch(0.58 0.16 45); +} + +.sunset ::-webkit-scrollbar-track, +.sunset .scrollbar-visible::-webkit-scrollbar-track { + background: oklch(0.18 0.03 280); +} + +.sunset .scrollbar-styled::-webkit-scrollbar-thumb { + background: oklch(0.5 0.14 45); +} + +.sunset .scrollbar-styled::-webkit-scrollbar-thumb:hover { + background: oklch(0.58 0.16 45); +} diff --git a/apps/ui/src/styles/themes/synthwave.css b/apps/ui/src/styles/themes/synthwave.css new file mode 100644 index 00000000..ddb956ba --- /dev/null +++ b/apps/ui/src/styles/themes/synthwave.css @@ -0,0 +1,149 @@ +/* Synthwave Theme */ + +.synthwave { + --background: oklch(0.15 0.05 290); /* #262335 */ + --background-50: oklch(0.15 0.05 290 / 0.5); + --background-80: oklch(0.15 0.05 290 / 0.8); + + --foreground: oklch(0.95 0.02 320); /* #ffffff with warm tint */ + --foreground-secondary: oklch(0.75 0.05 320); + --foreground-muted: oklch(0.55 0.08 290); + + --card: oklch(0.2 0.06 290); /* #34294f */ + --card-foreground: oklch(0.95 0.02 320); + --popover: oklch(0.18 0.05 290); + --popover-foreground: oklch(0.95 0.02 320); + + --primary: oklch(0.7 0.28 350); /* #f97e72 hot pink */ + --primary-foreground: oklch(0.15 0.05 290); + + --brand-400: oklch(0.75 0.28 350); + --brand-500: oklch(0.7 0.28 350); /* Hot pink */ + --brand-600: oklch(0.65 0.3 350); + + --secondary: oklch(0.25 0.07 290); + --secondary-foreground: oklch(0.95 0.02 320); + + --muted: oklch(0.25 0.07 290); + --muted-foreground: oklch(0.55 0.08 290); + + --accent: oklch(0.3 0.08 290); + --accent-foreground: oklch(0.95 0.02 320); + + --destructive: oklch(0.6 0.25 15); + + --border: oklch(0.4 0.1 290); + --border-glass: oklch(0.7 0.28 350 / 0.3); + + --input: oklch(0.2 0.06 290); + --ring: oklch(0.7 0.28 350); + + --chart-1: oklch(0.7 0.28 350); /* Hot pink */ + --chart-2: oklch(0.8 0.25 200); /* Cyan #72f1b8 */ + --chart-3: oklch(0.85 0.2 60); /* Yellow #fede5d */ + --chart-4: oklch(0.7 0.25 280); /* Purple #ff7edb */ + --chart-5: oklch(0.7 0.2 30); /* Orange #f97e72 */ + + --sidebar: oklch(0.13 0.05 290); + --sidebar-foreground: oklch(0.95 0.02 320); + --sidebar-primary: oklch(0.7 0.28 350); + --sidebar-primary-foreground: oklch(0.15 0.05 290); + --sidebar-accent: oklch(0.25 0.07 290); + --sidebar-accent-foreground: oklch(0.95 0.02 320); + --sidebar-border: oklch(0.4 0.1 290); + --sidebar-ring: oklch(0.7 0.28 350); + + /* Action button colors - Synthwave hot pink/cyan theme */ + --action-view: oklch(0.7 0.28 350); /* Hot pink */ + --action-view-hover: oklch(0.65 0.3 350); + --action-followup: oklch(0.8 0.25 200); /* Cyan */ + --action-followup-hover: oklch(0.75 0.27 200); + --action-commit: oklch(0.85 0.2 60); /* Yellow */ + --action-commit-hover: oklch(0.8 0.22 60); + --action-verify: oklch(0.85 0.2 60); /* Yellow */ + --action-verify-hover: oklch(0.8 0.22 60); + + /* Running indicator - Hot pink */ + --running-indicator: oklch(0.7 0.28 350); + --running-indicator-text: oklch(0.75 0.26 350); +} + +/* Red Theme - Bold crimson/red aesthetic */ + +/* Theme-specific overrides */ + +.synthwave .animated-outline-gradient { + background: conic-gradient(from 90deg at 50% 50%, #f97e72 0%, #72f1b8 25%, #ff7edb 50%, #72f1b8 75%, #f97e72 100%); + animation: spin 2s linear infinite, synthwave-glow 1.5s ease-in-out infinite alternate; +} + +.synthwave .animated-outline-inner { + background: oklch(0.15 0.05 290) !important; + color: #f97e72 !important; + text-shadow: 0 0 8px #f97e72; +} + +.synthwave [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { + background: oklch(0.22 0.07 290) !important; + color: #72f1b8 !important; + text-shadow: 0 0 12px #72f1b8; + box-shadow: 0 0 15px rgba(114, 241, 184, 0.3); +} + +.synthwave .slider-track { + background: oklch(0.25 0.07 290); +} + +.synthwave .slider-range { + background: linear-gradient(to right, #f97e72, #ff7edb); + box-shadow: 0 0 10px #f97e72, 0 0 5px #ff7edb; +} + +.synthwave .slider-thumb { + background: oklch(0.2 0.06 290); + border-color: #f97e72; + box-shadow: 0 0 8px #f97e72; +} + +.synthwave .xml-highlight { + color: oklch(0.95 0.02 320); /* Warm white */ +} + +.synthwave .xml-tag-bracket { + color: oklch(0.7 0.28 350); /* #f97e72 hot pink */ +} + +.synthwave .xml-tag-name { + color: oklch(0.7 0.28 350); /* Hot pink */ + text-shadow: 0 0 8px oklch(0.7 0.28 350 / 0.5); +} + +.synthwave .xml-attribute-name { + color: oklch(0.7 0.25 280); /* #ff7edb purple */ +} + +.synthwave .xml-attribute-equals { + color: oklch(0.8 0.02 320); /* White-ish */ +} + +.synthwave .xml-attribute-value { + color: oklch(0.85 0.2 60); /* #fede5d yellow */ + text-shadow: 0 0 5px oklch(0.85 0.2 60 / 0.3); +} + +.synthwave .xml-comment { + color: oklch(0.55 0.08 290); /* Dim purple */ + font-style: italic; +} + +.synthwave .xml-cdata { + color: oklch(0.8 0.25 200); /* #72f1b8 cyan */ +} + +.synthwave .xml-doctype { + color: oklch(0.8 0.25 200); /* Cyan */ +} + +.synthwave .xml-text { + color: oklch(0.95 0.02 320); /* White */ +} diff --git a/apps/ui/src/styles/themes/tokyonight.css b/apps/ui/src/styles/themes/tokyonight.css new file mode 100644 index 00000000..8bc907b7 --- /dev/null +++ b/apps/ui/src/styles/themes/tokyonight.css @@ -0,0 +1,144 @@ +/* Tokyonight Theme */ + +.tokyonight { + --background: oklch(0.16 0.03 260); /* #1a1b26 */ + --background-50: oklch(0.16 0.03 260 / 0.5); + --background-80: oklch(0.16 0.03 260 / 0.8); + + --foreground: oklch(0.85 0.02 250); /* #a9b1d6 */ + --foreground-secondary: oklch(0.7 0.03 250); + --foreground-muted: oklch(0.5 0.04 250); /* #565f89 */ + + --card: oklch(0.2 0.03 260); /* #24283b */ + --card-foreground: oklch(0.85 0.02 250); + --popover: oklch(0.18 0.03 260); + --popover-foreground: oklch(0.85 0.02 250); + + --primary: oklch(0.7 0.18 280); /* #7aa2f7 blue */ + --primary-foreground: oklch(0.16 0.03 260); + + --brand-400: oklch(0.75 0.18 280); + --brand-500: oklch(0.7 0.18 280); /* #7aa2f7 */ + --brand-600: oklch(0.65 0.2 280); /* #7dcfff */ + + --secondary: oklch(0.24 0.03 260); /* #292e42 */ + --secondary-foreground: oklch(0.85 0.02 250); + + --muted: oklch(0.24 0.03 260); + --muted-foreground: oklch(0.5 0.04 250); + + --accent: oklch(0.28 0.04 260); + --accent-foreground: oklch(0.85 0.02 250); + + --destructive: oklch(0.65 0.2 15); /* #f7768e */ + + --border: oklch(0.32 0.04 260); + --border-glass: oklch(0.7 0.18 280 / 0.3); + + --input: oklch(0.2 0.03 260); + --ring: oklch(0.7 0.18 280); + + --chart-1: oklch(0.7 0.18 280); /* Blue #7aa2f7 */ + --chart-2: oklch(0.75 0.18 200); /* Cyan #7dcfff */ + --chart-3: oklch(0.75 0.18 140); /* Green #9ece6a */ + --chart-4: oklch(0.7 0.2 320); /* Magenta #bb9af7 */ + --chart-5: oklch(0.8 0.18 70); /* Yellow #e0af68 */ + + --sidebar: oklch(0.14 0.03 260); + --sidebar-foreground: oklch(0.85 0.02 250); + --sidebar-primary: oklch(0.7 0.18 280); + --sidebar-primary-foreground: oklch(0.16 0.03 260); + --sidebar-accent: oklch(0.24 0.03 260); + --sidebar-accent-foreground: oklch(0.85 0.02 250); + --sidebar-border: oklch(0.32 0.04 260); + --sidebar-ring: oklch(0.7 0.18 280); + + /* Action button colors - Tokyo Night blue/magenta theme */ + --action-view: oklch(0.7 0.18 280); /* Blue */ + --action-view-hover: oklch(0.65 0.2 280); + --action-followup: oklch(0.75 0.18 200); /* Cyan */ + --action-followup-hover: oklch(0.7 0.2 200); + --action-commit: oklch(0.75 0.18 140); /* Green */ + --action-commit-hover: oklch(0.7 0.2 140); + --action-verify: oklch(0.75 0.18 140); /* Green */ + --action-verify-hover: oklch(0.7 0.2 140); + + /* Running indicator - Blue */ + --running-indicator: oklch(0.7 0.18 280); + --running-indicator-text: oklch(0.75 0.16 280); +} + +/* ======================================== + SOLARIZED DARK THEME + The classic color scheme by Ethan Schoonover + ======================================== */ + +/* Theme-specific overrides */ + +.tokyonight .animated-outline-gradient { + background: conic-gradient(from 90deg at 50% 50%, #7aa2f7 0%, #bb9af7 50%, #7aa2f7 100%); +} + +.tokyonight .animated-outline-inner { + background: oklch(0.16 0.03 260) !important; + color: #7aa2f7 !important; +} + +.tokyonight [data-slot="button"][class*="animated-outline"]:hover .animated-outline-inner { + background: oklch(0.22 0.04 260) !important; + color: #bb9af7 !important; +} + +.tokyonight .slider-track { + background: oklch(0.24 0.03 260); +} + +.tokyonight .slider-range { + background: linear-gradient(to right, #7aa2f7, #bb9af7); +} + +.tokyonight .slider-thumb { + background: oklch(0.2 0.03 260); + border-color: #7aa2f7; +} + +.tokyonight .xml-highlight { + color: oklch(0.85 0.02 250); /* #a9b1d6 */ +} + +.tokyonight .xml-tag-bracket { + color: oklch(0.65 0.2 15); /* #f7768e red */ +} + +.tokyonight .xml-tag-name { + color: oklch(0.65 0.2 15); /* Red for tags */ +} + +.tokyonight .xml-attribute-name { + color: oklch(0.7 0.2 320); /* #bb9af7 purple */ +} + +.tokyonight .xml-attribute-equals { + color: oklch(0.75 0.02 250); /* Dim text */ +} + +.tokyonight .xml-attribute-value { + color: oklch(0.75 0.18 140); /* #9ece6a green */ +} + +.tokyonight .xml-comment { + color: oklch(0.5 0.04 250); /* #565f89 */ + font-style: italic; +} + +.tokyonight .xml-cdata { + color: oklch(0.75 0.18 200); /* #7dcfff cyan */ +} + +.tokyonight .xml-doctype { + color: oklch(0.7 0.18 280); /* #7aa2f7 blue */ +} + +.tokyonight .xml-text { + color: oklch(0.85 0.02 250); /* Text color */ +}