feat(web): put the real terminal in the hero and back the promise with facts

The homepage opened on a headline beside an empty column, then a sponsored
banner, and only then the product. A first-time visitor now sees the
captured Codewhale TUI at the fold, with the live whale resting on its top
edge, then a proof strip read from facts: latest release, licence,
contributor count, built-in tool count and provider-route count. The
OrcaRouter offer moves beside the models section it relates to, and the
0.10.3 highlights move below availability, so first-read sections are the
product and its uses rather than the changelog.

- The hero is tighter (smaller title, less padding) so the terminal shows
  at the fold at 1440x900 and on a 390px phone.
- The terminal caption no longer calls the released v0.10.2 capture
  "Unreleased"; it says so only when the capture version is unpublished.
- On screens narrower than 30rem the copy button keeps its accessible name
  but shows only its icon, so more of the install command is visible.
- Three new proof labels are translated in all 18 locales; the GT catalog
  is re-exported.

Verified: tsc --noEmit clean; eslint . clean; check:locales PASS (GT catalog
OK); check:tokens up to date (150 tokens); check:docs and check:latest-release
pass; next build compiled, 993/993 static pages. check:facts fails on main's
working tree because another session's in-progress feature-registry edits
are not regenerated yet; not caused by this change. Before/after
screenshots (desktop 1440 and mobile 390, light and pinned dark, en and zh):
artifacts/design-20261027/{before,after}/.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
CodeWhale BotandClaude Opus 5.5 committed 2026-10-27 15:57:44 -07:00
1 parent 2b898fb041
commit 35c5a62b7c
24 files changed
+175 -88

No files matched your search

