feat: add bw themes
ci-front / build (push) Successful in 2m26s

This commit is contained in:
2026-04-03 21:49:39 +03:00
parent cc23cc2a1e
commit 88fb7a1888
9 changed files with 642 additions and 235 deletions
+10 -3
View File
@@ -2,6 +2,9 @@ import { Suspense } from "react";
import { Routes as ReactRoutes, Route, Navigate } from "react-router-dom";
import { HomePage } from "@/pages/home.page";
import { ThemesPage } from "@/pages/themes.page";
import { AuthPage } from "@/pages/auth.page";
import { RegisterPage } from "@/pages/register.page";
import { DefaultLayout } from "@/shared/layouts/DefaultLayout";
export const Routing = () => {
return (
@@ -13,10 +16,14 @@ export const Routing = () => {
}
>
<ReactRoutes>
<Route path="/" element={<HomePage />} />
<Route path="/themes" element={<ThemesPage />} />
<Route element={<DefaultLayout />}>
<Route path="/" element={<HomePage />} />
<Route path="/auth" element={<AuthPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/themes" element={<ThemesPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</ReactRoutes>
</Suspense>
);
@@ -0,0 +1,97 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import type {
AuthState,
LoginCredentials,
RegisterData,
User,
} from "../types/auth.types";
// Mock API functions - замените на реальные запросы
const mockLogin = async (
credentials: LoginCredentials,
): Promise<{ user: User; token: string }> => {
// Имитация API запроса
await new Promise((resolve) => setTimeout(resolve, 1000));
if (credentials.login === "admin" && credentials.password === "admin") {
return {
user: {
id: "1",
login: credentials.login,
firstName: "Admin",
lastName: "User",
},
token: "mock-jwt-token",
};
}
throw new Error("Invalid credentials");
};
const mockRegister = async (
data: RegisterData,
): Promise<{ user: User; token: string }> => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return {
user: {
id: Date.now().toString(),
login: data.login,
firstName: data.firstName,
lastName: data.lastName,
},
token: "mock-jwt-token",
};
};
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
user: null,
token: null,
isLoading: false,
error: null,
login: async (credentials: LoginCredentials) => {
set({ isLoading: true, error: null });
try {
const { user, token } = await mockLogin(credentials);
set({ user, token, isLoading: false });
} catch (error) {
set({
error: error instanceof Error ? error.message : "Login failed",
isLoading: false,
});
throw error;
}
},
register: async (data: RegisterData) => {
set({ isLoading: true, error: null });
try {
const { user, token } = await mockRegister(data);
set({ user, token, isLoading: false });
} catch (error) {
set({
error:
error instanceof Error ? error.message : "Registration failed",
isLoading: false,
});
throw error;
}
},
logout: () => {
set({ user: null, token: null, error: null });
},
clearError: () => {
set({ error: null });
},
}),
{
name: "auth-storage",
partialize: (state) => ({ token: state.token, user: state.user }),
},
),
);
@@ -0,0 +1,31 @@
export interface LoginCredentials {
login: string;
password: string;
}
export interface RegisterData {
login: string;
password: string;
firstName: string;
lastName: string;
}
export interface User {
id: string;
login: string;
firstName: string;
lastName: string;
}
export interface AuthState {
user: User | null;
token: string | null;
isLoading: boolean;
error: string | null;
login: (credentials: LoginCredentials) => Promise<void>;
register: (data: RegisterData) => Promise<void>;
logout: () => void;
clearError: () => void;
}
export type Theme = "light" | "dark";
@@ -0,0 +1,54 @@
import type { Theme } from "@/modules/auth/types/auth.types";
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface ThemeState {
theme: Theme;
toggleTheme: () => void;
setTheme: (theme: Theme) => void;
}
export const useThemeStore = create<ThemeState>()(
persist(
(set) => ({
theme: "dark",
toggleTheme: () => {
set((state) => {
const newTheme = state.theme === "dark" ? "light" : "dark";
// Применяем класс к documentElement
if (newTheme === "dark") {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
return { theme: newTheme };
});
},
setTheme: (theme: Theme) => {
if (theme === "dark") {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
set({ theme });
},
}),
{
name: "theme-storage",
},
),
);
// Инициализация темы при загрузке
if (typeof window !== "undefined") {
const storedTheme = localStorage.getItem("theme-storage");
if (storedTheme) {
const { state } = JSON.parse(storedTheme);
if (state.theme === "dark") {
document.documentElement.classList.add("dark");
}
} else {
// По умолчанию dark
document.documentElement.classList.add("dark");
}
}
@@ -0,0 +1,21 @@
import React from "react";
import { useThemeStore } from "../stores/theme.store";
import { FiSun, FiMoon } from "react-icons/fi";
export const ThemeToggle: React.FC = () => {
const { theme, toggleTheme } = useThemeStore();
return (
<button
onClick={toggleTheme}
className="p-2 rounded-lg bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 transition-colors duration-200"
aria-label="Toggle theme"
>
{theme === "dark" ? (
<FiSun className="w-5 h-5 text-yellow-500" />
) : (
<FiMoon className="w-5 h-5 text-gray-700" />
)}
</button>
);
};
+130
View File
@@ -0,0 +1,130 @@
import React, { useState, useEffect } from "react";
import { useNavigate, Link } from "react-router-dom";
import { FiUser, FiLock, FiLogIn } from "react-icons/fi";
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
export const AuthPage: React.FC = () => {
const navigate = useNavigate();
const { login, isLoading, error, clearError, token } = useAuthStore();
const [formData, setFormData] = useState({
login: "",
password: "",
});
useEffect(() => {
if (token) {
navigate("/dashboard");
}
}, [token, navigate]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await login(formData);
navigate("/dashboard");
} catch (err) {
// Error is handled by store
}
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
if (error) clearError();
};
return (
<div className="pt-[25%] flex items-center justify-center bg-white dark:bg-black transition-colors duration-200">
<div className="w-full max-w-md px-8">
{/* Card */}
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg shadow-xl p-8 border border-gray-200 dark:border-gray-800">
{/* Header */}
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Welcome Back
</h1>
<p className="text-gray-600 dark:text-gray-400">
Sign in to your account
</p>
</div>
{/* Error Message */}
{error && (
<div className="mb-4 p-3 bg-red-100 dark:bg-red-900/20 border border-red-400 dark:border-red-800 rounded text-red-700 dark:text-red-400 text-sm">
{error}
</div>
)}
{/* Form */}
<form onSubmit={handleSubmit} className="space-y-5">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Login
</label>
<div className="relative">
<FiUser className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-gray-500" />
<input
type="text"
name="login"
value={formData.login}
onChange={handleChange}
required
className="w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-gray-500 dark:focus:ring-gray-400 transition-colors"
placeholder="Enter your login"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Password
</label>
<div className="relative">
<FiLock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-gray-500" />
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
required
className="w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-gray-500 dark:focus:ring-gray-400 transition-colors"
placeholder="Enter your password"
/>
</div>
</div>
<button
type="submit"
disabled={isLoading}
className="w-full flex items-center justify-center gap-2 px-4 py-2 bg-gray-900 dark:bg-white text-white dark:text-gray-900 rounded-lg hover:bg-gray-800 dark:hover:bg-gray-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed font-medium"
>
{isLoading ? (
"Signing in..."
) : (
<>
<FiLogIn />
Sign In
</>
)}
</button>
</form>
{/* Footer */}
<div className="mt-6 text-center">
<p className="text-sm text-gray-600 dark:text-gray-400">
Don't have an account?{" "}
<Link
to="/register"
className="text-gray-900 dark:text-white hover:underline font-medium"
>
Sign up
</Link>
</p>
</div>
</div>
</div>
</div>
);
};
+203
View File
@@ -0,0 +1,203 @@
import React, { useState, useEffect } from "react";
import { useNavigate, Link } from "react-router-dom";
import { FiUser, FiLock, FiUserPlus, FiMail } from "react-icons/fi";
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
export const RegisterPage: React.FC = () => {
const navigate = useNavigate();
const { register, isLoading, error, clearError, token } = useAuthStore();
const [formData, setFormData] = useState({
login: "",
password: "",
confirmPassword: "",
firstName: "",
lastName: "",
});
const [passwordError, setPasswordError] = useState<string | null>(null);
useEffect(() => {
if (token) {
navigate("/dashboard");
}
}, [token, navigate]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (formData.password !== formData.confirmPassword) {
setPasswordError("Passwords do not match");
return;
}
setPasswordError(null);
try {
await register({
login: formData.login,
password: formData.password,
firstName: formData.firstName,
lastName: formData.lastName,
});
navigate("/dashboard");
} catch (err) {
// Error is handled by store
}
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
if (error) clearError();
if (passwordError) setPasswordError(null);
};
return (
<div className="min-h-screen flex items-center justify-center bg-white dark:bg-black transition-colors duration-200">
<div className="w-full max-w-md px-8">
{/* Card */}
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg shadow-xl p-8 border border-gray-200 dark:border-gray-800">
{/* Header */}
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Create Account
</h1>
<p className="text-gray-600 dark:text-gray-400">
Sign up to get started
</p>
</div>
{/* Error Message */}
{error && (
<div className="mb-4 p-3 bg-red-100 dark:bg-red-900/20 border border-red-400 dark:border-red-800 rounded text-red-700 dark:text-red-400 text-sm">
{error}
</div>
)}
{/* Form */}
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
First Name
</label>
<input
type="text"
name="firstName"
value={formData.firstName}
onChange={handleChange}
required
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-gray-500 dark:focus:ring-gray-400 transition-colors"
placeholder="John"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Last Name
</label>
<input
type="text"
name="lastName"
value={formData.lastName}
onChange={handleChange}
required
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-gray-500 dark:focus:ring-gray-400 transition-colors"
placeholder="Doe"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Login
</label>
<div className="relative">
<FiUser className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-gray-500" />
<input
type="text"
name="login"
value={formData.login}
onChange={handleChange}
required
className="w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-gray-500 dark:focus:ring-gray-400 transition-colors"
placeholder="Choose a login"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Password
</label>
<div className="relative">
<FiLock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-gray-500" />
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
required
className="w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-gray-500 dark:focus:ring-gray-400 transition-colors"
placeholder="Create a password"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Confirm Password
</label>
<div className="relative">
<FiLock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-gray-500" />
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
required
className="w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-gray-500 dark:focus:ring-gray-400 transition-colors"
placeholder="Confirm your password"
/>
</div>
{passwordError && (
<p className="mt-1 text-sm text-red-600 dark:text-red-400">
{passwordError}
</p>
)}
</div>
<button
type="submit"
disabled={isLoading}
className="w-full flex items-center justify-center gap-2 px-4 py-2 bg-gray-900 dark:bg-white text-white dark:text-gray-900 rounded-lg hover:bg-gray-800 dark:hover:bg-gray-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed font-medium"
>
{isLoading ? (
"Creating account..."
) : (
<>
<FiUserPlus />
Sign Up
</>
)}
</button>
</form>
{/* Footer */}
<div className="mt-6 text-center">
<p className="text-sm text-gray-600 dark:text-gray-400">
Already have an account?{" "}
<Link
to="/auth"
className="text-gray-900 dark:text-white hover:underline font-medium"
>
Sign in
</Link>
</p>
</div>
</div>
</div>
</div>
);
};
@@ -0,0 +1,55 @@
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
import { ThemeToggle } from "@/modules/theme-bw/ui/ThemeToggle";
import React from "react";
import { Outlet, useNavigate } from "react-router-dom";
interface DefaultLayoutProps {
children?: React.ReactNode;
}
export const DefaultLayout: React.FC<DefaultLayoutProps> = ({ children }) => {
const { user, logout } = useAuthStore();
const navigate = useNavigate();
const handleLogout = () => {
logout();
navigate("/login");
};
return (
<div className="min-h-screen bg-white dark:bg-black transition-colors duration-200">
{/* Header */}
<header className="border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-black sticky top-0 z-50">
<div className="container mx-auto px-4 py-3">
<div className="flex justify-between items-center">
{/* Logo */}
<div className="text-xl font-bold text-gray-900 dark:text-white">
HellreigN
</div>
{/* Right side */}
<div className="flex items-center gap-4">
<ThemeToggle />
{user && (
<div className="flex items-center gap-3">
<span className="text-sm text-gray-600 dark:text-gray-400">
{user.firstName} {user.lastName}
</span>
<button
onClick={handleLogout}
className="px-3 py-1 text-sm bg-red-500 text-white rounded hover:bg-red-600 transition-colors"
>
Logout
</button>
</div>
)}
</div>
</div>
</div>
</header>
{/* Main content */}
<main className="min-h-[calc(100vh-61px)]">{children || <Outlet />}</main>
</div>
);
};
+41 -232
View File
@@ -1,175 +1,48 @@
[data-theme="light"] {
@import "tailwindcss";
/* Кастомные темы для dark mode */
@custom-variant dark (&:where(.dark, .dark *));
/* Базовые стили */
@layer base {
body {
@apply bg-white dark:bg-black text-gray-900 dark:text-white;
}
}
/* Кастомные утилиты (опционально) */
@layer utilities {
.transition-theme {
transition-property: background-color, border-color, color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 200ms;
}
}
:root {
--bg-primary: #ffffff;
--bg-secondary: #f8fafc;
--bg-tertiary: #f1f5f9;
--text-primary: #1e293b;
--text-secondary: #64748b;
--text-tertiary: #94a3b8;
--border-primary: #e2e8f0;
--border-secondary: #cbd5e1;
--accent-primary: #4f46e5;
--accent-secondary: #6366f1;
--accent-hover: #4338ca;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.08), 0 1px 2px 0 rgba(0, 0, 0, 0.04);
--shadow-lg:
0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--bg-secondary: #f5f5f5;
--text-primary: #000000;
--text-secondary: #333333;
--border: #e5e5e5;
--card-bg: #ffffff;
--input-bg: #ffffff;
--button-bg: #000000;
--button-text: #ffffff;
--button-hover: #333333;
}
[data-theme="dark"] {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-tertiary: #334155;
--text-primary: #f1f5f9;
--text-secondary: #cbd5e1;
--text-tertiary: #94a3b8;
--border-primary: #475569;
--border-secondary: #64748b;
--accent-primary: #5061fc;
--accent-secondary: #4866ff;
--accent-hover: #6366f1;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.25), 0 1px 2px 0 rgba(0, 0, 0, 0.15);
--shadow-lg:
0 10px 15px -3px rgba(0, 0, 0, 0.25), 0 4px 6px -2px rgba(0, 0, 0, 0.15);
}
[data-theme="nightowl"] {
--bg-primary: #011627;
--bg-secondary: #0d293e;
--bg-tertiary: #1d3b53;
--text-primary: #d6deeb;
--text-secondary: #b4c7e0;
--text-tertiary: #7c8da5;
--border-primary: #1d3b53;
--border-secondary: #2d4b63;
--accent-primary: #046390;
--accent-secondary: #065783;
--accent-hover: #38bdf8;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.35), 0 1px 2px 0 rgba(0, 0, 0, 0.25);
--shadow-lg:
0 10px 15px -3px rgba(0, 0, 0, 0.35), 0 4px 6px -2px rgba(0, 0, 0, 0.25);
}
[data-theme="sunset"] {
--bg-primary: #1c1917;
--bg-secondary: #292524;
--bg-tertiary: #44403c;
--text-primary: #fafaf9;
--text-secondary: #e7e5e4;
--text-tertiary: #a8a29e;
--border-primary: #57534e;
--border-secondary: #78716c;
--accent-primary: #fb923c;
--accent-secondary: #fdba74;
--accent-hover: #f97316;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
--shadow-lg:
0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
}
[data-theme="forest"] {
--bg-primary: #052e16;
--bg-secondary: #14532d;
--bg-tertiary: #166534;
--text-primary: #f0fdf4;
--text-secondary: #dcfce7;
--text-tertiary: #86efac;
--border-primary: #15803d;
--border-secondary: #16a34a;
--accent-primary: #309254;
--accent-secondary: #2cef74;
--accent-hover: #22c55e;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.35), 0 1px 2px 0 rgba(0, 0, 0, 0.25);
--shadow-lg:
0 10px 15px -3px rgba(0, 0, 0, 0.35), 0 4px 6px -2px rgba(0, 0, 0, 0.25);
}
[data-theme="ocean"] {
--bg-primary: #0c4a6e;
--bg-secondary: #155e75;
--bg-tertiary: #0e7490;
--text-primary: #f0fdfd;
--text-secondary: #cffafe;
--text-tertiary: #a5f3fc;
--border-primary: #0891b2;
--border-secondary: #06b6d4;
--accent-primary: #22d3ee;
--accent-secondary: #67e8f9;
--accent-hover: #06b6d4;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
--shadow-lg:
0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
}
[data-theme="lavender"] {
--bg-primary: #faf5ff;
--bg-secondary: #f3e8ff;
--bg-tertiary: #e9d5ff;
--text-primary: #581c87;
--text-secondary: #7e22ce;
--text-tertiary: #a855f7;
--border-primary: #d8b4fe;
--border-secondary: #c084fc;
--accent-primary: #a855f7;
--accent-secondary: #c084fc;
--accent-hover: #9333ea;
--shadow:
0 1px 3px 0 rgba(168, 85, 247, 0.15), 0 1px 2px 0 rgba(168, 85, 247, 0.1);
--shadow-lg:
0 10px 15px -3px rgba(168, 85, 247, 0.15),
0 4px 6px -2px rgba(168, 85, 247, 0.1);
}
[data-theme="coffee"] {
--bg-primary: #292524;
--bg-secondary: #44403c;
--bg-tertiary: #57534e;
--text-primary: #fafaf9;
--text-secondary: #e7e5e4;
--text-tertiary: #d6d3d1;
--border-primary: #78716c;
--border-secondary: #a8a29e;
--accent-primary: #d97706;
--accent-secondary: #f59e0b;
--accent-hover: #b45309;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.35), 0 1px 2px 0 rgba(0, 0, 0, 0.25);
--shadow-lg:
0 10px 15px -3px rgba(0, 0, 0, 0.35), 0 4px 6px -2px rgba(0, 0, 0, 0.25);
}
[data-theme="midnight"] {
--bg-primary: #0a0a0a;
--bg-secondary: #171717;
--bg-tertiary: #262626;
--text-primary: #fafafa;
--text-secondary: #d4d4d4;
--text-tertiary: #a3a3a3;
--border-primary: #404040;
--border-secondary: #525252;
--accent-primary: #3b82f6;
--accent-secondary: #60a5fa;
--accent-hover: #2563eb;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px 0 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.3);
}
[data-theme="rose"] {
--bg-primary: #fff1f2;
--bg-secondary: #ffe4e6;
--bg-tertiary: #fecdd3;
--text-primary: #881337;
--text-secondary: #be123c;
--text-tertiary: #e11d48;
--border-primary: #fda4af;
--border-secondary: #fb7185;
--accent-primary: #e11d48;
--accent-secondary: #f43f5e;
--accent-hover: #be123c;
--shadow:
0 1px 3px 0 rgba(225, 29, 72, 0.15), 0 1px 2px 0 rgba(225, 29, 72, 0.1);
--shadow-lg:
0 10px 15px -3px rgba(225, 29, 72, 0.15),
0 4px 6px -2px rgba(225, 29, 72, 0.1);
--bg-primary: #000000;
--bg-secondary: #1a1a1a;
--text-primary: #ffffff;
--text-secondary: #cccccc;
--border: #333333;
--card-bg: #0a0a0a;
--input-bg: #1a1a1a;
--button-bg: #ffffff;
--button-text: #000000;
--button-hover: #cccccc;
}
body {
@@ -179,67 +52,3 @@ body {
background-color 0.3s ease,
color 0.3s ease;
}
.bg-primary {
background-color: var(--bg-primary);
}
.bg-secondary {
background-color: var(--bg-secondary);
}
.bg-tertiary {
background-color: var(--bg-tertiary);
}
.text-primary {
color: var(--text-primary);
}
.text-secondary {
color: var(--text-secondary);
}
.text-tertiary {
color: var(--text-tertiary);
}
.border-primary {
border-color: var(--border-primary);
}
.border-secondary {
border-color: var(--border-secondary);
}
.border-accent {
border-color: var(--accent-primary);
}
.accent-primary {
background-color: var(--accent-primary);
}
.accent-secondary {
background-color: var(--accent-secondary);
}
.text-accent {
color: var(--accent-primary);
}
.shadow-regular {
box-shadow: var(--shadow);
}
.shadow-large {
box-shadow: var(--shadow-lg);
}
.hover-accent:hover {
background-color: var(--accent-hover);
}
.hover-bg-secondary:hover {
background-color: var(--bg-secondary);
}