design: rework for a better DA and light/dark mode

This commit is contained in:
2026-04-28 07:54:07 +02:00
parent 490a364c00
commit f92f51273e
13 changed files with 252 additions and 81 deletions

View File

@ -4,9 +4,12 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0f172a" /> <meta name="theme-color" content="#f7f4ef" />
<meta name="description" content="Agrégateur de news financières avec résumés IA" /> <meta name="description" content="Votre journal financier augmenté par l'IA" />
<title>Tradarr</title> <title>FinancIAl</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700&display=swap" rel="stylesheet" />
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@ -12,7 +12,7 @@ const items = [
export function MobileNav() { export function MobileNav() {
return ( return (
<nav className="fixed bottom-0 left-0 right-0 z-50 flex border-t bg-card md:hidden"> <nav className="fixed bottom-0 left-0 right-0 z-50 flex border-t bg-card md:hidden shadow-sm">
{items.map(({ to, icon: Icon, label }) => ( {items.map(({ to, icon: Icon, label }) => (
<NavLink <NavLink
key={to} key={to}
@ -21,7 +21,7 @@ export function MobileNav() {
className={({ isActive }) => className={({ isActive }) =>
cn( cn(
'flex flex-1 flex-col items-center gap-1 py-3 text-xs transition-colors', 'flex flex-1 flex-col items-center gap-1 py-3 text-xs transition-colors',
isActive ? 'text-primary' : 'text-muted-foreground' isActive ? 'text-primary font-medium' : 'text-muted-foreground'
) )
} }
> >

View File

@ -1,5 +1,5 @@
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { LayoutDashboard, Newspaper, Star, Settings, Key, Cpu, Database, ClipboardList, Users, LogOut, TrendingUp, CalendarDays, FileText } from 'lucide-react' import { LayoutDashboard, Newspaper, Star, Settings, Key, Cpu, Database, ClipboardList, Users, LogOut, CalendarDays, FileText } from 'lucide-react'
import { useAuth } from '@/lib/auth' import { useAuth } from '@/lib/auth'
import { cn } from '@/lib/cn' import { cn } from '@/lib/cn'
@ -27,14 +27,14 @@ function NavItem({ to, icon: Icon, label }: { to: string; icon: React.ElementTyp
end={to === '/'} end={to === '/'}
className={({ isActive }) => className={({ isActive }) =>
cn( cn(
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors', 'flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors',
isActive isActive
? 'bg-primary/10 text-primary' ? 'bg-primary/8 text-primary font-medium border-l-2 border-primary pl-[10px]'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground' : 'text-muted-foreground hover:bg-accent hover:text-foreground font-normal'
) )
} }
> >
<Icon className="h-4 w-4" /> <Icon className="h-4 w-4 shrink-0" />
{label} {label}
</NavLink> </NavLink>
) )
@ -44,25 +44,32 @@ export function Sidebar() {
const { user, logout, isAdmin } = useAuth() const { user, logout, isAdmin } = useAuth()
return ( return (
<aside className="flex h-screen w-60 flex-col border-r bg-card px-3 py-4"> <aside className="flex h-screen w-60 flex-col border-r bg-card px-3 py-5">
{/* Logo */} {/* Logo */}
<div className="mb-6 flex items-center gap-2 px-3"> <div className="mb-7 px-3">
<TrendingUp className="h-6 w-6 text-primary" /> <span className="font-serif text-xl font-bold tracking-tight text-foreground">
<span className="text-lg font-bold">Tradarr</span> Financ<span className="text-primary">IA</span>l
</span>
<p className="text-[10px] text-muted-foreground mt-0.5 tracking-wide uppercase">Journal financier IA</p>
</div> </div>
{/* Séparateur */}
<div className="border-t mb-4" />
{/* Navigation principale */} {/* Navigation principale */}
<nav className="flex flex-col gap-1"> <nav className="flex flex-col gap-0.5">
{navItems.map(item => <NavItem key={item.to} {...item} />)} {navItems.map(item => <NavItem key={item.to} {...item} />)}
</nav> </nav>
{/* Section admin */} {/* Section admin */}
{isAdmin && ( {isAdmin && (
<> <>
<div className="mt-6 mb-2 px-3"> <div className="mt-5 mb-2 px-3 flex items-center gap-2">
<p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Administration</p> <div className="flex-1 border-t" />
<p className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">Admin</p>
<div className="flex-1 border-t" />
</div> </div>
<nav className="flex flex-col gap-1"> <nav className="flex flex-col gap-0.5">
{adminItems.map(item => <NavItem key={item.to} {...item} />)} {adminItems.map(item => <NavItem key={item.to} {...item} />)}
</nav> </nav>
</> </>
@ -71,12 +78,12 @@ export function Sidebar() {
{/* User + logout */} {/* User + logout */}
<div className="mt-auto border-t pt-4"> <div className="mt-auto border-t pt-4">
<div className="px-3 mb-2"> <div className="px-3 mb-2">
<p className="text-sm font-medium truncate">{user?.email}</p> <p className="text-sm font-medium truncate text-foreground">{user?.email}</p>
<p className="text-xs text-muted-foreground capitalize">{user?.role}</p> <p className="text-xs text-muted-foreground capitalize">{user?.role}</p>
</div> </div>
<button <button
onClick={logout} onClick={logout}
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors" className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
> >
<LogOut className="h-4 w-4" /> <LogOut className="h-4 w-4" />
Déconnexion Déconnexion

View File

@ -3,16 +3,16 @@ import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/cn' import { cn } from '@/lib/cn'
const badgeVariants = cva( const badgeVariants = cva(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors', 'inline-flex items-center rounded border px-2 py-0.5 text-xs font-medium transition-colors',
{ {
variants: { variants: {
variant: { variant: {
default: 'border-transparent bg-primary text-primary-foreground', default: 'border-transparent bg-primary text-primary-foreground',
secondary: 'border-transparent bg-secondary text-secondary-foreground', secondary: 'border-transparent bg-secondary text-secondary-foreground',
destructive: 'border-transparent bg-destructive text-destructive-foreground', destructive: 'border-transparent bg-destructive/10 text-destructive border-destructive/20',
outline: 'text-foreground', outline: 'text-foreground border-border',
bullish: 'border-transparent bg-bullish/20 text-bullish', bullish: 'border-transparent bg-bullish/10 text-bullish',
bearish: 'border-transparent bg-bearish/20 text-bearish', bearish: 'border-transparent bg-bearish/10 text-bearish',
}, },
}, },
defaultVariants: { variant: 'default' }, defaultVariants: { variant: 'default' },

View File

@ -16,9 +16,9 @@ const buttonVariants = cva(
}, },
size: { size: {
default: 'h-9 px-4 py-2', default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs', sm: 'h-7 rounded px-2.5 text-xs',
lg: 'h-10 rounded-md px-8', lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9', icon: 'h-8 w-8',
}, },
}, },
defaultVariants: { variant: 'default', size: 'default' }, defaultVariants: { variant: 'default', size: 'default' },

View File

@ -3,14 +3,14 @@ import { cn } from '@/lib/cn'
export const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( export const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<div ref={ref} className={cn('rounded-lg border bg-card text-card-foreground shadow-sm', className)} {...props} /> <div ref={ref} className={cn('rounded-md border bg-card text-card-foreground shadow-sm', className)} {...props} />
) )
) )
Card.displayName = 'Card' Card.displayName = 'Card'
export const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( export const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} /> <div ref={ref} className={cn('flex flex-col space-y-1.5 p-5', className)} {...props} />
) )
) )
CardHeader.displayName = 'CardHeader' CardHeader.displayName = 'CardHeader'
@ -24,14 +24,14 @@ CardTitle.displayName = 'CardTitle'
export const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( export const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} /> <div ref={ref} className={cn('p-5 pt-0', className)} {...props} />
) )
) )
CardContent.displayName = 'CardContent' CardContent.displayName = 'CardContent'
export const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( export const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} /> <div ref={ref} className={cn('flex items-center p-5 pt-0', className)} {...props} />
) )
) )
CardFooter.displayName = 'CardFooter' CardFooter.displayName = 'CardFooter'

View File

@ -4,24 +4,71 @@
@layer base { @layer base {
:root { :root {
--background: 222 47% 6%; /* ── Fond & texte ── */
--foreground: 210 40% 95%; --background: 36 28% 97%;
--card: 222 47% 9%; --foreground: 20 15% 9%;
--card-foreground: 210 40% 95%;
--border: 217 33% 18%; /* ── Cartes ── */
--input: 217 33% 18%; --card: 0 0% 100%;
--ring: 221 83% 53%; --card-foreground: 20 15% 9%;
--primary: 221 83% 53%;
--primary-foreground: 210 40% 98%; /* ── Bordures & inputs ── */
--secondary: 217 33% 18%; --border: 30 18% 87%;
--secondary-foreground: 210 40% 95%; --input: 30 18% 87%;
--muted: 217 33% 14%; --ring: 22 62% 28%;
--muted-foreground: 215 20% 55%;
--accent: 217 33% 18%; /* ── Primaire : brun cognac ── */
--accent-foreground: 210 40% 95%; --primary: 22 62% 28%;
--destructive: 0 84% 60%; --primary-foreground: 36 30% 96%;
--destructive-foreground: 210 40% 98%;
--radius: 0.5rem; /* ── Secondaire & muted ── */
--secondary: 36 20% 93%;
--secondary-foreground: 20 12% 25%;
--muted: 35 18% 92%;
--muted-foreground: 25 8% 48%;
/* ── Accent (hover) ── */
--accent: 35 18% 90%;
--accent-foreground: 20 15% 9%;
/* ── Destructif ── */
--destructive: 0 78% 52%;
--destructive-foreground: 0 0% 98%;
--radius: 0.375rem;
}
.dark {
/* ── Fond & texte ── */
--background: 22 18% 7%;
--foreground: 36 22% 91%;
/* ── Cartes ── */
--card: 22 15% 10%;
--card-foreground: 36 22% 91%;
/* ── Bordures & inputs ── */
--border: 25 14% 18%;
--input: 25 14% 18%;
--ring: 22 55% 48%;
/* ── Primaire : cognac clair (lisible sur fond sombre) ── */
--primary: 22 55% 50%;
--primary-foreground: 36 30% 96%;
/* ── Secondaire & muted ── */
--secondary: 22 13% 14%;
--secondary-foreground: 36 18% 78%;
--muted: 22 12% 13%;
--muted-foreground: 30 8% 52%;
/* ── Accent (hover) ── */
--accent: 22 13% 15%;
--accent-foreground: 36 22% 91%;
/* ── Destructif ── */
--destructive: 0 72% 55%;
--destructive-foreground: 0 0% 98%;
} }
} }
@ -33,10 +80,14 @@
} }
} }
/* Scrollbar style */ .font-serif {
::-webkit-scrollbar { width: 6px; height: 6px; } font-family: 'Playfair Display', Georgia, serif;
::-webkit-scrollbar-track { @apply bg-muted; } }
::-webkit-scrollbar-thumb { @apply bg-border rounded-full; }
/* Scrollbar */
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: hsl(var(--muted)); }
::-webkit-scrollbar-thumb { background: hsl(var(--border)); border-radius: 9999px; }
@layer utilities { @layer utilities {
.scrollbar-none { scrollbar-width: none; } .scrollbar-none { scrollbar-width: none; }

View File

@ -0,0 +1,40 @@
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
type Theme = 'light' | 'dark'
interface ThemeCtx {
theme: Theme
setTheme: (t: Theme) => void
}
const ThemeContext = createContext<ThemeCtx | null>(null)
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setThemeState] = useState<Theme>(() => {
const saved = localStorage.getItem('theme') as Theme | null
if (saved === 'light' || saved === 'dark') return saved
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
})
useEffect(() => {
const root = document.documentElement
root.classList.toggle('dark', theme === 'dark')
localStorage.setItem('theme', theme)
}, [theme])
function setTheme(t: Theme) {
setThemeState(t)
}
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
const ctx = useContext(ThemeContext)
if (!ctx) throw new Error('useTheme must be used within ThemeProvider')
return ctx
}

View File

@ -2,13 +2,16 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import { RouterProvider } from 'react-router-dom' import { RouterProvider } from 'react-router-dom'
import { AuthProvider } from '@/lib/auth' import { AuthProvider } from '@/lib/auth'
import { ThemeProvider } from '@/lib/theme'
import { router } from '@/lib/router' import { router } from '@/lib/router'
import './index.css' import './index.css'
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
<AuthProvider> <ThemeProvider>
<RouterProvider router={router} /> <AuthProvider>
</AuthProvider> <RouterProvider router={router} />
</AuthProvider>
</ThemeProvider>
</StrictMode> </StrictMode>
) )

