From e1f2c8d536aec3092f84359c70e8c5815e3415ea Mon Sep 17 00:00:00 2001
From: ddshi <8811906+ddshi@user.noreply.gitee.com>
Date: Thu, 29 Jan 2026 12:52:28 +0800
Subject: [PATCH] feat: configure Tailwind CSS, Mantine, Supabase client, and
routing
---
.env.example | 6 ++
src/App.css | 42 -----------
src/App.tsx | 35 +--------
src/index.css | 99 +++++++++----------------
src/main.tsx | 51 ++++++++++++-
src/pages/HomePage.tsx | 49 +++++++++++++
src/pages/LandingPage.tsx | 25 +++++++
src/pages/LoginPage.tsx | 68 +++++++++++++++++
src/pages/RegisterPage.tsx | 75 +++++++++++++++++++
src/routes.tsx | 75 +++++++++++++++++++
src/services/supabase.ts | 147 +++++++++++++++++++++++++++++++++++++
src/stores/index.ts | 115 +++++++++++++++++++++++++++++
src/types/index.ts | 100 +++++++++++++++++++++++++
tailwind.config.js | 67 +++++++++++++++++
14 files changed, 814 insertions(+), 140 deletions(-)
create mode 100644 .env.example
delete mode 100644 src/App.css
create mode 100644 src/pages/HomePage.tsx
create mode 100644 src/pages/LandingPage.tsx
create mode 100644 src/pages/LoginPage.tsx
create mode 100644 src/pages/RegisterPage.tsx
create mode 100644 src/routes.tsx
create mode 100644 src/services/supabase.ts
create mode 100644 src/stores/index.ts
create mode 100644 src/types/index.ts
create mode 100644 tailwind.config.js
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..69c7eb0
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,6 @@
+# Supabase Configuration
+VITE_SUPABASE_URL=your_supabase_project_url
+VITE_SUPABASE_ANON_KEY=your_supabase_anon_key
+
+# DeepSeek AI API (for backend)
+DEEPSEEK_API_KEY=your_deepseek_api_key
diff --git a/src/App.css b/src/App.css
deleted file mode 100644
index b9d355d..0000000
--- a/src/App.css
+++ /dev/null
@@ -1,42 +0,0 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
-
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
diff --git a/src/App.tsx b/src/App.tsx
index 3d7ded3..a342326 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,35 +1,8 @@
-import { useState } from 'react'
-import reactLogo from './assets/react.svg'
-import viteLogo from '/vite.svg'
-import './App.css'
+import { RouterProvider } from 'react-router-dom';
+import { router } from './routes';
function App() {
- const [count, setCount] = useState(0)
-
- return (
- <>
-
- Vite + React
-
-
-
- Edit src/App.tsx and save to test HMR
-
-
-
- Click on the Vite and React logos to learn more
-
- >
- )
+ return ;
}
-export default App
+export default App;
diff --git a/src/index.css b/src/index.css
index 08a3ac9..22d9cfd 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,68 +1,39 @@
-:root {
- font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
-
-body {
- margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
+@layer base {
+ html {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
+
+ body {
+ @apply bg-gray-50 text-gray-900;
+ }
+}
+
+@layer components {
+ .btn-primary {
+ @apply px-4 py-2 bg-primary-500 text-white rounded-lg font-medium
+ hover:bg-primary-600 active:bg-primary-700
+ transition-colors duration-200;
+ }
+
+ .btn-secondary {
+ @apply px-4 py-2 bg-gray-100 text-gray-900 rounded-lg font-medium
+ hover:bg-gray-200 active:bg-gray-300
+ transition-colors duration-200;
+ }
+
+ .card {
+ @apply bg-white rounded-xl shadow-card p-4
+ hover:shadow-card-hover transition-shadow duration-200;
+ }
+
+ .input {
+ @apply w-full px-3 py-2 border border-gray-200 rounded-lg
+ focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent
+ placeholder:text-gray-400;
}
}
diff --git a/src/main.tsx b/src/main.tsx
index bef5202..9a70d06 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,10 +1,55 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
+import { MantineProvider, createTheme } from '@mantine/core'
+import { Notifications } from '@mantine/notifications'
+import '@mantine/core/styles.css'
+import '@mantine/notifications/styles.css'
import './index.css'
-import App from './App.tsx'
+import App from './App'
+
+// Apple-inspired theme
+const theme = createTheme({
+ primaryColor: 'blue',
+ fontFamily: '-apple-system, BlinkMacSystemFont, SF Pro Text, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif',
+ defaultRadius: 'md',
+ colors: {
+ blue: [
+ '#e6f2ff',
+ '#cce5ff',
+ '#99cfff',
+ '#66b8ff',
+ '#33a1ff',
+ '#007AFF',
+ '#0062cc',
+ '#004999',
+ '#003166',
+ '#001833',
+ ],
+ },
+ components: {
+ Button: {
+ defaultProps: {
+ radius: 'md',
+ },
+ },
+ Card: {
+ defaultProps: {
+ radius: 'lg',
+ },
+ },
+ Input: {
+ defaultProps: {
+ radius: 'md',
+ },
+ },
+ },
+})
createRoot(document.getElementById('root')!).render(
-
+
+
+
+
,
-)
+)
\ No newline at end of file
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx
new file mode 100644
index 0000000..f5f8d1e
--- /dev/null
+++ b/src/pages/HomePage.tsx
@@ -0,0 +1,49 @@
+import { useEffect } from 'react';
+import { Container, Grid, Title, Button, Group, Text } from '@mantine/core';
+import { IconLogout } from '@tabler/icons-react';
+import { useAppStore } from '../stores';
+
+export function HomePage() {
+ const user = useAppStore((state) => state.user);
+ const logout = useAppStore((state) => state.logout);
+ const checkAuth = useAppStore((state) => state.checkAuth);
+
+ useEffect(() => {
+ checkAuth();
+ }, [checkAuth]);
+
+ const handleLogout = async () => {
+ await logout();
+ };
+
+ return (
+
+ {/* Header */}
+
+
+ 掐日子
+
+
+
+ {user?.email}
+
+ }
+ onClick={handleLogout}
+ >
+ 退出
+
+
+
+
+ {/* Main Content - Placeholder for now */}
+
+
+ Home Page - 待开发
+
+
+
+ );
+}
diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx
new file mode 100644
index 0000000..8c74c14
--- /dev/null
+++ b/src/pages/LandingPage.tsx
@@ -0,0 +1,25 @@
+import { Button, Container, Title, Text, Paper } from '@mantine/core';
+import { useNavigate } from 'react-router-dom';
+
+export function LandingPage() {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+ 掐日子
+
+
+ AI 纪念日 · 提醒
+
+
+ 轻便、灵活的倒数日和提醒App,专注提醒功能
+
+
+
+
+ );
+}
diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx
new file mode 100644
index 0000000..bca66a1
--- /dev/null
+++ b/src/pages/LoginPage.tsx
@@ -0,0 +1,68 @@
+import { useState } from 'react';
+import { Button, Container, Paper, TextInput, Title, Text, Stack, Anchor } from '@mantine/core';
+import { useNavigate } from 'react-router-dom';
+import { useAppStore } from '../stores';
+
+export function LoginPage() {
+ const navigate = useNavigate();
+ const login = useAppStore((state) => state.login);
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setLoading(true);
+ setError('');
+
+ const { error } = await login(email, password);
+ if (error) {
+ setError(error.message || '登录失败');
+ } else {
+ navigate('/');
+ }
+ setLoading(false);
+ };
+
+ return (
+
+
+
+ 登录
+
+
+
+
+
+ 还没有账号?{' '}
+ navigate('/register')}>
+ 立即注册
+
+
+
+
+ );
+}
diff --git a/src/pages/RegisterPage.tsx b/src/pages/RegisterPage.tsx
new file mode 100644
index 0000000..5b93d47
--- /dev/null
+++ b/src/pages/RegisterPage.tsx
@@ -0,0 +1,75 @@
+import { useState } from 'react';
+import { Button, Container, Paper, TextInput, Title, Text, Stack, Anchor } from '@mantine/core';
+import { useNavigate } from 'react-router-dom';
+import { useAppStore } from '../stores';
+
+export function RegisterPage() {
+ const navigate = useNavigate();
+ const register = useAppStore((state) => state.register);
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [nickname, setNickname] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setLoading(true);
+ setError('');
+
+ const { error } = await register(email, password, nickname);
+ if (error) {
+ setError(error.message || '注册失败');
+ } else {
+ navigate('/login');
+ }
+ setLoading(false);
+ };
+
+ return (
+
+
+
+ 注册
+
+
+
+
+
+ 已有账号?{' '}
+ navigate('/login')}>
+ 立即登录
+
+
+
+
+ );
+}
diff --git a/src/routes.tsx b/src/routes.tsx
new file mode 100644
index 0000000..3044c74
--- /dev/null
+++ b/src/routes.tsx
@@ -0,0 +1,75 @@
+import { createBrowserRouter } from 'react-router-dom';
+import { LandingPage } from './pages/LandingPage';
+import { LoginPage } from './pages/LoginPage';
+import { RegisterPage } from './pages/RegisterPage';
+import { HomePage } from './pages/HomePage';
+import { useAppStore } from './stores';
+import { useEffect } from 'react';
+
+// Protected Route wrapper
+function ProtectedRoute({ children }: { children: React.ReactNode }) {
+ const isAuthenticated = useAppStore((state) => state.isAuthenticated);
+ const checkAuth = useAppStore((state) => state.checkAuth);
+ const isLoading = useAppStore((state) => state.isLoading);
+
+ useEffect(() => {
+ checkAuth();
+ }, [checkAuth]);
+
+ if (isLoading) {
+ return null; // Or a loading spinner
+ }
+
+ return <>{children}>;
+}
+
+// Public Route wrapper (redirects to home if authenticated)
+function PublicRoute({ children }: { children: React.ReactNode }) {
+ const isAuthenticated = useAppStore((state) => state.isAuthenticated);
+ const checkAuth = useAppStore((state) => state.checkAuth);
+
+ useEffect(() => {
+ checkAuth();
+ }, [checkAuth]);
+
+ if (isAuthenticated) {
+ return ;
+ }
+
+ return <>{children}>;
+}
+
+export const router = createBrowserRouter([
+ {
+ path: '/',
+ element: (
+
+
+
+ ),
+ },
+ {
+ path: '/login',
+ element: (
+
+
+
+ ),
+ },
+ {
+ path: '/register',
+ element: (
+
+
+
+ ),
+ },
+ {
+ path: '/home',
+ element: (
+
+
+
+ ),
+ },
+]);
diff --git a/src/services/supabase.ts b/src/services/supabase.ts
new file mode 100644
index 0000000..0fa6564
--- /dev/null
+++ b/src/services/supabase.ts
@@ -0,0 +1,147 @@
+import { createClient } from '@supabase/supabase-js';
+
+// Environment variables (set these in .env file)
+const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || '';
+const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY || '';
+
+if (!supabaseUrl || !supabaseAnonKey) {
+ console.warn('Supabase URL or Anon Key not configured. Please set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY in .env');
+}
+
+export const supabase = createClient(supabaseUrl, supabaseAnonKey);
+
+// Auth helper functions
+export const auth = {
+ signUp: async (email: string, password: string, nickname?: string) => {
+ const { data, error } = await supabase.auth.signUp({
+ email,
+ password,
+ options: {
+ data: { nickname },
+ },
+ });
+ return { data, error };
+ },
+
+ signIn: async (email: string, password: string) => {
+ const { data, error } = await supabase.auth.signInWithPassword({
+ email,
+ password,
+ });
+ return { data, error };
+ },
+
+ signOut: async () => {
+ const { error } = await supabase.auth.signOut();
+ return { error };
+ },
+
+ getSession: async () => {
+ const { data: { session }, error } = await supabase.auth.getSession();
+ return { session, error };
+ },
+
+ onAuthStateChange: (callback: (event: string, session: any) => void) => {
+ return supabase.auth.onAuthStateChange(callback);
+ },
+
+ getUser: async () => {
+ const { data: { user }, error } = await supabase.auth.getUser();
+ return { user, error };
+ },
+};
+
+// Database helper functions
+export const db = {
+ // Events (Anniversaries and Reminders)
+ events: {
+ getAll: async (userId: string) => {
+ const { data, error } = await supabase
+ .from('events')
+ .select('*')
+ .eq('user_id', userId)
+ .order('date', { ascending: true });
+ return { data, error };
+ },
+
+ create: async (event: Partial) => {
+ const { data, error } = await supabase
+ .from('events')
+ .insert(event)
+ .select()
+ .single();
+ return { data, error };
+ },
+
+ update: async (id: string, updates: Partial) => {
+ const { data, error } = await supabase
+ .from('events')
+ .update(updates)
+ .eq('id', id)
+ .select()
+ .single();
+ return { data, error };
+ },
+
+ delete: async (id: string) => {
+ const { error } = await supabase
+ .from('events')
+ .delete()
+ .eq('id', id);
+ return { error };
+ },
+ },
+
+ // Notes
+ notes: {
+ getByUser: async (userId: string) => {
+ const { data, error } = await supabase
+ .from('notes')
+ .select('*')
+ .eq('user_id', userId)
+ .single();
+ return { data, error };
+ },
+
+ create: async (note: Partial) => {
+ const { data, error } = await supabase
+ .from('notes')
+ .insert(note)
+ .select()
+ .single();
+ return { data, error };
+ },
+
+ update: async (id: string, content: string) => {
+ const { data, error } = await supabase
+ .from('notes')
+ .update({ content, updated_at: new Date().toISOString() })
+ .eq('id', id)
+ .select()
+ .single();
+ return { data, error };
+ },
+ },
+
+ // AI Conversations
+ conversations: {
+ getRecent: async (userId: string, limit = 10) => {
+ const { data, error } = await supabase
+ .from('ai_conversations')
+ .select('*')
+ .eq('user_id', userId)
+ .order('created_at', { ascending: false })
+ .limit(limit);
+ return { data, error };
+ },
+
+ create: async (conversation: Partial) => {
+ const { data, error } = await supabase
+ .from('ai_conversations')
+ .insert(conversation)
+ .select()
+ .single();
+ return { data, error };
+ },
+ },
+};
diff --git a/src/stores/index.ts b/src/stores/index.ts
new file mode 100644
index 0000000..fa5fbad
--- /dev/null
+++ b/src/stores/index.ts
@@ -0,0 +1,115 @@
+import { create } from 'zustand';
+import { persist } from 'zustand/middleware';
+import type { User, Event, Note, AIConversation } from '../types';
+import { auth } from '../services/supabase';
+
+interface AppState {
+ // Auth state
+ user: User | null;
+ isAuthenticated: boolean;
+ isLoading: boolean;
+
+ // Data state
+ events: Event[];
+ notes: Note | null;
+ conversations: AIConversation[];
+
+ // Actions
+ setUser: (user: User | null) => void;
+ setLoading: (loading: boolean) => void;
+ setEvents: (events: Event[]) => void;
+ addEvent: (event: Event) => void;
+ updateEvent: (id: string, updates: Partial) => void;
+ deleteEvent: (id: string) => void;
+ setNotes: (notes: Note | null) => void;
+ updateNotesContent: (content: string) => void;
+ setConversations: (conversations: AIConversation[]) => void;
+
+ // Auth actions
+ login: (email: string, password: string) => Promise<{ error: any }>;
+ register: (email: string, password: string, nickname?: string) => Promise<{ error: any }>;
+ logout: () => Promise;
+ checkAuth: () => Promise;
+}
+
+export const useAppStore = create()(
+ persist(
+ (set, get) => ({
+ // Initial state
+ user: null,
+ isAuthenticated: false,
+ isLoading: true,
+ events: [],
+ notes: null,
+ conversations: [],
+
+ // Setters
+ setUser: (user) => set({ user, isAuthenticated: !!user }),
+ setLoading: (isLoading) => set({ isLoading }),
+ setEvents: (events) => set({ events }),
+ addEvent: (event) => set((state) => ({ events: [...state.events, event] })),
+ updateEvent: (id, updates) => set((state) => ({
+ events: state.events.map((e) => (e.id === id ? { ...e, ...updates } : e)),
+ })),
+ deleteEvent: (id) => set((state) => ({
+ events: state.events.filter((e) => e.id !== id),
+ })),
+ setNotes: (notes) => set({ notes }),
+ updateNotesContent: (content) => set((state) => ({
+ notes: state.notes ? { ...state.notes, content } : null,
+ })),
+ setConversations: (conversations) => set({ conversations }),
+
+ // Auth actions
+ login: async (email, password) => {
+ const { error } = await auth.signIn(email, password);
+ if (!error) {
+ const { session } = await auth.getSession();
+ if (session?.user) {
+ const user: User = {
+ id: session.user.id,
+ email: session.user.email!,
+ created_at: session.user.created_at,
+ updated_at: session.user.updated_at,
+ };
+ set({ user, isAuthenticated: true });
+ }
+ }
+ return { error };
+ },
+
+ register: async (email, password, nickname) => {
+ const { error } = await auth.signUp(email, password, nickname);
+ return { error };
+ },
+
+ logout: async () => {
+ await auth.signOut();
+ set({ user: null, isAuthenticated: false, events: [], notes: null });
+ },
+
+ checkAuth: async () => {
+ set({ isLoading: true });
+ const { session } = await auth.getSession();
+ if (session?.user) {
+ const user: User = {
+ id: session.user.id,
+ email: session.user.email!,
+ created_at: session.user.created_at,
+ updated_at: session.user.updated_at,
+ };
+ set({ user, isAuthenticated: true, isLoading: false });
+ } else {
+ set({ isAuthenticated: false, isLoading: false });
+ }
+ },
+ }),
+ {
+ name: 'qia-storage',
+ partialize: (state) => ({
+ user: state.user,
+ isAuthenticated: state.isAuthenticated,
+ }),
+ }
+ )
+);
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..edc4e42
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,100 @@
+// User types
+export interface User {
+ id: string;
+ email: string;
+ nickname?: string;
+ created_at: string;
+ updated_at: string;
+}
+
+// Event types - for both Anniversary and Reminder
+export type EventType = 'anniversary' | 'reminder';
+
+export type RepeatType = 'yearly' | 'monthly' | 'none';
+
+export interface BaseEvent {
+ id: string;
+ user_id: string;
+ title: string;
+ date: string; // ISO date string
+ is_lunar: boolean;
+ repeat_type: RepeatType;
+ created_at: string;
+ updated_at: string;
+}
+
+export interface Anniversary extends BaseEvent {
+ type: 'anniversary';
+ is_holiday: boolean;
+}
+
+export interface Reminder extends BaseEvent {
+ type: 'reminder';
+ content: string;
+ reminder_time: string; // ISO datetime string
+ is_completed: boolean;
+}
+
+// Combined event type for lists
+export type Event = Anniversary | Reminder;
+
+// Note types
+export interface Note {
+ id: string;
+ user_id: string;
+ content: string; // HTML content from rich text editor
+ created_at: string;
+ updated_at: string;
+}
+
+// AI Parsing types
+export interface AIParsedEvent {
+ title: string;
+ date: string;
+ is_lunar: boolean;
+ repeat_type: RepeatType;
+ reminder_time?: string;
+ type: EventType;
+}
+
+export interface AIConversation {
+ id: string;
+ user_id: string;
+ message: string;
+ response: string;
+ parsed_events?: AIParsedEvent[];
+ created_at: string;
+}
+
+// Auth types
+export interface AuthState {
+ user: User | null;
+ isAuthenticated: boolean;
+ isLoading: boolean;
+}
+
+export interface LoginCredentials {
+ email: string;
+ password: string;
+}
+
+export interface RegisterCredentials extends LoginCredentials {
+ nickname?: string;
+}
+
+// API Response types
+export interface ApiResponse {
+ data: T | null;
+ error: string | null;
+}
+
+// Loading state types
+export type LoadingState = 'idle' | 'loading' | 'success' | 'error';
+
+// Grouped reminder types
+export interface GroupedReminders {
+ today: Reminder[];
+ tomorrow: Reminder[];
+ later: Reminder[];
+ missed: Reminder[];
+}
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..f5841b6
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,67 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ colors: {
+ // Primary Colors - Apple-inspired Blue
+ primary: {
+ 50: '#e6f2ff',
+ 100: '#cce5ff',
+ 200: '#99cfff',
+ 300: '#66b8ff',
+ 400: '#33a1ff',
+ 500: '#007AFF',
+ 600: '#0062cc',
+ 700: '#004999',
+ 800: '#003166',
+ 900: '#001833',
+ },
+ // Neutral Colors
+ gray: {
+ 50: '#f5f5f7',
+ 100: '#e5e5ea',
+ 200: '#d1d1d6',
+ 300: '#c6c6cc',
+ 400: '#aeaeb2',
+ 500: '#8e8e93',
+ 600: '#636366',
+ 700: '#48484a',
+ 800: '#3a3a3c',
+ 900: '#1c1c1e',
+ },
+ // Background Colors
+ background: {
+ primary: '#ffffff',
+ secondary: '#f5f5f7',
+ tertiary: '#ffffff',
+ },
+ },
+ fontFamily: {
+ sans: [
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ 'SF Pro Text',
+ 'Segoe UI',
+ 'Roboto',
+ 'Helvetica Neue',
+ 'Arial',
+ 'sans-serif',
+ ],
+ },
+ boxShadow: {
+ 'card': '0 2px 8px rgba(0, 0, 0, 0.06)',
+ 'card-hover': '0 4px 16px rgba(0, 0, 0, 0.1)',
+ 'modal': '0 8px 32px rgba(0, 0, 0, 0.12)',
+ },
+ borderRadius: {
+ 'card': '12px',
+ 'button': '8px',
+ },
+ },
+ },
+ plugins: [],
+}