cistack 6.1.0 → 6.2.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/package.json +1 -1
- package/product-site/.github/workflows/pipeline.yml +14 -1
- package/product-site/components/CopyButton.tsx +54 -38
- package/product-site/components/HomeClient.tsx +490 -811
- package/product-site/components/InstallToggle.tsx +70 -34
- package/product-site/components/TerminalCard.tsx +9 -5
- package/product-site/components/TerminalCardMotion.tsx +21 -14
- package/product-site/components/site-motion.tsx +229 -0
- package/product-site/dictionaries/br.json +246 -107
- package/product-site/dictionaries/cn.json +246 -107
- package/product-site/dictionaries/de.json +237 -98
- package/product-site/dictionaries/en.json +228 -91
- package/product-site/dictionaries/es.json +240 -146
- package/product-site/dictionaries/fr.json +237 -143
- package/product-site/dictionaries/pt.json +246 -107
- package/product-site/package.json +1 -0
- package/product-site/scripts/validate-i18n.mjs +45 -0
- package/src/index.js +12 -13
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import Link from "next/link";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
Box,
|
|
7
|
-
Check,
|
|
8
|
-
Globe,
|
|
9
|
-
Package,
|
|
10
|
-
Shield,
|
|
11
|
-
Terminal,
|
|
12
|
-
} from "lucide-react";
|
|
4
|
+
import { useReducedMotion } from "framer-motion";
|
|
5
|
+
import { Globe, Package, Terminal } from "lucide-react";
|
|
13
6
|
import { useEffect, useState } from "react";
|
|
14
7
|
|
|
15
8
|
import CopyButton from "@/components/CopyButton";
|
|
16
9
|
import InstallToggle from "@/components/InstallToggle";
|
|
17
10
|
import TerminalCard from "@/components/TerminalCard";
|
|
18
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
Accordion,
|
|
13
|
+
AccordionContent,
|
|
14
|
+
AccordionItem,
|
|
15
|
+
AccordionTrigger,
|
|
16
|
+
} from "@/components/ui/accordion";
|
|
17
|
+
import {
|
|
18
|
+
HeroStagger,
|
|
19
|
+
HeroStaggerItem,
|
|
20
|
+
MotionHeader,
|
|
21
|
+
MotionTagList,
|
|
22
|
+
Reveal,
|
|
23
|
+
SiteMotionRoot,
|
|
24
|
+
StaggerItem,
|
|
25
|
+
StaggerList,
|
|
26
|
+
m,
|
|
27
|
+
scrollViewport,
|
|
28
|
+
SITE_EASE,
|
|
29
|
+
} from "@/components/site-motion";
|
|
30
|
+
import { Separator } from "@/components/ui/separator";
|
|
19
31
|
import type { Dictionary } from "@/lib/dictionary-types";
|
|
20
|
-
import { motion } from "framer-motion";
|
|
21
32
|
|
|
22
33
|
interface GithubIconProps {
|
|
23
34
|
size?: number;
|
|
@@ -42,158 +53,6 @@ const localeOptions = [
|
|
|
42
53
|
{ code: "cn", label: "简体中文" },
|
|
43
54
|
] as const;
|
|
44
55
|
|
|
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
56
|
const GithubIcon = ({ size = 24, className = "" }: GithubIconProps) => (
|
|
198
57
|
<svg
|
|
199
58
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -211,6 +70,114 @@ const GithubIcon = ({ size = 24, className = "" }: GithubIconProps) => (
|
|
|
211
70
|
</svg>
|
|
212
71
|
);
|
|
213
72
|
|
|
73
|
+
function SectionKicker({ children }: { children: React.ReactNode }) {
|
|
74
|
+
return (
|
|
75
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-zinc-400">{children}</p>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function SectionTitle({ children, className = "" }: { children: React.ReactNode; className?: string }) {
|
|
80
|
+
return (
|
|
81
|
+
<h2 className={`mt-1.5 text-xl font-semibold tracking-tight text-zinc-950 sm:text-2xl ${className}`}>
|
|
82
|
+
{children}
|
|
83
|
+
</h2>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function SnippetStack({
|
|
88
|
+
snippets,
|
|
89
|
+
copyLabels,
|
|
90
|
+
}: {
|
|
91
|
+
snippets: readonly string[];
|
|
92
|
+
copyLabels?: { idle: string; success: string };
|
|
93
|
+
}) {
|
|
94
|
+
const reduce = useReducedMotion();
|
|
95
|
+
if (snippets.length === 0) return null;
|
|
96
|
+
const preClass =
|
|
97
|
+
"min-w-0 flex-1 overflow-x-auto p-2.5 font-mono text-[11px] leading-relaxed text-zinc-900 whitespace-pre-wrap sm:text-xs";
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div className="border border-zinc-200 bg-white">
|
|
101
|
+
{snippets.map((line, i) => (
|
|
102
|
+
<div key={`${line.slice(0, 48)}-${i}`}>
|
|
103
|
+
{i > 0 && <Separator className="bg-zinc-200" />}
|
|
104
|
+
<div className="flex min-h-11 items-stretch">
|
|
105
|
+
{reduce ? (
|
|
106
|
+
<pre className={preClass}>
|
|
107
|
+
<code>{line}</code>
|
|
108
|
+
</pre>
|
|
109
|
+
) : (
|
|
110
|
+
<m.pre
|
|
111
|
+
className={preClass}
|
|
112
|
+
initial={{ opacity: 0, y: 8 }}
|
|
113
|
+
whileInView={{ opacity: 1, y: 0 }}
|
|
114
|
+
viewport={scrollViewport}
|
|
115
|
+
transition={{ duration: 0.38, ease: SITE_EASE, delay: i * 0.06 }}
|
|
116
|
+
>
|
|
117
|
+
<code>{line}</code>
|
|
118
|
+
</m.pre>
|
|
119
|
+
)}
|
|
120
|
+
{copyLabels ? (
|
|
121
|
+
<>
|
|
122
|
+
<Separator orientation="vertical" className="h-auto bg-zinc-200" />
|
|
123
|
+
<div className="flex shrink-0 items-center px-1">
|
|
124
|
+
<CopyButton
|
|
125
|
+
text={line}
|
|
126
|
+
idleLabel={copyLabels.idle}
|
|
127
|
+
successLabel={copyLabels.success}
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
</>
|
|
131
|
+
) : null}
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
))}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function InstallCodeBlock({
|
|
140
|
+
command,
|
|
141
|
+
idleLabel,
|
|
142
|
+
successLabel,
|
|
143
|
+
}: {
|
|
144
|
+
command: string;
|
|
145
|
+
idleLabel: string;
|
|
146
|
+
successLabel: string;
|
|
147
|
+
}) {
|
|
148
|
+
const reduce = useReducedMotion();
|
|
149
|
+
const inner = (
|
|
150
|
+
<div className="flex min-h-12 items-stretch border border-zinc-200 bg-white">
|
|
151
|
+
<pre className="flex flex-1 items-center overflow-x-auto p-3 font-mono text-[13px] leading-snug text-zinc-900">
|
|
152
|
+
<code>{command}</code>
|
|
153
|
+
</pre>
|
|
154
|
+
<Separator orientation="vertical" className="bg-zinc-200" />
|
|
155
|
+
<div className="flex shrink-0 items-center px-3">
|
|
156
|
+
<CopyButton text={command} idleLabel={idleLabel} successLabel={successLabel} />
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
if (reduce) {
|
|
161
|
+
return inner;
|
|
162
|
+
}
|
|
163
|
+
return (
|
|
164
|
+
<m.div
|
|
165
|
+
initial={{ opacity: 0, scale: 0.985 }}
|
|
166
|
+
whileInView={{ opacity: 1, scale: 1 }}
|
|
167
|
+
viewport={scrollViewport}
|
|
168
|
+
transition={{ duration: 0.45, ease: SITE_EASE }}
|
|
169
|
+
>
|
|
170
|
+
{inner}
|
|
171
|
+
</m.div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Bento rows: items-start prevents short columns (e.g. Detection) stretching to match a tall neighbor. */
|
|
176
|
+
const pad = "p-5 sm:p-6 lg:px-8 lg:py-6";
|
|
177
|
+
const bentoRow = "grid grid-cols-1 border-b border-zinc-200 lg:grid-cols-12 lg:items-start";
|
|
178
|
+
const colLeft = `${pad} border-b border-zinc-200 lg:border-b-0 lg:border-e lg:border-zinc-200`;
|
|
179
|
+
const colRight = pad;
|
|
180
|
+
|
|
214
181
|
export default function HomeClient({
|
|
215
182
|
dict,
|
|
216
183
|
lang,
|
|
@@ -220,6 +187,7 @@ export default function HomeClient({
|
|
|
220
187
|
}) {
|
|
221
188
|
const [version, setVersion] = useState("3.0.0");
|
|
222
189
|
const [downloads, setDownloads] = useState("2.4k");
|
|
190
|
+
const reduceMotion = useReducedMotion();
|
|
223
191
|
|
|
224
192
|
useEffect(() => {
|
|
225
193
|
let cancelled = false;
|
|
@@ -240,7 +208,9 @@ export default function HomeClient({
|
|
|
240
208
|
const data = (await downloadsRes.json()) as DownloadStatsResponse;
|
|
241
209
|
if (!cancelled && typeof data.downloads === "number") {
|
|
242
210
|
const count = data.downloads;
|
|
243
|
-
setDownloads(
|
|
211
|
+
setDownloads(
|
|
212
|
+
count >= 1000 ? `${(count / 1000).toFixed(1)}k` : count.toLocaleString()
|
|
213
|
+
);
|
|
244
214
|
}
|
|
245
215
|
}
|
|
246
216
|
} catch (e) {
|
|
@@ -249,12 +219,12 @@ export default function HomeClient({
|
|
|
249
219
|
};
|
|
250
220
|
|
|
251
221
|
void loadStats();
|
|
252
|
-
return () => {
|
|
222
|
+
return () => {
|
|
223
|
+
cancelled = true;
|
|
224
|
+
};
|
|
253
225
|
}, []);
|
|
254
226
|
|
|
255
|
-
const
|
|
256
|
-
const manifestStamp = `${now.getFullYear()}_${(now.getMonth() + 1).toString().padStart(2, "0")}`;
|
|
257
|
-
const currentYear = now.getFullYear();
|
|
227
|
+
const currentYear = new Date().getFullYear();
|
|
258
228
|
|
|
259
229
|
return (
|
|
260
230
|
<>
|
|
@@ -274,114 +244,89 @@ export default function HomeClient({
|
|
|
274
244
|
priceCurrency: "USD",
|
|
275
245
|
availability: "https://schema.org/InStock",
|
|
276
246
|
},
|
|
277
|
-
description: dict.hero.
|
|
247
|
+
description: `${dict.hero.tagline} ${dict.hero.intro}`,
|
|
278
248
|
creator: {
|
|
279
249
|
"@type": "Person",
|
|
280
250
|
name: "Edwin Vakayil",
|
|
281
251
|
url: "https://www.edwinvakayil.info/",
|
|
282
252
|
},
|
|
283
|
-
featureList:
|
|
284
|
-
keywords:
|
|
253
|
+
featureList: dict.why.items,
|
|
254
|
+
keywords:
|
|
255
|
+
"github actions, automation, ci/cd, devops, workflow generator, docker, vercel, aws, firebase",
|
|
285
256
|
}),
|
|
286
257
|
}}
|
|
287
258
|
/>
|
|
288
|
-
<div
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
<
|
|
304
|
-
|
|
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}
|
|
259
|
+
<div className="min-h-screen bg-white text-zinc-900 antialiased selection:bg-zinc-900 selection:text-white">
|
|
260
|
+
<SiteMotionRoot>
|
|
261
|
+
<MotionHeader className="sticky top-0 z-50 border-b border-zinc-200 bg-white/95 backdrop-blur-sm">
|
|
262
|
+
<div className="mx-auto flex max-w-6xl flex-col gap-4 px-4 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-6 lg:px-8">
|
|
263
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
264
|
+
<Link
|
|
265
|
+
href={`/${lang}`}
|
|
266
|
+
className="flex items-center gap-2 text-lg font-semibold tracking-tight text-zinc-950"
|
|
267
|
+
>
|
|
268
|
+
<Terminal className="h-5 w-5 text-zinc-500" aria-hidden />
|
|
269
|
+
{dict.hero.product_name}
|
|
270
|
+
</Link>
|
|
271
|
+
<span className="border border-zinc-200 px-2 py-0.5 font-mono text-xs font-medium text-zinc-600">
|
|
272
|
+
{dict.navigation.version} {version}
|
|
273
|
+
</span>
|
|
274
|
+
<span className="text-xs font-medium uppercase tracking-wider text-zinc-400">
|
|
275
|
+
{dict.navigation.status}
|
|
309
276
|
</span>
|
|
310
277
|
</div>
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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">
|
|
278
|
+
<nav className="flex flex-wrap items-center gap-4 text-sm font-medium text-zinc-600">
|
|
279
|
+
<a
|
|
280
|
+
href="https://github.com/edwinvakayil/cistack"
|
|
281
|
+
target="_blank"
|
|
282
|
+
rel="noopener noreferrer"
|
|
283
|
+
className="inline-flex items-center gap-1.5 transition-colors hover:text-zinc-950"
|
|
284
|
+
>
|
|
285
|
+
<GithubIcon size={16} className="opacity-70" />
|
|
286
|
+
{dict.navigation.repository}
|
|
287
|
+
</a>
|
|
288
|
+
<a
|
|
289
|
+
href="https://www.npmjs.com/package/cistack"
|
|
290
|
+
target="_blank"
|
|
291
|
+
rel="noopener noreferrer"
|
|
292
|
+
className="inline-flex items-center gap-1.5 transition-colors hover:text-zinc-950"
|
|
293
|
+
>
|
|
294
|
+
<Package size={16} className="opacity-70" />
|
|
295
|
+
{dict.navigation.registry}
|
|
296
|
+
</a>
|
|
297
|
+
<a href="#reference" className="transition-colors hover:text-zinc-950">
|
|
298
|
+
{dict.navigation.reference}
|
|
299
|
+
</a>
|
|
300
|
+
<div className="flex items-center gap-2 border-s border-zinc-200 ps-4">
|
|
349
301
|
<Link
|
|
350
302
|
href="/en"
|
|
351
|
-
className={`
|
|
303
|
+
className={`border px-2 py-1 text-xs font-semibold uppercase tracking-wide transition-colors ${
|
|
352
304
|
lang === "en"
|
|
353
|
-
? "border-zinc-
|
|
354
|
-
: "border-transparent text-zinc-500 hover:
|
|
305
|
+
? "border-zinc-900 bg-zinc-900 text-white"
|
|
306
|
+
: "border-transparent text-zinc-500 hover:text-zinc-900"
|
|
355
307
|
}`}
|
|
356
308
|
>
|
|
357
309
|
EN
|
|
358
310
|
</Link>
|
|
359
311
|
{lang !== "en" && (
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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"
|
|
312
|
+
<Link
|
|
313
|
+
href={`/${lang}`}
|
|
314
|
+
className="border border-zinc-900 bg-zinc-900 px-2 py-1 text-xs font-semibold uppercase tracking-wide text-white"
|
|
375
315
|
>
|
|
376
|
-
|
|
316
|
+
{lang.toUpperCase()}
|
|
317
|
+
</Link>
|
|
318
|
+
)}
|
|
319
|
+
<details className="relative">
|
|
320
|
+
<summary className="flex cursor-pointer list-none items-center gap-1 py-1 text-xs font-semibold uppercase tracking-wide text-zinc-600 hover:text-zinc-950 [&::-webkit-details-marker]:hidden">
|
|
321
|
+
<Globe size={14} aria-hidden />
|
|
377
322
|
Lang
|
|
378
323
|
</summary>
|
|
379
|
-
<div className="absolute
|
|
324
|
+
<div className="absolute inset-e-0 top-full z-100 mt-2 flex min-w-36 flex-col gap-0.5 border border-zinc-200 bg-white p-1 shadow-lg">
|
|
380
325
|
{localeOptions.map((locale) => (
|
|
381
326
|
<Link
|
|
382
327
|
key={locale.code}
|
|
383
328
|
href={`/${locale.code}`}
|
|
384
|
-
className="
|
|
329
|
+
className="px-3 py-2 text-xs font-medium text-zinc-700 hover:bg-zinc-50"
|
|
385
330
|
>
|
|
386
331
|
{locale.label}
|
|
387
332
|
</Link>
|
|
@@ -389,596 +334,330 @@ export default function HomeClient({
|
|
|
389
334
|
</div>
|
|
390
335
|
</details>
|
|
391
336
|
</div>
|
|
392
|
-
</
|
|
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>
|
|
337
|
+
</nav>
|
|
405
338
|
</div>
|
|
406
|
-
</
|
|
407
|
-
|
|
408
|
-
<main className="
|
|
409
|
-
<div className="
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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>
|
|
339
|
+
</MotionHeader>
|
|
340
|
+
|
|
341
|
+
<main className="mx-auto max-w-6xl px-4 pb-14 pt-8 sm:px-6 lg:px-8">
|
|
342
|
+
<div className="border border-zinc-200 bg-white">
|
|
343
|
+
{/* Hero + metrics */}
|
|
344
|
+
<Reveal className={bentoRow} y={22}>
|
|
345
|
+
<div className={`${colLeft} lg:col-span-8`}>
|
|
346
|
+
<HeroStagger>
|
|
347
|
+
<HeroStaggerItem>
|
|
348
|
+
<SectionKicker>{dict.hero.live_registry}</SectionKicker>
|
|
349
|
+
</HeroStaggerItem>
|
|
350
|
+
<HeroStaggerItem>
|
|
351
|
+
<SectionTitle>{dict.hero.tagline}</SectionTitle>
|
|
352
|
+
</HeroStaggerItem>
|
|
353
|
+
<HeroStaggerItem>
|
|
354
|
+
<Separator className="my-4 bg-zinc-200" />
|
|
355
|
+
</HeroStaggerItem>
|
|
356
|
+
<HeroStaggerItem>
|
|
357
|
+
<p className="max-w-2xl text-pretty text-sm leading-relaxed text-zinc-600 sm:text-base">
|
|
358
|
+
{dict.hero.intro}
|
|
359
|
+
</p>
|
|
360
|
+
</HeroStaggerItem>
|
|
361
|
+
</HeroStagger>
|
|
498
362
|
</div>
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
<
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
<
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
363
|
+
{reduceMotion ? (
|
|
364
|
+
<div className={`${colRight} lg:col-span-4`}>
|
|
365
|
+
<SectionKicker>{dict.hero.weekly_downloads}</SectionKicker>
|
|
366
|
+
<p className="mt-2 text-3xl font-semibold tracking-tight text-zinc-950">{downloads}</p>
|
|
367
|
+
<p className="mt-0.5 text-sm text-zinc-500">{dict.hero.per_week}</p>
|
|
368
|
+
<Separator className="my-4 bg-zinc-200" />
|
|
369
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-zinc-400">
|
|
370
|
+
{dict.install.quick_command}
|
|
371
|
+
</p>
|
|
372
|
+
<div className="mt-2">
|
|
373
|
+
<InstallCodeBlock
|
|
374
|
+
command={dict.hero.npx_command}
|
|
375
|
+
idleLabel={dict.copy_button.idle}
|
|
376
|
+
successLabel={dict.copy_button.success}
|
|
377
|
+
/>
|
|
510
378
|
</div>
|
|
511
379
|
</div>
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
380
|
+
) : (
|
|
381
|
+
<m.div
|
|
382
|
+
className={`${colRight} lg:col-span-4`}
|
|
383
|
+
initial={{ opacity: 0, y: 26, x: 12 }}
|
|
384
|
+
animate={{ opacity: 1, y: 0, x: 0 }}
|
|
385
|
+
transition={{ duration: 0.58, ease: SITE_EASE, delay: 0.18 }}
|
|
386
|
+
>
|
|
387
|
+
<SectionKicker>{dict.hero.weekly_downloads}</SectionKicker>
|
|
388
|
+
<p className="mt-2 text-3xl font-semibold tracking-tight text-zinc-950">{downloads}</p>
|
|
389
|
+
<p className="mt-0.5 text-sm text-zinc-500">{dict.hero.per_week}</p>
|
|
390
|
+
<Separator className="my-4 bg-zinc-200" />
|
|
391
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-zinc-400">
|
|
392
|
+
{dict.install.quick_command}
|
|
393
|
+
</p>
|
|
394
|
+
<div className="mt-2">
|
|
395
|
+
<InstallCodeBlock
|
|
396
|
+
command={dict.hero.npx_command}
|
|
397
|
+
idleLabel={dict.copy_button.idle}
|
|
398
|
+
successLabel={dict.copy_button.success}
|
|
399
|
+
/>
|
|
519
400
|
</div>
|
|
520
|
-
</div>
|
|
521
|
-
|
|
522
|
-
</
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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}
|
|
401
|
+
</m.div>
|
|
402
|
+
)}
|
|
403
|
+
</Reveal>
|
|
404
|
+
|
|
405
|
+
{/* Install + preview */}
|
|
406
|
+
<Reveal className={bentoRow} delay={0.04} y={20}>
|
|
407
|
+
<div className={`${colLeft} lg:col-span-5`}>
|
|
408
|
+
<SectionTitle className="mb-3">{dict.install.title}</SectionTitle>
|
|
409
|
+
<InstallToggle dict={dict} />
|
|
410
|
+
<Separator className="my-4 bg-zinc-200" />
|
|
411
|
+
<p className="text-sm leading-relaxed text-zinc-600">{dict.install.node_note}</p>
|
|
560
412
|
</div>
|
|
561
|
-
<div className=
|
|
562
|
-
<
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
<
|
|
413
|
+
<div className={`${colRight} lg:col-span-7`}>
|
|
414
|
+
<SectionTitle className="mb-1">{dict.preview.title}</SectionTitle>
|
|
415
|
+
<p className="text-sm text-zinc-500">{dict.preview.caption}</p>
|
|
416
|
+
<Separator className="my-4 bg-zinc-200" />
|
|
417
|
+
<div className="min-h-[260px] sm:min-h-[300px] lg:min-h-[320px]">
|
|
418
|
+
<TerminalCard
|
|
419
|
+
dict={dict.terminal}
|
|
420
|
+
version={version}
|
|
421
|
+
copyLabels={dict.copy_button}
|
|
422
|
+
/>
|
|
567
423
|
</div>
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
</
|
|
582
|
-
</
|
|
424
|
+
</div>
|
|
425
|
+
</Reveal>
|
|
426
|
+
|
|
427
|
+
{/* Why */}
|
|
428
|
+
<Reveal className={bentoRow} delay={0.06} y={20}>
|
|
429
|
+
<div id="reference" className={`${pad} scroll-mt-24 lg:col-span-12`}>
|
|
430
|
+
<SectionKicker>{dict.navigation.reference}</SectionKicker>
|
|
431
|
+
<SectionTitle>{dict.why.title}</SectionTitle>
|
|
432
|
+
<Separator className="my-4 bg-zinc-200" />
|
|
433
|
+
<StaggerList className="grid gap-2.5 text-sm leading-snug text-zinc-700 sm:grid-cols-2 sm:gap-x-8 sm:gap-y-2">
|
|
434
|
+
{dict.why.items.map((item) => (
|
|
435
|
+
<StaggerItem key={item} className="flex gap-2.5">
|
|
436
|
+
<span className="mt-1.5 h-1 w-1 shrink-0 bg-zinc-400" aria-hidden />
|
|
437
|
+
<span>{item}</span>
|
|
438
|
+
</StaggerItem>
|
|
583
439
|
))}
|
|
584
|
-
</
|
|
440
|
+
</StaggerList>
|
|
585
441
|
</div>
|
|
586
|
-
</
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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>
|
|
442
|
+
</Reveal>
|
|
443
|
+
|
|
444
|
+
{/* CLI + Generated */}
|
|
445
|
+
<Reveal className={bentoRow} delay={0.08} y={18}>
|
|
446
|
+
<div className={`${colLeft} lg:col-span-6`}>
|
|
447
|
+
<SectionTitle className="mb-3">{dict.cli.section_title}</SectionTitle>
|
|
448
|
+
<Accordion multiple className="w-full border-t border-zinc-200">
|
|
449
|
+
{dict.cli.items.map((item, i) => (
|
|
450
|
+
<AccordionItem key={item.title} value={`cli-${i}`} className="border-zinc-200">
|
|
451
|
+
<AccordionTrigger className="py-3 text-left text-sm font-semibold text-zinc-900 hover:no-underline">
|
|
452
|
+
{item.title}
|
|
453
|
+
</AccordionTrigger>
|
|
454
|
+
<AccordionContent className="space-y-3 pb-4 text-zinc-600">
|
|
455
|
+
{item.paragraphs.map((p) => (
|
|
456
|
+
<p key={p} className="text-sm leading-relaxed">
|
|
457
|
+
{p}
|
|
458
|
+
</p>
|
|
619
459
|
))}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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>
|
|
460
|
+
<SnippetStack snippets={item.snippets} copyLabels={dict.copy_button} />
|
|
461
|
+
</AccordionContent>
|
|
462
|
+
</AccordionItem>
|
|
463
|
+
))}
|
|
464
|
+
</Accordion>
|
|
663
465
|
</div>
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
<
|
|
667
|
-
|
|
668
|
-
<
|
|
669
|
-
<
|
|
670
|
-
{
|
|
671
|
-
</
|
|
672
|
-
<
|
|
673
|
-
{
|
|
674
|
-
|
|
675
|
-
|
|
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>
|
|
466
|
+
<div className={`${colRight} lg:col-span-6`}>
|
|
467
|
+
<SectionTitle className="mb-3">{dict.generated.section_title}</SectionTitle>
|
|
468
|
+
<Accordion multiple className="w-full border-t border-zinc-200">
|
|
469
|
+
{dict.generated.items.map((item, i) => (
|
|
470
|
+
<AccordionItem key={item.title} value={`gen-${i}`} className="border-zinc-200">
|
|
471
|
+
<AccordionTrigger className="py-3 text-left text-sm font-semibold text-zinc-900 hover:no-underline">
|
|
472
|
+
{item.title}
|
|
473
|
+
</AccordionTrigger>
|
|
474
|
+
<AccordionContent className="space-y-3 pb-4 text-zinc-600">
|
|
475
|
+
{item.paragraphs.map((p) => (
|
|
476
|
+
<p key={p} className="text-sm leading-relaxed">
|
|
477
|
+
{p}
|
|
478
|
+
</p>
|
|
722
479
|
))}
|
|
480
|
+
<SnippetStack snippets={item.snippets} copyLabels={dict.copy_button} />
|
|
481
|
+
</AccordionContent>
|
|
482
|
+
</AccordionItem>
|
|
483
|
+
))}
|
|
484
|
+
</Accordion>
|
|
485
|
+
</div>
|
|
486
|
+
</Reveal>
|
|
487
|
+
|
|
488
|
+
{/* Detection + Configuration */}
|
|
489
|
+
<Reveal className={bentoRow} delay={0.1} y={18}>
|
|
490
|
+
<div className={`${colLeft} lg:col-span-6`}>
|
|
491
|
+
<SectionTitle className="mb-3">{dict.detection.section_title}</SectionTitle>
|
|
492
|
+
<Accordion multiple defaultValue={["hosting"]} className="w-full border-t border-zinc-200">
|
|
493
|
+
<AccordionItem value="hosting" className="border-zinc-200">
|
|
494
|
+
<AccordionTrigger className="py-3 text-left text-sm font-semibold text-zinc-900 hover:no-underline">
|
|
495
|
+
{dict.detection.hosting_title}
|
|
496
|
+
</AccordionTrigger>
|
|
497
|
+
<AccordionContent className="space-y-4 pb-4 text-zinc-600">
|
|
498
|
+
<MotionTagList tags={dict.detection.hosting_tags} />
|
|
499
|
+
<Separator className="bg-zinc-200" />
|
|
500
|
+
<div>
|
|
501
|
+
<h3 className="text-sm font-semibold text-zinc-900">
|
|
502
|
+
{dict.configuration.keys_title}
|
|
503
|
+
</h3>
|
|
504
|
+
<StaggerList className="mt-2 grid gap-1.5 sm:grid-cols-2">
|
|
505
|
+
{dict.configuration.keys.map((key) => (
|
|
506
|
+
<StaggerItem key={key} className="font-mono text-xs text-zinc-700">
|
|
507
|
+
{key}
|
|
508
|
+
</StaggerItem>
|
|
509
|
+
))}
|
|
510
|
+
</StaggerList>
|
|
723
511
|
</div>
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
>
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
512
|
+
<Separator className="bg-zinc-200" />
|
|
513
|
+
<div>
|
|
514
|
+
<h3 className="text-sm font-semibold text-zinc-900">
|
|
515
|
+
{dict.configuration.branches_title}
|
|
516
|
+
</h3>
|
|
517
|
+
<StaggerList className="mt-2 space-y-1.5 text-sm leading-snug text-zinc-600">
|
|
518
|
+
{dict.configuration.branches.map((line) => (
|
|
519
|
+
<StaggerItem key={line} className="flex gap-2">
|
|
520
|
+
<span className="text-zinc-400" aria-hidden>
|
|
521
|
+
—
|
|
522
|
+
</span>
|
|
523
|
+
{line}
|
|
524
|
+
</StaggerItem>
|
|
525
|
+
))}
|
|
526
|
+
</StaggerList>
|
|
527
|
+
</div>
|
|
528
|
+
</AccordionContent>
|
|
529
|
+
</AccordionItem>
|
|
530
|
+
<AccordionItem value="frameworks" className="border-zinc-200">
|
|
531
|
+
<AccordionTrigger className="py-3 text-left text-sm font-semibold text-zinc-900 hover:no-underline">
|
|
532
|
+
{dict.detection.frameworks_title}
|
|
533
|
+
</AccordionTrigger>
|
|
534
|
+
<AccordionContent className="pb-4">
|
|
535
|
+
<MotionTagList tags={dict.detection.frameworks_tags} />
|
|
536
|
+
</AccordionContent>
|
|
537
|
+
</AccordionItem>
|
|
538
|
+
<AccordionItem value="testing" className="border-zinc-200">
|
|
539
|
+
<AccordionTrigger className="py-3 text-left text-sm font-semibold text-zinc-900 hover:no-underline">
|
|
540
|
+
{dict.detection.testing_title}
|
|
541
|
+
</AccordionTrigger>
|
|
542
|
+
<AccordionContent className="pb-4">
|
|
543
|
+
<MotionTagList tags={dict.detection.testing_tags} />
|
|
544
|
+
</AccordionContent>
|
|
545
|
+
</AccordionItem>
|
|
546
|
+
</Accordion>
|
|
547
|
+
</div>
|
|
548
|
+
<div className={`${colRight} lg:col-span-6`}>
|
|
549
|
+
<SectionTitle className="mb-3">{dict.configuration.section_title}</SectionTitle>
|
|
550
|
+
<p className="text-sm leading-relaxed text-zinc-600">{dict.configuration.intro}</p>
|
|
551
|
+
<Separator className="my-4 bg-zinc-200" />
|
|
552
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-zinc-400">
|
|
553
|
+
{dict.configuration.example_caption}
|
|
554
|
+
</p>
|
|
555
|
+
<div className="mt-1.5">
|
|
556
|
+
<div className="flex min-h-12 items-stretch border border-zinc-200 bg-white">
|
|
557
|
+
{reduceMotion ? (
|
|
558
|
+
<pre className="min-w-0 flex-1 overflow-x-auto p-3 font-mono text-[11px] leading-snug text-zinc-900 whitespace-pre-wrap sm:text-xs">
|
|
559
|
+
<code>{dict.configuration.config_snippet}</code>
|
|
560
|
+
</pre>
|
|
561
|
+
) : (
|
|
562
|
+
<m.pre
|
|
563
|
+
className="min-w-0 flex-1 overflow-x-auto p-3 font-mono text-[11px] leading-snug text-zinc-900 whitespace-pre-wrap sm:text-xs"
|
|
564
|
+
initial={{ opacity: 0, y: 14, scale: 0.99 }}
|
|
565
|
+
whileInView={{ opacity: 1, y: 0, scale: 1 }}
|
|
566
|
+
viewport={scrollViewport}
|
|
567
|
+
transition={{ duration: 0.55, ease: SITE_EASE }}
|
|
568
|
+
>
|
|
569
|
+
<code>{dict.configuration.config_snippet}</code>
|
|
570
|
+
</m.pre>
|
|
571
|
+
)}
|
|
572
|
+
<Separator orientation="vertical" className="h-auto bg-zinc-200" />
|
|
573
|
+
<div className="flex shrink-0 items-center px-1">
|
|
574
|
+
<CopyButton
|
|
575
|
+
text={dict.configuration.config_snippet}
|
|
576
|
+
idleLabel={dict.copy_button.idle}
|
|
577
|
+
successLabel={dict.copy_button.success}
|
|
578
|
+
/>
|
|
758
579
|
</div>
|
|
759
580
|
</div>
|
|
760
581
|
</div>
|
|
761
582
|
</div>
|
|
762
|
-
</
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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" />
|
|
583
|
+
</Reveal>
|
|
584
|
+
|
|
585
|
+
{/* Secrets + local checks (left) | Quality (right) */}
|
|
586
|
+
<Reveal className={`${bentoRow} border-b-0`} delay={0.12} y={16}>
|
|
587
|
+
<div className={`${colLeft} lg:col-span-6`}>
|
|
588
|
+
<SectionTitle className="mb-3">{dict.secrets.section_title}</SectionTitle>
|
|
589
|
+
<Separator className="mb-4 bg-zinc-200" />
|
|
590
|
+
<p className="text-sm leading-relaxed text-zinc-600">{dict.secrets.body}</p>
|
|
591
|
+
<Separator className="my-4 bg-zinc-200" />
|
|
592
|
+
<h3 className="text-sm font-semibold text-zinc-900">{dict.quality.commands_title}</h3>
|
|
593
|
+
<div className="mt-2 border border-zinc-200 bg-white">
|
|
594
|
+
{dict.quality.commands.map((cmd, i) => (
|
|
595
|
+
<div key={cmd}>
|
|
596
|
+
{i > 0 && <Separator className="bg-zinc-200" />}
|
|
597
|
+
<div className="flex min-h-11 items-stretch">
|
|
598
|
+
<pre className="min-w-0 flex-1 overflow-x-auto p-2.5 font-mono text-[11px] text-zinc-800 sm:text-xs">
|
|
599
|
+
<code>{cmd}</code>
|
|
600
|
+
</pre>
|
|
601
|
+
<Separator orientation="vertical" className="h-auto bg-zinc-200" />
|
|
602
|
+
<div className="flex shrink-0 items-center px-1">
|
|
603
|
+
<CopyButton
|
|
604
|
+
text={cmd}
|
|
605
|
+
idleLabel={dict.copy_button.idle}
|
|
606
|
+
successLabel={dict.copy_button.success}
|
|
607
|
+
/>
|
|
798
608
|
</div>
|
|
799
|
-
<span className="text-[12px] font-bold uppercase tracking-tight text-zinc-500">{wf.l}</span>
|
|
800
609
|
</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
610
|
</div>
|
|
805
611
|
))}
|
|
806
612
|
</div>
|
|
613
|
+
<p className="mt-3 text-sm leading-relaxed text-zinc-500">{dict.quality.repo_note}</p>
|
|
807
614
|
</div>
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
<
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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
|
|
615
|
+
<div className={`${colRight} lg:col-span-6`}>
|
|
616
|
+
<SectionTitle className="mb-3">{dict.quality.section_title}</SectionTitle>
|
|
617
|
+
<p className="text-sm leading-snug text-zinc-600">{dict.quality.intro}</p>
|
|
618
|
+
<Separator className="my-4 bg-zinc-200" />
|
|
619
|
+
<StaggerList className="space-y-1.5 text-sm leading-snug text-zinc-700">
|
|
620
|
+
{dict.quality.items.map((item) => (
|
|
621
|
+
<StaggerItem key={item} className="flex gap-2">
|
|
622
|
+
<span className="text-zinc-400" aria-hidden>
|
|
623
|
+
·
|
|
838
624
|
</span>
|
|
839
|
-
|
|
840
|
-
|
|
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>
|
|
625
|
+
{item}
|
|
626
|
+
</StaggerItem>
|
|
876
627
|
))}
|
|
877
|
-
</
|
|
628
|
+
</StaggerList>
|
|
878
629
|
</div>
|
|
879
|
-
</
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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>
|
|
630
|
+
</Reveal>
|
|
631
|
+
|
|
632
|
+
{/* Footer */}
|
|
633
|
+
<Reveal className="border-t border-zinc-200" y={14} delay={0.02}>
|
|
634
|
+
<div className="grid gap-6 p-5 sm:p-6 lg:grid-cols-2 lg:gap-0 lg:px-8 lg:py-6">
|
|
635
|
+
<div className="lg:pe-8">
|
|
636
|
+
<p className="text-sm font-semibold text-zinc-900">{dict.footer.license}</p>
|
|
637
|
+
<p className="mt-1.5 max-w-md text-sm leading-relaxed text-zinc-500">{dict.footer.tagline}</p>
|
|
917
638
|
</div>
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
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>
|
|
639
|
+
<div className="lg:border-s lg:border-zinc-200 lg:ps-8">
|
|
640
|
+
<p className="text-sm text-zinc-600">
|
|
641
|
+
<span>{dict.footer.architect_credit} </span>
|
|
642
|
+
<a
|
|
643
|
+
href="https://www.edwinvakayil.info/"
|
|
644
|
+
target="_blank"
|
|
645
|
+
rel="noopener noreferrer"
|
|
646
|
+
className="font-medium text-zinc-950 underline-offset-4 hover:underline"
|
|
647
|
+
>
|
|
648
|
+
{dict.footer.architect_name}
|
|
649
|
+
</a>
|
|
650
|
+
</p>
|
|
937
651
|
</div>
|
|
938
|
-
<p className="text-[12px] font-medium leading-relaxed text-zinc-500">{dict.footer.footer_desc}</p>
|
|
939
652
|
</div>
|
|
940
|
-
|
|
941
|
-
<
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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>
|
|
653
|
+
<Separator className="bg-zinc-200" />
|
|
654
|
+
<p className="px-5 py-3 text-center text-xs text-zinc-400 sm:px-8">
|
|
655
|
+
© {currentYear} {dict.footer.copyright_suffix}
|
|
656
|
+
</p>
|
|
657
|
+
</Reveal>
|
|
980
658
|
</div>
|
|
981
|
-
</
|
|
659
|
+
</main>
|
|
660
|
+
</SiteMotionRoot>
|
|
982
661
|
</div>
|
|
983
662
|
</>
|
|
984
663
|
);
|