View File

@ -1,12 +1,10 @@
import { useState, type FormEvent } from 'react' import { useState, type FormEvent } from 'react'
import { Navigate } from 'react-router-dom' import { Navigate } from 'react-router-dom'
import { TrendingUp } from 'lucide-react'
import { useAuth } from '@/lib/auth' import { useAuth } from '@/lib/auth'
import { authApi } from '@/api/auth' import { authApi } from '@/api/auth'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
export function Login() { export function Login() {
const { token, login } = useAuth() const { token, login } = useAuth()
@ -33,31 +31,64 @@ export function Login() {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-background p-4"> <div className="flex min-h-screen items-center justify-center bg-background p-4">
<Card className="w-full max-w-sm"> {/* Motif de fond discret */}
<CardHeader className="text-center"> <div className="absolute inset-0 opacity-[0.03] pointer-events-none"
<div className="mb-2 flex justify-center"> style={{ backgroundImage: 'repeating-linear-gradient(0deg, hsl(20 15% 9%), hsl(20 15% 9%) 1px, transparent 1px, transparent 40px), repeating-linear-gradient(90deg, hsl(20 15% 9%), hsl(20 15% 9%) 1px, transparent 1px, transparent 40px)' }}
<TrendingUp className="h-10 w-10 text-primary" /> />
</div>
<CardTitle className="text-2xl">Tradarr</CardTitle> <div className="relative w-full max-w-sm">
<p className="text-sm text-muted-foreground">Votre assistant trading IA</p> {/* En-tête */}
</CardHeader> <div className="mb-8 text-center">
<CardContent> <h1 className="font-serif text-4xl font-bold text-foreground">
Financ<span className="text-primary">IA</span>l
</h1>
<p className="mt-2 text-sm text-muted-foreground tracking-wide">
Votre journal financier augmenté par l'IA
</p>
</div>
{/* Carte formulaire */}
<div className="rounded-lg border bg-card shadow-sm p-6">
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-1"> <div className="space-y-1.5">
<Label htmlFor="email">Email</Label> <Label htmlFor="email">Adresse email</Label>
<Input id="email" type="email" value={email} onChange={e => setEmail(e.target.value)} required autoComplete="email" /> <Input
id="email"
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
required
autoComplete="email"
placeholder="vous@exemple.com"
/>
</div> </div>
<div className="space-y-1"> <div className="space-y-1.5">
<Label htmlFor="password">Mot de passe</Label> <Label htmlFor="password">Mot de passe</Label>
<Input id="password" type="password" value={password} onChange={e => setPassword(e.target.value)} required autoComplete="current-password" /> <Input
id="password"
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
required
autoComplete="current-password"
placeholder="••••••••"
/>
</div> </div>
{error && <p className="text-sm text-destructive">{error}</p>} {error && (
<Button type="submit" className="w-full" disabled={loading}> <p className="text-sm text-destructive border border-destructive/20 bg-destructive/5 rounded px-3 py-2">
{error}
</p>
)}
<Button type="submit" className="w-full mt-2" disabled={loading}>
{loading ? 'Connexion' : 'Se connecter'} {loading ? 'Connexion' : 'Se connecter'}
</Button> </Button>
</form> </form>
</CardContent> </div>
</Card>
<p className="mt-6 text-center text-xs text-muted-foreground">
© {new Date().getFullYear()} FinancIAl Accès restreint
</p>
</div>
</div> </div>
) )
} }

