@renseiai/agentfactory-dashboard 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +259 -0
  3. package/components.json +16 -0
  4. package/package.json +78 -0
  5. package/src/components/fleet/agent-card.tsx +97 -0
  6. package/src/components/fleet/fleet-overview.tsx +139 -0
  7. package/src/components/fleet/provider-icon.tsx +61 -0
  8. package/src/components/fleet/stat-card.tsx +77 -0
  9. package/src/components/fleet/status-dot.tsx +35 -0
  10. package/src/components/layout/bottom-bar.tsx +58 -0
  11. package/src/components/layout/shell.tsx +65 -0
  12. package/src/components/layout/sidebar.tsx +97 -0
  13. package/src/components/layout/top-bar.tsx +63 -0
  14. package/src/components/pipeline/pipeline-card.tsx +65 -0
  15. package/src/components/pipeline/pipeline-column.tsx +44 -0
  16. package/src/components/pipeline/pipeline-view.tsx +85 -0
  17. package/src/components/sessions/session-detail.tsx +153 -0
  18. package/src/components/sessions/session-list.tsx +125 -0
  19. package/src/components/sessions/session-timeline.tsx +76 -0
  20. package/src/components/sessions/token-chart.tsx +51 -0
  21. package/src/components/settings/settings-view.tsx +175 -0
  22. package/src/components/shared/empty-state.tsx +34 -0
  23. package/src/components/shared/logo.tsx +37 -0
  24. package/src/components/ui/badge.tsx +33 -0
  25. package/src/components/ui/button.tsx +54 -0
  26. package/src/components/ui/card.tsx +57 -0
  27. package/src/components/ui/dropdown-menu.tsx +77 -0
  28. package/src/components/ui/scroll-area.tsx +45 -0
  29. package/src/components/ui/separator.tsx +25 -0
  30. package/src/components/ui/sheet.tsx +88 -0
  31. package/src/components/ui/skeleton.tsx +15 -0
  32. package/src/components/ui/tabs.tsx +54 -0
  33. package/src/components/ui/tooltip.tsx +29 -0
  34. package/src/hooks/use-sessions.ts +13 -0
  35. package/src/hooks/use-stats.ts +13 -0
  36. package/src/hooks/use-workers.ts +17 -0
  37. package/src/index.ts +82 -0
  38. package/src/lib/format.ts +36 -0
  39. package/src/lib/status-config.ts +72 -0
  40. package/src/lib/utils.ts +6 -0
  41. package/src/lib/work-type-config.ts +116 -0
  42. package/src/pages/dashboard-page.tsx +11 -0
  43. package/src/pages/pipeline-page.tsx +11 -0
  44. package/src/pages/session-page.tsx +29 -0
  45. package/src/pages/settings-page.tsx +7 -0
  46. package/src/styles/globals.css +218 -0
  47. package/src/types/api.ts +48 -0
  48. package/tailwind.config.ts +139 -0
