cistack 5.5.0 → 6.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.
- package/.github/dependabot.yml +42 -0
- package/.github/workflows/ci.yml +2 -1
- package/.github/workflows/pipeline.yml +250 -0
- package/README.md +4 -0
- package/package.json +7 -2
- package/product-site/.github/dependabot.yml +27 -0
- package/product-site/.github/workflows/pipeline.yml +202 -0
- package/product-site/.lighthouserc.json +22 -0
- package/product-site/README.md +1 -0
- package/product-site/app/[lang]/layout.tsx +95 -0
- package/product-site/app/[lang]/page.tsx +19 -0
- package/product-site/app/favicon.ico +0 -0
- package/product-site/app/globals.css +228 -0
- package/product-site/app/manifest.ts +20 -0
- package/product-site/app/robots.ts +12 -0
- package/product-site/app/sitemap.ts +12 -0
- package/product-site/components/CanvasText.tsx +219 -0
- package/product-site/components/CopyButton.tsx +85 -0
- package/product-site/components/HomeClient.tsx +985 -0
- package/product-site/components/InstallToggle.tsx +87 -0
- package/product-site/components/MotionRevealClient.tsx +53 -0
- package/product-site/components/TerminalCard.tsx +61 -0
- package/product-site/components/TerminalCardMotion.tsx +317 -0
- package/product-site/components/ui/accordion.tsx +74 -0
- package/product-site/components/ui/badge.tsx +52 -0
- package/product-site/components/ui/button.tsx +60 -0
- package/product-site/components/ui/card.tsx +103 -0
- package/product-site/components/ui/checkbox.tsx +29 -0
- package/product-site/components/ui/separator.tsx +25 -0
- package/product-site/components/ui/table.tsx +116 -0
- package/product-site/components/ui/tabs.tsx +82 -0
- package/product-site/components.json +25 -0
- package/product-site/dictionaries/br.json +137 -0
- package/product-site/dictionaries/cn.json +137 -0
- package/product-site/dictionaries/de.json +137 -0
- package/product-site/dictionaries/en.json +137 -0
- package/product-site/dictionaries/es.json +182 -0
- package/product-site/dictionaries/fr.json +182 -0
- package/product-site/dictionaries/pt.json +137 -0
- package/product-site/eslint.config.mjs +18 -0
- package/product-site/lib/dictionaries.ts +18 -0
- package/product-site/lib/dictionary-types.ts +3 -0
- package/product-site/lib/utils.ts +6 -0
- package/product-site/middleware.ts +39 -0
- package/product-site/next.config.mjs +14 -0
- package/product-site/package-lock.json +14201 -0
- package/product-site/package.json +41 -0
- package/product-site/postcss.config.mjs +7 -0
- package/product-site/public/file.svg +1 -0
- package/product-site/public/globe.svg +1 -0
- package/product-site/public/next.svg +1 -0
- package/product-site/public/og-image.png +0 -0
- package/product-site/public/vercel.svg +1 -0
- package/product-site/public/window.svg +1 -0
- package/product-site/scripts/sync-i18n.mjs +58 -0
- package/product-site/tsconfig.json +34 -0
- package/product-site/types/negotiator.d.ts +14 -0
- package/product-site/vercel.json +5 -0
- package/src/analyzers/codebase.js +53 -0
- package/src/detectors/framework.js +6 -6
- package/src/detectors/testing.js +37 -5
- package/src/generators/workflow.js +3 -63
- package/tests/run.js +38 -63
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import {
|
|
5
|
+
ArrowUpRight,
|
|
6
|
+
Box,
|
|
7
|
+
Check,
|
|
8
|
+
Globe,
|
|
9
|
+
Package,
|
|
10
|
+
Shield,
|
|
11
|
+
Terminal,
|
|
12
|
+
} from "lucide-react";
|
|
13
|
+
import { useEffect, useState } from "react";
|
|
14
|
+
|
|
15
|
+
import CopyButton from "@/components/CopyButton";
|
|
16
|
+
import InstallToggle from "@/components/InstallToggle";
|
|
17
|
+
import TerminalCard from "@/components/TerminalCard";
|
|
18
|
+
import { Badge } from "@/components/ui/badge";
|
|
19
|
+
import type { Dictionary } from "@/lib/dictionary-types";
|
|
20
|
+
import { motion } from "framer-motion";
|
|
21
|
+
|
|
22
|
+
interface GithubIconProps {
|
|
23
|
+
size?: number;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface RegistryPackageResponse {
|
|
28
|
+
version?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface DownloadStatsResponse {
|
|
32
|
+
downloads?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const localeOptions = [
|
|
36
|
+
{ code: "en", label: "English" },
|
|
37
|
+
{ code: "fr", label: "Français" },
|
|
38
|
+
{ code: "es", label: "Español" },
|
|
39
|
+
{ code: "pt", label: "Português" },
|
|
40
|
+
{ code: "br", label: "BR (Brasil)" },
|
|
41
|
+
{ code: "de", label: "Deutsch" },
|
|
42
|
+
{ code: "cn", label: "简体中文" },
|
|
43
|
+
] as const;
|
|
44
|
+
|
|
45
|
+
const detectionPanels = [
|
|
46
|
+
{
|
|
47
|
+
id: "p1",
|
|
48
|
+
idx: "S_01",
|
|
49
|
+
name: "Firebase",
|
|
50
|
+
signal: "firebase.json, firebase-tools dep",
|
|
51
|
+
description:
|
|
52
|
+
"Automatic Firebase Hosting detection with automated multi-project branch logic.",
|
|
53
|
+
Icon: Package,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "p2",
|
|
57
|
+
idx: "S_02",
|
|
58
|
+
name: "Vercel",
|
|
59
|
+
signal: "vercel.json, .vercel dir, vercel dep",
|
|
60
|
+
description:
|
|
61
|
+
"Vercel deploy-gate generation with branch-aware environment provisioning.",
|
|
62
|
+
Icon: Shield,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "p3",
|
|
66
|
+
idx: "S_03",
|
|
67
|
+
name: "Netlify",
|
|
68
|
+
signal: "netlify.toml, _redirects, netlify-cli dep",
|
|
69
|
+
description:
|
|
70
|
+
"Netlify edge detection with custom header and redirect validation.",
|
|
71
|
+
Icon: Terminal,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "p4",
|
|
75
|
+
idx: "S_04",
|
|
76
|
+
name: "GitHub Pages",
|
|
77
|
+
signal: "gh-pages dep, github.io package.json",
|
|
78
|
+
description:
|
|
79
|
+
"Static site hosting recognition for the native GitHub Pages deployment service.",
|
|
80
|
+
Icon: Globe,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "p5",
|
|
84
|
+
idx: "S_05",
|
|
85
|
+
name: "AWS Cloud",
|
|
86
|
+
signal: "serverless.yml, appspec.yml, cdk.json, aws-sdk dep",
|
|
87
|
+
description:
|
|
88
|
+
"Infrastructure-as-Code (IaC) recognition for AWS CDK, EC2, and Serverless.",
|
|
89
|
+
Icon: Box,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "p6",
|
|
93
|
+
idx: "S_06",
|
|
94
|
+
name: "GCP App Engine",
|
|
95
|
+
signal: "app.yaml",
|
|
96
|
+
description:
|
|
97
|
+
"Automated deployment scanning for Google Cloud Platform App Engine targets.",
|
|
98
|
+
Icon: Globe,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "p7",
|
|
102
|
+
idx: "S_07",
|
|
103
|
+
name: "Azure",
|
|
104
|
+
signal: "azure/pipelines.yml, @azure/* deps",
|
|
105
|
+
description:
|
|
106
|
+
"Azure App Service and native Azure pipeline definition identification.",
|
|
107
|
+
Icon: Package,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "p8",
|
|
111
|
+
idx: "S_08",
|
|
112
|
+
name: "Heroku",
|
|
113
|
+
signal: "Procfile, heroku.yml",
|
|
114
|
+
description:
|
|
115
|
+
"Classic PaaS deployment via Heroku registry and git push logic.",
|
|
116
|
+
Icon: Box,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "p9",
|
|
120
|
+
idx: "S_09",
|
|
121
|
+
name: "Render / Railway",
|
|
122
|
+
signal: "render.yaml, railway.json, railway.toml",
|
|
123
|
+
description:
|
|
124
|
+
"Modern PaaS detection utilizing automated app service descriptors.",
|
|
125
|
+
Icon: Terminal,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "p10",
|
|
129
|
+
idx: "S_10",
|
|
130
|
+
name: "Docker",
|
|
131
|
+
signal: "Dockerfile, docker-compose.yml",
|
|
132
|
+
description:
|
|
133
|
+
"Containerization recognition with Buildx layer caching and GHCR authentication.",
|
|
134
|
+
Icon: Box,
|
|
135
|
+
},
|
|
136
|
+
] as const;
|
|
137
|
+
|
|
138
|
+
const frameworkCoverage = [
|
|
139
|
+
"Next.js",
|
|
140
|
+
"Nuxt",
|
|
141
|
+
"SvelteKit",
|
|
142
|
+
"Remix",
|
|
143
|
+
"Astro",
|
|
144
|
+
"Vite",
|
|
145
|
+
"React",
|
|
146
|
+
"Vue",
|
|
147
|
+
"Angular",
|
|
148
|
+
"Svelte",
|
|
149
|
+
"Gatsby",
|
|
150
|
+
"Express",
|
|
151
|
+
"Fastify",
|
|
152
|
+
"NestJS",
|
|
153
|
+
"Hono",
|
|
154
|
+
"Koa",
|
|
155
|
+
"tRPC",
|
|
156
|
+
"Django",
|
|
157
|
+
"Flask",
|
|
158
|
+
"FastAPI",
|
|
159
|
+
"Ruby on Rails",
|
|
160
|
+
"Spring Boot",
|
|
161
|
+
"Laravel",
|
|
162
|
+
"Go (gin)",
|
|
163
|
+
"Rust (Cargo)",
|
|
164
|
+
] as const;
|
|
165
|
+
|
|
166
|
+
const testingTools = [
|
|
167
|
+
"Jest",
|
|
168
|
+
"Vitest",
|
|
169
|
+
"Mocha",
|
|
170
|
+
"Cypress",
|
|
171
|
+
"Playwright",
|
|
172
|
+
"Pytest",
|
|
173
|
+
"RSpec",
|
|
174
|
+
"Go Test",
|
|
175
|
+
"Cargo Test",
|
|
176
|
+
"PHPUnit",
|
|
177
|
+
"JUnit/Maven",
|
|
178
|
+
"Storybook",
|
|
179
|
+
] as const;
|
|
180
|
+
|
|
181
|
+
const exampleStacks = [
|
|
182
|
+
{
|
|
183
|
+
name: "Next.js + Vercel",
|
|
184
|
+
description: "ci.yml (lint, test, build), deploy.yml (vercel deploy), security.yml",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "React + Firebase",
|
|
188
|
+
description:
|
|
189
|
+
"ci.yml, deploy.yml (firebase deploy --only hosting), security.yml",
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: "Node.js API + Docker",
|
|
193
|
+
description: "ci.yml, docker.yml (GHCR push), security.yml",
|
|
194
|
+
},
|
|
195
|
+
] as const;
|
|
196
|
+
|
|
197
|
+
const GithubIcon = ({ size = 24, className = "" }: GithubIconProps) => (
|
|
198
|
+
<svg
|
|
199
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
200
|
+
width={size}
|
|
201
|
+
height={size}
|
|
202
|
+
viewBox="0 0 24 24"
|
|
203
|
+
fill="none"
|
|
204
|
+
stroke="currentColor"
|
|
205
|
+
strokeWidth="2"
|
|
206
|
+
strokeLinecap="round"
|
|
207
|
+
strokeLinejoin="round"
|
|
208
|
+
className={className}
|
|
209
|
+
>
|
|
210
|
+
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.02c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A4.8 4.8 0 0 0 8 18v4" />
|
|
211
|
+
</svg>
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
export default function HomeClient({
|
|
215
|
+
dict,
|
|
216
|
+
lang,
|
|
217
|
+
}: {
|
|
218
|
+
dict: Dictionary;
|
|
219
|
+
lang: string;
|
|
220
|
+
}) {
|
|
221
|
+
const [version, setVersion] = useState("3.0.0");
|
|
222
|
+
const [downloads, setDownloads] = useState("2.4k");
|
|
223
|
+
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
let cancelled = false;
|
|
226
|
+
|
|
227
|
+
const loadStats = async () => {
|
|
228
|
+
try {
|
|
229
|
+
const [registryRes, downloadsRes] = await Promise.all([
|
|
230
|
+
fetch("https://registry.npmjs.org/cistack/latest"),
|
|
231
|
+
fetch("https://api.npmjs.org/downloads/point/last-week/cistack"),
|
|
232
|
+
]);
|
|
233
|
+
|
|
234
|
+
if (registryRes.ok) {
|
|
235
|
+
const data = (await registryRes.json()) as RegistryPackageResponse;
|
|
236
|
+
if (!cancelled && data.version) setVersion(data.version);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (downloadsRes.ok) {
|
|
240
|
+
const data = (await downloadsRes.json()) as DownloadStatsResponse;
|
|
241
|
+
if (!cancelled && typeof data.downloads === "number") {
|
|
242
|
+
const count = data.downloads;
|
|
243
|
+
setDownloads(count >= 1000 ? `${(count / 1000).toFixed(1)}k` : count.toLocaleString());
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} catch (e) {
|
|
247
|
+
console.error("Stats fetch error", e);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
void loadStats();
|
|
252
|
+
return () => { cancelled = true; };
|
|
253
|
+
}, []);
|
|
254
|
+
|
|
255
|
+
const now = new Date();
|
|
256
|
+
const manifestStamp = `${now.getFullYear()}_${(now.getMonth() + 1).toString().padStart(2, "0")}`;
|
|
257
|
+
const currentYear = now.getFullYear();
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<>
|
|
261
|
+
<script
|
|
262
|
+
type="application/ld+json"
|
|
263
|
+
dangerouslySetInnerHTML={{
|
|
264
|
+
__html: JSON.stringify({
|
|
265
|
+
"@context": "https://schema.org",
|
|
266
|
+
"@type": "SoftwareApplication",
|
|
267
|
+
name: "cistack",
|
|
268
|
+
operatingSystem: "Any",
|
|
269
|
+
applicationCategory: "DeveloperApplication",
|
|
270
|
+
softwareVersion: version,
|
|
271
|
+
offers: {
|
|
272
|
+
"@type": "Offer",
|
|
273
|
+
price: "0",
|
|
274
|
+
priceCurrency: "USD",
|
|
275
|
+
availability: "https://schema.org/InStock",
|
|
276
|
+
},
|
|
277
|
+
description: dict.hero.description,
|
|
278
|
+
creator: {
|
|
279
|
+
"@type": "Person",
|
|
280
|
+
name: "Edwin Vakayil",
|
|
281
|
+
url: "https://www.edwinvakayil.info/",
|
|
282
|
+
},
|
|
283
|
+
featureList: Object.values(dict.docs.capabilities).map((capability) => capability.label),
|
|
284
|
+
keywords: "github actions, automation, ci/cd, devops, workflow generator, nextjs, docker, vercel, aws, firebase",
|
|
285
|
+
}),
|
|
286
|
+
}}
|
|
287
|
+
/>
|
|
288
|
+
<div
|
|
289
|
+
className="page-root relative flex min-h-screen flex-col overflow-x-hidden bg-white text-zinc-900 selection:bg-black selection:text-white"
|
|
290
|
+
style={{
|
|
291
|
+
backgroundImage: "radial-gradient(circle at 1px 1px, rgba(0, 0, 0, 0.05) 1px, transparent 0)",
|
|
292
|
+
backgroundSize: "24px 24px",
|
|
293
|
+
}}
|
|
294
|
+
>
|
|
295
|
+
<motion.nav
|
|
296
|
+
initial={{ opacity: 0, y: -10 }}
|
|
297
|
+
animate={{ opacity: 1, y: 0 }}
|
|
298
|
+
transition={{ duration: 0.8 }}
|
|
299
|
+
className="relative z-50 mx-auto w-full max-w-[1400px] bg-white/80 backdrop-blur-md"
|
|
300
|
+
>
|
|
301
|
+
<div className="grid grid-cols-1 border-x border-b border-zinc-100 md:grid-cols-12">
|
|
302
|
+
<div className="flex items-center gap-3 border-b border-zinc-100 p-5 md:col-span-3 md:border-b-0 md:border-r md:p-6">
|
|
303
|
+
<div className="flex items-center gap-2 text-[20px] font-bold tracking-tighter text-zinc-950">
|
|
304
|
+
<Terminal size={18} className="text-zinc-500" />
|
|
305
|
+
cistack
|
|
306
|
+
</div>
|
|
307
|
+
<span className="rounded-sm border border-zinc-100 bg-zinc-50 px-1.5 py-0.5 font-mono text-[12px] font-bold text-zinc-500">
|
|
308
|
+
V{version}
|
|
309
|
+
</span>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
<div className="flex items-center justify-between border-b border-zinc-100 p-5 md:col-span-6 md:border-b-0 md:border-r md:p-6">
|
|
313
|
+
<div className="flex items-center gap-6">
|
|
314
|
+
<div className="flex flex-col gap-0.5">
|
|
315
|
+
<span className="text-[12px] font-black uppercase leading-none tracking-[0.2em] text-zinc-500">
|
|
316
|
+
{dict.navigation.version}
|
|
317
|
+
</span>
|
|
318
|
+
<div className="flex items-center gap-2">
|
|
319
|
+
<div className="h-1.5 w-1.5 animate-pulse rounded-full bg-emerald-500" />
|
|
320
|
+
<span className="text-[12px] font-bold uppercase tracking-tighter text-zinc-600">
|
|
321
|
+
{dict.navigation.status}
|
|
322
|
+
</span>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
<div className="mx-2 hidden h-6 w-px bg-zinc-100 lg:block" />
|
|
326
|
+
<div className="hidden items-center gap-6 text-[12px] font-bold text-zinc-500 lg:flex">
|
|
327
|
+
<a
|
|
328
|
+
href="https://github.com/edwinvakayil/cistack"
|
|
329
|
+
target="_blank"
|
|
330
|
+
rel="noopener noreferrer"
|
|
331
|
+
className="group flex items-center gap-2 uppercase tracking-widest transition-colors hover:text-zinc-950"
|
|
332
|
+
>
|
|
333
|
+
<GithubIcon size={14} className="opacity-40 transition-opacity group-hover:opacity-100" />
|
|
334
|
+
{dict.navigation.repository}
|
|
335
|
+
</a>
|
|
336
|
+
<a
|
|
337
|
+
href="https://www.npmjs.com/package/cistack"
|
|
338
|
+
target="_blank"
|
|
339
|
+
rel="noopener noreferrer"
|
|
340
|
+
className="group flex items-center gap-2 uppercase tracking-widest transition-colors hover:text-zinc-950"
|
|
341
|
+
>
|
|
342
|
+
<Package size={14} className="opacity-40 transition-opacity group-hover:opacity-100" />
|
|
343
|
+
{dict.navigation.registry}
|
|
344
|
+
</a>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
<div className="flex shrink-0 items-center gap-1.5 sm:gap-2">
|
|
349
|
+
<Link
|
|
350
|
+
href="/en"
|
|
351
|
+
className={`rounded-sm border px-1.5 py-0.5 text-[12px] font-bold uppercase tracking-widest transition-colors ${
|
|
352
|
+
lang === "en"
|
|
353
|
+
? "border-zinc-950 bg-zinc-950 text-white"
|
|
354
|
+
: "border-transparent text-zinc-500 hover:border-zinc-100 hover:text-zinc-950"
|
|
355
|
+
}`}
|
|
356
|
+
>
|
|
357
|
+
EN
|
|
358
|
+
</Link>
|
|
359
|
+
{lang !== "en" && (
|
|
360
|
+
<>
|
|
361
|
+
<div className="mx-0.5 h-3 w-px bg-zinc-100" />
|
|
362
|
+
<Link
|
|
363
|
+
href={`/${lang}`}
|
|
364
|
+
className="rounded-sm border border-zinc-950 bg-zinc-950 px-1.5 py-0.5 text-[12px] font-bold uppercase tracking-widest text-white"
|
|
365
|
+
>
|
|
366
|
+
{lang.toUpperCase()}
|
|
367
|
+
</Link>
|
|
368
|
+
</>
|
|
369
|
+
)}
|
|
370
|
+
|
|
371
|
+
<details className="group/lang relative ml-1 border-l border-zinc-100 pl-2">
|
|
372
|
+
<summary
|
|
373
|
+
aria-label="Change language"
|
|
374
|
+
className="flex list-none cursor-pointer items-center gap-1 py-1 text-[12px] font-bold uppercase tracking-[0.14em] text-zinc-600 transition-colors hover:text-zinc-950 [&::-webkit-details-marker]:hidden"
|
|
375
|
+
>
|
|
376
|
+
<Globe size={13} className="text-current" />
|
|
377
|
+
Lang
|
|
378
|
+
</summary>
|
|
379
|
+
<div className="absolute right-0 top-full z-[100] mt-2 flex w-32 flex-col gap-1 rounded-sm border border-zinc-200 bg-white p-1 shadow-xl">
|
|
380
|
+
{localeOptions.map((locale) => (
|
|
381
|
+
<Link
|
|
382
|
+
key={locale.code}
|
|
383
|
+
href={`/${locale.code}`}
|
|
384
|
+
className="rounded-sm p-2 text-[12px] font-bold uppercase tracking-[0.12em] text-zinc-700 transition-colors hover:bg-zinc-50 hover:text-zinc-950"
|
|
385
|
+
>
|
|
386
|
+
{locale.label}
|
|
387
|
+
</Link>
|
|
388
|
+
))}
|
|
389
|
+
</div>
|
|
390
|
+
</details>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
<div className="flex items-center justify-between gap-6 bg-zinc-50/20 p-5 md:col-span-3 md:justify-end md:p-6">
|
|
395
|
+
<span className="font-mono text-[12px] font-bold uppercase tracking-[0.2em] text-zinc-500">
|
|
396
|
+
{`${dict.navigation.core_manifest} // ${manifestStamp}`}
|
|
397
|
+
</span>
|
|
398
|
+
<a
|
|
399
|
+
href="#docs"
|
|
400
|
+
className="rounded-sm border border-zinc-900 px-4 py-2 text-[12px] font-black uppercase tracking-widest text-zinc-900 transition-all hover:bg-zinc-950 hover:text-white"
|
|
401
|
+
>
|
|
402
|
+
{dict.navigation.docs}
|
|
403
|
+
</a>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
</motion.nav>
|
|
407
|
+
|
|
408
|
+
<main className="relative z-10 mx-auto w-full max-w-[1500px] px-4 pt-6 sm:px-6 md:px-8 lg:pt-12">
|
|
409
|
+
<div className="pointer-events-none absolute inset-x-0 top-0 -mt-40 h-[600px] bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] opacity-[0.35] [background-size:24px_24px] [mask-image:linear-gradient(to_bottom,black,transparent)]" />
|
|
410
|
+
|
|
411
|
+
<div className="relative grid grid-cols-1 border-l border-t border-zinc-100 bg-white/40 lg:grid-cols-12">
|
|
412
|
+
<div className="group/hero relative border-b border-r border-zinc-100 p-8 sm:p-12 lg:col-span-7 lg:p-16">
|
|
413
|
+
<motion.div
|
|
414
|
+
initial={{ opacity: 0, y: 15 }}
|
|
415
|
+
whileInView={{ opacity: 1, y: 0 }}
|
|
416
|
+
viewport={{ once: true }}
|
|
417
|
+
transition={{ duration: 0.6 }}
|
|
418
|
+
className="relative z-10 flex flex-col gap-8"
|
|
419
|
+
>
|
|
420
|
+
<div className="flex items-center gap-3">
|
|
421
|
+
<Badge
|
|
422
|
+
variant="outline"
|
|
423
|
+
className="rounded-sm border-zinc-200 bg-white px-2 py-0.5 text-[12px] font-black uppercase tracking-[0.2em] text-zinc-500"
|
|
424
|
+
>
|
|
425
|
+
VERSION_{version}
|
|
426
|
+
</Badge>
|
|
427
|
+
<div className="h-px w-12 bg-zinc-100" />
|
|
428
|
+
<span className="font-mono text-[12px] font-bold uppercase tracking-widest text-zinc-500">
|
|
429
|
+
{dict.hero.scan_identity}
|
|
430
|
+
</span>
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
<div className="flex flex-col gap-8 md:gap-8">
|
|
434
|
+
<div className="group/h1 flex flex-col gap-1 sm:flex-row sm:items-baseline sm:gap-4">
|
|
435
|
+
<span className="font-mono text-[12px] font-bold text-zinc-400 transition-colors sm:text-zinc-300 md:text-[12px] group-hover/h1:text-zinc-500">
|
|
436
|
+
{dict.hero.s1_label}
|
|
437
|
+
</span>
|
|
438
|
+
<h1 className="break-words text-[2.2rem] font-[1000] leading-[0.9] tracking-tighter text-black min-[400px]:text-[2.5rem] sm:leading-none md:text-[3.8rem]">
|
|
439
|
+
{dict.hero.s1_title}
|
|
440
|
+
</h1>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<div className="group/h2 flex flex-col gap-1 sm:ml-6 sm:flex-row sm:items-baseline sm:gap-4 md:ml-12">
|
|
444
|
+
<span className="font-mono text-[12px] font-bold text-zinc-400 transition-colors sm:text-zinc-300 md:text-[12px] group-hover/h2:text-zinc-500">
|
|
445
|
+
{dict.hero.s2_label}
|
|
446
|
+
</span>
|
|
447
|
+
<h2 className="break-words text-[2.2rem] font-[1000] leading-[0.9] tracking-tighter text-black min-[400px]:text-[2.5rem] sm:leading-none md:text-[3.8rem]">
|
|
448
|
+
{dict.hero.s2_title}
|
|
449
|
+
</h2>
|
|
450
|
+
</div>
|
|
451
|
+
|
|
452
|
+
<div className="group/h3 flex flex-col gap-1 sm:ml-12 sm:flex-row sm:items-baseline sm:gap-4 md:ml-24">
|
|
453
|
+
<span className="font-mono text-[12px] font-bold text-zinc-400 transition-colors sm:text-zinc-300 md:text-[12px] group-hover/h3:text-zinc-500">
|
|
454
|
+
{dict.hero.s3_label}
|
|
455
|
+
</span>
|
|
456
|
+
<h2 className="break-words text-[2.2rem] font-[1000] leading-[0.9] tracking-tighter text-zinc-500 min-[400px]:text-[2.5rem] sm:leading-none sm:text-zinc-500 md:text-[3.8rem]">
|
|
457
|
+
{dict.hero.s3_title}
|
|
458
|
+
</h2>
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
|
|
462
|
+
<p className="max-w-[540px] text-xs font-medium leading-relaxed text-zinc-500 sm:text-sm">
|
|
463
|
+
{dict.hero.description}
|
|
464
|
+
</p>
|
|
465
|
+
|
|
466
|
+
<div className="mt-4 flex flex-wrap items-center gap-6">
|
|
467
|
+
<CopyButton text={dict.hero.npx_command} variant="hero" />
|
|
468
|
+
<div className="flex flex-col gap-1 border-l-2 border-zinc-100 px-2">
|
|
469
|
+
<span className="text-[12px] font-black uppercase tracking-widest text-zinc-500">
|
|
470
|
+
{dict.hero.active_installs}
|
|
471
|
+
</span>
|
|
472
|
+
<span className="text-[14px] font-bold tracking-tight text-zinc-900">
|
|
473
|
+
{downloads} {dict.hero.per_week}
|
|
474
|
+
</span>
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
</motion.div>
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
<div className="flex flex-col border-b border-r border-zinc-100 lg:col-span-5">
|
|
481
|
+
<div className="group/runtime flex flex-1 items-center justify-center overflow-hidden bg-zinc-50/20 p-8 transition-colors duration-700 hover:bg-zinc-50/40 sm:p-12">
|
|
482
|
+
<div className="absolute inset-0 bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] opacity-[0.2] [background-size:16px_16px]" />
|
|
483
|
+
<motion.div
|
|
484
|
+
initial={{ opacity: 0, scale: 0.98 }}
|
|
485
|
+
whileInView={{ opacity: 1, scale: 1 }}
|
|
486
|
+
viewport={{ once: true }}
|
|
487
|
+
transition={{ duration: 0.8, delay: 0.1 }}
|
|
488
|
+
className="relative z-20 w-full overflow-hidden rounded-sm"
|
|
489
|
+
>
|
|
490
|
+
<TerminalCard dict={dict.terminal} version={version} />
|
|
491
|
+
</motion.div>
|
|
492
|
+
<div className="absolute right-8 top-6 hidden items-center gap-2 sm:flex">
|
|
493
|
+
<div className="h-1.5 w-1.5 animate-pulse rounded-full bg-emerald-500" />
|
|
494
|
+
<span className="font-mono text-[12px] font-black uppercase tracking-widest text-zinc-500">
|
|
495
|
+
{dict.hero.realtime_synth}
|
|
496
|
+
</span>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
|
|
500
|
+
<div className="grid grid-cols-2 border-t border-zinc-100 bg-white">
|
|
501
|
+
<div className="group/meta flex cursor-default flex-col gap-2 border-r border-zinc-100 p-8 transition-colors hover:bg-zinc-50/50">
|
|
502
|
+
<span className="text-[12px] font-black uppercase tracking-[0.2em] text-zinc-500 transition-colors group-hover/meta:text-zinc-700">
|
|
503
|
+
{dict.hero.integrations}
|
|
504
|
+
</span>
|
|
505
|
+
<div className="flex items-center gap-2">
|
|
506
|
+
<span className="text-[20px] font-black leading-none text-zinc-900">30+</span>
|
|
507
|
+
<span className="text-[12px] font-bold uppercase tracking-tighter text-zinc-500">
|
|
508
|
+
{dict.hero.stack_aware}
|
|
509
|
+
</span>
|
|
510
|
+
</div>
|
|
511
|
+
</div>
|
|
512
|
+
<div className="group/meta flex cursor-default flex-col gap-2 bg-zinc-50/30 p-8 transition-colors hover:bg-zinc-50/60">
|
|
513
|
+
<span className="text-[12px] font-black uppercase tracking-[0.2em] text-zinc-500 transition-colors group-hover/meta:text-zinc-700">
|
|
514
|
+
{dict.hero.success_rate}
|
|
515
|
+
</span>
|
|
516
|
+
<div className="flex items-center gap-2">
|
|
517
|
+
<span className="text-[20px] font-black leading-none tracking-tighter text-emerald-600">99.9%</span>
|
|
518
|
+
<Check size={14} className="text-emerald-500/50" />
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
</main>
|
|
525
|
+
|
|
526
|
+
<section id="docs" className="relative z-10 mx-auto w-full max-w-[1400px] px-4 py-24 pb-40 sm:px-6 md:px-8">
|
|
527
|
+
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] opacity-[0.4] [background-size:32px_32px]" />
|
|
528
|
+
|
|
529
|
+
<div className="mb-16 flex max-w-[900px] flex-col gap-4">
|
|
530
|
+
<div className="flex items-center gap-3">
|
|
531
|
+
<Badge
|
|
532
|
+
variant="outline"
|
|
533
|
+
className="rounded-sm border-zinc-200 bg-white px-3 py-1 text-[12px] font-black uppercase tracking-[0.2em] text-zinc-700"
|
|
534
|
+
>
|
|
535
|
+
{dict.docs.badge}
|
|
536
|
+
{version}
|
|
537
|
+
</Badge>
|
|
538
|
+
<div className="h-px flex-1 bg-zinc-200/60" />
|
|
539
|
+
</div>
|
|
540
|
+
<h2 className="text-[36px] font-bold leading-[1.1] tracking-tight text-zinc-900 sm:text-[44px]">
|
|
541
|
+
{dict.docs.title_part1}
|
|
542
|
+
<br />
|
|
543
|
+
{dict.docs.title_part2}
|
|
544
|
+
</h2>
|
|
545
|
+
<p className="max-w-[760px] text-xs font-medium leading-relaxed text-zinc-500 sm:text-sm">
|
|
546
|
+
{dict.docs.description}
|
|
547
|
+
</p>
|
|
548
|
+
</div>
|
|
549
|
+
|
|
550
|
+
<motion.div
|
|
551
|
+
initial={{ opacity: 0, y: 20 }}
|
|
552
|
+
whileInView={{ opacity: 1, y: 0 }}
|
|
553
|
+
viewport={{ once: true }}
|
|
554
|
+
transition={{ duration: 0.8 }}
|
|
555
|
+
className="grid grid-cols-1 border-l border-t border-zinc-100 lg:grid-cols-12"
|
|
556
|
+
>
|
|
557
|
+
<div className="group/cell relative border-b border-r border-zinc-100 bg-zinc-50/20 p-8 transition-colors duration-500 hover:bg-zinc-50/40 lg:col-span-4 lg:p-12">
|
|
558
|
+
<div className="absolute right-4 top-4 font-mono text-[12px] font-bold text-zinc-500 transition-colors group-hover/cell:text-zinc-700">
|
|
559
|
+
{dict.docs.section1_id}
|
|
560
|
+
</div>
|
|
561
|
+
<div className="flex flex-col gap-10">
|
|
562
|
+
<div className="flex flex-col gap-2">
|
|
563
|
+
<span className="text-[12px] font-black uppercase tracking-[0.18em] text-zinc-500">
|
|
564
|
+
{dict.docs.section1_num}
|
|
565
|
+
</span>
|
|
566
|
+
<h3 className="text-[18px] font-bold text-zinc-900">{dict.docs.section1_title}</h3>
|
|
567
|
+
</div>
|
|
568
|
+
<div className="flex flex-col divide-y divide-zinc-200/40">
|
|
569
|
+
{Object.values(dict.docs.capabilities).map((item) => (
|
|
570
|
+
<div key={item.label} className="flex items-start gap-3 py-3.5 first:pt-0 last:pb-0">
|
|
571
|
+
<span
|
|
572
|
+
role="img"
|
|
573
|
+
aria-label="Available feature"
|
|
574
|
+
className="mt-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-[4px] border border-zinc-200 bg-zinc-900 text-white"
|
|
575
|
+
>
|
|
576
|
+
<Check size={11} strokeWidth={3} />
|
|
577
|
+
</span>
|
|
578
|
+
<div className="flex flex-col">
|
|
579
|
+
<span className="text-[14px] font-semibold leading-tight text-zinc-800">{item.label}</span>
|
|
580
|
+
<span className="mt-0.5 text-[12px] leading-snug text-zinc-500">{item.sub}</span>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
))}
|
|
584
|
+
</div>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
|
|
588
|
+
<div className="flex flex-col lg:col-span-8">
|
|
589
|
+
<div className="border-b border-r border-zinc-100 p-8 lg:p-12">
|
|
590
|
+
<div className="grid grid-cols-1 gap-12 md:grid-cols-2 lg:gap-16">
|
|
591
|
+
<div className="flex flex-col gap-6">
|
|
592
|
+
<div className="flex flex-col gap-2">
|
|
593
|
+
<span className="text-[12px] font-black uppercase tracking-[0.18em] text-zinc-500">
|
|
594
|
+
{dict.docs.section2_num}
|
|
595
|
+
</span>
|
|
596
|
+
<h3 className="text-[18px] font-bold text-zinc-900">{dict.docs.section2_title}</h3>
|
|
597
|
+
</div>
|
|
598
|
+
<InstallToggle dict={dict} />
|
|
599
|
+
|
|
600
|
+
<div className="mt-2 flex flex-col gap-3">
|
|
601
|
+
<span className="text-[12px] font-black uppercase tracking-[0.2em] text-zinc-500">
|
|
602
|
+
{dict.docs.command_registry}
|
|
603
|
+
</span>
|
|
604
|
+
<div className="flex flex-col gap-2">
|
|
605
|
+
{[
|
|
606
|
+
{ command: "audit", description: dict.docs.commands.audit },
|
|
607
|
+
{ command: "upgrade", description: dict.docs.commands.upgrade },
|
|
608
|
+
{ command: "init", description: dict.docs.commands.init },
|
|
609
|
+
].map((cmd) => (
|
|
610
|
+
<div
|
|
611
|
+
key={cmd.command}
|
|
612
|
+
className="group flex items-center justify-between rounded-sm border border-zinc-100 bg-zinc-50/50 px-3 py-2 transition-colors hover:border-zinc-200"
|
|
613
|
+
>
|
|
614
|
+
<code className="text-[12px] font-extrabold text-zinc-800">
|
|
615
|
+
npx cistack {cmd.command}
|
|
616
|
+
</code>
|
|
617
|
+
<span className="text-[12px] font-medium text-zinc-500">{cmd.description}</span>
|
|
618
|
+
</div>
|
|
619
|
+
))}
|
|
620
|
+
</div>
|
|
621
|
+
</div>
|
|
622
|
+
<p className="text-[13px] italic text-zinc-400">{dict.docs.recommended_npx}</p>
|
|
623
|
+
</div>
|
|
624
|
+
<div className="mt-4 flex flex-col gap-4">
|
|
625
|
+
<div className="flex items-center gap-3">
|
|
626
|
+
<span className="text-[12px] font-black uppercase tracking-[0.2em] text-zinc-500">
|
|
627
|
+
{dict.docs.parameters_manifest}
|
|
628
|
+
</span>
|
|
629
|
+
<div className="h-px flex-1 bg-zinc-200 opacity-60" />
|
|
630
|
+
</div>
|
|
631
|
+
|
|
632
|
+
<div className="flex flex-col divide-y divide-zinc-100 border-y border-zinc-200 bg-white/40">
|
|
633
|
+
{[
|
|
634
|
+
{ flag: "--explain", type: "boolean", description: dict.docs.flags.explain },
|
|
635
|
+
{ flag: "--path", type: "string", description: dict.docs.flags.path },
|
|
636
|
+
{ flag: "--output", type: "string", description: dict.docs.flags.output },
|
|
637
|
+
{ flag: "--dry-run", type: "boolean", description: dict.docs.flags.dry_run },
|
|
638
|
+
{ flag: "--no-prompt", type: "boolean", description: dict.docs.flags.no_prompt },
|
|
639
|
+
{ flag: "--verbose", type: "boolean", description: dict.docs.flags.verbose },
|
|
640
|
+
{ flag: "--force", type: "boolean", description: dict.docs.flags.force },
|
|
641
|
+
].map((flag) => (
|
|
642
|
+
<div
|
|
643
|
+
key={flag.flag}
|
|
644
|
+
className="group flex flex-col px-2 py-3 transition-colors hover:bg-zinc-50/80 md:flex-row md:items-center"
|
|
645
|
+
>
|
|
646
|
+
<div className="flex shrink-0 items-center gap-3 md:w-[150px]">
|
|
647
|
+
<div className="h-[12px] w-[3px] bg-zinc-200 transition-colors group-hover:bg-emerald-500" />
|
|
648
|
+
<code className="font-mono text-[12px] font-bold tracking-tight text-zinc-800">
|
|
649
|
+
{flag.flag}
|
|
650
|
+
</code>
|
|
651
|
+
</div>
|
|
652
|
+
<div className="mt-2 flex flex-1 flex-row items-center gap-4 pl-[15px] md:mt-0 md:pl-0">
|
|
653
|
+
<span className="rounded-sm bg-zinc-100 px-1.5 py-0.5 font-mono text-[12px] uppercase tracking-widest text-zinc-600">
|
|
654
|
+
{flag.type}
|
|
655
|
+
</span>
|
|
656
|
+
<span className="text-[12px] font-medium text-zinc-600">{flag.description}</span>
|
|
657
|
+
</div>
|
|
658
|
+
</div>
|
|
659
|
+
))}
|
|
660
|
+
</div>
|
|
661
|
+
</div>
|
|
662
|
+
</div>
|
|
663
|
+
</div>
|
|
664
|
+
|
|
665
|
+
<div className="grid flex-1 grid-cols-1 border-b border-r border-zinc-100 md:grid-cols-2">
|
|
666
|
+
<div className="border-b border-zinc-100 p-8 md:border-b-0 md:border-r lg:p-12">
|
|
667
|
+
<div className="flex flex-col gap-8">
|
|
668
|
+
<div className="flex flex-col gap-2">
|
|
669
|
+
<h4 className="text-[14px] font-bold uppercase tracking-widest text-zinc-900">
|
|
670
|
+
{dict.docs.detection_logic_title}
|
|
671
|
+
</h4>
|
|
672
|
+
<p className="text-[13px] font-medium leading-relaxed text-zinc-400">
|
|
673
|
+
{dict.docs.detection_logic_desc}
|
|
674
|
+
</p>
|
|
675
|
+
</div>
|
|
676
|
+
|
|
677
|
+
<div className="max-h-[250px] w-full overflow-y-auto overflow-x-hidden pr-3 sm:max-h-[265px] [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-200 hover:[&::-webkit-scrollbar-thumb]:bg-zinc-300 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar]:w-1">
|
|
678
|
+
<div className="w-full">
|
|
679
|
+
{detectionPanels.map((panel) => (
|
|
680
|
+
<details key={panel.id} className="group border-b border-zinc-100 last:border-0">
|
|
681
|
+
<summary className="list-none cursor-pointer py-4 text-zinc-500 transition-colors hover:text-zinc-900 sm:py-5 [&::-webkit-details-marker]:hidden">
|
|
682
|
+
<div className="flex min-w-0 flex-1 flex-col justify-center gap-1.5 overflow-hidden text-start sm:gap-2">
|
|
683
|
+
<div className="flex shrink-0 items-center gap-3 sm:gap-4">
|
|
684
|
+
<span className="w-[24px] shrink-0 font-mono text-[12px] font-bold text-zinc-500 transition-colors group-hover:text-zinc-700">
|
|
685
|
+
{panel.idx}
|
|
686
|
+
</span>
|
|
687
|
+
<div className="flex items-center gap-2 sm:gap-3">
|
|
688
|
+
<panel.Icon
|
|
689
|
+
size={14}
|
|
690
|
+
className="shrink-0 text-zinc-600 transition-colors group-hover:text-zinc-900"
|
|
691
|
+
/>
|
|
692
|
+
<span className="text-[15px] font-semibold whitespace-nowrap sm:text-[16px]">
|
|
693
|
+
{panel.name}
|
|
694
|
+
</span>
|
|
695
|
+
</div>
|
|
696
|
+
<span className="ml-auto text-[12px] font-black uppercase tracking-[0.2em] text-zinc-500">
|
|
697
|
+
OPEN
|
|
698
|
+
</span>
|
|
699
|
+
</div>
|
|
700
|
+
<div className="flex w-full justify-start overflow-hidden ps-[36px] sm:ps-[44px]">
|
|
701
|
+
<Badge
|
|
702
|
+
variant="outline"
|
|
703
|
+
className="max-w-full truncate rounded-sm border-zinc-200 bg-zinc-50/50 text-[12px] font-mono font-normal tracking-tight opacity-80"
|
|
704
|
+
>
|
|
705
|
+
{panel.signal}
|
|
706
|
+
</Badge>
|
|
707
|
+
</div>
|
|
708
|
+
</div>
|
|
709
|
+
</summary>
|
|
710
|
+
<div className="border-s-2 border-zinc-200 pb-6 ps-6">
|
|
711
|
+
<p className="max-w-[440px] text-[15px] leading-relaxed text-zinc-500">{panel.description}</p>
|
|
712
|
+
<div className="mt-4 flex items-center gap-2">
|
|
713
|
+
<span className="text-[12px] font-bold uppercase tracking-widest text-zinc-500">
|
|
714
|
+
{dict.docs.signal_source}
|
|
715
|
+
</span>
|
|
716
|
+
<code className="rounded-sm border border-zinc-100 bg-white px-2 py-0.5 font-mono text-[12px] text-zinc-600">
|
|
717
|
+
{panel.signal}
|
|
718
|
+
</code>
|
|
719
|
+
</div>
|
|
720
|
+
</div>
|
|
721
|
+
</details>
|
|
722
|
+
))}
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
</div>
|
|
726
|
+
</div>
|
|
727
|
+
<div className="flex flex-col gap-10 bg-zinc-50/20 p-8 lg:p-12">
|
|
728
|
+
<div className="flex flex-col gap-6">
|
|
729
|
+
<h4 className="text-[14px] font-bold uppercase tracking-wider text-zinc-900">
|
|
730
|
+
{dict.docs.framework_coverage}
|
|
731
|
+
</h4>
|
|
732
|
+
<div className="flex flex-wrap gap-2">
|
|
733
|
+
{frameworkCoverage.map((framework) => (
|
|
734
|
+
<Badge
|
|
735
|
+
key={framework}
|
|
736
|
+
variant="outline"
|
|
737
|
+
className="rounded-sm border-zinc-100 bg-white px-2 py-0.5 text-[12px] font-medium text-zinc-600"
|
|
738
|
+
>
|
|
739
|
+
{framework}
|
|
740
|
+
</Badge>
|
|
741
|
+
))}
|
|
742
|
+
</div>
|
|
743
|
+
</div>
|
|
744
|
+
<div className="flex flex-col gap-6">
|
|
745
|
+
<h4 className="text-[14px] font-bold uppercase tracking-wider text-zinc-900">
|
|
746
|
+
{dict.docs.testing_tools}
|
|
747
|
+
</h4>
|
|
748
|
+
<div className="flex flex-wrap gap-2">
|
|
749
|
+
{testingTools.map((tool) => (
|
|
750
|
+
<Badge
|
|
751
|
+
key={tool}
|
|
752
|
+
variant="secondary"
|
|
753
|
+
className="rounded-sm border-transparent bg-zinc-100 px-2 py-0.5 text-[12px] font-bold text-zinc-600"
|
|
754
|
+
>
|
|
755
|
+
{tool}
|
|
756
|
+
</Badge>
|
|
757
|
+
))}
|
|
758
|
+
</div>
|
|
759
|
+
</div>
|
|
760
|
+
</div>
|
|
761
|
+
</div>
|
|
762
|
+
</div>
|
|
763
|
+
</motion.div>
|
|
764
|
+
|
|
765
|
+
{/* Workflow Outputs */}
|
|
766
|
+
<motion.div
|
|
767
|
+
initial={{ opacity: 0, y: 20 }}
|
|
768
|
+
whileInView={{ opacity: 1, y: 0 }}
|
|
769
|
+
viewport={{ once: true }}
|
|
770
|
+
transition={{ duration: 0.8, delay: 0.1 }}
|
|
771
|
+
className="grid grid-cols-1 border-b border-e border-s border-zinc-100 lg:grid-cols-12"
|
|
772
|
+
>
|
|
773
|
+
<div className="group/artifacts border-e border-zinc-100 p-8 transition-colors duration-500 hover:bg-zinc-50/20 lg:col-span-8 lg:p-12">
|
|
774
|
+
<div className="flex flex-col gap-10">
|
|
775
|
+
<div className="flex flex-col gap-2">
|
|
776
|
+
<div className="flex items-center justify-between">
|
|
777
|
+
<span className="text-[12px] font-black uppercase tracking-[0.18em] text-zinc-500">
|
|
778
|
+
{dict.docs.section4_num}
|
|
779
|
+
</span>
|
|
780
|
+
<Terminal size={14} className="text-zinc-400 transition-colors group-hover/artifacts:text-zinc-500" />
|
|
781
|
+
</div>
|
|
782
|
+
<h3 className="text-[18px] font-bold text-zinc-900">{dict.docs.section3_title}</h3>
|
|
783
|
+
</div>
|
|
784
|
+
<div className="grid grid-cols-1 gap-x-16 gap-y-12 md:grid-cols-2">
|
|
785
|
+
{[
|
|
786
|
+
{ f: "ci.yml", l: dict.docs.workflows.ci.label, d: dict.docs.workflows.ci.desc },
|
|
787
|
+
{ f: "deploy.yml", l: dict.docs.workflows.deploy.label, d: dict.docs.workflows.deploy.desc },
|
|
788
|
+
{ f: "docker.yml", l: dict.docs.workflows.docker.label, d: dict.docs.workflows.docker.desc },
|
|
789
|
+
{ f: "security.yml", l: dict.docs.workflows.security.label, d: dict.docs.workflows.security.desc },
|
|
790
|
+
].map((wf) => (
|
|
791
|
+
<div key={wf.f} className="group/item flex flex-col gap-3">
|
|
792
|
+
<div className="flex flex-col gap-1">
|
|
793
|
+
<div className="flex items-center gap-3">
|
|
794
|
+
<code className="rounded-sm border border-zinc-100 px-2.5 py-1 text-[14px] font-black leading-none text-zinc-800 transition-colors group-hover/item:border-zinc-200">
|
|
795
|
+
{wf.f}
|
|
796
|
+
</code>
|
|
797
|
+
<div className="h-[1px] flex-1 bg-zinc-100 transition-colors group-hover/item:bg-zinc-200" />
|
|
798
|
+
</div>
|
|
799
|
+
<span className="text-[12px] font-bold uppercase tracking-tight text-zinc-500">{wf.l}</span>
|
|
800
|
+
</div>
|
|
801
|
+
<p className="text-[14px] text-zinc-500 transition-colors leading-relaxed group-hover/item:text-zinc-600">
|
|
802
|
+
{wf.d}
|
|
803
|
+
</p>
|
|
804
|
+
</div>
|
|
805
|
+
))}
|
|
806
|
+
</div>
|
|
807
|
+
</div>
|
|
808
|
+
</div>
|
|
809
|
+
|
|
810
|
+
<div className="flex flex-col lg:col-span-4">
|
|
811
|
+
<div className="group relative flex-1 overflow-hidden bg-zinc-950 p-8 text-white lg:p-12">
|
|
812
|
+
<div className="absolute end-0 top-0 -me-32 -mt-32 h-64 w-64 rounded-full bg-emerald-500/10 blur-[100px]" />
|
|
813
|
+
<div className="relative z-10 flex flex-col gap-8">
|
|
814
|
+
<div className="flex flex-col gap-2">
|
|
815
|
+
<span className="text-[12px] font-black uppercase tracking-[0.18em] text-zinc-500">
|
|
816
|
+
{dict.docs.security_requirement}
|
|
817
|
+
</span>
|
|
818
|
+
<h4 className="text-[18px] font-bold">{dict.docs.encrypted_secrets}</h4>
|
|
819
|
+
</div>
|
|
820
|
+
<div className="flex flex-col gap-1">
|
|
821
|
+
{["VERCEL_TOKEN", "AWS_ACCESS_KEY", "FIREBASE_TOKEN", "GHCR_TOKEN"].map((token) => (
|
|
822
|
+
<div
|
|
823
|
+
key={token}
|
|
824
|
+
className="group/token flex items-center justify-between border-b border-white/5 py-3 transition-colors last:border-0"
|
|
825
|
+
>
|
|
826
|
+
<code className="text-[13px] font-mono text-zinc-400 transition-colors group-hover/token:text-white">
|
|
827
|
+
{token}
|
|
828
|
+
</code>
|
|
829
|
+
<Shield size={12} className="text-zinc-700 transition-colors group-hover/token:text-emerald-500" />
|
|
830
|
+
</div>
|
|
831
|
+
))}
|
|
832
|
+
</div>
|
|
833
|
+
<div className="flex flex-col gap-3">
|
|
834
|
+
<p className="border-l-2 border-emerald-500/30 py-2 pl-4 text-[13px] italic leading-relaxed text-zinc-500">
|
|
835
|
+
{dict.docs.add_secrets_at} <br />
|
|
836
|
+
<span className="font-bold not-italic tracking-tight text-zinc-300">
|
|
837
|
+
Settings → Secrets and variables → Actions
|
|
838
|
+
</span>
|
|
839
|
+
</p>
|
|
840
|
+
</div>
|
|
841
|
+
</div>
|
|
842
|
+
</div>
|
|
843
|
+
</div>
|
|
844
|
+
</motion.div>
|
|
845
|
+
|
|
846
|
+
<motion.div
|
|
847
|
+
initial={{ opacity: 0, scale: 0.995 }}
|
|
848
|
+
whileInView={{ opacity: 1, scale: 1 }}
|
|
849
|
+
viewport={{ once: true }}
|
|
850
|
+
transition={{ duration: 0.8, delay: 0.2 }}
|
|
851
|
+
className="grid grid-cols-1 border-b border-l border-r border-zinc-100 bg-zinc-50/10 lg:grid-cols-12"
|
|
852
|
+
>
|
|
853
|
+
<div className="p-8 lg:col-span-12 lg:p-12">
|
|
854
|
+
<div className="flex flex-col gap-8">
|
|
855
|
+
<div className="flex flex-col gap-2">
|
|
856
|
+
<span className="text-[12px] font-black uppercase tracking-[0.18em] text-zinc-500">
|
|
857
|
+
{dict.docs.section4_num}
|
|
858
|
+
</span>
|
|
859
|
+
<h3 className="text-[18px] font-bold text-zinc-900">{dict.docs.section4_title}</h3>
|
|
860
|
+
</div>
|
|
861
|
+
<div className="grid grid-cols-1 gap-8 md:grid-cols-3">
|
|
862
|
+
{exampleStacks.map((example) => (
|
|
863
|
+
<div
|
|
864
|
+
key={example.name}
|
|
865
|
+
className="flex cursor-default flex-col gap-3 rounded-sm border border-zinc-200/60 bg-white p-6 transition-all hover:shadow-xl hover:shadow-zinc-200/40"
|
|
866
|
+
>
|
|
867
|
+
<span className="text-[14px] font-black text-zinc-900">{example.name}</span>
|
|
868
|
+
<p className="text-[13px] font-medium leading-relaxed text-zinc-500">{example.description}</p>
|
|
869
|
+
<div className="mt-2 flex items-center gap-1.5">
|
|
870
|
+
<div className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
|
|
871
|
+
<span className="font-mono text-[12px] font-bold uppercase tracking-widest text-zinc-500">
|
|
872
|
+
{dict.docs.validated_output}
|
|
873
|
+
</span>
|
|
874
|
+
</div>
|
|
875
|
+
</div>
|
|
876
|
+
))}
|
|
877
|
+
</div>
|
|
878
|
+
</div>
|
|
879
|
+
</div>
|
|
880
|
+
</motion.div>
|
|
881
|
+
</section>
|
|
882
|
+
|
|
883
|
+
<motion.footer
|
|
884
|
+
initial={{ opacity: 0 }}
|
|
885
|
+
whileInView={{ opacity: 1 }}
|
|
886
|
+
viewport={{ once: true }}
|
|
887
|
+
transition={{ duration: 1.2 }}
|
|
888
|
+
className="relative z-20 border-t border-zinc-100 bg-white"
|
|
889
|
+
>
|
|
890
|
+
<div className="mx-auto max-w-[1400px] border-l border-r border-zinc-100">
|
|
891
|
+
<div className="grid grid-cols-1 md:grid-cols-12">
|
|
892
|
+
<div className="flex flex-col gap-6 border-b border-zinc-100 p-8 md:col-span-4 md:border-b-0 md:border-r">
|
|
893
|
+
<div className="flex flex-col gap-1">
|
|
894
|
+
<span className="text-[12px] font-black uppercase tracking-[0.2em] text-zinc-500">
|
|
895
|
+
{dict.footer.project_origin}
|
|
896
|
+
</span>
|
|
897
|
+
<div className="flex items-center gap-2">
|
|
898
|
+
<span className="text-[18px] font-bold tracking-tighter text-zinc-900">cistack</span>
|
|
899
|
+
<span className="rounded-sm border border-zinc-100 bg-zinc-50 px-1.5 py-0.5 font-mono text-[12px] font-bold text-zinc-500">
|
|
900
|
+
{`V_${version} // PRODUCTION`}
|
|
901
|
+
</span>
|
|
902
|
+
</div>
|
|
903
|
+
</div>
|
|
904
|
+
<div className="flex flex-col gap-1">
|
|
905
|
+
<span className="text-[12px] font-black uppercase tracking-[0.2em] text-zinc-500">
|
|
906
|
+
{dict.footer.architected_by}
|
|
907
|
+
</span>
|
|
908
|
+
<a
|
|
909
|
+
href="https://www.edwinvakayil.info/"
|
|
910
|
+
target="_blank"
|
|
911
|
+
rel="noopener noreferrer"
|
|
912
|
+
className="group flex items-center gap-2 text-[14px] font-bold text-zinc-600 transition-colors hover:text-zinc-950"
|
|
913
|
+
>
|
|
914
|
+
Edwin Vakayil
|
|
915
|
+
<ArrowUpRight size={12} className="text-zinc-400 transition-colors group-hover:text-zinc-950" />
|
|
916
|
+
</a>
|
|
917
|
+
</div>
|
|
918
|
+
</div>
|
|
919
|
+
|
|
920
|
+
<div className="flex flex-col justify-between gap-8 border-b border-zinc-100 p-8 md:col-span-5 md:border-b-0 md:border-r">
|
|
921
|
+
<div className="flex flex-wrap gap-x-8 gap-y-4">
|
|
922
|
+
<div className="flex flex-col gap-1">
|
|
923
|
+
<span className="text-[12px] font-black uppercase tracking-[0.2em] text-zinc-500">
|
|
924
|
+
{dict.footer.status}
|
|
925
|
+
</span>
|
|
926
|
+
<div className="flex items-center gap-2">
|
|
927
|
+
<div className="h-1.5 w-1.5 animate-pulse rounded-full bg-emerald-500" />
|
|
928
|
+
<span className="text-[12px] font-bold text-zinc-700">{dict.footer.operational}</span>
|
|
929
|
+
</div>
|
|
930
|
+
</div>
|
|
931
|
+
<div className="flex flex-col gap-1">
|
|
932
|
+
<span className="text-[12px] font-black uppercase tracking-[0.2em] text-zinc-500">
|
|
933
|
+
{dict.footer.license}
|
|
934
|
+
</span>
|
|
935
|
+
<span className="text-[12px] font-bold text-zinc-700 uppercase">{dict.footer.open_source}</span>
|
|
936
|
+
</div>
|
|
937
|
+
</div>
|
|
938
|
+
<p className="text-[12px] font-medium leading-relaxed text-zinc-500">{dict.footer.footer_desc}</p>
|
|
939
|
+
</div>
|
|
940
|
+
|
|
941
|
+
<div className="flex flex-col items-start justify-center gap-3 bg-zinc-50/30 p-8 md:col-span-3 md:items-end">
|
|
942
|
+
<span className="text-[12px] font-black uppercase tracking-[0.2em] text-zinc-500">
|
|
943
|
+
{dict.footer.global_install}
|
|
944
|
+
</span>
|
|
945
|
+
<code className="cursor-default rounded-sm border border-zinc-100 bg-white px-3 py-1.5 font-mono text-[13px] font-bold text-zinc-800 transition-colors">
|
|
946
|
+
npm install -g cistack
|
|
947
|
+
</code>
|
|
948
|
+
</div>
|
|
949
|
+
</div>
|
|
950
|
+
|
|
951
|
+
<div className="flex flex-col items-center justify-between gap-4 border-t border-zinc-100 p-4 px-8 sm:flex-row">
|
|
952
|
+
<span className="font-mono text-[12px] font-bold uppercase tracking-widest text-zinc-500">
|
|
953
|
+
{`© ${currentYear} ${dict.footer.copyright}`}
|
|
954
|
+
</span>
|
|
955
|
+
<div className="flex items-center gap-6">
|
|
956
|
+
<a
|
|
957
|
+
href="https://github.com/edwinvakayil/cistack"
|
|
958
|
+
target="_blank"
|
|
959
|
+
rel="noopener noreferrer"
|
|
960
|
+
className="text-[12px] font-black uppercase tracking-widest text-zinc-500 transition-colors hover:text-zinc-950"
|
|
961
|
+
>
|
|
962
|
+
Github
|
|
963
|
+
</a>
|
|
964
|
+
<a
|
|
965
|
+
href="https://www.npmjs.com/package/cistack"
|
|
966
|
+
target="_blank"
|
|
967
|
+
rel="noopener noreferrer"
|
|
968
|
+
className="text-[12px] font-black uppercase tracking-widest text-zinc-500 transition-colors hover:text-zinc-950"
|
|
969
|
+
>
|
|
970
|
+
Npm
|
|
971
|
+
</a>
|
|
972
|
+
<a
|
|
973
|
+
href="#docs"
|
|
974
|
+
className="text-[12px] font-black uppercase tracking-widest text-zinc-500 transition-colors hover:text-zinc-950"
|
|
975
|
+
>
|
|
976
|
+
{dict.navigation.docs}
|
|
977
|
+
</a>
|
|
978
|
+
</div>
|
|
979
|
+
</div>
|
|
980
|
+
</div>
|
|
981
|
+
</motion.footer>
|
|
982
|
+
</div>
|
|
983
|
+
</>
|
|
984
|
+
);
|
|
985
|
+
}
|