@nordsym/apiclaw 1.0.0 → 1.1.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 (154) hide show
  1. package/AGENTS.md +74 -0
  2. package/HEARTBEAT.md +4 -0
  3. package/IDENTITY.md +22 -0
  4. package/README.md +197 -202
  5. package/SOUL.md +36 -0
  6. package/STATUS.md +237 -0
  7. package/TOOLS.md +36 -0
  8. package/USER.md +17 -0
  9. package/{backend/convex → convex}/_generated/api.d.ts +6 -6
  10. package/convex/credits.ts +211 -0
  11. package/convex/http.ts +490 -0
  12. package/convex/providers.ts +516 -0
  13. package/convex/purchases.ts +183 -0
  14. package/convex/schema.ts +180 -0
  15. package/convex.json +3 -0
  16. package/dist/credentials.d.ts +19 -0
  17. package/dist/credentials.d.ts.map +1 -0
  18. package/dist/credentials.js +158 -0
  19. package/dist/credentials.js.map +1 -0
  20. package/dist/credits.d.ts +14 -11
  21. package/dist/credits.d.ts.map +1 -1
  22. package/dist/credits.js +151 -99
  23. package/dist/credits.js.map +1 -1
  24. package/dist/discovery.d.ts +7 -16
  25. package/dist/discovery.d.ts.map +1 -1
  26. package/dist/discovery.js +33 -40
  27. package/dist/discovery.js.map +1 -1
  28. package/dist/execute.d.ts +19 -0
  29. package/dist/execute.d.ts.map +1 -0
  30. package/dist/execute.js +285 -0
  31. package/dist/execute.js.map +1 -0
  32. package/dist/index.js +106 -30
  33. package/dist/index.js.map +1 -1
  34. package/dist/proxy.d.ts +6 -0
  35. package/dist/proxy.d.ts.map +1 -0
  36. package/dist/proxy.js +19 -0
  37. package/dist/proxy.js.map +1 -0
  38. package/dist/registry/apis.json +95362 -202
  39. package/dist/registry/apis_expanded.json +100853 -0
  40. package/dist/stripe.d.ts +68 -0
  41. package/dist/stripe.d.ts.map +1 -0
  42. package/dist/stripe.js +196 -0
  43. package/dist/stripe.js.map +1 -0
  44. package/dist/test.d.ts +3 -2
  45. package/dist/test.d.ts.map +1 -1
  46. package/dist/test.js +105 -75
  47. package/dist/test.js.map +1 -1
  48. package/dist/types.d.ts +0 -28
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/webhook.d.ts +2 -0
  51. package/dist/webhook.d.ts.map +1 -0
  52. package/dist/webhook.js +90 -0
  53. package/dist/webhook.js.map +1 -0
  54. package/landing/DESIGN.md +343 -0
  55. package/landing/package-lock.json +1190 -40
  56. package/landing/package.json +5 -2
  57. package/landing/public/android-chrome-192x192.png +0 -0
  58. package/landing/public/android-chrome-512x512.png +0 -0
  59. package/landing/public/apple-touch-icon.png +0 -0
  60. package/landing/public/demo.gif +0 -0
  61. package/landing/public/demo.mp4 +0 -0
  62. package/landing/public/favicon-16x16.png +0 -0
  63. package/landing/public/favicon-32x32.png +0 -0
  64. package/landing/public/favicon.ico +0 -0
  65. package/landing/public/favicon.svg +3 -0
  66. package/landing/public/icon.svg +47 -0
  67. package/landing/public/logo-mono.svg +37 -0
  68. package/landing/public/logo-simple.svg +45 -0
  69. package/landing/public/logo.svg +84 -0
  70. package/landing/public/og-image.png +0 -0
  71. package/landing/public/og-template.html +184 -0
  72. package/landing/public/site.webmanifest +31 -0
  73. package/landing/scripts/generate-assets.js +284 -0
  74. package/landing/scripts/generate-pngs.js +48 -0
  75. package/landing/scripts/generate-stats.js +42 -0
  76. package/landing/src/app/admin/page.tsx +348 -0
  77. package/landing/src/app/api/auth/magic-link/route.ts +73 -0
  78. package/landing/src/app/api/auth/session/route.ts +38 -0
  79. package/landing/src/app/api/auth/verify/route.ts +43 -0
  80. package/landing/src/app/api/og/route.tsx +74 -0
  81. package/landing/src/app/globals.css +439 -100
  82. package/landing/src/app/layout.tsx +37 -9
  83. package/landing/src/app/page.tsx +640 -552
  84. package/landing/src/app/providers/dashboard/login/page.tsx +176 -0
  85. package/landing/src/app/providers/dashboard/page.tsx +589 -0
  86. package/landing/src/app/providers/dashboard/verify/page.tsx +106 -0
  87. package/landing/src/app/providers/layout.tsx +14 -0
  88. package/landing/src/app/providers/page.tsx +402 -0
  89. package/landing/src/app/providers/register/page.tsx +670 -0
  90. package/landing/src/components/ProviderDashboard.tsx +794 -0
  91. package/landing/src/hooks/useDashboardData.ts +99 -0
  92. package/landing/src/lib/apis.json +116054 -0
  93. package/landing/src/lib/convex-client.ts +106 -0
  94. package/landing/src/lib/mock-data.ts +285 -0
  95. package/landing/src/lib/stats.json +6 -0
  96. package/landing/tailwind.config.ts +12 -11
  97. package/landing/tsconfig.tsbuildinfo +1 -0
  98. package/package.json +21 -20
  99. package/scripts/SYMBOT-FIX.md +238 -0
  100. package/scripts/demo-simulation.py +177 -0
  101. package/scripts/expand-more.py +502 -0
  102. package/scripts/expand-registry.py +434 -0
  103. package/scripts/history-sanitizer.ts +272 -0
  104. package/scripts/mass-scrape.py +1308 -0
  105. package/scripts/sync-and-deploy.sh +36 -0
  106. package/src/credentials.ts +177 -0
  107. package/src/credits.ts +190 -122
  108. package/src/discovery.ts +45 -58
  109. package/src/execute.ts +350 -0
  110. package/src/index.ts +113 -31
  111. package/src/proxy.ts +24 -0
  112. package/src/registry/apis.json +95362 -202
  113. package/src/registry/apis_expanded.json +100853 -0
  114. package/src/stripe.ts +243 -0
  115. package/src/test.ts +127 -89
  116. package/src/types.ts +0 -34
  117. package/src/webhook.ts +107 -0
  118. package/.github/ISSUE_TEMPLATE/add-api.yml +0 -123
  119. package/BRIEFING.md +0 -30
  120. package/backend/convex/apiKeys.ts +0 -75
  121. package/backend/convex/purchases.ts +0 -74
  122. package/backend/convex/schema.ts +0 -45
  123. package/backend/convex/transactions.ts +0 -57
  124. package/backend/convex/users.ts +0 -94
  125. package/backend/package-lock.json +0 -521
  126. package/backend/package.json +0 -15
  127. package/dist/registry/parse_apis.py +0 -146
  128. package/dist/revenuecat.d.ts +0 -61
  129. package/dist/revenuecat.d.ts.map +0 -1
  130. package/dist/revenuecat.js +0 -166
  131. package/dist/revenuecat.js.map +0 -1
  132. package/dist/webhooks/revenuecat.d.ts +0 -48
  133. package/dist/webhooks/revenuecat.d.ts.map +0 -1
  134. package/dist/webhooks/revenuecat.js +0 -119
  135. package/dist/webhooks/revenuecat.js.map +0 -1
  136. package/docs/revenuecat-setup.md +0 -89
  137. package/landing/src/app/api/keys/route.ts +0 -71
  138. package/landing/src/app/api/log/route.ts +0 -37
  139. package/landing/src/app/api/stats/route.ts +0 -37
  140. package/landing/src/app/page.tsx.bak +0 -567
  141. package/landing/src/components/AddKeyModal.tsx +0 -159
  142. package/newsletter-template.html +0 -71
  143. package/outreach/OUTREACH-SYSTEM.md +0 -211
  144. package/outreach/email-template.html +0 -179
  145. package/outreach/targets.md +0 -133
  146. package/src/registry/parse_apis.py +0 -146
  147. package/src/revenuecat.ts +0 -239
  148. package/src/webhooks/revenuecat.ts +0 -187
  149. /package/{backend/convex → convex}/README.md +0 -0
  150. /package/{backend/convex → convex}/_generated/api.js +0 -0
  151. /package/{backend/convex → convex}/_generated/dataModel.d.ts +0 -0
  152. /package/{backend/convex → convex}/_generated/server.d.ts +0 -0
  153. /package/{backend/convex → convex}/_generated/server.js +0 -0
  154. /package/{backend/convex → convex}/tsconfig.json +0 -0