+69 -63
View File
@@ -35,8 +35,9 @@ const AVAILABILITY_TONES: StatusTone[] = ["ready", "ready", "idle", "idle"];
/**
* The whale-road homepage: the promise, capabilities and reading
* sections sit on paper. The live v2 whale (the desktop app's own Director)
* rests beside the hero, and the page ends in the sea with the install
* command, running into the footer.
* rests on the top edge of a real terminal capture just under the promise,
* and the page ends in the sea with the install command, running into the
* footer.
*
* One memorable thing moves: the whale. It breathes, glances toward the
* pointer, and performs its existing poses. Everything else is still.
@@ -75,7 +76,7 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
/>
{/* PAPER — the promise, one primary action, the install command, and
the whale resting on the horizon. */}
the whale resting on the terminal below. */}
<section className="home-hero" aria-labelledby="home-title">
<div className="home-hero-inner">
<div className="home-hero-copy">
@@ -105,45 +106,6 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
</div>
</section>
<div
className="home-release status-line"
data-source-state={sourceIsPublished ? "published release" : "source candidate"}
data-source-state-label={sourceIsPublished ? d.publishedRelease : d.sourceCandidate}
>
<Status tone={publishedRelease ? "ready" : "idle"}>
{publishedRelease
? fill(d.latestRelease, { tag: publishedRelease.tag })
: d.releaseUnavailable}
</Status>
<span>{`${sourceIsPublished ? d.currentSource : d.sourceCandidate} v${sourceVersion}`}</span>
<span>{facts.license ?? "MIT"}</span>
</div>
{showPromo && (
<section className="home-promo" aria-labelledby="home-promo-title">
<a href={PARTNER_PROMO.href} rel="sponsored" className="home-promo-card">
<span className="figure-frame">
<Image
src={PARTNER_PROMO.image.src}
alt={pickText(PARTNER_PROMO.image.alt, locale)}
width={PARTNER_PROMO.image.width}
height={PARTNER_PROMO.image.height}
unoptimized
/>
</span>
<span className="home-promo-text">
<span className="home-promo-title" id="home-promo-title">{PARTNER_PROMO.title}</span>
<span className="home-promo-body">{pickText(PARTNER_PROMO.body, locale)}</span>
<span className="home-promo-body">{pickText(PARTNER_PROMO.disclosure, locale)}</span>
<span className="section-link">
{pickText(PARTNER_PROMO.cta, locale)}
<Icon name="external" />
</span>
</span>
</a>
</section>
)}
<figure className="home-terminal figure">
<div className="figure-frame" dir="ltr">
<TerminalCapture
@@ -153,32 +115,31 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
/>
</div>
<figcaption className="figure-caption">
{d.shotPreview} · {d.sourceCandidate} v{TERMINAL_SCREENSHOT.version}
{TERMINAL_SCREENSHOT.version === publishedRelease?.version
? `${d.shotPreview} · v${TERMINAL_SCREENSHOT.version}`
: `${d.shotPreview} · ${d.sourceCandidate} v${TERMINAL_SCREENSHOT.version}`}
</figcaption>
</figure>
<div
className="home-proof"
data-source-state={sourceIsPublished ? "published release" : "source candidate"}
data-source-state-label={sourceIsPublished ? d.publishedRelease : d.sourceCandidate}
>
<Status tone={publishedRelease ? "ready" : "idle"}>
{publishedRelease
? fill(d.latestRelease, { tag: publishedRelease.tag })
: d.releaseUnavailable}
</Status>
{!sourceIsPublished && <span>{`${d.sourceCandidate} v${sourceVersion}`}</span>}
<span>{facts.license ?? "MIT"}</span>
<span>{fill(d.proofContributors, { count: facts.contributorCount })}</span>
{facts.toolCount ? <span>{fill(d.proofTools, { count: facts.toolCount })}</span> : null}
{facts.providers.length > 0 && <span>{fill(d.proofProviders, { count: facts.providers.length })}</span>}
</div>
{/* PAPER — models, connections, and the getting-started path. */}
<div className="home-body">
{showHighlights && (
<Section
id="home-release-highlights"
layout="split"
className="home-section"
title={fill(pickText(highlightsPublished ? RELEASE_HIGHLIGHTS.newHeading : RELEASE_HIGHLIGHTS.comingHeading, locale), { version: RELEASE_HIGHLIGHTS.version })}
scope={highlightsPublished ? undefined : pickText(RELEASE_HIGHLIGHTS.comingLede, locale)}
link={<Link href={`/${locale}/changelog`} className="section-link">{pickText(RELEASE_HIGHLIGHTS.changelogLink, locale)}</Link>}
>
<dl className="ruled-list">
{RELEASE_HIGHLIGHTS.items.map((item) => (
<div key={item.title}>
<dt>{item.title}</dt>
<dd>{pickText(item.body, locale)}</dd>
</div>
))}
</dl>
</Section>
)}
<Section
id="home-capabilities"
layout="split"
@@ -220,6 +181,31 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
</dl>
</Section>
{showPromo && (
<section className="home-section home-promo" aria-labelledby="home-promo-title">
<a href={PARTNER_PROMO.href} rel="sponsored" className="home-promo-card">
<span className="figure-frame">
<Image
src={PARTNER_PROMO.image.src}
alt={pickText(PARTNER_PROMO.image.alt, locale)}
width={PARTNER_PROMO.image.width}
height={PARTNER_PROMO.image.height}
unoptimized
/>
</span>
<span className="home-promo-text">
<span className="home-promo-title" id="home-promo-title">{PARTNER_PROMO.title}</span>
<span className="home-promo-body">{pickText(PARTNER_PROMO.body, locale)}</span>
<span className="home-promo-body">{pickText(PARTNER_PROMO.disclosure, locale)}</span>
<span className="section-link">
{pickText(PARTNER_PROMO.cta, locale)}
<Icon name="external" />
</span>
</span>
</a>
</section>
)}
<Section
id="home-surfaces"
layout="split"
@@ -284,6 +270,26 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
<p className="section-scope mt-4">{d.availabilityNote}</p>
</Section>
{showHighlights && (
<Section
id="home-release-highlights"
layout="split"
className="home-section"
title={fill(pickText(highlightsPublished ? RELEASE_HIGHLIGHTS.newHeading : RELEASE_HIGHLIGHTS.comingHeading, locale), { version: RELEASE_HIGHLIGHTS.version })}
scope={highlightsPublished ? undefined : pickText(RELEASE_HIGHLIGHTS.comingLede, locale)}
link={<Link href={`/${locale}/changelog`} className="section-link">{pickText(RELEASE_HIGHLIGHTS.changelogLink, locale)}</Link>}
>
<dl className="ruled-list">
{RELEASE_HIGHLIGHTS.items.map((item) => (
<div key={item.title}>
<dt>{item.title}</dt>
<dd>{pickText(item.body, locale)}</dd>
</div>
))}
</dl>
</Section>
)}
{/* COMMUNITY */}
<section className="home-section" aria-labelledby="home-community">
<div className="section-split">
+32 -25
View File
@@ -3,8 +3,9 @@
/* ------------------------------------------------------------------ */
/*
* Paper holds the promise, capabilities and reading sections.
* The live whale stays beside the hero. The page ends in the sea with the
* install command, which runs on into the footer.
* The live whale rests on the top edge of the real terminal capture under
* the hero. The page ends in the sea with the install command, which runs
* on into the footer.
*/
.home { --home-measure: 36rem; }
@@ -12,7 +13,7 @@
/* ---------- paper: the hero ---------- */
.home-hero {
position: relative;
padding-block: clamp(3.5rem, 9vw, 7.5rem) 0;
padding-block: clamp(2.5rem, 6vw, 4.5rem) 0;
background: var(--bg);
}
@@ -28,7 +29,7 @@
.home-hero-copy {
display: grid;
gap: var(--space-5);
padding-bottom: clamp(3rem, 7vw, 5.5rem);
padding-bottom: clamp(2rem, 4vw, 3rem);
min-width: 0;
}
@@ -37,7 +38,7 @@
English; hyphenation lets German compounds break at syllables. */
.home-title {
max-width: 9.5em;
font-size: clamp(2.5rem, 1.4rem + 4.4vw, 5rem);
font-size: clamp(2.5rem, 1.4rem + 3.6vw, 4.5rem);
hyphens: auto;
font-weight: 650;
line-height: 0.98;
@@ -73,7 +74,8 @@
.home-hero-copy .hero-install { max-width: 32rem; margin-top: var(--space-2); }
.home-hero-copy .actions { margin-top: calc(var(--space-2) * -1); }
/* The whale rests on the horizon: its lowest point meets the line. */
/* The whale rests on the terminal: its lowest point meets the frame's top
edge. */
.home-whale {
display: flex;
justify-content: center;
@@ -81,10 +83,10 @@
min-width: 0;
}
.home-whale .whale-live {
--whale-size: clamp(18rem, 40vw, 34rem);
--whale-size: clamp(16rem, 30vw, 26rem);
width: var(--whale-size);
/* The rig fills the middle 72% of the canvas; drop its belly onto the
waterline where the tide begins. */
terminal frame below. */
margin-bottom: calc(var(--whale-size) * -0.26);
position: relative;
z-index: 2;
@@ -159,29 +161,34 @@
background: var(--horizon-light);
}
/* ---------- release and source status ---------- */
.home-release {
/* ---------- the product, then what backs it ---------- */
.home-terminal {
position: relative;
z-index: 1;
width: var(--container);
margin-inline: auto;
}
.home-terminal .figure-frame {
box-shadow: 0 1px 0 var(--line), 0 32px 64px -32px rgb(10 30 51 / 0.5);
}
/* Release, licence and counts read from facts, never typed into copy. */
.home-proof {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-2) var(--space-6);
width: var(--container);
margin-inline: auto;
padding-block: var(--space-5);
border-block: 1px solid var(--line);
border-bottom: 1px solid var(--line);
color: var(--muted);
font-size: var(--text-ui);
}
.home-terminal {
width: var(--container);
max-width: 1000px;
margin-inline: auto;
padding-block-start: var(--space-6);
font-variant-numeric: tabular-nums;
}
/* ---------- partner offer ---------- */
.home-promo {
width: var(--container);
max-width: 1000px;
margin-inline: auto;
padding-block-start: var(--space-6);
}
.home-promo { max-width: 1000px; }
.home-promo-card {
display: grid;
gap: var(--space-4) var(--space-6);
@@ -245,7 +252,7 @@
.home-hero-inner { grid-template-columns: minmax(0, 1fr); }
.home-hero-copy { padding-bottom: var(--space-4); }
.home-whale { justify-content: flex-end; }
.home-whale .whale-live { --whale-size: clamp(14rem, 70vw, 20rem); margin-inline: auto; }
.home-whale .whale-live { --whale-size: clamp(11rem, 48vw, 16rem); }
}
@media (forced-colors: active) {
+11
View File
@@ -588,6 +588,17 @@ a.dir-row:hover .dir-action { color: var(--text); }
.code .code-copy:focus-visible { box-shadow: var(--glow-focus); }
.code .code-copy:hover { background: var(--hover); color: var(--text); }
.code-copy[data-copied="true"] { color: var(--live); }
@media (max-width: 30rem) {
.code pre.code-block { padding-right: 3rem; }
.code .code-copy-label {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
}
/* ---------- plate ---------- */
/* The install plate: a raised sheet that holds a choice, a command and its
+3
View File
@@ -203,6 +203,9 @@
"screenshotAlt": "Codewhale v{version}: a captured terminal view with the conversation, message composer and session controls.",
"latestRelease": "Latest release {tag}",
"releaseUnavailable": "Release status unavailable",
"proofContributors": "{count} contributors",
"proofTools": "{count} built-in tools",
"proofProviders": "{count} provider routes",
"currentSource": "Source",
"sourceCandidate": "Unreleased",
"publishedRelease": "released",
+3
View File
@@ -203,6 +203,9 @@
"screenshotAlt": "Codewhale v{version} 的终端实录画面,包含对话、消息输入框与会话控制。",
"latestRelease": "最新发布 {tag}",
"releaseUnavailable": "发布状态暂不可用",
"proofContributors": "{count} 位贡献者",
"proofTools": "{count} 个内置工具",
"proofProviders": "{count} 条服务商路由",
"currentSource": "源码",
"sourceCandidate": "未发布",
"publishedRelease": "已发布",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: لقطة طرفية تعرض المحادثة وحقل الرسالة وعناصر التحكم في الجلسة.",
latestRelease: "أحدث إصدار {tag}",
releaseUnavailable: "حالة الإصدار غير متاحة",
proofContributors: "{count} مساهمًا",
proofTools: "{count} أداة مدمجة",
proofProviders: "{count} مسار مزوّد",
currentSource: "المصدر",
sourceCandidate: "غير منشور",
publishedRelease: "منشور",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: captura del terminal amb la conversa, el camp de missatge i els controls de sessió.",
latestRelease: "Última versió {tag}",
releaseUnavailable: "Estat de la versió no disponible",
proofContributors: "{count} col·laboradors",
proofTools: "{count} eines integrades",
proofProviders: "{count} rutes de proveïdor",
currentSource: "Font",
sourceCandidate: "Sense publicar",
publishedRelease: "publicada",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: eine Terminalaufnahme mit Gespräch, Nachrichteneingabe und Sitzungssteuerung.",
latestRelease: "Aktuellstes Release {tag}",
releaseUnavailable: "Release-Status nicht verfügbar",
proofContributors: "{count} Mitwirkende",
proofTools: "{count} integrierte Werkzeuge",
proofProviders: "{count} Anbieter-Routen",
currentSource: "Quelle",
sourceCandidate: "Unveröffentlicht",
publishedRelease: "veröffentlicht",
+3
View File
@@ -17,6 +17,9 @@ export const home: HomeDict = {
latestRelease: "Latest release {tag}",
releaseUnavailable: "Release status unavailable",
proofContributors: "{count} contributors",
proofTools: "{count} built-in tools",
proofProviders: "{count} provider routes",
currentSource: "Source",
sourceCandidate: "Unreleased",
publishedRelease: "released",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: captura del terminal con la conversación, el campo de mensaje y los controles de sesión.",
latestRelease: "Último lanzamiento {tag}",
releaseUnavailable: "Estado del lanzamiento no disponible",
proofContributors: "{count} colaboradores",
proofTools: "{count} herramientas integradas",
proofProviders: "{count} rutas de proveedor",
currentSource: "Fuente",
sourceCandidate: "Sin publicar",
publishedRelease: "publicado",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version} : capture du terminal avec la conversation, le champ de message et les commandes de session.",
latestRelease: "Dernière version {tag}",
releaseUnavailable: "État des versions indisponible",
proofContributors: "{count} contributeurs",
proofTools: "{count} outils intégrés",
proofProviders: "{count} routes de fournisseur",
currentSource: "Source",
sourceCandidate: "Non publiée",
publishedRelease: "publiée",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: बातचीत, संदेश बॉक्स और सत्र नियंत्रण दिखाने वाला टर्मिनल कैप्चर।",
latestRelease: "नवीनतम रिलीज़ {tag}",
releaseUnavailable: "रिलीज़ स्थिति उपलब्ध नहीं",
proofContributors: "{count} योगदानकर्ता",
proofTools: "{count} अंतर्निहित टूल",
proofProviders: "{count} प्रदाता रूट",
currentSource: "सोर्स",
sourceCandidate: "अप्रकाशित",
publishedRelease: "प्रकाशित",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: tangkapan terminal berisi percakapan, kolom pesan, dan kontrol sesi.",
latestRelease: "Rilis terbaru {tag}",
releaseUnavailable: "Status rilis tidak tersedia",
proofContributors: "{count} kontributor",
proofTools: "{count} alat bawaan",
proofProviders: "{count} rute penyedia",
currentSource: "Sumber",
sourceCandidate: "Belum dirilis",
publishedRelease: "dirilis",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: una cattura del terminale con conversazione, campo del messaggio e controlli della sessione.",
latestRelease: "Ultima release {tag}",
releaseUnavailable: "Stato delle release non disponibile",
proofContributors: "{count} contributori",
proofTools: "{count} strumenti integrati",
proofProviders: "{count} percorsi dei provider",
currentSource: "Sorgente",
sourceCandidate: "Non rilasciata",
publishedRelease: "rilasciata",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version} のターミナル収録画面。会話、メッセージ入力欄、セッション操作を表示。",
latestRelease: "最新リリース {tag}",
releaseUnavailable: "リリース情報を取得できません",
proofContributors: "コントリビューター {count} 人",
proofTools: "組み込みツール {count} 個",
proofProviders: "プロバイダールート {count} 件",
currentSource: "ソース",
sourceCandidate: "未リリース",
publishedRelease: "リリース済み",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: 대화, 메시지 입력창, 세션 제어를 보여 주는 실제 터미널 캡처.",
latestRelease: "최신 릴리스 {tag}",
releaseUnavailable: "릴리스 상태를 확인할 수 없음",
proofContributors: "기여자 {count}명",
proofTools: "내장 도구 {count}개",
proofProviders: "제공자 경로 {count}개",
currentSource: "소스",
sourceCandidate: "미공개",
publishedRelease: "공개됨",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: zrzut terminala z rozmową, polem wiadomości i kontrolkami sesji.",
latestRelease: "Najnowsze wydanie {tag}",
releaseUnavailable: "Status wydania niedostępny",
proofContributors: "Współtwórcy: {count}",
proofTools: "Wbudowane narzędzia: {count}",
proofProviders: "Trasy dostawców: {count}",
currentSource: "Źródło",
sourceCandidate: "Niewydane",
publishedRelease: "wydane",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: captura do terminal com conversa, campo de mensagem e controles da sessão.",
latestRelease: "Último lançamento {tag}",
releaseUnavailable: "Status do lançamento indisponível",
proofContributors: "{count} colaboradores",
proofTools: "{count} ferramentas integradas",
proofProviders: "{count} rotas de provedor",
currentSource: "Código-fonte",
sourceCandidate: "Não publicado",
publishedRelease: "publicado",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: снимок терминала с беседой, полем сообщения и управлением сессией.",
latestRelease: "Последний релиз {tag}",
releaseUnavailable: "Статус релиза недоступен",
proofContributors: "Участников: {count}",
proofTools: "Встроенных инструментов: {count}",
proofProviders: "Маршрутов провайдеров: {count}",
currentSource: "Исходники",
sourceCandidate: "Не выпущено",
publishedRelease: "выпущено",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: sohbeti, mesaj alanını ve oturum kontrollerini gösteren terminal kaydı.",
latestRelease: "En yeni sürüm {tag}",
releaseUnavailable: "Sürüm durumu kullanılamıyor",
proofContributors: "{count} katkıda bulunan",
proofTools: "{count} yerleşik araç",
proofProviders: "{count} sağlayıcı rotası",
currentSource: "Kaynak",
sourceCandidate: "Yayımlanmadı",
publishedRelease: "yayımlandı",
+3
View File
@@ -162,6 +162,9 @@ export interface HomeDict {
/** "Latest release {tag}" */
latestRelease: string;
releaseUnavailable: string;
proofContributors: string;
proofTools: string;
proofProviders: string;
/** "Source" / "Unreleased" — prepended to `v{version}`. */
currentSource: string;
sourceCandidate: string;
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: знімок термінала з розмовою, полем повідомлення й керуванням сесією.",
latestRelease: "Останній реліз {tag}",
releaseUnavailable: "Статус релізу недоступний",
proofContributors: "Учасників: {count}",
proofTools: "Вбудованих інструментів: {count}",
proofProviders: "Маршрутів провайдерів: {count}",
currentSource: "Джерело",
sourceCandidate: "Не випущено",
publishedRelease: "випущено",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version}: ảnh chụp terminal với cuộc trò chuyện, ô nhập tin nhắn và điều khiển phiên.",
latestRelease: "Bản phát hành mới nhất {tag}",
releaseUnavailable: "Không có trạng thái phát hành",
proofContributors: "{count} người đóng góp",
proofTools: "{count} công cụ tích hợp",
proofProviders: "{count} tuyến nhà cung cấp",
currentSource: "Mã nguồn",
sourceCandidate: "Chưa phát hành",
publishedRelease: "đã phát hành",
+3
View File
@@ -15,6 +15,9 @@ export const home: HomeDict = {
"Codewhale v{version} 的终端实录画面,包含对话、消息输入框与会话控制。",
latestRelease: "最新发布 {tag}",
releaseUnavailable: "发布状态暂不可用",
proofContributors: "{count} 位贡献者",
proofTools: "{count} 个内置工具",
proofProviders: "{count} 条服务商路由",
currentSource: "源码",
sourceCandidate: "未发布",
publishedRelease: "已发布",