@@ -0,0 +1,72 @@
1
+ export type SessionStatus = 'queued' | 'parked' | 'working' | 'completed' | 'failed' | 'stopped'
2
+
3
+ export interface StatusConfig {
4
+ label: string
5
+ dotColor: string
6
+ textColor: string
7
+ bgColor: string
8
+ borderColor: string
9
+ glowClass: string
10
+ animate: boolean
11
+ }
12
+
13
+ const statuses: Record<SessionStatus, StatusConfig> = {
14
+ working: {
15
+ label: 'Working',
16
+ dotColor: 'bg-af-status-success',
17
+ textColor: 'text-af-status-success',
18
+ bgColor: 'bg-af-status-success/10',
19
+ borderColor: 'border-af-status-success/20',
20
+ glowClass: 'glow-dot-green',
21
+ animate: true,
22
+ },
23
+ queued: {
24
+ label: 'Queued',
25
+ dotColor: 'bg-af-status-warning',
26
+ textColor: 'text-af-status-warning',
27
+ bgColor: 'bg-af-status-warning/10',
28
+ borderColor: 'border-af-status-warning/20',
29
+ glowClass: 'glow-dot-yellow',
30
+ animate: true,
31
+ },
32
+ parked: {
33
+ label: 'Parked',
34
+ dotColor: 'bg-af-text-tertiary',
35
+ textColor: 'text-af-text-secondary',
36
+ bgColor: 'bg-af-text-secondary/8',
37
+ borderColor: 'border-af-text-secondary/10',
38
+ glowClass: '',
39
+ animate: false,
40
+ },
41
+ completed: {
42
+ label: 'Completed',
43
+ dotColor: 'bg-af-status-success',
44
+ textColor: 'text-af-status-success',
45
+ bgColor: 'bg-af-status-success/10',
46
+ borderColor: 'border-af-status-success/15',
47
+ glowClass: '',
48
+ animate: false,
49
+ },
50
+ failed: {
51
+ label: 'Failed',
52
+ dotColor: 'bg-af-status-error',
53
+ textColor: 'text-af-status-error',
54
+ bgColor: 'bg-af-status-error/10',
55
+ borderColor: 'border-af-status-error/20',
56
+ glowClass: 'glow-dot-red',
57
+ animate: false,
58
+ },
59
+ stopped: {
60
+ label: 'Stopped',
61
+ dotColor: 'bg-af-text-tertiary',
62
+ textColor: 'text-af-text-secondary',
63
+ bgColor: 'bg-af-text-secondary/8',
64
+ borderColor: 'border-af-text-secondary/10',
65
+ glowClass: '',
66
+ animate: false,
67
+ },
68
+ }
69
+
70
+ export function getStatusConfig(status: SessionStatus): StatusConfig {
71
+ return statuses[status] ?? statuses.queued
72
+ }
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,116 @@
1
+ export interface WorkTypeConfig {
2
+ label: string
3
+ color: string
4
+ bgColor: string
5
+ borderColor: string
6
+ }
7
+
8
+ const workTypes: Record<string, WorkTypeConfig> = {
9
+ development: {
10
+ label: 'Development',
11
+ color: 'text-blue-400',
12
+ bgColor: 'bg-blue-400/10',
13
+ borderColor: 'border-blue-400/15',
14
+ },
15
+ bugfix: {
16
+ label: 'Bug Fix',
17
+ color: 'text-red-400',
18
+ bgColor: 'bg-red-400/10',
19
+ borderColor: 'border-red-400/15',
20
+ },
21
+ feature: {
22
+ label: 'Feature',
23
+ color: 'text-emerald-400',
24
+ bgColor: 'bg-emerald-400/10',
25
+ borderColor: 'border-emerald-400/15',
26
+ },
27
+ qa: {
28
+ label: 'QA',
29
+ color: 'text-purple-400',
30
+ bgColor: 'bg-purple-400/10',
31
+ borderColor: 'border-purple-400/15',
32
+ },
33
+ 'qa-coordination': {
34
+ label: 'QA Coord',
35
+ color: 'text-purple-300',
36
+ bgColor: 'bg-purple-300/10',
37
+ borderColor: 'border-purple-300/15',
38
+ },
39
+ acceptance: {
40
+ label: 'Acceptance',
41
+ color: 'text-pink-400',
42
+ bgColor: 'bg-pink-400/10',
43
+ borderColor: 'border-pink-400/15',
44
+ },
45
+ 'acceptance-coordination': {
46
+ label: 'Accept Coord',
47
+ color: 'text-pink-300',
48
+ bgColor: 'bg-pink-300/10',
49
+ borderColor: 'border-pink-300/15',
50
+ },
51
+ coordination: {
52
+ label: 'Coordination',
53
+ color: 'text-orange-400',
54
+ bgColor: 'bg-orange-400/10',
55
+ borderColor: 'border-orange-400/15',
56
+ },
57
+ research: {
58
+ label: 'Research',
59
+ color: 'text-teal-400',
60
+ bgColor: 'bg-teal-400/10',
61
+ borderColor: 'border-teal-400/15',
62
+ },
63
+ 'backlog-creation': {
64
+ label: 'Backlog',
65
+ color: 'text-slate-400',
66
+ bgColor: 'bg-slate-400/10',
67
+ borderColor: 'border-slate-400/15',
68
+ },
69
+ inflight: {
70
+ label: 'Inflight',
71
+ color: 'text-yellow-400',
72
+ bgColor: 'bg-yellow-400/10',
73
+ borderColor: 'border-yellow-400/15',
74
+ },
75
+ refinement: {
76
+ label: 'Refinement',
77
+ color: 'text-lime-400',
78
+ bgColor: 'bg-lime-400/10',
79
+ borderColor: 'border-lime-400/15',
80
+ },
81
+ 'refinement-coordination': {
82
+ label: 'Refine Coord',
83
+ color: 'text-lime-300',
84
+ bgColor: 'bg-lime-300/10',
85
+ borderColor: 'border-lime-300/15',
86
+ },
87
+ refactor: {
88
+ label: 'Refactor',
89
+ color: 'text-amber-400',
90
+ bgColor: 'bg-amber-400/10',
91
+ borderColor: 'border-amber-400/15',
92
+ },
93
+ review: {
94
+ label: 'Review',
95
+ color: 'text-cyan-400',
96
+ bgColor: 'bg-cyan-400/10',
97
+ borderColor: 'border-cyan-400/15',
98
+ },
99
+ docs: {
100
+ label: 'Docs',
101
+ color: 'text-indigo-400',
102
+ bgColor: 'bg-indigo-400/10',
103
+ borderColor: 'border-indigo-400/15',
104
+ },
105
+ }
106
+
107
+ const defaultWorkType: WorkTypeConfig = {
108
+ label: 'Unknown',
109
+ color: 'text-af-text-secondary',
110
+ bgColor: 'bg-af-text-secondary/8',
111
+ borderColor: 'border-af-text-secondary/10',
112
+ }
113
+
114
+ export function getWorkTypeConfig(workType: string): WorkTypeConfig {
115
+ return workTypes[workType.toLowerCase()] ?? defaultWorkType
116
+ }
@@ -0,0 +1,11 @@
1
+ 'use client'
2
+
3
+ import { FleetOverview } from '../components/fleet/fleet-overview'
4
+
5
+ interface DashboardPageProps {
6
+ onSessionSelect?: (sessionId: string) => void
7
+ }
8
+
9
+ export function DashboardPage({ onSessionSelect }: DashboardPageProps) {
10
+ return <FleetOverview onSessionSelect={onSessionSelect} />
11
+ }
@@ -0,0 +1,11 @@
1
+ 'use client'
2
+
3
+ import { PipelineView } from '../components/pipeline/pipeline-view'
4
+
5
+ interface PipelinePageProps {
6
+ onSessionSelect?: (sessionId: string) => void
7
+ }
8
+
9
+ export function PipelinePage({ onSessionSelect }: PipelinePageProps) {
10
+ return <PipelineView onSessionSelect={onSessionSelect} />
11
+ }
@@ -0,0 +1,29 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useSessions } from '../hooks/use-sessions'
5
+ import { SessionList } from '../components/sessions/session-list'
6
+ import { SessionDetail } from '../components/sessions/session-detail'
7
+
8
+ interface SessionPageProps {
9
+ sessionId?: string
10
+ }
11
+
12
+ export function SessionPage({ sessionId: initialId }: SessionPageProps) {
13
+ const [selectedId, setSelectedId] = useState<string | undefined>(initialId)
14
+ const { data } = useSessions()
15
+ const sessions = data?.sessions ?? []
16
+
17
+ const selected = selectedId ? sessions.find((s) => s.id === selectedId) : undefined
18
+
19
+ if (selected) {
20
+ return (
21
+ <SessionDetail
22
+ session={selected}
23
+ onBack={() => setSelectedId(undefined)}
24
+ />
25
+ )
26
+ }
27
+
28
+ return <SessionList onSelect={setSelectedId} />
29
+ }
@@ -0,0 +1,7 @@
1
+ 'use client'
2
+
3
+ import { SettingsView } from '../components/settings/settings-view'
4
+
5
+ export function SettingsPage() {
6
+ return <SettingsView />
7
+ }
@@ -0,0 +1,218 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 222 55% 5%;
8
+ --foreground: 210 40% 98%;
9
+ --card: 222 40% 8%;
10
+ --card-foreground: 210 40% 98%;
11
+ --popover: 222 40% 8%;
12
+ --popover-foreground: 210 40% 98%;
13
+ --primary: 19 100% 60%;
14
+ --primary-foreground: 210 40% 98%;
15
+ --secondary: 222 35% 12%;
16
+ --secondary-foreground: 210 40% 98%;
17
+ --muted: 222 25% 16%;
18
+ --muted-foreground: 215 16% 54%;
19
+ --accent: 222 35% 12%;
20
+ --accent-foreground: 210 40% 98%;
21
+ --destructive: 0 84% 60%;
22
+ --destructive-foreground: 210 40% 98%;
23
+ --border: 222 25% 14%;
24
+ --input: 222 25% 14%;
25
+ --ring: 19 100% 60%;
26
+ --radius: 0.625rem;
27
+
28
+ /* Extended palette */
29
+ --glow-orange: 19 100% 60%;
30
+ --glow-teal: 164 100% 42%;
31
+ --glow-blue: 217 91% 60%;
32
+ }
33
+ }
34
+
35
+ @layer base {
36
+ * {
37
+ @apply border-border;
38
+ }
39
+ body {
40
+ @apply bg-background text-foreground;
41
+ font-feature-settings: "rlig" 1, "calt" 1, "ss01" 1;
42
+ -webkit-font-smoothing: antialiased;
43
+ -moz-osx-font-smoothing: grayscale;
44
+ }
45
+ }
46
+
47
+ @layer utilities {
48
+ /* Glass surface effect */
49
+ .glass {
50
+ background: linear-gradient(
51
+ 135deg,
52
+ hsl(222 40% 8% / 0.8) 0%,
53
+ hsl(222 40% 6% / 0.6) 100%
54
+ );
55
+ backdrop-filter: blur(16px) saturate(1.2);
56
+ -webkit-backdrop-filter: blur(16px) saturate(1.2);
57
+ }
58
+
59
+ .glass-subtle {
60
+ background: linear-gradient(
61
+ 135deg,
62
+ hsl(222 40% 10% / 0.5) 0%,
63
+ hsl(222 40% 7% / 0.4) 100%
64
+ );
65
+ backdrop-filter: blur(12px);
66
+ -webkit-backdrop-filter: blur(12px);
67
+ }
68
+
69
+ /* Glow effects */
70
+ .glow-orange {
71
+ box-shadow:
72
+ 0 0 0 1px hsl(var(--glow-orange) / 0.15),
73
+ 0 0 20px -4px hsl(var(--glow-orange) / 0.2),
74
+ 0 0 40px -8px hsl(var(--glow-orange) / 0.1);
75
+ }
76
+
77
+ .glow-teal {
78
+ box-shadow:
79
+ 0 0 0 1px hsl(var(--glow-teal) / 0.15),
80
+ 0 0 20px -4px hsl(var(--glow-teal) / 0.2),
81
+ 0 0 40px -8px hsl(var(--glow-teal) / 0.1);
82
+ }
83
+
84
+ .glow-soft {
85
+ box-shadow:
86
+ 0 0 0 1px hsl(222 25% 18% / 0.6),
87
+ 0 4px 24px -4px hsl(222 50% 4% / 0.5);
88
+ }
89
+
90
+ .glow-dot-green {
91
+ box-shadow: 0 0 8px 2px hsl(142 71% 45% / 0.4);
92
+ }
93
+
94
+ .glow-dot-orange {
95
+ box-shadow: 0 0 8px 2px hsl(var(--glow-orange) / 0.4);
96
+ }
97
+
98
+ .glow-dot-red {
99
+ box-shadow: 0 0 8px 2px hsl(0 84% 60% / 0.4);
100
+ }
101
+
102
+ .glow-dot-yellow {
103
+ box-shadow: 0 0 8px 2px hsl(38 92% 50% / 0.35);
104
+ }
105
+
106
+ /* Grid background */
107
+ .grid-bg {
108
+ background-image:
109
+ linear-gradient(hsl(222 30% 15% / 0.3) 1px, transparent 1px),
110
+ linear-gradient(90deg, hsl(222 30% 15% / 0.3) 1px, transparent 1px);
111
+ background-size: 40px 40px;
112
+ }
113
+
114
+ .grid-bg-fine {
115
+ background-image:
116
+ linear-gradient(hsl(222 30% 15% / 0.15) 1px, transparent 1px),
117
+ linear-gradient(90deg, hsl(222 30% 15% / 0.15) 1px, transparent 1px);
118
+ background-size: 20px 20px;
119
+ }
120
+
121
+ /* Noise texture */
122
+ .noise {
123
+ position: relative;
124
+ }
125
+ .noise::after {
126
+ content: '';
127
+ position: absolute;
128
+ inset: 0;
129
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");
130
+ pointer-events: none;
131
+ z-index: 0;
132
+ opacity: 0.4;
133
+ mix-blend-mode: overlay;
134
+ }
135
+
136
+ /* Gradient mesh background */
137
+ .mesh-gradient {
138
+ background:
139
+ radial-gradient(ellipse 600px 400px at 15% 20%, hsl(var(--glow-orange) / 0.06) 0%, transparent 70%),
140
+ radial-gradient(ellipse 500px 500px at 85% 80%, hsl(var(--glow-teal) / 0.04) 0%, transparent 70%),
141
+ radial-gradient(ellipse 800px 300px at 50% 50%, hsl(var(--glow-blue) / 0.03) 0%, transparent 70%);
142
+ }
143
+
144
+ /* Tabular nums utility */
145
+ .nums {
146
+ font-variant-numeric: tabular-nums;
147
+ }
148
+
149
+ /* Border glow on hover */
150
+ .hover-glow {
151
+ transition: box-shadow 0.3s ease, border-color 0.3s ease;
152
+ }
153
+ .hover-glow:hover {
154
+ border-color: hsl(var(--glow-orange) / 0.25);
155
+ box-shadow:
156
+ 0 0 0 1px hsl(var(--glow-orange) / 0.1),
157
+ 0 0 20px -8px hsl(var(--glow-orange) / 0.15);
158
+ }
159
+
160
+ /* Staggered animation delays */
161
+ .stagger-1 { animation-delay: 50ms; }
162
+ .stagger-2 { animation-delay: 100ms; }
163
+ .stagger-3 { animation-delay: 150ms; }
164
+ .stagger-4 { animation-delay: 200ms; }
165
+ .stagger-5 { animation-delay: 250ms; }
166
+ .stagger-6 { animation-delay: 300ms; }
167
+ }
168
+
169
+ @layer components {
170
+ /* Accent line for active sidebar items */
171
+ .nav-active-indicator {
172
+ position: relative;
173
+ }
174
+ .nav-active-indicator::before {
175
+ content: '';
176
+ position: absolute;
177
+ left: 0;
178
+ top: 25%;
179
+ bottom: 25%;
180
+ width: 2px;
181
+ border-radius: 1px;
182
+ background: hsl(var(--glow-orange));
183
+ box-shadow: 0 0 8px 1px hsl(var(--glow-orange) / 0.5);
184
+ }
185
+
186
+ /* Column accent strip */
187
+ .column-accent {
188
+ position: relative;
189
+ }
190
+ .column-accent::before {
191
+ content: '';
192
+ position: absolute;
193
+ top: 0;
194
+ left: 12px;
195
+ right: 12px;
196
+ height: 1px;
197
+ border-radius: 0 0 2px 2px;
198
+ }
199
+ .column-accent-green::before {
200
+ background: linear-gradient(90deg, transparent, hsl(142 71% 45% / 0.6), transparent);
201
+ box-shadow: 0 0 12px 2px hsl(142 71% 45% / 0.2);
202
+ }
203
+ .column-accent-gray::before {
204
+ background: linear-gradient(90deg, transparent, hsl(215 16% 47% / 0.4), transparent);
205
+ }
206
+ .column-accent-blue::before {
207
+ background: linear-gradient(90deg, transparent, hsl(217 91% 60% / 0.6), transparent);
208
+ box-shadow: 0 0 12px 2px hsl(217 91% 60% / 0.15);
209
+ }
210
+ .column-accent-red::before {
211
+ background: linear-gradient(90deg, transparent, hsl(0 84% 60% / 0.6), transparent);
212
+ box-shadow: 0 0 12px 2px hsl(0 84% 60% / 0.15);
213
+ }
214
+ .column-accent-yellow::before {
215
+ background: linear-gradient(90deg, transparent, hsl(38 92% 50% / 0.5), transparent);
216
+ box-shadow: 0 0 12px 2px hsl(38 92% 50% / 0.12);
217
+ }
218
+ }
@@ -0,0 +1,48 @@
1
+ export interface PublicStatsResponse {
2
+ workersOnline: number
3
+ agentsWorking: number
4
+ queueDepth: number
5
+ completedToday: number
6
+ availableCapacity: number
7
+ totalCostToday?: number
8
+ totalCostAllTime?: number
9
+ sessionCountToday?: number
10
+ timestamp: string
11
+ }
12
+
13
+ export type SessionStatus = 'queued' | 'parked' | 'working' | 'completed' | 'failed' | 'stopped'
14
+
15
+ export interface PublicSessionResponse {
16
+ id: string
17
+ identifier: string
18
+ status: SessionStatus
19
+ workType: string
20
+ startedAt: string
21
+ duration: number
22
+ costUsd?: number
23
+ provider?: string
24
+ }
25
+
26
+ export interface PublicSessionsListResponse {
27
+ sessions: PublicSessionResponse[]
28
+ count: number
29
+ timestamp: string
30
+ }
31
+
32
+ export interface WorkerResponse {
33
+ id: string
34
+ status: 'active' | 'draining' | 'offline'
35
+ capacity: number
36
+ activeSessions: number
37
+ lastHeartbeat: string
38
+ provider?: string
39
+ hostname?: string
40
+ }
41
+
42
+ export interface WorkersListResponse {
43
+ workers: WorkerResponse[]
44
+ count: number
45
+ timestamp: string
46
+ }
47
+
48
+ export type PipelineStatus = 'backlog' | 'started' | 'finished' | 'delivered' | 'accepted'
@@ -0,0 +1,139 @@
1
+ import type { Config } from 'tailwindcss'
2
+
3
+ const dashboardPreset: Config = {
4
+ content: [],
5
+ theme: {
6
+ extend: {
7
+ colors: {
8
+ // Void backgrounds - deeper, richer
9
+ 'af-bg-primary': '#080C16',
10
+ 'af-bg-secondary': '#0D1220',
11
+ 'af-bg-tertiary': '#111828',
12
+
13
+ // Glass surfaces
14
+ 'af-surface': '#141B2D',
15
+ 'af-surface-raised': '#1A2236',
16
+ 'af-surface-border': '#1E2740',
17
+ 'af-surface-border-bright': '#283350',
18
+
19
+ // Accent palette
20
+ 'af-accent': '#FF6B35',
21
+ 'af-accent-dim': '#CC5529',
22
+ 'af-teal': '#00D4AA',
23
+ 'af-teal-dim': '#00A886',
24
+ 'af-blue': '#4B8BF5',
25
+
26
+ // Status
27
+ 'af-status-success': '#22C55E',
28
+ 'af-status-warning': '#F59E0B',
29
+ 'af-status-error': '#EF4444',
30
+
31
+ // Text hierarchy
32
+ 'af-text-primary': '#F1F5F9',
33
+ 'af-text-secondary': '#7C8DB5',
34
+ 'af-text-tertiary': '#4B5B80',
35
+ 'af-code': '#A5B4FC',
36
+
37
+ // shadcn tokens
38
+ border: 'hsl(var(--border))',
39
+ input: 'hsl(var(--input))',
40
+ ring: 'hsl(var(--ring))',
41
+ background: 'hsl(var(--background))',
42
+ foreground: 'hsl(var(--foreground))',
43
+ primary: {
44
+ DEFAULT: 'hsl(var(--primary))',
45
+ foreground: 'hsl(var(--primary-foreground))',
46
+ },
47
+ secondary: {
48
+ DEFAULT: 'hsl(var(--secondary))',
49
+ foreground: 'hsl(var(--secondary-foreground))',
50
+ },
51
+ destructive: {
52
+ DEFAULT: 'hsl(var(--destructive))',
53
+ foreground: 'hsl(var(--destructive-foreground))',
54
+ },
55
+ muted: {
56
+ DEFAULT: 'hsl(var(--muted))',
57
+ foreground: 'hsl(var(--muted-foreground))',
58
+ },
59
+ accent: {
60
+ DEFAULT: 'hsl(var(--accent))',
61
+ foreground: 'hsl(var(--accent-foreground))',
62
+ },
63
+ popover: {
64
+ DEFAULT: 'hsl(var(--popover))',
65
+ foreground: 'hsl(var(--popover-foreground))',
66
+ },
67
+ card: {
68
+ DEFAULT: 'hsl(var(--card))',
69
+ foreground: 'hsl(var(--card-foreground))',
70
+ },
71
+ },
72
+ fontFamily: {
73
+ sans: ['Syne', 'DM Sans', 'system-ui', 'sans-serif'],
74
+ body: ['DM Sans', 'system-ui', 'sans-serif'],
75
+ mono: ['Fira Code', 'JetBrains Mono', 'ui-monospace', 'monospace'],
76
+ display: ['Syne', 'system-ui', 'sans-serif'],
77
+ },
78
+ fontSize: {
79
+ '2xs': ['0.625rem', { lineHeight: '0.875rem' }],
80
+ xs: ['0.6875rem', { lineHeight: '1rem' }],
81
+ sm: ['0.8125rem', { lineHeight: '1.25rem' }],
82
+ base: ['0.875rem', { lineHeight: '1.5rem' }],
83
+ lg: ['1rem', { lineHeight: '1.5rem' }],
84
+ xl: ['1.25rem', { lineHeight: '1.75rem' }],
85
+ '2xl': ['1.75rem', { lineHeight: '2rem' }],
86
+ '3xl': ['2.25rem', { lineHeight: '2.5rem' }],
87
+ '4xl': ['3rem', { lineHeight: '3.25rem' }],
88
+ },
89
+ borderRadius: {
90
+ lg: 'var(--radius)',
91
+ md: 'calc(var(--radius) - 2px)',
92
+ sm: 'calc(var(--radius) - 4px)',
93
+ },
94
+ keyframes: {
95
+ 'pulse-dot': {
96
+ '0%, 100%': { opacity: '1' },
97
+ '50%': { opacity: '0.35' },
98
+ },
99
+ heartbeat: {
100
+ '0%': { transform: 'scale(1)', opacity: '0.5' },
101
+ '50%': { transform: 'scale(2.2)', opacity: '0' },
102
+ '100%': { transform: 'scale(1)', opacity: '0' },
103
+ },
104
+ 'fade-in': {
105
+ '0%': { opacity: '0', transform: 'translateY(8px)' },
106
+ '100%': { opacity: '1', transform: 'translateY(0)' },
107
+ },
108
+ 'fade-in-scale': {
109
+ '0%': { opacity: '0', transform: 'scale(0.95)' },
110
+ '100%': { opacity: '1', transform: 'scale(1)' },
111
+ },
112
+ shimmer: {
113
+ '0%': { backgroundPosition: '-200% 0' },
114
+ '100%': { backgroundPosition: '200% 0' },
115
+ },
116
+ 'glow-breathe': {
117
+ '0%, 100%': { opacity: '0.6' },
118
+ '50%': { opacity: '1' },
119
+ },
120
+ 'slide-in-left': {
121
+ '0%': { opacity: '0', transform: 'translateX(-12px)' },
122
+ '100%': { opacity: '1', transform: 'translateX(0)' },
123
+ },
124
+ },
125
+ animation: {
126
+ 'pulse-dot': 'pulse-dot 2s ease-in-out infinite',
127
+ heartbeat: 'heartbeat 2s ease-out infinite',
128
+ 'fade-in': 'fade-in 0.4s ease-out both',
129
+ 'fade-in-scale': 'fade-in-scale 0.3s ease-out both',
130
+ shimmer: 'shimmer 2s linear infinite',
131
+ 'glow-breathe': 'glow-breathe 3s ease-in-out infinite',
132
+ 'slide-in-left': 'slide-in-left 0.3s ease-out both',
133
+ },
134
+ },
135
+ },
136
+ plugins: [],
137
+ }
138
+
139
+ export default dashboardPreset