claudmax 2.0.0 → 2.0.1
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.
- package/claudmax-1.0.16.tgz +0 -0
- package/{packages/cli/index.js → index.js} +2 -0
- package/package.json +27 -55
- package/.claude/settings.local.json +0 -7
- package/.env.example +0 -24
- package/.github/workflows/publish.yml +0 -31
- package/README.md +0 -178
- package/claudmax-mcp-1.0.2.tgz +0 -0
- package/help +0 -0
- package/help-wal +0 -0
- package/next-env.d.ts +0 -6
- package/next.config.mjs +0 -43
- package/packages/cli/claudmax-1.0.16.tgz +0 -0
- package/packages/cli/package.json +0 -33
- package/packages/mcp/claudmax-mcp-1.0.0.tgz +0 -0
- package/packages/mcp/claudmax-mcp-1.0.1.tgz +0 -0
- package/packages/mcp/claudmax-mcp-1.0.2.tgz +0 -0
- package/packages/mcp/claudmax-mcp-1.0.3.tgz +0 -0
- package/packages/mcp/index.js +0 -129
- package/packages/mcp/package-lock.json +0 -1146
- package/packages/mcp/package.json +0 -32
- package/postcss.config.mjs +0 -6
- package/prisma/schema.prisma +0 -130
- package/prisma/seed.ts +0 -27
- package/public/favicon.svg +0 -10
- package/public/robots.txt +0 -10
- package/run_build.sh +0 -4
- package/scripts/migrate-plans.js +0 -98
- package/scripts/seed-blog.ts +0 -1014
- package/src/app/admin/dashboard/AdminDashboardClient.tsx +0 -1546
- package/src/app/admin/dashboard/page.tsx +0 -13
- package/src/app/admin/page.tsx +0 -132
- package/src/app/api/admin/auth/me/route.ts +0 -34
- package/src/app/api/admin/health/route.ts +0 -110
- package/src/app/api/admin/keys/[id]/route.ts +0 -116
- package/src/app/api/admin/keys/route.ts +0 -192
- package/src/app/api/admin/keys-list/route.ts +0 -81
- package/src/app/api/admin/login/route.ts +0 -72
- package/src/app/api/admin/logout/route.ts +0 -8
- package/src/app/api/admin/migrate/route.ts +0 -133
- package/src/app/api/admin/plans/[id]/route.ts +0 -65
- package/src/app/api/admin/plans/route.ts +0 -66
- package/src/app/api/admin/posts/[id]/route.ts +0 -81
- package/src/app/api/admin/posts/route.ts +0 -83
- package/src/app/api/admin/seed/route.ts +0 -145
- package/src/app/api/admin/settings/route.ts +0 -44
- package/src/app/api/admin/stats/route.ts +0 -74
- package/src/app/api/admin/users/[id]/route.ts +0 -166
- package/src/app/api/admin/users/plans/route.ts +0 -45
- package/src/app/api/admin/users/route.ts +0 -202
- package/src/app/api/blog/[slug]/route.ts +0 -22
- package/src/app/api/blog/route.ts +0 -40
- package/src/app/api/cron/daily-status/route.ts +0 -208
- package/src/app/api/support/chat/route.ts +0 -55
- package/src/app/api/support/chat/session/route.ts +0 -62
- package/src/app/api/support/chat/stream/route.ts +0 -44
- package/src/app/api/support/email/route.ts +0 -63
- package/src/app/api/tools/understand_image/route.ts +0 -113
- package/src/app/api/tools/upload/route.ts +0 -179
- package/src/app/api/tools/web_search/route.ts +0 -99
- package/src/app/api/v1/audio/route.ts +0 -67
- package/src/app/api/v1/audio/speech/route.ts +0 -73
- package/src/app/api/v1/chat/completions/route.ts +0 -3
- package/src/app/api/v1/chat/route.ts +0 -1079
- package/src/app/api/v1/images/generations/route.ts +0 -93
- package/src/app/api/v1/info/route.ts +0 -30
- package/src/app/api/v1/key-status/route.ts +0 -109
- package/src/app/api/v1/key-status/stream/route.ts +0 -135
- package/src/app/api/v1/messages/count_tokens/route.ts +0 -22
- package/src/app/api/v1/messages/route.ts +0 -807
- package/src/app/api/v1/models/route.ts +0 -14
- package/src/app/api/v1/route.ts +0 -18
- package/src/app/blog/BlogClient.tsx +0 -193
- package/src/app/blog/[slug]/page.tsx +0 -117
- package/src/app/blog/page.tsx +0 -20
- package/src/app/check-usage/CheckUsageClient.tsx +0 -186
- package/src/app/check-usage/layout.tsx +0 -11
- package/src/app/check-usage/page.tsx +0 -15
- package/src/app/docs/layout.tsx +0 -16
- package/src/app/docs/page.tsx +0 -1055
- package/src/app/faq/FAQClient.tsx +0 -227
- package/src/app/faq/page.tsx +0 -21
- package/src/app/globals.css +0 -75
- package/src/app/layout.tsx +0 -80
- package/src/app/page.tsx +0 -256
- package/src/app/reseller/ResellerClient.tsx +0 -435
- package/src/app/reseller/page.tsx +0 -15
- package/src/app/setup.ps1/route.ts +0 -79
- package/src/app/setup.sh/route.ts +0 -113
- package/src/app/sitemap.ts +0 -50
- package/src/app/status/StatusClient.tsx +0 -103
- package/src/app/status/layout.tsx +0 -11
- package/src/app/status/page.tsx +0 -15
- package/src/app/support/SupportClient.tsx +0 -411
- package/src/app/support/page.tsx +0 -25
- package/src/app/v1/chat/completions/route.ts +0 -3
- package/src/app/v1/chat/route.ts +0 -4
- package/src/app/v1/messages/route.ts +0 -3
- package/src/components/Footer.tsx +0 -120
- package/src/components/Header.tsx +0 -131
- package/src/components/landing/features.tsx +0 -99
- package/src/components/ui/badge.tsx +0 -32
- package/src/components/ui/button.tsx +0 -46
- package/src/components/ui/card.tsx +0 -50
- package/src/components/ui/dialog.tsx +0 -97
- package/src/components/ui/dropdown-menu.tsx +0 -156
- package/src/components/ui/input.tsx +0 -21
- package/src/components/ui/label.tsx +0 -15
- package/src/components/ui/separator.tsx +0 -22
- package/src/components/ui/switch.tsx +0 -27
- package/src/components/ui/tabs.tsx +0 -51
- package/src/components/ui/toast.tsx +0 -103
- package/src/lib/auth.ts +0 -45
- package/src/lib/prisma.ts +0 -20
- package/src/lib/providers.ts +0 -158
- package/src/lib/security.ts +0 -165
- package/src/lib/utils.ts +0 -14
- package/src/middleware.ts +0 -30
- package/tailwind.config.ts +0 -53
- package/tsconfig.json +0 -41
- package/tsconfig.tsbuildinfo +0 -1
- package/vercel.json +0 -8
- /package/{packages/cli/bin → bin}/claudmax.js +0 -0
- /package/{packages/cli/claudmax-1.0.17.tgz → claudmax-1.0.17.tgz} +0 -0
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import Link from 'next/link';
|
|
4
|
-
import { useState, useEffect } from 'react';
|
|
5
|
-
import { BookOpen, Activity, FileText, Github, Mail, MessageCircle, HelpCircle, PenLine } from 'lucide-react';
|
|
6
|
-
|
|
7
|
-
export default function Footer() {
|
|
8
|
-
const year = new Date().getFullYear();
|
|
9
|
-
const [supportEnabled, setSupportEnabled] = useState(true);
|
|
10
|
-
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
fetch('/api/admin/settings')
|
|
13
|
-
.then(r => r.ok ? r.json() : { supportEnabled: true })
|
|
14
|
-
.then(d => setSupportEnabled(d.supportEnabled ?? true))
|
|
15
|
-
.catch(() => setSupportEnabled(true));
|
|
16
|
-
}, []);
|
|
17
|
-
|
|
18
|
-
const baseFooterLinks = [
|
|
19
|
-
{
|
|
20
|
-
Product: [
|
|
21
|
-
{ href: '/docs', label: 'Documentation' },
|
|
22
|
-
{ href: '/blog', label: 'Blog' },
|
|
23
|
-
{ href: '/faq', label: 'FAQ' },
|
|
24
|
-
{ href: '/support', label: 'Support', show: supportEnabled },
|
|
25
|
-
{ href: '/status', label: 'API Status' },
|
|
26
|
-
],
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
Resources: [
|
|
30
|
-
{ href: '/docs?section=claude_code', label: 'Claude Code Setup' },
|
|
31
|
-
{ href: '/docs?section=cursor', label: 'Cursor Setup' },
|
|
32
|
-
{ href: '/docs?section=limits', label: 'Rate Limits' },
|
|
33
|
-
{ href: '/docs?section=auth', label: 'Authentication' },
|
|
34
|
-
],
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
Company: [
|
|
38
|
-
{ href: '/check-usage', label: 'Check Usage' },
|
|
39
|
-
{ href: '/docs?section=troubleshooting', label: 'Troubleshooting' },
|
|
40
|
-
{ href: '/docs?section=mcp', label: 'MCP Tools' },
|
|
41
|
-
{ href: '/docs?section=models', label: 'Models' },
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
const footerLinks = baseFooterLinks.map(group => {
|
|
47
|
-
const [[category, links]] = Object.entries(group);
|
|
48
|
-
return {
|
|
49
|
-
[category]: links.filter((l: any) => l.show !== false),
|
|
50
|
-
};
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const socialLinks: never[] = [];
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<footer className="bg-white border-t border-gray-200 mt-auto">
|
|
57
|
-
{/* Main footer */}
|
|
58
|
-
<div className="max-w-7xl mx-auto px-4 sm:px-6 py-12">
|
|
59
|
-
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 lg:gap-12">
|
|
60
|
-
{/* Brand */}
|
|
61
|
-
<div className="col-span-2 md:col-span-1">
|
|
62
|
-
<Link href="/" className="flex items-center gap-2.5 mb-4">
|
|
63
|
-
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-[#5244F3] to-[#6B63E8] flex items-center justify-center">
|
|
64
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" className="text-white">
|
|
65
|
-
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" fill="currentColor"/>
|
|
66
|
-
</svg>
|
|
67
|
-
</div>
|
|
68
|
-
<span className="text-[#111827] font-bold text-base">ClaudMax</span>
|
|
69
|
-
</Link>
|
|
70
|
-
<p className="text-[#9CA3AF] text-sm leading-relaxed mb-4">
|
|
71
|
-
Claude API gateway powered by OpenRouter. Works with Claude Code, Cursor, Windsurf, and every Anthropic SDK.
|
|
72
|
-
</p>
|
|
73
|
-
<div />
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
{/* Links */}
|
|
77
|
-
{footerLinks.map((group, i) => (
|
|
78
|
-
<div key={i}>
|
|
79
|
-
{Object.entries(group).map(([category, links]) => (
|
|
80
|
-
<div key={category}>
|
|
81
|
-
<p className="text-[#111827] text-xs font-bold uppercase tracking-widest mb-4">
|
|
82
|
-
{category}
|
|
83
|
-
</p>
|
|
84
|
-
<ul className="space-y-2.5">
|
|
85
|
-
{(links as any[]).map((link) => (
|
|
86
|
-
<li key={link.href}>
|
|
87
|
-
<Link
|
|
88
|
-
href={link.href}
|
|
89
|
-
className="text-[#6B7280] hover:text-[#5244F3] text-sm transition-colors"
|
|
90
|
-
>
|
|
91
|
-
{link.label}
|
|
92
|
-
</Link>
|
|
93
|
-
</li>
|
|
94
|
-
))}
|
|
95
|
-
</ul>
|
|
96
|
-
</div>
|
|
97
|
-
))}
|
|
98
|
-
</div>
|
|
99
|
-
))}
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
|
-
{/* Bottom bar */}
|
|
103
|
-
<div className="mt-10 pt-8 border-t border-gray-100 flex flex-col sm:flex-row items-center justify-between gap-4">
|
|
104
|
-
<div className="flex flex-wrap items-center gap-4 text-xs text-[#9CA3AF]">
|
|
105
|
-
<span>© {year} ClaudMax. All rights reserved.</span>
|
|
106
|
-
<span className="hidden sm:inline text-gray-200">·</span>
|
|
107
|
-
<span>API Base: <code className="font-mono text-[#6B7280]">https://api.claudmax.pro</code></span>
|
|
108
|
-
</div>
|
|
109
|
-
<div className="flex items-center gap-4 text-xs text-[#9CA3AF]">
|
|
110
|
-
<Link href="/docs" className="hover:text-[#5244F3] transition-colors">Docs</Link>
|
|
111
|
-
<Link href="/blog" className="hover:text-[#5244F3] transition-colors">Blog</Link>
|
|
112
|
-
<Link href="/faq" className="hover:text-[#5244F3] transition-colors">FAQ</Link>
|
|
113
|
-
{supportEnabled && <Link href="/support" className="hover:text-[#5244F3] transition-colors">Support</Link>}
|
|
114
|
-
<Link href="/status" className="hover:text-[#5244F3] transition-colors">Status</Link>
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
</footer>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import Link from 'next/link';
|
|
4
|
-
import { usePathname } from 'next/navigation';
|
|
5
|
-
import { useState, useEffect } from 'react';
|
|
6
|
-
import { BookOpen, Activity, FileText, MessageCircle, HelpCircle, PenLine, X, Menu } from 'lucide-react';
|
|
7
|
-
|
|
8
|
-
export default function Header() {
|
|
9
|
-
const pathname = usePathname();
|
|
10
|
-
const [mobileOpen, setMobileOpen] = useState(false);
|
|
11
|
-
const [supportEnabled, setSupportEnabled] = useState(true);
|
|
12
|
-
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
fetch('/api/admin/settings')
|
|
15
|
-
.then(r => r.ok ? r.json() : { supportEnabled: true })
|
|
16
|
-
.then(d => setSupportEnabled(d.supportEnabled ?? true))
|
|
17
|
-
.catch(() => setSupportEnabled(true));
|
|
18
|
-
}, []);
|
|
19
|
-
|
|
20
|
-
const baseLinks = [
|
|
21
|
-
{ href: '/docs', label: 'Documentation', icon: <BookOpen className="w-4 h-4" /> },
|
|
22
|
-
{ href: '/blog', label: 'Blog', icon: <PenLine className="w-4 h-4" /> },
|
|
23
|
-
{ href: '/faq', label: 'FAQ', icon: <HelpCircle className="w-4 h-4" /> },
|
|
24
|
-
{ href: '/support', label: 'Support', icon: <MessageCircle className="w-4 h-4" />, show: supportEnabled },
|
|
25
|
-
{ href: '/status', label: 'Status', icon: <Activity className="w-4 h-4" /> },
|
|
26
|
-
{ href: '/check-usage', label: 'Usage', icon: <FileText className="w-4 h-4" /> },
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
const navLinks = baseLinks.filter(l => l.show !== false);
|
|
30
|
-
|
|
31
|
-
const isActive = (href: string) => {
|
|
32
|
-
if (href === '/') return pathname === '/';
|
|
33
|
-
return pathname.startsWith(href);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<>
|
|
38
|
-
<header className="sticky top-0 z-50 bg-white/95 backdrop-blur-md border-b border-gray-200 shrink-0">
|
|
39
|
-
<div className="max-w-7xl mx-auto px-4 sm:px-6 h-14 flex items-center justify-between gap-4">
|
|
40
|
-
{/* Logo */}
|
|
41
|
-
<Link href="/" className="flex items-center gap-2.5 shrink-0">
|
|
42
|
-
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-[#5244F3] to-[#6B63E8] flex items-center justify-center shadow-sm">
|
|
43
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" className="text-white">
|
|
44
|
-
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" fill="currentColor"/>
|
|
45
|
-
</svg>
|
|
46
|
-
</div>
|
|
47
|
-
<span className="text-[#111827] font-bold text-base tracking-tight">ClaudMax</span>
|
|
48
|
-
</Link>
|
|
49
|
-
|
|
50
|
-
{/* Desktop nav */}
|
|
51
|
-
<nav className="hidden md:flex items-center gap-1">
|
|
52
|
-
{navLinks.map((link) => (
|
|
53
|
-
<Link
|
|
54
|
-
key={link.href}
|
|
55
|
-
href={link.href}
|
|
56
|
-
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-medium transition-all ${
|
|
57
|
-
isActive(link.href)
|
|
58
|
-
? 'bg-purple-50 text-purple-700'
|
|
59
|
-
: 'text-[#6B7280] hover:text-[#111827] hover:bg-gray-100'
|
|
60
|
-
}`}
|
|
61
|
-
>
|
|
62
|
-
{link.icon}
|
|
63
|
-
{link.label}
|
|
64
|
-
</Link>
|
|
65
|
-
))}
|
|
66
|
-
</nav>
|
|
67
|
-
|
|
68
|
-
{/* Desktop CTA */}
|
|
69
|
-
<div className="hidden md:flex items-center gap-2 shrink-0">
|
|
70
|
-
<Link href="/check-usage">
|
|
71
|
-
<button className="bg-white hover:bg-gray-50 text-[#374151] border border-gray-200 rounded-full px-4 py-1.5 text-sm font-medium transition-all">
|
|
72
|
-
Check Usage
|
|
73
|
-
</button>
|
|
74
|
-
</Link>
|
|
75
|
-
<Link href="/docs">
|
|
76
|
-
<button className="bg-[#5244F3] hover:bg-[#4338CA] text-white rounded-full px-4 py-1.5 text-sm font-semibold transition-all shadow-sm">
|
|
77
|
-
Get Started
|
|
78
|
-
</button>
|
|
79
|
-
</Link>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
{/* Mobile hamburger */}
|
|
83
|
-
<button
|
|
84
|
-
onClick={() => setMobileOpen(!mobileOpen)}
|
|
85
|
-
className="md:hidden w-9 h-9 flex items-center justify-center rounded-lg hover:bg-gray-100 transition-colors"
|
|
86
|
-
aria-label="Toggle menu"
|
|
87
|
-
>
|
|
88
|
-
{mobileOpen ? (
|
|
89
|
-
<X className="w-5 h-5 text-[#374151]" />
|
|
90
|
-
) : (
|
|
91
|
-
<Menu className="w-5 h-5 text-[#374151]" />
|
|
92
|
-
)}
|
|
93
|
-
</button>
|
|
94
|
-
</div>
|
|
95
|
-
</header>
|
|
96
|
-
|
|
97
|
-
{/* Mobile menu */}
|
|
98
|
-
{mobileOpen && (
|
|
99
|
-
<div className="md:hidden bg-white border-b border-gray-200 px-4 py-3 space-y-1">
|
|
100
|
-
{navLinks.map((link) => (
|
|
101
|
-
<Link
|
|
102
|
-
key={link.href}
|
|
103
|
-
href={link.href}
|
|
104
|
-
onClick={() => setMobileOpen(false)}
|
|
105
|
-
className={`flex items-center gap-2.5 px-3 py-2.5 rounded-lg text-sm font-medium transition-all ${
|
|
106
|
-
isActive(link.href)
|
|
107
|
-
? 'bg-purple-50 text-purple-700'
|
|
108
|
-
: 'text-[#6B7280] hover:text-[#111827] hover:bg-gray-50'
|
|
109
|
-
}`}
|
|
110
|
-
>
|
|
111
|
-
{link.icon}
|
|
112
|
-
{link.label}
|
|
113
|
-
</Link>
|
|
114
|
-
))}
|
|
115
|
-
<div className="flex items-center gap-2 pt-2 border-t border-gray-100 mt-2">
|
|
116
|
-
<Link href="/check-usage" onClick={() => setMobileOpen(false)} className="flex-1">
|
|
117
|
-
<button className="w-full bg-white hover:bg-gray-50 text-[#374151] border border-gray-200 rounded-full px-4 py-2 text-sm font-medium transition-all">
|
|
118
|
-
Check Usage
|
|
119
|
-
</button>
|
|
120
|
-
</Link>
|
|
121
|
-
<Link href="/docs" onClick={() => setMobileOpen(false)} className="flex-1">
|
|
122
|
-
<button className="w-full bg-[#5244F3] hover:bg-[#4338CA] text-white rounded-full px-4 py-2 text-sm font-semibold transition-all">
|
|
123
|
-
Get Started
|
|
124
|
-
</button>
|
|
125
|
-
</Link>
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
)}
|
|
129
|
-
</>
|
|
130
|
-
);
|
|
131
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Code2, Globe, Zap, Shield, KeyRound, BarChart3, Terminal } from 'lucide-react';
|
|
4
|
-
import { useEffect, useState, useRef } from 'react';
|
|
5
|
-
|
|
6
|
-
const features = [
|
|
7
|
-
{
|
|
8
|
-
icon: Globe,
|
|
9
|
-
title: 'OpenAI-Compatible',
|
|
10
|
-
description:
|
|
11
|
-
'Drop-in replacement for OpenAI API. Change your baseURL and add your API key — everything works instantly.',
|
|
12
|
-
color: 'from-blue-500 to-cyan-500',
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
icon: Code2,
|
|
16
|
-
title: 'Claude Code Models',
|
|
17
|
-
description:
|
|
18
|
-
'Access the full lineup: Claude 3.5 Haiku, Sonnet, and Opus. All the power of Claude Code in your IDE.',
|
|
19
|
-
color: 'from-purple-500 to-pink-500',
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
icon: Zap,
|
|
23
|
-
title: 'Blazing Fast',
|
|
24
|
-
description:
|
|
25
|
-
'Optimized proxy infrastructure with sub-200ms latency. No rate limit headaches, just productive coding.',
|
|
26
|
-
color: 'from-amber-500 to-orange-500',
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
icon: KeyRound,
|
|
30
|
-
title: 'Simple API Keys',
|
|
31
|
-
description:
|
|
32
|
-
'Generate keys in seconds from your dashboard. Use them in Cursor, Cline, OpenClau, Continue.dev, and 50+ tools.',
|
|
33
|
-
color: 'from-emerald-500 to-teal-500',
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
icon: BarChart3,
|
|
37
|
-
title: 'Usage Analytics',
|
|
38
|
-
description:
|
|
39
|
-
'Track every request, token, and cost in real-time. Know exactly what you\'re spending and optimize accordingly.',
|
|
40
|
-
color: 'from-indigo-500 to-violet-500',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
icon: Shield,
|
|
44
|
-
title: 'Secure by Default',
|
|
45
|
-
description:
|
|
46
|
-
'Keys are hashed, never logged in plaintext. Per-key rate limits and usage caps keep your account safe.',
|
|
47
|
-
color: 'from-rose-500 to-red-500',
|
|
48
|
-
},
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
export function Features() {
|
|
52
|
-
const [visible, setVisible] = useState(false);
|
|
53
|
-
const ref = useRef<HTMLElement>(null);
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
const observer = new IntersectionObserver(
|
|
57
|
-
([entry]) => { if (entry.isIntersecting) setVisible(true); },
|
|
58
|
-
{ threshold: 0.1 }
|
|
59
|
-
);
|
|
60
|
-
if (ref.current) observer.observe(ref.current);
|
|
61
|
-
return () => observer.disconnect();
|
|
62
|
-
}, []);
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<section ref={ref} id="features" className="py-24 relative">
|
|
66
|
-
<div className="absolute inset-0 grid-bg" />
|
|
67
|
-
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
68
|
-
<div className={`text-center mb-16 transition-all duration-700 ${visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
|
|
69
|
-
<h2 className="text-3xl sm:text-4xl font-bold mb-4">
|
|
70
|
-
Everything you need to{' '}
|
|
71
|
-
<span className="gradient-text">ship faster</span>
|
|
72
|
-
</h2>
|
|
73
|
-
<p className="text-muted-foreground text-lg max-w-xl mx-auto">
|
|
74
|
-
Built for developers who want Claude\'s intelligence without the complexity.
|
|
75
|
-
</p>
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
|
-
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
79
|
-
{features.map((feature, i) => {
|
|
80
|
-
const Icon = feature.icon;
|
|
81
|
-
return (
|
|
82
|
-
<div
|
|
83
|
-
key={feature.title}
|
|
84
|
-
className={`group relative rounded-2xl border border-border/50 bg-card/50 backdrop-blur p-6 hover:border-purple-500/30 transition-all duration-500 hover:shadow-lg hover:shadow-purple-500/5 ${visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}
|
|
85
|
-
style={{ transitionDelay: `${i * 80}ms` }}
|
|
86
|
-
>
|
|
87
|
-
<div className={`inline-flex p-2.5 rounded-xl bg-gradient-to-br ${feature.color} shadow-lg mb-4`}>
|
|
88
|
-
<Icon className="w-5 h-5 text-white" />
|
|
89
|
-
</div>
|
|
90
|
-
<h3 className="text-lg font-bold mb-2">{feature.title}</h3>
|
|
91
|
-
<p className="text-sm text-muted-foreground leading-relaxed">{feature.description}</p>
|
|
92
|
-
</div>
|
|
93
|
-
);
|
|
94
|
-
})}
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
</section>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
-
import { cn } from '@/lib/utils';
|
|
4
|
-
|
|
5
|
-
const badgeVariants = cva(
|
|
6
|
-
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
|
7
|
-
{
|
|
8
|
-
variants: {
|
|
9
|
-
variant: {
|
|
10
|
-
default: 'border-transparent bg-primary text-primary-foreground',
|
|
11
|
-
secondary: 'border-transparent bg-secondary text-secondary-foreground',
|
|
12
|
-
destructive: 'border-transparent bg-destructive text-destructive-foreground',
|
|
13
|
-
outline: 'text-foreground',
|
|
14
|
-
success: 'border-transparent bg-emerald-500/20 text-emerald-400',
|
|
15
|
-
warning: 'border-transparent bg-amber-500/20 text-amber-400',
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
defaultVariants: {
|
|
19
|
-
variant: 'default',
|
|
20
|
-
},
|
|
21
|
-
}
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
export interface BadgeProps
|
|
25
|
-
extends React.HTMLAttributes<HTMLDivElement>,
|
|
26
|
-
VariantProps<typeof badgeVariants> {}
|
|
27
|
-
|
|
28
|
-
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
29
|
-
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export { Badge, badgeVariants };
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { Slot } from '@radix-ui/react-slot';
|
|
3
|
-
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
-
import { cn } from '@/lib/utils';
|
|
5
|
-
|
|
6
|
-
const buttonVariants = cva(
|
|
7
|
-
'inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-semibold ring-offset-background transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
8
|
-
{
|
|
9
|
-
variants: {
|
|
10
|
-
variant: {
|
|
11
|
-
default: 'bg-primary text-primary-foreground hover:opacity-90 shadow-lg shadow-primary/25',
|
|
12
|
-
destructive: 'bg-destructive text-destructive-foreground hover:opacity-90',
|
|
13
|
-
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
|
14
|
-
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
15
|
-
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
16
|
-
link: 'text-primary underline-offset-4 hover:underline',
|
|
17
|
-
},
|
|
18
|
-
size: {
|
|
19
|
-
default: 'h-10 px-5 py-2',
|
|
20
|
-
sm: 'h-9 rounded-md px-3',
|
|
21
|
-
lg: 'h-12 rounded-xl px-8 text-base',
|
|
22
|
-
icon: 'h-10 w-10',
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
defaultVariants: {
|
|
26
|
-
variant: 'default',
|
|
27
|
-
size: 'default',
|
|
28
|
-
},
|
|
29
|
-
}
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
export interface ButtonProps
|
|
33
|
-
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
34
|
-
VariantProps<typeof buttonVariants> {
|
|
35
|
-
asChild?: boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
39
|
-
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
40
|
-
const Comp = asChild ? Slot : 'button';
|
|
41
|
-
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
Button.displayName = 'Button';
|
|
45
|
-
|
|
46
|
-
export { Button, buttonVariants };
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { cn } from '@/lib/utils';
|
|
3
|
-
|
|
4
|
-
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
5
|
-
({ className, ...props }, ref) => (
|
|
6
|
-
<div
|
|
7
|
-
ref={ref}
|
|
8
|
-
className={cn('rounded-xl border bg-card text-card-foreground shadow-sm', className)}
|
|
9
|
-
{...props}
|
|
10
|
-
/>
|
|
11
|
-
)
|
|
12
|
-
);
|
|
13
|
-
Card.displayName = 'Card';
|
|
14
|
-
|
|
15
|
-
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
16
|
-
({ className, ...props }, ref) => (
|
|
17
|
-
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
|
|
18
|
-
)
|
|
19
|
-
);
|
|
20
|
-
CardHeader.displayName = 'CardHeader';
|
|
21
|
-
|
|
22
|
-
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
|
23
|
-
({ className, ...props }, ref) => (
|
|
24
|
-
<h3 ref={ref} className={cn('text-2xl font-bold leading-none tracking-tight', className)} {...props} />
|
|
25
|
-
)
|
|
26
|
-
);
|
|
27
|
-
CardTitle.displayName = 'CardTitle';
|
|
28
|
-
|
|
29
|
-
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
|
30
|
-
({ className, ...props }, ref) => (
|
|
31
|
-
<p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
|
|
32
|
-
)
|
|
33
|
-
);
|
|
34
|
-
CardDescription.displayName = 'CardDescription';
|
|
35
|
-
|
|
36
|
-
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
37
|
-
({ className, ...props }, ref) => (
|
|
38
|
-
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
|
39
|
-
)
|
|
40
|
-
);
|
|
41
|
-
CardContent.displayName = 'CardContent';
|
|
42
|
-
|
|
43
|
-
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
44
|
-
({ className, ...props }, ref) => (
|
|
45
|
-
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
|
|
46
|
-
)
|
|
47
|
-
);
|
|
48
|
-
CardFooter.displayName = 'CardFooter';
|
|
49
|
-
|
|
50
|
-
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
5
|
-
import { X } from 'lucide-react';
|
|
6
|
-
import { cn } from '@/lib/utils';
|
|
7
|
-
|
|
8
|
-
const Dialog = DialogPrimitive.Root;
|
|
9
|
-
const DialogTrigger = DialogPrimitive.Trigger;
|
|
10
|
-
const DialogPortal = DialogPrimitive.Portal;
|
|
11
|
-
const DialogClose = DialogPrimitive.Close;
|
|
12
|
-
|
|
13
|
-
const DialogOverlay = React.forwardRef<
|
|
14
|
-
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
15
|
-
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
16
|
-
>(({ className, ...props }, ref) => (
|
|
17
|
-
<DialogPrimitive.Overlay
|
|
18
|
-
ref={ref}
|
|
19
|
-
className={cn(
|
|
20
|
-
'fixed inset-0 z-50 bg-black/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
21
|
-
className
|
|
22
|
-
)}
|
|
23
|
-
{...props}
|
|
24
|
-
/>
|
|
25
|
-
));
|
|
26
|
-
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
27
|
-
|
|
28
|
-
const DialogContent = React.forwardRef<
|
|
29
|
-
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
30
|
-
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
31
|
-
>(({ className, children, ...props }, ref) => (
|
|
32
|
-
<DialogPortal>
|
|
33
|
-
<DialogOverlay />
|
|
34
|
-
<DialogPrimitive.Content
|
|
35
|
-
ref={ref}
|
|
36
|
-
className={cn(
|
|
37
|
-
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-card shadow-2xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-2xl',
|
|
38
|
-
className
|
|
39
|
-
)}
|
|
40
|
-
{...props}
|
|
41
|
-
>
|
|
42
|
-
{children}
|
|
43
|
-
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
|
44
|
-
<X className="h-4 w-4" />
|
|
45
|
-
<span className="sr-only">Close</span>
|
|
46
|
-
</DialogPrimitive.Close>
|
|
47
|
-
</DialogPrimitive.Content>
|
|
48
|
-
</DialogPortal>
|
|
49
|
-
));
|
|
50
|
-
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
|
51
|
-
|
|
52
|
-
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
53
|
-
<div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />
|
|
54
|
-
);
|
|
55
|
-
DialogHeader.displayName = 'DialogHeader';
|
|
56
|
-
|
|
57
|
-
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
58
|
-
<div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />
|
|
59
|
-
);
|
|
60
|
-
DialogFooter.displayName = 'DialogFooter';
|
|
61
|
-
|
|
62
|
-
const DialogTitle = React.forwardRef<
|
|
63
|
-
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
64
|
-
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
65
|
-
>(({ className, ...props }, ref) => (
|
|
66
|
-
<DialogPrimitive.Title
|
|
67
|
-
ref={ref}
|
|
68
|
-
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
|
|
69
|
-
{...props}
|
|
70
|
-
/>
|
|
71
|
-
));
|
|
72
|
-
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
|
73
|
-
|
|
74
|
-
const DialogDescription = React.forwardRef<
|
|
75
|
-
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
76
|
-
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
77
|
-
>(({ className, ...props }, ref) => (
|
|
78
|
-
<DialogPrimitive.Description
|
|
79
|
-
ref={ref}
|
|
80
|
-
className={cn('text-sm text-muted-foreground', className)}
|
|
81
|
-
{...props}
|
|
82
|
-
/>
|
|
83
|
-
));
|
|
84
|
-
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|
85
|
-
|
|
86
|
-
export {
|
|
87
|
-
Dialog,
|
|
88
|
-
DialogPortal,
|
|
89
|
-
DialogOverlay,
|
|
90
|
-
DialogClose,
|
|
91
|
-
DialogTrigger,
|
|
92
|
-
DialogContent,
|
|
93
|
-
DialogHeader,
|
|
94
|
-
DialogFooter,
|
|
95
|
-
DialogTitle,
|
|
96
|
-
DialogDescription,
|
|
97
|
-
};
|