View File

@ -1,11 +1,12 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { Save, RotateCcw } from 'lucide-react' import { Save, RotateCcw, Sun, Moon } from 'lucide-react'
import { adminApi, type Setting } from '@/api/admin' import { adminApi, type Setting } from '@/api/admin'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { Spinner } from '@/components/ui/spinner' import { Spinner } from '@/components/ui/spinner'
import { useTheme } from '@/lib/theme'
const NUMERIC_SETTINGS: Record<string, { label: string; description: string }> = { const NUMERIC_SETTINGS: Record<string, { label: string; description: string }> = {
summary_max_articles: { label: 'Articles max par résumé', description: 'Nombre maximum d\'articles envoyés à l\'IA pour la passe 2 (résumé)' }, summary_max_articles: { label: 'Articles max par résumé', description: 'Nombre maximum d\'articles envoyés à l\'IA pour la passe 2 (résumé)' },
@ -20,6 +21,7 @@ const COMMON_TIMEZONES = [
] ]
export function AdminSettings() { export function AdminSettings() {
const { theme, setTheme } = useTheme()
const [settings, setSettings] = useState<Setting[]>([]) const [settings, setSettings] = useState<Setting[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [values, setValues] = useState<Record<string, string>>({}) const [values, setValues] = useState<Record<string, string>>({})
@ -90,6 +92,37 @@ export function AdminSettings() {
<p className="text-muted-foreground text-sm">Configuration globale du service</p> <p className="text-muted-foreground text-sm">Configuration globale du service</p>
</div> </div>
{/* Apparence */}
<Card>
<CardHeader><CardTitle>Apparence</CardTitle></CardHeader>
<CardContent>
<div className="flex items-center gap-3">
<button
onClick={() => setTheme('light')}
className={`flex items-center gap-2 px-4 py-2 rounded-md border text-sm font-medium transition-colors ${
theme === 'light'
? 'bg-primary text-primary-foreground border-primary'
: 'bg-transparent text-muted-foreground border-border hover:bg-accent hover:text-foreground'
}`}
>
<Sun className="h-4 w-4" />
Clair
</button>
<button
onClick={() => setTheme('dark')}
className={`flex items-center gap-2 px-4 py-2 rounded-md border text-sm font-medium transition-colors ${
theme === 'dark'
? 'bg-primary text-primary-foreground border-primary'
: 'bg-transparent text-muted-foreground border-border hover:bg-accent hover:text-foreground'
}`}
>
<Moon className="h-4 w-4" />
Sombre
</button>
</div>
</CardContent>
</Card>
{/* Fuseau horaire */} {/* Fuseau horaire */}
<Card> <Card>
<CardHeader><CardTitle>Fuseau horaire</CardTitle></CardHeader> <CardHeader><CardTitle>Fuseau horaire</CardTitle></CardHeader>

View File

@ -5,6 +5,9 @@ export default {
content: ['./index.html', './src/**/*.{ts,tsx}'], content: ['./index.html', './src/**/*.{ts,tsx}'],
theme: { theme: {
extend: { extend: {
fontFamily: {
serif: ['Playfair Display', 'Georgia', 'serif'],
},
colors: { colors: {
border: 'hsl(var(--border))', border: 'hsl(var(--border))',
input: 'hsl(var(--input))', input: 'hsl(var(--input))',
@ -17,9 +20,9 @@ export default {
muted: { DEFAULT: 'hsl(var(--muted))', foreground: 'hsl(var(--muted-foreground))' }, muted: { DEFAULT: 'hsl(var(--muted))', foreground: 'hsl(var(--muted-foreground))' },
accent: { DEFAULT: 'hsl(var(--accent))', foreground: 'hsl(var(--accent-foreground))' }, accent: { DEFAULT: 'hsl(var(--accent))', foreground: 'hsl(var(--accent-foreground))' },
card: { DEFAULT: 'hsl(var(--card))', foreground: 'hsl(var(--card-foreground))' }, card: { DEFAULT: 'hsl(var(--card))', foreground: 'hsl(var(--card-foreground))' },
bullish: '#22c55e', bullish: '#15803d',
bearish: '#ef4444', bearish: '#b91c1c',
neutral: '#94a3b8', neutral: '#78716c',
}, },
borderRadius: { borderRadius: {
lg: 'var(--radius)', lg: 'var(--radius)',

View File

@ -1 +1 @@
{"root":["./src/main.tsx","./src/api/admin.ts","./src/api/articles.ts","./src/api/assets.ts","./src/api/auth.ts","./src/api/client.ts","./src/api/reports.ts","./src/api/summaries.ts","./src/components/layout/AppLayout.tsx","./src/components/layout/MobileNav.tsx","./src/components/layout/Sidebar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/markdown.tsx","./src/components/ui/select.tsx","./src/components/ui/spinner.tsx","./src/lib/auth.tsx","./src/lib/cn.ts","./src/lib/router.tsx","./src/pages/Dashboard.tsx","./src/pages/Feed.tsx","./src/pages/Login.tsx","./src/pages/Reports.tsx","./src/pages/Watchlist.tsx","./src/pages/admin/AIProviders.tsx","./src/pages/admin/AdminLayout.tsx","./src/pages/admin/AdminSettings.tsx","./src/pages/admin/AdminUsers.tsx","./src/pages/admin/Credentials.tsx","./src/pages/admin/Jobs.tsx","./src/pages/admin/Schedule.tsx","./src/pages/admin/Sources.tsx"],"version":"5.9.3"} {"root":["./src/main.tsx","./src/api/admin.ts","./src/api/articles.ts","./src/api/assets.ts","./src/api/auth.ts","./src/api/client.ts","./src/api/reports.ts","./src/api/summaries.ts","./src/components/layout/AppLayout.tsx","./src/components/layout/MobileNav.tsx","./src/components/layout/Sidebar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/markdown.tsx","./src/components/ui/select.tsx","./src/components/ui/spinner.tsx","./src/lib/auth.tsx","./src/lib/cn.ts","./src/lib/router.tsx","./src/lib/theme.tsx","./src/pages/Dashboard.tsx","./src/pages/Feed.tsx","./src/pages/Login.tsx","./src/pages/Reports.tsx","./src/pages/Watchlist.tsx","./src/pages/admin/AIProviders.tsx","./src/pages/admin/AdminLayout.tsx","./src/pages/admin/AdminSettings.tsx","./src/pages/admin/AdminUsers.tsx","./src/pages/admin/Credentials.tsx","./src/pages/admin/Jobs.tsx","./src/pages/admin/Schedule.tsx","./src/pages/admin/Sources.tsx"],"version":"5.9.3"}