skannr 0.1.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/.eslintrc.js +22 -0
- package/README.md +109 -0
- package/dist/agent-cli.d.ts +7 -0
- package/dist/agent-cli.d.ts.map +1 -0
- package/dist/agent-cli.js +364 -0
- package/dist/agent-cli.js.map +1 -0
- package/dist/agent.d.ts +115 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +340 -0
- package/dist/agent.js.map +1 -0
- package/dist/benchmark.d.ts +53 -0
- package/dist/benchmark.d.ts.map +1 -0
- package/dist/benchmark.js +307 -0
- package/dist/benchmark.js.map +1 -0
- package/dist/cache.d.ts +97 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +284 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +238 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +52 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +176 -0
- package/dist/index.js.map +1 -0
- package/dist/languages/GenericAdapter.d.ts +10 -0
- package/dist/languages/GenericAdapter.d.ts.map +1 -0
- package/dist/languages/GenericAdapter.js +41 -0
- package/dist/languages/GenericAdapter.js.map +1 -0
- package/dist/languages/LanguageAdapter.d.ts +14 -0
- package/dist/languages/LanguageAdapter.d.ts.map +1 -0
- package/dist/languages/LanguageAdapter.js +3 -0
- package/dist/languages/LanguageAdapter.js.map +1 -0
- package/dist/languages/PythonAdapter.d.ts +10 -0
- package/dist/languages/PythonAdapter.d.ts.map +1 -0
- package/dist/languages/PythonAdapter.js +98 -0
- package/dist/languages/PythonAdapter.js.map +1 -0
- package/dist/languages/TypeScriptAdapter.d.ts +17 -0
- package/dist/languages/TypeScriptAdapter.d.ts.map +1 -0
- package/dist/languages/TypeScriptAdapter.js +321 -0
- package/dist/languages/TypeScriptAdapter.js.map +1 -0
- package/dist/languages/registry.d.ts +4 -0
- package/dist/languages/registry.d.ts.map +1 -0
- package/dist/languages/registry.js +86 -0
- package/dist/languages/registry.js.map +1 -0
- package/dist/mapper.d.ts +49 -0
- package/dist/mapper.d.ts.map +1 -0
- package/dist/mapper.js +386 -0
- package/dist/mapper.js.map +1 -0
- package/dist/ranker-enhanced.d.ts +37 -0
- package/dist/ranker-enhanced.d.ts.map +1 -0
- package/dist/ranker-enhanced.js +395 -0
- package/dist/ranker-enhanced.js.map +1 -0
- package/dist/ranker.d.ts +14 -0
- package/dist/ranker.d.ts.map +1 -0
- package/dist/ranker.js +105 -0
- package/dist/ranker.js.map +1 -0
- package/dist/rocket-chat-scope.d.ts +7 -0
- package/dist/rocket-chat-scope.d.ts.map +1 -0
- package/dist/rocket-chat-scope.js +95 -0
- package/dist/rocket-chat-scope.js.map +1 -0
- package/dist/scanner.d.ts +16 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +228 -0
- package/dist/scanner.js.map +1 -0
- package/dist/skeletonizer.d.ts +5 -0
- package/dist/skeletonizer.d.ts.map +1 -0
- package/dist/skeletonizer.js +52 -0
- package/dist/skeletonizer.js.map +1 -0
- package/dist/tokenizer.d.ts +9 -0
- package/dist/tokenizer.d.ts.map +1 -0
- package/dist/tokenizer.js +21 -0
- package/dist/tokenizer.js.map +1 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/gemini-extension/GEMINI.md +26 -0
- package/gemini-extension/gemini-extension.json +12 -0
- package/gemini-extension/package.json +14 -0
- package/gemini-extension/src/server.ts +63 -0
- package/gemini-extension/tsconfig.json +14 -0
- package/jest.config.js +5 -0
- package/package.json +46 -0
- package/src/agent-cli.ts +383 -0
- package/src/agent.ts +344 -0
- package/src/benchmark.ts +389 -0
- package/src/cache.ts +317 -0
- package/src/cli.ts +223 -0
- package/src/config.ts +22 -0
- package/src/index.ts +215 -0
- package/src/languages/GenericAdapter.ts +44 -0
- package/src/languages/LanguageAdapter.ts +14 -0
- package/src/languages/PythonAdapter.ts +74 -0
- package/src/languages/TypeScriptAdapter.ts +338 -0
- package/src/languages/registry.ts +49 -0
- package/src/mapper.ts +448 -0
- package/src/ranker-enhanced.ts +460 -0
- package/src/ranker.ts +92 -0
- package/src/scanner.ts +201 -0
- package/src/skeletonizer.ts +16 -0
- package/src/tokenizer.ts +20 -0
- package/src/types.ts +71 -0
- package/tests/agent.tools.test.ts +81 -0
- package/tests/benchmark.test.ts +31 -0
- package/tests/fixtures/sample.py +17 -0
- package/tests/fixtures/sample.ts +13 -0
- package/tests/fixtures/src/api/routes.ts +1 -0
- package/tests/fixtures/src/auth/permission.ts +3 -0
- package/tests/ranker-enhanced.test.ts +68 -0
- package/tests/ranker.test.ts +75 -0
- package/tests/scanner.scope.test.ts +41 -0
- package/tests/setup-fixtures.js +29 -0
- package/tests/skeletonizer.test.ts +142 -0
- package/tsconfig.json +21 -0
- package/uca-landing/index.html +17 -0
- package/uca-landing/package.json +23 -0
- package/uca-landing/postcss.config.js +6 -0
- package/uca-landing/src/App.jsx +43 -0
- package/uca-landing/src/components/AgentMode.jsx +45 -0
- package/uca-landing/src/components/CliReference.jsx +49 -0
- package/uca-landing/src/components/Features.jsx +63 -0
- package/uca-landing/src/components/Footer.jsx +35 -0
- package/uca-landing/src/components/Hero.jsx +124 -0
- package/uca-landing/src/components/HowItWorks.jsx +60 -0
- package/uca-landing/src/components/Install.jsx +90 -0
- package/uca-landing/src/components/LanguageSupport.jsx +63 -0
- package/uca-landing/src/components/Navbar.jsx +86 -0
- package/uca-landing/src/components/Problem.jsx +51 -0
- package/uca-landing/src/components/Reveal.jsx +40 -0
- package/uca-landing/src/components/WorksWith.jsx +59 -0
- package/uca-landing/src/hooks/useScrollNav.js +13 -0
- package/uca-landing/src/hooks/useTypewriter.js +41 -0
- package/uca-landing/src/index.css +13 -0
- package/uca-landing/src/main.jsx +10 -0
- package/uca-landing/tailwind.config.js +68 -0
- package/uca-landing/vite.config.js +6 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import Reveal from './Reveal'
|
|
3
|
+
|
|
4
|
+
const tabs = ['CLI Install', 'npx', 'MCP Server']
|
|
5
|
+
|
|
6
|
+
const snippets = {
|
|
7
|
+
'CLI Install': `npm install -g skannr
|
|
8
|
+
|
|
9
|
+
# Ask a question about any repo
|
|
10
|
+
skannr --question "how does auth work?" --root /path/to/repo
|
|
11
|
+
|
|
12
|
+
# Interactive agent mode
|
|
13
|
+
skannr-agent --root /path/to/repo`,
|
|
14
|
+
npx: `# Run without installing
|
|
15
|
+
npx skannr --question "how does this work?" --root .
|
|
16
|
+
|
|
17
|
+
# One-time cache check
|
|
18
|
+
npx skannr --cache-stats`,
|
|
19
|
+
'MCP Server': `{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"skannr": {
|
|
22
|
+
"command": "npx",
|
|
23
|
+
"args": ["skannr"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}`,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function Install() {
|
|
30
|
+
const [active, setActive] = useState('CLI Install')
|
|
31
|
+
const [copied, setCopied] = useState(false)
|
|
32
|
+
|
|
33
|
+
const doCopy = async () => {
|
|
34
|
+
await navigator.clipboard.writeText(snippets[active])
|
|
35
|
+
setCopied(true)
|
|
36
|
+
setTimeout(() => setCopied(false), 2000)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<section id="install" className="py-24 px-5">
|
|
41
|
+
<div className="max-w-5xl mx-auto">
|
|
42
|
+
<Reveal>
|
|
43
|
+
<h2 className="text-3xl md:text-5xl font-bold text-center tracking-tight">Up in 30 seconds.</h2>
|
|
44
|
+
</Reveal>
|
|
45
|
+
|
|
46
|
+
<div className="mt-10 flex flex-wrap justify-center gap-2">
|
|
47
|
+
{tabs.map((tab) => (
|
|
48
|
+
<button
|
|
49
|
+
key={tab}
|
|
50
|
+
onClick={() => setActive(tab)}
|
|
51
|
+
className={`rounded-lg border px-4 py-2 text-sm transition-all duration-300 ${
|
|
52
|
+
active === tab
|
|
53
|
+
? 'border-accent bg-accent/10 text-accent shadow-[0_0_24px_-8px_rgba(0,212,255,0.35)]'
|
|
54
|
+
: 'border-white/10 text-[#888] hover:border-white/25 hover:text-white'
|
|
55
|
+
}`}
|
|
56
|
+
>
|
|
57
|
+
{tab}
|
|
58
|
+
</button>
|
|
59
|
+
))}
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div
|
|
63
|
+
key={active}
|
|
64
|
+
className="mt-8 overflow-hidden rounded-xl border border-border bg-card motion-safe:animate-fade-in motion-reduce:animate-none"
|
|
65
|
+
>
|
|
66
|
+
<div className="flex items-center justify-between border-b border-white/5 px-4 py-3">
|
|
67
|
+
<div className="flex gap-2">
|
|
68
|
+
<span className="h-3 w-3 rounded-full bg-[#ff5f57]" />
|
|
69
|
+
<span className="h-3 w-3 rounded-full bg-[#febc2e]" />
|
|
70
|
+
<span className="h-3 w-3 rounded-full bg-[#28c840]" />
|
|
71
|
+
</div>
|
|
72
|
+
<button
|
|
73
|
+
onClick={doCopy}
|
|
74
|
+
className="rounded-md border border-white/15 px-3 py-1 text-sm transition-all duration-300 hover:border-accent hover:text-accent hover:shadow-[0_0_16px_-4px_rgba(0,212,255,0.4)] active:scale-95"
|
|
75
|
+
>
|
|
76
|
+
{copied ? 'Copied!' : 'Copy'}
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
<pre className="overflow-x-auto p-5 font-mono text-sm text-[#e8e8e8]">{snippets[active]}</pre>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{active === 'MCP Server' && (
|
|
83
|
+
<p className="mt-4 text-sm text-[#888]">
|
|
84
|
+
Add this to ~/.cursor/mcp.json, ~/.gemini/config.json, or Claude Desktop config.
|
|
85
|
+
</p>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
</section>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import Reveal from './Reveal'
|
|
2
|
+
|
|
3
|
+
const rows = [
|
|
4
|
+
['TypeScript / TSX', 'Full', 'AST via ts-morph'],
|
|
5
|
+
['JavaScript / JSX', 'Full', 'AST via ts-morph'],
|
|
6
|
+
['Python', 'Structural', 'Regex (signatures + type hints)'],
|
|
7
|
+
['Go, Rust, Java, others', 'Basic', 'First 50 lines fallback'],
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
function badgeClass(level) {
|
|
11
|
+
if (level === 'Full') return 'bg-green/15 text-green'
|
|
12
|
+
if (level === 'Structural') return 'bg-yellow/20 text-yellow'
|
|
13
|
+
return 'bg-white/10 text-[#c5c5c5]'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function LanguageSupport() {
|
|
17
|
+
return (
|
|
18
|
+
<section className="py-24 px-5">
|
|
19
|
+
<div className="max-w-5xl mx-auto">
|
|
20
|
+
<Reveal>
|
|
21
|
+
<h2 className="text-3xl md:text-5xl font-bold text-center tracking-tight">Works on your stack.</h2>
|
|
22
|
+
</Reveal>
|
|
23
|
+
<Reveal delay={80}>
|
|
24
|
+
<div className="mt-12 overflow-hidden rounded-xl border border-border bg-card transition-colors duration-300 hover:border-white/10">
|
|
25
|
+
<table className="w-full text-left">
|
|
26
|
+
<thead className="border-b border-white/5">
|
|
27
|
+
<tr className="text-sm text-[#888]">
|
|
28
|
+
<th className="px-6 py-4 font-medium">Language</th>
|
|
29
|
+
<th className="px-6 py-4 font-medium">Support</th>
|
|
30
|
+
<th className="px-6 py-4 font-medium">Method</th>
|
|
31
|
+
</tr>
|
|
32
|
+
</thead>
|
|
33
|
+
<tbody>
|
|
34
|
+
{rows.map((row) => (
|
|
35
|
+
<tr
|
|
36
|
+
key={row[0]}
|
|
37
|
+
className="border-b border-white/5 transition-colors duration-200 last:border-b-0 hover:bg-white/[0.03]"
|
|
38
|
+
>
|
|
39
|
+
<td className="px-6 py-4">{row[0]}</td>
|
|
40
|
+
<td className="px-6 py-4">
|
|
41
|
+
<span className={`inline-flex rounded-full px-3 py-1 text-sm ${badgeClass(row[1])}`}>
|
|
42
|
+
{row[1]}
|
|
43
|
+
</span>
|
|
44
|
+
</td>
|
|
45
|
+
<td className="px-6 py-4 text-[#888]">{row[2]}</td>
|
|
46
|
+
</tr>
|
|
47
|
+
))}
|
|
48
|
+
</tbody>
|
|
49
|
+
</table>
|
|
50
|
+
</div>
|
|
51
|
+
</Reveal>
|
|
52
|
+
<Reveal delay={120}>
|
|
53
|
+
<a
|
|
54
|
+
href="https://github.com/7vignesh/code-analyzer"
|
|
55
|
+
className="mt-6 inline-block text-[#888] transition-all duration-300 hover:text-accent hover:translate-x-1"
|
|
56
|
+
>
|
|
57
|
+
More language adapters coming. PRs welcome →
|
|
58
|
+
</a>
|
|
59
|
+
</Reveal>
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { Menu, X } from 'lucide-react'
|
|
3
|
+
import { useScrollNav } from '../hooks/useScrollNav'
|
|
4
|
+
|
|
5
|
+
const links = [
|
|
6
|
+
{ label: 'How it works', href: '#how-it-works' },
|
|
7
|
+
{ label: 'Features', href: '#features' },
|
|
8
|
+
{ label: 'Install', href: '#install' },
|
|
9
|
+
{ label: 'Works with', href: '#works-with' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
export default function Navbar() {
|
|
13
|
+
const [open, setOpen] = useState(false)
|
|
14
|
+
const scrolled = useScrollNav(60)
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<nav
|
|
18
|
+
className={`fixed top-0 z-50 w-full transition-all duration-300 ${
|
|
19
|
+
scrolled ? 'backdrop-blur-md bg-black/60 border-b border-white/5' : ''
|
|
20
|
+
}`}
|
|
21
|
+
>
|
|
22
|
+
<div className="mx-auto max-w-6xl px-5 py-4">
|
|
23
|
+
<div className="flex items-center justify-between">
|
|
24
|
+
<a
|
|
25
|
+
href="#"
|
|
26
|
+
className="inline-flex items-center gap-2 transition-transform duration-300 hover:scale-[1.03] motion-reduce:hover:scale-100"
|
|
27
|
+
>
|
|
28
|
+
<span className="font-mono text-lg font-bold text-accent">uca</span>
|
|
29
|
+
<span className="h-2 w-2 rounded-full bg-green motion-safe:animate-pulse" />
|
|
30
|
+
</a>
|
|
31
|
+
|
|
32
|
+
<div className="hidden md:flex items-center gap-7">
|
|
33
|
+
{links.map((link) => (
|
|
34
|
+
<a
|
|
35
|
+
key={link.label}
|
|
36
|
+
href={link.href}
|
|
37
|
+
className="group relative py-1 text-sm text-[#888] transition-colors hover:text-white"
|
|
38
|
+
>
|
|
39
|
+
{link.label}
|
|
40
|
+
<span className="absolute bottom-0 left-0 h-[2px] w-0 bg-accent transition-all duration-300 ease-out group-hover:w-full" />
|
|
41
|
+
</a>
|
|
42
|
+
))}
|
|
43
|
+
<a
|
|
44
|
+
href="https://github.com/7vignesh/code-analyzer"
|
|
45
|
+
className="border border-white/20 text-sm px-4 py-1.5 rounded-lg transition-all duration-300 hover:border-accent hover:text-accent hover:shadow-[0_0_20px_-6px_rgba(0,212,255,0.45)] hover:-translate-y-px"
|
|
46
|
+
>
|
|
47
|
+
GitHub
|
|
48
|
+
</a>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<button
|
|
52
|
+
className="md:hidden text-white/80 hover:text-accent transition-colors"
|
|
53
|
+
onClick={() => setOpen((v) => !v)}
|
|
54
|
+
aria-label="Toggle menu"
|
|
55
|
+
>
|
|
56
|
+
{open ? <X size={20} /> : <Menu size={20} />}
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
{open && (
|
|
61
|
+
<div className="mt-4 md:hidden rounded-xl border border-border bg-card p-4">
|
|
62
|
+
<div className="flex flex-col gap-3">
|
|
63
|
+
{links.map((link) => (
|
|
64
|
+
<a
|
|
65
|
+
key={link.label}
|
|
66
|
+
href={link.href}
|
|
67
|
+
className="text-sm text-[#888] hover:text-white transition-colors"
|
|
68
|
+
onClick={() => setOpen(false)}
|
|
69
|
+
>
|
|
70
|
+
{link.label}
|
|
71
|
+
</a>
|
|
72
|
+
))}
|
|
73
|
+
<a
|
|
74
|
+
href="https://github.com/7vignesh/code-analyzer"
|
|
75
|
+
className="mt-1 w-fit border border-white/20 text-sm px-4 py-1.5 rounded-lg hover:border-accent hover:text-accent transition-all"
|
|
76
|
+
onClick={() => setOpen(false)}
|
|
77
|
+
>
|
|
78
|
+
GitHub
|
|
79
|
+
</a>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
</nav>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AppWindow, Clock, GitBranch } from 'lucide-react'
|
|
2
|
+
import Reveal from './Reveal'
|
|
3
|
+
|
|
4
|
+
const cards = [
|
|
5
|
+
{
|
|
6
|
+
icon: AppWindow,
|
|
7
|
+
title: 'Context window limits',
|
|
8
|
+
body: "Large repos have hundreds of files. You can't paste them all. The AI only sees what you show it - and guesses the rest.",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
icon: Clock,
|
|
12
|
+
title: 'One file at a time is slow',
|
|
13
|
+
body: "You manually hunt for relevant files, paste them one by one, and re-explain the architecture every session. It's exhausting.",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
icon: GitBranch,
|
|
17
|
+
title: 'No structural awareness',
|
|
18
|
+
body: "Keyword search finds files that mention your term. It doesn't find the files that ARE the answer architecturally - the central import hubs and interfaces.",
|
|
19
|
+
},
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
export default function Problem() {
|
|
23
|
+
return (
|
|
24
|
+
<section className="py-24 px-5">
|
|
25
|
+
<div className="max-w-6xl mx-auto">
|
|
26
|
+
<Reveal>
|
|
27
|
+
<h2 className="text-3xl md:text-5xl font-bold text-center tracking-tight">
|
|
28
|
+
AI is brilliant. But it can't read your whole codebase.
|
|
29
|
+
</h2>
|
|
30
|
+
</Reveal>
|
|
31
|
+
<div className="mt-14 grid grid-cols-1 md:grid-cols-3 gap-5">
|
|
32
|
+
{cards.map(({ icon: Icon, title, body }, i) => (
|
|
33
|
+
<Reveal key={title} delay={i * 100} className="h-full">
|
|
34
|
+
<article className="group h-full rounded-xl border border-border bg-card p-6 transition-all duration-300 hover:-translate-y-1 hover:border-accent/20 hover:bg-card-hover hover:shadow-[0_20px_40px_-24px_rgba(0,0,0,0.8)]">
|
|
35
|
+
<Icon
|
|
36
|
+
className="mb-5 text-accent transition-transform duration-300 group-hover:scale-110 group-hover:text-accent"
|
|
37
|
+
size={24}
|
|
38
|
+
/>
|
|
39
|
+
<h3 className="mb-2 text-xl font-semibold">{title}</h3>
|
|
40
|
+
<p className="text-[#888]">{body}</p>
|
|
41
|
+
</article>
|
|
42
|
+
</Reveal>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
<Reveal delay={200}>
|
|
46
|
+
<p className="mt-10 text-center text-[#888]">Universal Code Analyzer fixes all three.</p>
|
|
47
|
+
</Reveal>
|
|
48
|
+
</div>
|
|
49
|
+
</section>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
export default function Reveal({ children, delay = 0, className = '' }) {
|
|
4
|
+
const ref = useRef(null)
|
|
5
|
+
const [visible, setVisible] = useState(false)
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
9
|
+
setVisible(true)
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const el = ref.current
|
|
14
|
+
if (!el) return
|
|
15
|
+
|
|
16
|
+
const observer = new IntersectionObserver(
|
|
17
|
+
([entry]) => {
|
|
18
|
+
if (entry.isIntersecting) setVisible(true)
|
|
19
|
+
},
|
|
20
|
+
{ threshold: 0.12, rootMargin: '0px 0px -6% 0px' }
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
observer.observe(el)
|
|
24
|
+
return () => observer.disconnect()
|
|
25
|
+
}, [])
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
ref={ref}
|
|
30
|
+
className={[
|
|
31
|
+
'transition-[opacity,transform] duration-700 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none',
|
|
32
|
+
visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8',
|
|
33
|
+
className,
|
|
34
|
+
].join(' ')}
|
|
35
|
+
style={{ transitionDelay: visible ? `${delay}ms` : '0ms' }}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import Reveal from './Reveal'
|
|
2
|
+
|
|
3
|
+
const tools = [
|
|
4
|
+
{
|
|
5
|
+
name: 'Gemini CLI',
|
|
6
|
+
desc: 'Add as MCP server in ~/.gemini/config.json',
|
|
7
|
+
barClass: 'bg-accent',
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
name: 'Claude Code',
|
|
11
|
+
desc: 'Works via MCP over stdio',
|
|
12
|
+
barClass: 'bg-purple',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'Cursor',
|
|
16
|
+
desc: 'Add to ~/.cursor/mcp.json',
|
|
17
|
+
barClass: 'bg-green',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'Any MCP Client',
|
|
21
|
+
desc: 'Standard Model Context Protocol over stdio',
|
|
22
|
+
barClass: 'bg-yellow',
|
|
23
|
+
},
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
export default function WorksWith() {
|
|
27
|
+
return (
|
|
28
|
+
<section id="works-with" className="py-24 px-5">
|
|
29
|
+
<div className="max-w-6xl mx-auto">
|
|
30
|
+
<Reveal>
|
|
31
|
+
<h2 className="text-3xl md:text-5xl font-bold text-center tracking-tight">
|
|
32
|
+
Works with the tools you already use.
|
|
33
|
+
</h2>
|
|
34
|
+
</Reveal>
|
|
35
|
+
<div className="mt-12 grid grid-cols-1 items-stretch gap-5 sm:grid-cols-2 lg:grid-cols-4">
|
|
36
|
+
{tools.map((tool, i) => (
|
|
37
|
+
<Reveal key={tool.name} delay={i * 90} className="h-full">
|
|
38
|
+
<article className="group flex h-full min-h-[160px] gap-4 rounded-xl border border-border bg-card p-6 transition-all duration-300 hover:-translate-y-1.5 hover:border-accent/25 hover:bg-card-hover hover:shadow-[0_24px_48px_-28px_rgba(0,0,0,0.9)]">
|
|
39
|
+
<div
|
|
40
|
+
className={`h-12 w-1 shrink-0 origin-top rounded-full ${tool.barClass} motion-safe:animate-bar-grow motion-reduce:opacity-100 motion-reduce:scale-y-100 transition-transform duration-300 motion-safe:group-hover:scale-y-110`}
|
|
41
|
+
aria-hidden
|
|
42
|
+
/>
|
|
43
|
+
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
|
44
|
+
<h3 className="text-lg font-semibold leading-snug text-[#f0f0f0] transition-colors duration-300 group-hover:text-white">
|
|
45
|
+
{tool.name}
|
|
46
|
+
</h3>
|
|
47
|
+
<p className="flex-1 text-sm leading-relaxed text-[#888]">{tool.desc}</p>
|
|
48
|
+
<span className="mt-3 inline-flex w-fit rounded-full bg-green/15 px-3 py-1 text-xs font-medium text-green transition-transform duration-300 group-hover:scale-105">
|
|
49
|
+
Ready
|
|
50
|
+
</span>
|
|
51
|
+
</div>
|
|
52
|
+
</article>
|
|
53
|
+
</Reveal>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</section>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useScrollNav(threshold = 60) {
|
|
4
|
+
const [scrolled, setScrolled] = useState(false)
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const handler = () => setScrolled(window.scrollY > threshold)
|
|
8
|
+
window.addEventListener('scroll', handler)
|
|
9
|
+
return () => window.removeEventListener('scroll', handler)
|
|
10
|
+
}, [threshold])
|
|
11
|
+
|
|
12
|
+
return scrolled
|
|
13
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useTypewriter(lines, charDelay = 25, lineDelay = 350) {
|
|
4
|
+
const [displayed, setDisplayed] = useState([])
|
|
5
|
+
const [currentLine, setCurrentLine] = useState(0)
|
|
6
|
+
const [currentChar, setCurrentChar] = useState(0)
|
|
7
|
+
const [done, setDone] = useState(false)
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
setDisplayed([])
|
|
11
|
+
setCurrentLine(0)
|
|
12
|
+
setCurrentChar(0)
|
|
13
|
+
setDone(false)
|
|
14
|
+
}, [lines])
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (currentLine >= lines.length) {
|
|
18
|
+
setDone(true)
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
if (currentChar < lines[currentLine].length) {
|
|
22
|
+
const t = setTimeout(() => {
|
|
23
|
+
setDisplayed((prev) => {
|
|
24
|
+
const next = [...prev]
|
|
25
|
+
if (!next[currentLine]) next[currentLine] = ''
|
|
26
|
+
next[currentLine] = lines[currentLine].slice(0, currentChar + 1)
|
|
27
|
+
return next
|
|
28
|
+
})
|
|
29
|
+
setCurrentChar((c) => c + 1)
|
|
30
|
+
}, charDelay)
|
|
31
|
+
return () => clearTimeout(t)
|
|
32
|
+
}
|
|
33
|
+
const t = setTimeout(() => {
|
|
34
|
+
setCurrentLine((l) => l + 1)
|
|
35
|
+
setCurrentChar(0)
|
|
36
|
+
}, lineDelay)
|
|
37
|
+
return () => clearTimeout(t)
|
|
38
|
+
}, [currentLine, currentChar, lines, charDelay, lineDelay])
|
|
39
|
+
|
|
40
|
+
return { displayed, done }
|
|
41
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
content: ['./index.html', './src/**/*.{js,jsx}'],
|
|
3
|
+
theme: {
|
|
4
|
+
extend: {
|
|
5
|
+
colors: {
|
|
6
|
+
bg: '#0a0a0a',
|
|
7
|
+
card: '#111111',
|
|
8
|
+
'card-hover': '#161616',
|
|
9
|
+
accent: '#00d4ff',
|
|
10
|
+
purple: '#a855f7',
|
|
11
|
+
green: '#22c55e',
|
|
12
|
+
yellow: '#eab308',
|
|
13
|
+
border: 'rgba(255,255,255,0.07)',
|
|
14
|
+
},
|
|
15
|
+
fontFamily: {
|
|
16
|
+
sans: ['Inter', 'sans-serif'],
|
|
17
|
+
mono: ['JetBrains Mono', 'monospace'],
|
|
18
|
+
},
|
|
19
|
+
keyframes: {
|
|
20
|
+
fadeInUp: {
|
|
21
|
+
'0%': { opacity: '0', transform: 'translateY(28px)' },
|
|
22
|
+
'100%': { opacity: '1', transform: 'translateY(0)' },
|
|
23
|
+
},
|
|
24
|
+
fadeIn: {
|
|
25
|
+
'0%': { opacity: '0' },
|
|
26
|
+
'100%': { opacity: '1' },
|
|
27
|
+
},
|
|
28
|
+
scaleIn: {
|
|
29
|
+
'0%': { opacity: '0', transform: 'scale(0.97)' },
|
|
30
|
+
'100%': { opacity: '1', transform: 'scale(1)' },
|
|
31
|
+
},
|
|
32
|
+
float: {
|
|
33
|
+
'0%, 100%': { transform: 'translateY(0)' },
|
|
34
|
+
'50%': { transform: 'translateY(-10px)' },
|
|
35
|
+
},
|
|
36
|
+
drift: {
|
|
37
|
+
'0%, 100%': { transform: 'translate(0, 0) scale(1)' },
|
|
38
|
+
'33%': { transform: 'translate(40px, -30px) scale(1.08)' },
|
|
39
|
+
'66%': { transform: 'translate(-30px, 20px) scale(0.92)' },
|
|
40
|
+
},
|
|
41
|
+
pulseGlow: {
|
|
42
|
+
'0%, 100%': {
|
|
43
|
+
boxShadow: '0 0 0 0 rgba(0, 212, 255, 0)',
|
|
44
|
+
borderColor: 'rgba(255, 255, 255, 0.07)',
|
|
45
|
+
},
|
|
46
|
+
'50%': {
|
|
47
|
+
boxShadow: '0 0 56px -8px rgba(0, 212, 255, 0.45)',
|
|
48
|
+
borderColor: 'rgba(0, 212, 255, 0.35)',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
barGrow: {
|
|
52
|
+
'0%': { transform: 'scaleY(0.6)', opacity: '0.6' },
|
|
53
|
+
'100%': { transform: 'scaleY(1)', opacity: '1' },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
animation: {
|
|
57
|
+
'fade-in-up': 'fadeInUp 0.85s cubic-bezier(0.22, 1, 0.36, 1) forwards',
|
|
58
|
+
'fade-in': 'fadeIn 0.45s ease-out forwards',
|
|
59
|
+
'scale-in': 'scaleIn 0.65s cubic-bezier(0.22, 1, 0.36, 1) forwards',
|
|
60
|
+
'float-slow': 'float 6s ease-in-out infinite',
|
|
61
|
+
drift: 'drift 22s ease-in-out infinite',
|
|
62
|
+
'pulse-glow': 'pulseGlow 3s ease-in-out infinite',
|
|
63
|
+
'bar-grow': 'barGrow 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
plugins: [],
|
|
68
|
+
}
|