diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 30bd37a..7d86181 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
+import { ThemeProvider } from '@/components/providers/theme-provider';
const inter = Inter({ subsets: ['latin'] });
@@ -15,8 +16,10 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
-
-
{children}
+
+
+ {children}
+
);
}
diff --git a/apps/web/components/providers/theme-provider.tsx b/apps/web/components/providers/theme-provider.tsx
new file mode 100644
index 0000000..b685368
--- /dev/null
+++ b/apps/web/components/providers/theme-provider.tsx
@@ -0,0 +1,26 @@
+'use client';
+
+import { useEffect } from 'react';
+import { useThemeStore } from '@/stores/theme-store';
+import { themes } from '@/themes';
+
+export function ThemeProvider({ children }: { children: React.ReactNode }) {
+ const { theme } = useThemeStore();
+
+ useEffect(() => {
+ const selectedTheme = themes[theme];
+ const root = document.documentElement;
+
+ Object.entries(selectedTheme.cssVars).forEach(([key, value]) => {
+ root.style.setProperty(key, value);
+ });
+
+ if (theme === 'dark') {
+ root.classList.add('dark');
+ } else {
+ root.classList.remove('dark');
+ }
+ }, [theme]);
+
+ return <>{children}>;
+}
diff --git a/apps/web/lib/utils.ts b/apps/web/lib/utils.ts
new file mode 100644
index 0000000..9ad0df4
--- /dev/null
+++ b/apps/web/lib/utils.ts
@@ -0,0 +1,6 @@
+import { type ClassValue, clsx } from 'clsx';
+import { twMerge } from 'tailwind-merge';
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/apps/web/stores/theme-store.ts b/apps/web/stores/theme-store.ts
new file mode 100644
index 0000000..3018e00
--- /dev/null
+++ b/apps/web/stores/theme-store.ts
@@ -0,0 +1,20 @@
+import { create } from 'zustand';
+import { persist } from 'zustand/middleware';
+import type { ThemeName } from '@/themes';
+
+interface ThemeState {
+ theme: ThemeName;
+ setTheme: (theme: ThemeName) => void;
+}
+
+export const useThemeStore = create()(
+ persist(
+ (set) => ({
+ theme: 'light',
+ setTheme: (theme) => set({ theme }),
+ }),
+ {
+ name: 'horux-theme',
+ }
+ )
+);
diff --git a/apps/web/themes/corporate.ts b/apps/web/themes/corporate.ts
new file mode 100644
index 0000000..2c5e398
--- /dev/null
+++ b/apps/web/themes/corporate.ts
@@ -0,0 +1,32 @@
+export const corporateTheme = {
+ name: 'corporate' as const,
+ label: 'Corporate',
+ layout: 'multi-panel',
+ cssVars: {
+ '--background': '210 20% 96%',
+ '--foreground': '210 50% 10%',
+ '--card': '0 0% 100%',
+ '--card-foreground': '210 50% 10%',
+ '--primary': '210 100% 25%',
+ '--primary-foreground': '0 0% 100%',
+ '--secondary': '210 20% 92%',
+ '--secondary-foreground': '210 50% 10%',
+ '--muted': '210 20% 92%',
+ '--muted-foreground': '210 15% 45%',
+ '--accent': '43 96% 56%',
+ '--accent-foreground': '210 50% 10%',
+ '--destructive': '0 84.2% 60.2%',
+ '--destructive-foreground': '210 40% 98%',
+ '--success': '142.1 76.2% 36.3%',
+ '--success-foreground': '355.7 100% 97.3%',
+ '--border': '210 20% 85%',
+ '--input': '210 20% 85%',
+ '--ring': '210 100% 25%',
+ '--radius': '0.25rem',
+ },
+ sidebar: {
+ width: '200px',
+ collapsible: false,
+ },
+ density: 'compact',
+};
diff --git a/apps/web/themes/dark.ts b/apps/web/themes/dark.ts
new file mode 100644
index 0000000..90326d7
--- /dev/null
+++ b/apps/web/themes/dark.ts
@@ -0,0 +1,36 @@
+export const darkTheme = {
+ name: 'dark' as const,
+ label: 'Dark',
+ layout: 'minimal-floating',
+ cssVars: {
+ '--background': '0 0% 3.9%',
+ '--foreground': '0 0% 98%',
+ '--card': '0 0% 6%',
+ '--card-foreground': '0 0% 98%',
+ '--primary': '187.2 85.7% 53.3%',
+ '--primary-foreground': '0 0% 3.9%',
+ '--secondary': '0 0% 12%',
+ '--secondary-foreground': '0 0% 98%',
+ '--muted': '0 0% 12%',
+ '--muted-foreground': '0 0% 63.9%',
+ '--accent': '142.1 70.6% 45.3%',
+ '--accent-foreground': '0 0% 3.9%',
+ '--destructive': '0 62.8% 30.6%',
+ '--destructive-foreground': '0 0% 98%',
+ '--success': '142.1 70.6% 45.3%',
+ '--success-foreground': '144.9 80.4% 10%',
+ '--border': '0 0% 14.9%',
+ '--input': '0 0% 14.9%',
+ '--ring': '187.2 85.7% 53.3%',
+ '--radius': '0.75rem',
+ },
+ sidebar: {
+ width: '64px',
+ collapsible: false,
+ iconsOnly: true,
+ },
+ effects: {
+ blur: '10px',
+ glow: '0 0 20px rgba(34,211,238,0.3)',
+ },
+};
diff --git a/apps/web/themes/index.ts b/apps/web/themes/index.ts
new file mode 100644
index 0000000..6bf60e4
--- /dev/null
+++ b/apps/web/themes/index.ts
@@ -0,0 +1,16 @@
+import { lightTheme } from './light';
+import { vibrantTheme } from './vibrant';
+import { corporateTheme } from './corporate';
+import { darkTheme } from './dark';
+
+export const themes = {
+ light: lightTheme,
+ vibrant: vibrantTheme,
+ corporate: corporateTheme,
+ dark: darkTheme,
+} as const;
+
+export type ThemeName = keyof typeof themes;
+export type Theme = (typeof themes)[ThemeName];
+
+export { lightTheme, vibrantTheme, corporateTheme, darkTheme };
diff --git a/apps/web/themes/light.ts b/apps/web/themes/light.ts
new file mode 100644
index 0000000..c8014e3
--- /dev/null
+++ b/apps/web/themes/light.ts
@@ -0,0 +1,31 @@
+export const lightTheme = {
+ name: 'light' as const,
+ label: 'Light',
+ layout: 'sidebar-fixed',
+ cssVars: {
+ '--background': '0 0% 100%',
+ '--foreground': '222.2 84% 4.9%',
+ '--card': '0 0% 100%',
+ '--card-foreground': '222.2 84% 4.9%',
+ '--primary': '221.2 83.2% 53.3%',
+ '--primary-foreground': '210 40% 98%',
+ '--secondary': '210 40% 96.1%',
+ '--secondary-foreground': '222.2 47.4% 11.2%',
+ '--muted': '210 40% 96.1%',
+ '--muted-foreground': '215.4 16.3% 46.9%',
+ '--accent': '210 40% 96.1%',
+ '--accent-foreground': '222.2 47.4% 11.2%',
+ '--destructive': '0 84.2% 60.2%',
+ '--destructive-foreground': '210 40% 98%',
+ '--success': '142.1 76.2% 36.3%',
+ '--success-foreground': '355.7 100% 97.3%',
+ '--border': '214.3 31.8% 91.4%',
+ '--input': '214.3 31.8% 91.4%',
+ '--ring': '221.2 83.2% 53.3%',
+ '--radius': '0.5rem',
+ },
+ sidebar: {
+ width: '240px',
+ collapsible: false,
+ },
+};
diff --git a/apps/web/themes/vibrant.ts b/apps/web/themes/vibrant.ts
new file mode 100644
index 0000000..4fb2441
--- /dev/null
+++ b/apps/web/themes/vibrant.ts
@@ -0,0 +1,31 @@
+export const vibrantTheme = {
+ name: 'vibrant' as const,
+ label: 'Vibrant',
+ layout: 'sidebar-collapsible',
+ cssVars: {
+ '--background': '270 50% 98%',
+ '--foreground': '263.4 84% 6.7%',
+ '--card': '0 0% 100%',
+ '--card-foreground': '263.4 84% 6.7%',
+ '--primary': '262.1 83.3% 57.8%',
+ '--primary-foreground': '210 40% 98%',
+ '--secondary': '187 85.7% 53.3%',
+ '--secondary-foreground': '222.2 47.4% 11.2%',
+ '--muted': '270 30% 94%',
+ '--muted-foreground': '263.4 25% 40%',
+ '--accent': '24.6 95% 53.1%',
+ '--accent-foreground': '0 0% 100%',
+ '--destructive': '0 84.2% 60.2%',
+ '--destructive-foreground': '210 40% 98%',
+ '--success': '142.1 76.2% 36.3%',
+ '--success-foreground': '355.7 100% 97.3%',
+ '--border': '270 30% 88%',
+ '--input': '270 30% 88%',
+ '--ring': '262.1 83.3% 57.8%',
+ '--radius': '1rem',
+ },
+ sidebar: {
+ width: '280px',
+ collapsible: true,
+ },
+};