@@ -0,0 +1,106 @@
1
+ // Simple Convex HTTP client for the dashboard
2
+
3
+ const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://brilliant-puffin-712.eu-west-1.convex.cloud";
4
+
5
+ export async function convexQuery<T>(path: string, args: Record<string, unknown>): Promise<T> {
6
+ const response = await fetch(`${CONVEX_URL}/api/query`, {
7
+ method: "POST",
8
+ headers: { "Content-Type": "application/json" },
9
+ body: JSON.stringify({ path, args }),
10
+ });
11
+
12
+ if (!response.ok) {
13
+ throw new Error(`Convex query failed: ${response.statusText}`);
14
+ }
15
+
16
+ return response.json();
17
+ }
18
+
19
+ export async function convexMutation<T>(path: string, args: Record<string, unknown>): Promise<T> {
20
+ const response = await fetch(`${CONVEX_URL}/api/mutation`, {
21
+ method: "POST",
22
+ headers: { "Content-Type": "application/json" },
23
+ body: JSON.stringify({ path, args }),
24
+ });
25
+
26
+ if (!response.ok) {
27
+ throw new Error(`Convex mutation failed: ${response.statusText}`);
28
+ }
29
+
30
+ return response.json();
31
+ }
32
+
33
+ // Provider dashboard types
34
+ export interface ProviderSession {
35
+ providerId: string;
36
+ email: string;
37
+ name: string;
38
+ stripeOnboardingComplete?: boolean;
39
+ }
40
+
41
+ export interface ProviderAPI {
42
+ _id: string;
43
+ name: string;
44
+ description: string;
45
+ category: string;
46
+ status: string;
47
+ openApiUrl?: string;
48
+ docsUrl?: string;
49
+ pricingModel: string;
50
+ pricingNotes?: string;
51
+ discoveryCount?: number;
52
+ createdAt: number;
53
+ }
54
+
55
+ export interface DailyStats {
56
+ date: string;
57
+ calls: number;
58
+ revenue: number;
59
+ }
60
+
61
+ export interface Analytics {
62
+ totalCalls: number;
63
+ uniqueAgents: number;
64
+ totalRevenue: number;
65
+ callsByDay: DailyStats[];
66
+ topAgents: { agentId: string; calls: number }[];
67
+ callsByRegion: Record<string, number>;
68
+ apis: { id: string; name: string; calls: number; status: string }[];
69
+ }
70
+
71
+ export interface Earnings {
72
+ pendingAmount: number;
73
+ totalEarned: number;
74
+ totalPaidOut: number;
75
+ stripeConnected: boolean;
76
+ stripeOnboardingComplete: boolean;
77
+ payouts: {
78
+ id: string;
79
+ amount: number;
80
+ status: string;
81
+ periodStart: number;
82
+ periodEnd: number;
83
+ createdAt: number;
84
+ completedAt?: number;
85
+ }[];
86
+ }
87
+
88
+ // Get session
89
+ export async function getSession(token: string): Promise<ProviderSession | null> {
90
+ return convexQuery("providers:getSession", { token });
91
+ }
92
+
93
+ // Get provider APIs
94
+ export async function getProviderAPIs(providerId: string): Promise<ProviderAPI[]> {
95
+ return convexQuery("providers:getProviderAPIs", { providerId });
96
+ }
97
+
98
+ // Get analytics
99
+ export async function getAnalytics(token: string, period?: string): Promise<Analytics | null> {
100
+ return convexQuery("providers:getAnalytics", { token, period });
101
+ }
102
+
103
+ // Get earnings
104
+ export async function getEarnings(token: string): Promise<Earnings | null> {
105
+ return convexQuery("providers:getEarnings", { token });
106
+ }
@@ -0,0 +1,285 @@
1
+ // Mock data for the provider dashboard demo
2
+ // This simulates what would come from Convex in production
3
+
4
+ export interface Provider {
5
+ id: string;
6
+ email: string;
7
+ name: string;
8
+ company?: string;
9
+ avatarUrl?: string;
10
+ stripeOnboardingComplete: boolean;
11
+ }
12
+
13
+ export interface Api {
14
+ id: string;
15
+ name: string;
16
+ description: string;
17
+ category: string;
18
+ icon: string;
19
+ baseUrl: string;
20
+ docsUrl?: string;
21
+ authType: string;
22
+ pricingModel: string;
23
+ pricePerCall?: number;
24
+ monthlyPrice?: number;
25
+ rateLimitPerMinute?: number;
26
+ regions?: string[];
27
+ tags?: string[];
28
+ status: "active" | "paused";
29
+ calls: number;
30
+ createdAt: number;
31
+ }
32
+
33
+ export interface DailyStats {
34
+ date: string;
35
+ calls: number;
36
+ revenue: number;
37
+ }
38
+
39
+ export interface TopAgent {
40
+ agentId: string;
41
+ calls: number;
42
+ }
43
+
44
+ export interface Analytics {
45
+ totalCalls: number;
46
+ totalCallsWeek: number;
47
+ totalCallsMonth: number;
48
+ uniqueAgents: number;
49
+ totalRevenue: number;
50
+ revenueWeek: number;
51
+ revenueMonth: number;
52
+ callsByDay: DailyStats[];
53
+ topAgents: TopAgent[];
54
+ callsByRegion: Record<string, number>;
55
+ apis: { id: string; name: string; calls: number; status: string }[];
56
+ }
57
+
58
+ export interface Payout {
59
+ id: string;
60
+ amount: number;
61
+ status: "pending" | "processing" | "completed" | "failed";
62
+ periodStart: number;
63
+ periodEnd: number;
64
+ createdAt: number;
65
+ completedAt?: number;
66
+ }
67
+
68
+ export interface Earnings {
69
+ pendingAmount: number;
70
+ totalEarned: number;
71
+ totalPaidOut: number;
72
+ stripeConnected: boolean;
73
+ stripeOnboardingComplete: boolean;
74
+ payouts: Payout[];
75
+ }
76
+
77
+ // Generate realistic mock data
78
+ function generateCallsByDay(days: number): DailyStats[] {
79
+ const result: DailyStats[] = [];
80
+ const now = new Date();
81
+
82
+ for (let i = days - 1; i >= 0; i--) {
83
+ const date = new Date(now);
84
+ date.setDate(date.getDate() - i);
85
+
86
+ // Growth trend with some variance and weekend dips
87
+ const dayOfWeek = date.getDay();
88
+ const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
89
+ const baseCalls = 150 + (days - i) * 3.5;
90
+ const variance = Math.random() * 60 - 30;
91
+ const weekendFactor = isWeekend ? 0.6 : 1;
92
+ const calls = Math.max(20, Math.round((baseCalls + variance) * weekendFactor));
93
+ const revenue = calls * 0.00085; // ~$0.00085 per call average
94
+
95
+ result.push({
96
+ date: date.toISOString().split("T")[0],
97
+ calls,
98
+ revenue: Math.round(revenue * 100) / 100,
99
+ });
100
+ }
101
+
102
+ return result;
103
+ }
104
+
105
+ export function getMockProvider(): Provider {
106
+ return {
107
+ id: "provider_demo_123",
108
+ email: "demo@example.com",
109
+ name: "Demo Provider",
110
+ company: "WeatherTech Inc.",
111
+ stripeOnboardingComplete: true,
112
+ };
113
+ }
114
+
115
+ export function getMockApis(): Api[] {
116
+ return [
117
+ {
118
+ id: "api_weather_001",
119
+ name: "WeatherAPI Pro",
120
+ description: "Real-time weather data for 200+ countries. Includes forecasts, historical data, and severe weather alerts.",
121
+ category: "Weather",
122
+ icon: "🌤️",
123
+ baseUrl: "https://api.weatherpro.example.com",
124
+ docsUrl: "https://docs.weatherpro.example.com",
125
+ authType: "api_key",
126
+ pricingModel: "per_call",
127
+ pricePerCall: 0.1, // cents
128
+ rateLimitPerMinute: 1000,
129
+ regions: ["US", "EU", "APAC"],
130
+ tags: ["weather", "forecast", "climate", "alerts"],
131
+ status: "active",
132
+ calls: 8247,
133
+ createdAt: Date.now() - 60 * 24 * 60 * 60 * 1000,
134
+ },
135
+ {
136
+ id: "api_geo_002",
137
+ name: "GeoLocator API",
138
+ description: "IP geolocation and address lookup. High accuracy with ISP and organization data.",
139
+ category: "Location",
140
+ icon: "📍",
141
+ baseUrl: "https://api.geolocator.example.com",
142
+ docsUrl: "https://docs.geolocator.example.com",
143
+ authType: "bearer",
144
+ pricingModel: "per_call",
145
+ pricePerCall: 0.05,
146
+ rateLimitPerMinute: 500,
147
+ regions: ["US", "EU"],
148
+ tags: ["geo", "location", "ip", "lookup"],
149
+ status: "active",
150
+ calls: 4521,
151
+ createdAt: Date.now() - 45 * 24 * 60 * 60 * 1000,
152
+ },
153
+ {
154
+ id: "api_translate_003",
155
+ name: "TranslateNow API",
156
+ description: "Neural machine translation for 100+ languages. Includes language detection.",
157
+ category: "Translation",
158
+ icon: "🌐",
159
+ baseUrl: "https://api.translatenow.example.com",
160
+ authType: "api_key",
161
+ pricingModel: "per_call",
162
+ pricePerCall: 0.2,
163
+ rateLimitPerMinute: 200,
164
+ regions: ["US", "EU", "APAC"],
165
+ tags: ["translation", "language", "nlp"],
166
+ status: "paused",
167
+ calls: 1832,
168
+ createdAt: Date.now() - 30 * 24 * 60 * 60 * 1000,
169
+ },
170
+ ];
171
+ }
172
+
173
+ export function getMockAnalytics(): Analytics {
174
+ const callsByDay = generateCallsByDay(60);
175
+ const last7Days = callsByDay.slice(-7);
176
+ const last30Days = callsByDay.slice(-30);
177
+
178
+ const totalCalls = callsByDay.reduce((sum, d) => sum + d.calls, 0);
179
+ const totalCallsWeek = last7Days.reduce((sum, d) => sum + d.calls, 0);
180
+ const totalCallsMonth = last30Days.reduce((sum, d) => sum + d.calls, 0);
181
+
182
+ const totalRevenue = callsByDay.reduce((sum, d) => sum + d.revenue, 0);
183
+ const revenueWeek = last7Days.reduce((sum, d) => sum + d.revenue, 0);
184
+ const revenueMonth = last30Days.reduce((sum, d) => sum + d.revenue, 0);
185
+
186
+ return {
187
+ totalCalls,
188
+ totalCallsWeek,
189
+ totalCallsMonth,
190
+ uniqueAgents: 23,
191
+ totalRevenue: Math.round(totalRevenue * 100) / 100,
192
+ revenueWeek: Math.round(revenueWeek * 100) / 100,
193
+ revenueMonth: Math.round(revenueMonth * 100) / 100,
194
+ callsByDay,
195
+ topAgents: [
196
+ { agentId: "agent_claude_prod", calls: 3421 },
197
+ { agentId: "agent_gpt4_beta", calls: 2847 },
198
+ { agentId: "agent_custom_001", calls: 1923 },
199
+ { agentId: "agent_automation_x", calls: 1456 },
200
+ { agentId: "agent_databot", calls: 987 },
201
+ { agentId: "agent_insights_ai", calls: 743 },
202
+ { agentId: "agent_pipeline_v2", calls: 521 },
203
+ { agentId: "agent_scraper_pro", calls: 302 },
204
+ ],
205
+ callsByRegion: {
206
+ US: 6234,
207
+ EU: 4521,
208
+ APAC: 2345,
209
+ LATAM: 500,
210
+ },
211
+ apis: [
212
+ { id: "api_weather_001", name: "WeatherAPI Pro", calls: 8247, status: "active" },
213
+ { id: "api_geo_002", name: "GeoLocator API", calls: 4521, status: "active" },
214
+ { id: "api_translate_003", name: "TranslateNow API", calls: 1832, status: "paused" },
215
+ ],
216
+ };
217
+ }
218
+
219
+ export function getMockEarnings(): Earnings {
220
+ const now = Date.now();
221
+ const dayMs = 24 * 60 * 60 * 1000;
222
+
223
+ return {
224
+ pendingAmount: 47.82,
225
+ totalEarned: 423.45,
226
+ totalPaidOut: 375.63,
227
+ stripeConnected: true,
228
+ stripeOnboardingComplete: true,
229
+ payouts: [
230
+ {
231
+ id: "po_001",
232
+ amount: 142.50,
233
+ status: "completed",
234
+ periodStart: now - 60 * dayMs,
235
+ periodEnd: now - 30 * dayMs,
236
+ createdAt: now - 29 * dayMs,
237
+ completedAt: now - 27 * dayMs,
238
+ },
239
+ {
240
+ id: "po_002",
241
+ amount: 118.63,
242
+ status: "completed",
243
+ periodStart: now - 90 * dayMs,
244
+ periodEnd: now - 60 * dayMs,
245
+ createdAt: now - 59 * dayMs,
246
+ completedAt: now - 57 * dayMs,
247
+ },
248
+ {
249
+ id: "po_003",
250
+ amount: 114.50,
251
+ status: "completed",
252
+ periodStart: now - 120 * dayMs,
253
+ periodEnd: now - 90 * dayMs,
254
+ createdAt: now - 89 * dayMs,
255
+ completedAt: now - 87 * dayMs,
256
+ },
257
+ ],
258
+ };
259
+ }
260
+
261
+ // API credentials mock
262
+ export interface ApiCredentials {
263
+ apiKey: string;
264
+ secretKey?: string;
265
+ createdAt: number;
266
+ lastUsed?: number;
267
+ }
268
+
269
+ export function getMockCredentials(apiId: string): ApiCredentials {
270
+ return {
271
+ apiKey: `apiclaw_live_${apiId.slice(-8)}_${generateRandomString(24)}`,
272
+ secretKey: `sk_live_${generateRandomString(32)}`,
273
+ createdAt: Date.now() - 45 * 24 * 60 * 60 * 1000,
274
+ lastUsed: Date.now() - 2 * 60 * 60 * 1000,
275
+ };
276
+ }
277
+
278
+ function generateRandomString(length: number): string {
279
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
280
+ let result = "";
281
+ for (let i = 0; i < length; i++) {
282
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
283
+ }
284
+ return result;
285
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "apiCount": 10001,
3
+ "categoryCount": 446,
4
+ "lastUpdated": "2026-02-21",
5
+ "generatedAt": "2026-02-21T16:37:38.628Z"
6
+ }
@@ -4,20 +4,21 @@ const config: Config = {
4
4
  content: [
5
5
  "./src/**/*.{js,ts,jsx,tsx,mdx}",
6
6
  ],
7
+ darkMode: 'class',
7
8
  theme: {
8
9
  extend: {
9
10
  colors: {
10
- background: "#0d0d0d",
11
- surface: "#141414",
12
- "surface-elevated": "#1a1a1a",
13
- border: "#2a2a2a",
14
- "border-subtle": "#222222",
15
- "text-primary": "#ffffff",
16
- "text-secondary": "#a3a3a3",
17
- "text-muted": "#737373",
18
- accent: "#22c55e",
19
- "accent-hover": "#16a34a",
20
- "accent-dim": "rgba(34, 197, 94, 0.15)",
11
+ background: "var(--background)",
12
+ surface: "var(--surface)",
13
+ "surface-elevated": "var(--surface-elevated)",
14
+ border: "var(--border)",
15
+ "border-subtle": "var(--border)",
16
+ "text-primary": "var(--text-primary)",
17
+ "text-secondary": "var(--text-secondary)",
18
+ "text-muted": "var(--text-muted)",
19
+ accent: "var(--accent)",
20
+ "accent-hover": "#dc2626",
21
+ "accent-dim": "rgba(239, 68, 68, 0.15)",
21
22
  },
22
23
  fontFamily: {
23
24
  sans: ["Inter", "system-ui", "sans-serif"],