返回 CodeWhale
page.tsx
根目录 / web / app / [locale] / page.tsx
1 import { Fragment } from "react";
2 import Image from "next/image";
3 import Link from "next/link";
4 import { GettingStartedSteps } from "@/components/getting-started-steps";
5 import { InstallCodeBlock } from "@/components/install-code-block";
6 import { Strata } from "@/components/strata";
7 import { getFacts } from "@/lib/facts";
8 import { GETTING_STARTED_STEPS } from "@/lib/content/getting-started";
9 import { fill, getHome, splitToken } from "@/lib/i18n/dictionaries";
10 import {
11 APP_SIGNUP_URL,
12 DISCORD_URL,
13 REPO_ISSUES_URL,
14 REPO_RELEASES_URL,
15 REPO_URL,
16 } from "@/lib/i18n/links";
17 import { serializeJsonLd } from "@/lib/json-ld";
18 import { buildSoftwareApplicationJsonLd } from "@/lib/software-application-schema";
19 import { TERMINAL_SCREENSHOT } from "@/lib/media-manifest";
20
21 // Revalidate against source-proven runtime facts without giving up static edge
22 // caching. `getFacts()` rejects legacy or older KV snapshots.
23 export const revalidate = 300;
24
25 /**
26 * The Tidal Folio homepage: a sheet read under the sea.
27 *
28 * Every visible string resolves through `getHome(locale)` — English,
29 * Chinese, and every other routed locale take the identical path, with the
30 * English dictionary as the build-time-guaranteed fallback. The only literals
31 * left in this file are code-owned per docs/VOICE.md: the product control
32 * vocabulary (`Plan · Work · Operate`, `Ask · Auto-Review · Full Access`),
33 * the install command, package-manager and mirror proper nouns, the chapter
34 * numerals, and the screenshot path.
35 */
36 export default async function HomePage({ params }: { params: Promise<{ locale: string }> }) {
37 const { locale } = await params;
38 const d = getHome(locale);
39 const facts = await getFacts();
40 const sourceVersion = facts.version ?? "unknown";
41 const publishedRelease = facts.latestPublishedRelease;
42 const sourceIsPublished = publishedRelease?.version === sourceVersion;
43
44 // The install URL resolves published artifacts, so its structured version
45 // must come from the published-release receipt rather than source-candidate
46 // facts. When no release is known, the schema omits softwareVersion.
47 const jsonLd = buildSoftwareApplicationJsonLd(publishedRelease);
48
49 // The lede typesets the brand in its own span. Splitting on the {brand}
50 // token keeps the sentence a single translated unit — no concatenation of
51 // fragments around a variable, and a locale may place the brand anywhere.
52 const ledeParts = splitToken(d.heroIntro, "brand");
53
54 return (
55 <div className="product-home">
56 <script
57 type="application/ld+json"
58 dangerouslySetInnerHTML={{ __html: serializeJsonLd(jsonLd) }}
59 />
60
61 {/* THE PLATE — paper at the top left, the water rising from the bottom
62 right, the real terminal floating at the waterline. */}
63 <section className="folio-hero">
64 <Strata variant="hero" />
65 <div className="product-container folio-hero-grid">
66 <div className="folio-hero-copy">
67 <h1>{d.heroTitle}</h1>
68 <p className="folio-lede">
69 {ledeParts.map((part, index) => (
70 <Fragment key={index}>
71 {index > 0 && <span className="font-semibold text-ink">Codewhale</span>}
72 {part}
73 </Fragment>
74 ))}
75 </p>
76 <div className="folio-actions">
77 <Link href={`/${locale}/install`} className="folio-button folio-button-primary">
78 {d.getCodewhale}
79 </Link>
80 <Link href={`/${locale}/product`} className="folio-button">
81 {d.exploreProduct}
82 </Link>
83 </div>
84 </div>
85
86 {/* Exact-build PTY capture of an empty session. No fabricated
87 conversation, connected tools or completion metrics. */}
88 <figure className="folio-shot">
89 <Image
90 src={TERMINAL_SCREENSHOT.src}
91 alt={fill(d.screenshotAlt, { version: TERMINAL_SCREENSHOT.version })}
92 width={TERMINAL_SCREENSHOT.width}
93 height={TERMINAL_SCREENSHOT.height}
94 sizes="(max-width: 58rem) calc(100vw - 2rem), 56rem"
95 unoptimized
96 priority
97 />
98 <figcaption>
99 <p className="dotline">
100 <span>{d.shotPreview}</span>
101 <span>{fill(d.shotBuild, { version: TERMINAL_SCREENSHOT.version })}</span>
102 </p>
103 {/*
104 The TUI header grammar: a `cw` chip and a dot chain. Each fact is
105 its own translated unit — the separators are CSS punctuation, so
106 nothing is concatenated around a token and no locale inherits an
107 English joining word.
108 */}
109 <p
110 className="paper-facts dotline"
111 data-source-state={sourceIsPublished ? "published release" : "source candidate"}
112 data-source-state-label={sourceIsPublished ? d.publishedRelease : d.figcaptionSourceCandidate}
113 >
114 <span className="dotline-chip">cw</span>
115 <span>
116 {publishedRelease
117 ? fill(d.latestRelease, { tag: publishedRelease.tag })
118 : d.releaseUnavailable}
119 </span>
120 <span>
121 {`${sourceIsPublished ? d.currentSource : d.sourceCandidate} v${sourceVersion}`}
122 </span>
123 <span>{facts.license ?? "MIT"}</span>
124 </p>
125 </figcaption>
126 </figure>
127
128 <aside className="folio-chapter">
129 <span className="folio-chapter-num">01 / {d.chapterTerminal}</span>
130 <p className="folio-chapter-title">{d.chapterTerminalTitle}</p>
131 </aside>
132 </div>
133 </section>
134
135 {/* WHAT YOU GAIN — three ruled columns on paper. */}
136 <section className="folio-section">
137 <div className="product-container">
138 <h2>{d.gainHeading}</h2>
139 <p className="folio-section-lede">{d.gainLede}</p>
140 <div className="folio-gain-grid">
141 {d.gain.map(([title, body]) => (
142 <div key={title}>
143 <h3>{title}</h3>
144 <p>{body}</p>
145 </div>
146 ))}
147 </div>
148 </div>
149 </section>
150
151 {/* Tools and saved work, before setup details. */}
152 <section className="folio-section">
153 <div className="product-container">
154 <h2>{d.surfacesHeading}</h2>
155 <dl className="folio-fact-list mt-8">
156 {d.surfaces.map(([name, description]) => (
157 <div key={name}>
158 <dt>{name}</dt>
159 <dd>{description}</dd>
160 </div>
161 ))}
162 </dl>
163 <Link href={`/${locale}/runtime`} className="folio-link">
164 {d.runtimeLink}
165 </Link>
166 </div>
167 </section>
168
169 {/* 02 — YOUR MODELS */}
170 <section className="folio-section">
171 <div className="product-container folio-chapter-grid">
172 <div>
173 <span className="folio-running-head">02 / {d.chapterModels}</span>
174 <h2>{d.modelsHeading}</h2>
175 <p className="folio-section-lede">{d.modelsBody}</p>
176 <Link href={`/${locale}/models`} className="folio-link">
177 {d.modelsLink}
178 </Link>
179 </div>
180 <dl className="folio-fact-list">
181 {d.modelsFacts.map(([kind, description]) => (
182 <div key={kind}>
183 <dt>{kind}</dt>
184 <dd>{description}</dd>
185 </div>
186 ))}
187 <div>
188 <dt>Plan · Work · Operate</dt>
189 <dd>Ask · Auto-Review · Full Access</dd>
190 </div>
191 </dl>
192 </div>
193 </section>
194
195 {/* 03 — START */}
196 <section className="product-start">
197 <div className="product-container">
198 <span className="folio-running-head">03 / {d.startHeading}</span>
199 <h2>{d.startHeading}</h2>
200 <p className="product-start-lede">{d.startLede}</p>
201 <GettingStartedSteps locale={locale} />
202 <div className="product-start-links">
203 <Link href={`/${locale}/docs/guide`}>{d.startGuideLink}</Link>
204 <Link href={`/${locale}/docs/vocabulary`}>{d.startVocabularyLink}</Link>
205 </div>
206 </div>
207 </section>
208
209 {/* THE WATERLINE. Everything below is one water column: the same
210 component grammar, re-inked with the dark whale tokens by the
211 shared below-the-waterline rule in globals.css. */}
212 <div className="folio-waterline" aria-hidden="true">
213 <Strata variant="band" />
214 </div>
215 <div className="ocean-column">
216 {/* 04 — WHERE IT RUNS TODAY */}
217 <section className="folio-availability">
218 <div className="product-container">
219 <span className="folio-running-head">04 / {d.chapterAccount}</span>
220 <h2>{d.availabilityHeading}</h2>
221 <p className="folio-section-lede">{d.availabilityLede}</p>
222 <dl className="folio-availability-list">
223 {d.availability.map(([surface, status, detail]) => (
224 <div key={surface}>
225 <dt>{surface}</dt>
226 <dd>
227 <strong>{status}</strong>
228 {detail}
229 </dd>
230 </div>
231 ))}
232 </dl>
233 <p className="folio-availability-note">{d.availabilityNote}</p>
234 <a href={APP_SIGNUP_URL} className="folio-link" data-usage="signup">
235 {d.accountLink}
236 </a>
237 </div>
238 </section>
239
240 {/* Install band — the composer plate. */}
241 <section className="product-install-band">
242 <div className="product-container product-install-grid">
243 <h2>{d.installBandHeading}</h2>
244 <div>
245 {/* `❯` is a code-owned literal, like the install command it
246 prompts for — it is the product's glyph, not a sentence. */}
247 <div className="product-composer">
248 <span className="product-composer-prompt" aria-hidden>
249
250 </span>
251 <InstallCodeBlock
252 cmd={GETTING_STARTED_STEPS[0].commands[0]}
253 copyLabel={d.copy}
254 copiedLabel={d.copied}
255 />
256 </div>
257 <p className="dotline">
258 <span>GitHub Releases · {d.binaries}</span>
259 <span>npm</span>
260 <span>Cargo</span>
261 <span>Docker</span>
262 <span>Nix</span>
263 <span>Windows</span>
264 <span>Android / Termux</span>
265 <span>{d.chinaMirrors}</span>
266 </p>
267 <Link href={`/${locale}/install`}>{d.installGuideLink}</Link>
268 </div>
269 </div>
270 </section>
271
272 {/* Community */}
273 <section className="product-community">
274 <div className="product-container product-community-grid">
275 <div>
276 <h2>{d.communityHeading}</h2>
277 <p>{d.communityBody}</p>
278 </div>
279 <nav aria-label={d.communityLinksAria}>
280 <a href={REPO_URL}>GitHub</a>
281 <a href={REPO_ISSUES_URL}>Issues</a>
282 <a href={DISCORD_URL}>Discord</a>
283 <Link href={`/${locale}/contribute`}>{d.contribute}</Link>
284 {publishedRelease ? (
285 <a href={publishedRelease.url}>{publishedRelease.tag}</a>
286 ) : (
287 <a href={REPO_RELEASES_URL}>Releases</a>
288 )}
289 </nav>
290 </div>
291 </section>
292 </div>
293 </div>
294 );
295 }
296
296 lines Plain Text