fix(web): follow §46 and the site history for the OrcaRouter offer; derive home makers from facts (CW-7933)

A lessons review of the site history (codewhale-design vintage) flagged
that the offer strip above the hero repeated a reverted choice: on
2026-10-27 (35c5a62b7c) the offer left the top because the page opened on
a sponsored banner, and moved beside the models section it relates to.
The strip now sits inside the models section, still dated and labelled.

CURRENT_DECISIONS §46: English surfaces only, no featuring in zh-Hans, and
the disclosure scoped to the campaign link (founder amendment 2026-10-28:
the general "referral fee" line "isn't totally accurate"). The promo is
gated to locale en, and the disclosure now reads "This link is part of a
referral campaign; Codewhale may earn a referral reward on top-ups made
through it."

The home maker chips were a hand-typed list; they are now the distinct
makers in facts.models (gateways excluded), alphabetical, so new makers
appear without an edit and no provider is ranked (§46 neutral order).

Also removes components/whale-live.tsx, lib/content/release-highlights.ts
and the whale-live CSS, which nothing imported.

Checks in web/: tsc pass; eslint pass; vitest 793 passed; next build pass.
Browser: /en shows the offer card in the models section with the
disclosure; /zh shows no OrcaRouter text.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
CodeWhale BotandClaude Opus 5.5 committed 2026-11-10 13:54:59 -08:00
1 parent a072208590
commit cc0d42bf4a
6 files changed
+32 -333

No files matched your search

+25 -21
View File
@@ -12,7 +12,7 @@ import { GitHubStar, FaceWall, MergedList, PulseStats } from "@/components/proje
import { LiveStarLink } from "@/components/github-live";
import { loadProjectPulse } from "@/lib/project-pulse";
import { PROJECT_COPY } from "@/lib/content/project";
import { HOME_MAKERS, HOME_OVERVIEW } from "@/lib/content/home-overview";
import { HOME_OVERVIEW } from "@/lib/content/home-overview";
import { PRODUCT_COPY } from "@/lib/content/product";
import { PARTNER_PROMO } from "@/lib/content/partner-promo";
import { fill, getChrome, getComputerUse, getHome, pickText, splitToken } from "@/lib/i18n/dictionaries";
@@ -38,9 +38,13 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
const sourceIsPublished = publishedRelease?.version === sourceVersion;
const promoLeft = Date.parse(PARTNER_PROMO.endsAt) - Date.now();
const promoDays = Math.max(1, Math.ceil(promoLeft / 86_400_000));
const showPromo = promoLeft > 0 && PARTNER_PROMO.locales.includes(locale);
const jsonLd = buildSoftwareApplicationJsonLd(publishedRelease);
const ledeParts = splitToken(d.heroIntro, "brand");
const firstParty = new Set(HOME_MAKERS);
const gateways = new Set(["OpenRouter", "Together", "OpenAI Codex"]);
const makers = [...new Set(facts.models.map((model) => model.provider).filter((name): name is string => !!name && !gateways.has(name)))]
.sort((a, b) => a.localeCompare(b));
const firstParty = new Set(makers);
const newest = facts.models
.filter((model) => model.addedAt && model.provider && firstParty.has(model.provider))
.sort((a, b) => (b.addedAt ?? "").localeCompare(a.addedAt ?? ""))
@@ -56,24 +60,6 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
dangerouslySetInnerHTML={{ __html: serializeJsonLd(jsonLd) }}
/>
{promoLeft > 0 && (
<aside className="home-promo-bar" aria-label={PARTNER_PROMO.title}>
<div className="home-promo-bar-inner">
<Image src={PARTNER_PROMO.image.src} alt="" width={64} height={36} className="home-promo-bar-art" unoptimized />
<p>
<strong>{PARTNER_PROMO.title}</strong>
<span>{pickText(PARTNER_PROMO.bar, locale)}</span>
<span className="home-promo-bar-days">{fill(pickText(promoDays === 1 ? PARTNER_PROMO.dayLeft : PARTNER_PROMO.daysLeft, locale), { count: promoDays })}</span>
</p>
<span className="home-promo-bar-tag" title={pickText(PARTNER_PROMO.disclosure, locale)}>{pickText(PARTNER_PROMO.referralTag, locale)}</span>
<a href={PARTNER_PROMO.href} rel="sponsored noopener" className="btn btn-primary">
{pickText(PARTNER_PROMO.cta, locale)}
<Icon name="external" className="icon" />
</a>
</div>
</aside>
)}
<div className="home-intro">
<div className="home-water" aria-hidden="true"><Strata /></div>
<section className="home-hero" aria-labelledby="home-title">
@@ -149,7 +135,7 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
link={<Link href={`/${locale}/models`} className="section-link">{d.modelsLink}<Icon name="arrow-right" className="icon icon-flip" /></Link>}
>
<ul className="home-makers" role="list">
{HOME_MAKERS.map((maker) => <li key={maker} className="pill">{maker}</li>)}
{makers.map((maker) => <li key={maker} className="pill">{maker}</li>)}
<li className="pill pill-quiet">{pickText(HOME_OVERVIEW.modelsAny, locale)}</li>
</ul>
{newest.length > 0 && (
@@ -165,6 +151,24 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
</ol>
</div>
)}
{showPromo && (
<aside className="home-promo-bar home-promo-inline" aria-label={PARTNER_PROMO.title}>
<div className="home-promo-bar-inner">
<Image src={PARTNER_PROMO.image.src} alt="" width={64} height={36} className="home-promo-bar-art" unoptimized />
<p>
<strong>{PARTNER_PROMO.title}</strong>
<span>{pickText(PARTNER_PROMO.bar, locale)}</span>
<span className="home-promo-bar-days">{fill(pickText(promoDays === 1 ? PARTNER_PROMO.dayLeft : PARTNER_PROMO.daysLeft, locale), { count: promoDays })}</span>
</p>
<span className="home-promo-bar-tag">{pickText(PARTNER_PROMO.referralTag, locale)}</span>
<p className="home-promo-bar-disclosure">{pickText(PARTNER_PROMO.disclosure, locale)}</p>
<a href={PARTNER_PROMO.href} rel="sponsored noopener" className="btn btn-primary">
{pickText(PARTNER_PROMO.cta, locale)}
<Icon name="external" className="icon" />
</a>
</div>
</aside>
)}
</Section>
<Section
+4 -55
View File
@@ -138,60 +138,6 @@
.living-whale-mark { display: none; transition: none; }
}
/* ---------- the live whale ---------- */
.whale-live {
position: relative;
aspect-ratio: 1;
}
.whale-live > .whale-live-poster,
.whale-live > .whale-live-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
/* The phase, in a word: a small point of light and a label, above the
whale's back. Light means live work (DIRECTION.md). */
.whale-live-state {
position: absolute;
inset-inline-start: 50%;
top: 8%;
translate: -50% 0;
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: 0.3rem 0.75rem;
border: 1px solid var(--line);
border-radius: var(--radius-pill);
background: var(--panel);
color: var(--text);
font-size: var(--text-caption);
font-weight: 500;
white-space: nowrap;
box-shadow: 0 6px 18px -10px rgb(11 72 187 / 0.35);
}
.whale-live-dot {
width: 0.45rem;
height: 0.45rem;
border-radius: var(--radius-pill);
background: var(--brand-light);
}
.whale-live-state[data-state="done"] .whale-live-dot { background: var(--live); }
.whale-live-state[data-state="rest"] .whale-live-dot { background: var(--muted); }
@media (prefers-reduced-motion: no-preference) {
.whale-live-state:not([data-state="rest"]):not([data-state="done"]) .whale-live-dot {
animation: whale-pulse 1.4s var(--ease-out) infinite;
}
}
@keyframes whale-pulse {
0% { box-shadow: 0 0 0 0 rgb(30 143 216 / 0.55); }
100% { box-shadow: 0 0 0 0.5rem rgb(30 143 216 / 0); }
}
.whale-live-poster { --pose-size: 100%; padding: 14%; box-sizing: border-box; }
.whale-live-canvas { opacity: 0; }
.whale-live[data-ready] .whale-live-canvas { opacity: 1; }
.whale-live[data-ready] .whale-live-poster { visibility: hidden; }
/* The install band ends the page in the sea: one line of light at its top,
and water that reaches the floor colour by its own bottom edge, so it runs
into the footer without a seam. */
@@ -294,8 +240,11 @@
.home-proof-stars { display: inline-flex; align-items: center; gap: var(--space-1); }
.home-proof-date { color: var(--muted); }
/* ---------- partner offer: a dated strip above the hero ---------- */
/* ---------- partner offer: a dated strip in the models section (English only, §46) ---------- */
.home-promo-bar { border-bottom: 1px solid var(--line); background: var(--surface); }
.home-promo-inline { margin-top: var(--space-7); border: 1px solid var(--line); border-radius: var(--radius-surface); }
.home-promo-inline .home-promo-bar-inner { width: auto; padding: var(--space-4) var(--space-5); }
.home-promo-bar-disclosure { flex-basis: 100%; order: 10; margin: 0; color: var(--muted); font-size: var(--text-caption); }
.home-promo-bar-inner {
display: flex;
flex-wrap: wrap;
-224
View File
@@ -1,224 +0,0 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { WhalePose } from "@/components/whale-pose";
import { whaleStateLabels, type WhaleStateKey } from "@/lib/content/whale-states";
import {
cueWhale,
loadWhaleReference,
onWhaleCue,
WHALE_PERFORMANCE,
type WhaleCue,
type WhaleDirector,
type WhaleReference,
} from "@/lib/whale-motion";
/**
* The live v2 whale: the desktop app's own Director and rig, drawn on a
* canvas. Until the reference loads (or if it fails) the static rest pose
* stands in, so the hero never shows an empty box.
*
* Motion budget: 30 fps while visible, nothing while hidden or off screen.
* Under prefers-reduced-motion the Director paints its poster pose once per
* change and never runs a clock. The gaze follows the pointer only at rest,
* and only with motion allowed.
*
* With `perform`, it acts out a short session on its own (WHALE_PERFORMANCE)
* and names each phase in a word beneath it. A reader's choice elsewhere on
* the page (a terminal view) takes over for a while, then the loop resumes.
*
* Known limits: one live whale per page (habitat gaze is module state in the
* vendored reference), and cues are illustrative — this component observes
* no real session.
*/
const CHOSEN_HOLD_MS = 15000;
/** Share of the canvas the 124-unit rig box fills; the rest is room for props. */
const INSET = 0.72;
export function WhaleLive({
locale = "en",
className = "",
follow = true,
perform = false,
asleep = false,
}: {
locale?: string;
className?: string;
follow?: boolean;
perform?: boolean;
asleep?: boolean;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const asleepRef = useRef(asleep);
const wakeRef = useRef<() => void>(() => {});
const [ready, setReady] = useState(false);
const [state, setState] = useState<WhaleStateKey>("rest");
const labels = whaleStateLabels(locale);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
let cancelled = false;
let frame = 0;
let last = 0;
let visible = true;
let live: { reference: WhaleReference; director: WhaleDirector } | null = null;
const motion = window.matchMedia("(prefers-reduced-motion: reduce)");
const cleanups: (() => void)[] = [];
const paint = () => {
if (!live) return;
const box = canvas.getBoundingClientRect();
if (!box.width || !box.height) return;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const width = Math.round(box.width * dpr), height = Math.round(box.height * dpr);
if (canvas.width !== width) canvas.width = width;
if (canvas.height !== height) canvas.height = height;
const ctx = canvas.getContext("2d");
if (!ctx) return;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.clearRect(0, 0, box.width, box.height);
// The rig's 124-unit box holds the body; props (page, pad, wrench,
// calves) reach past it, so the whale draws at INSET of the canvas.
const fit = (Math.min(box.width, box.height) / 124) * INSET;
ctx.translate(box.width / 2, box.height / 2);
ctx.scale(fit, fit);
live.reference.acting.render(ctx, live.director, {
theme: "paper",
size: Math.min(box.width, box.height),
dpr,
lod: 0,
pose: follow ? live.reference.habitat.pose(live.director) : undefined,
});
};
const tick = (now: number) => {
frame = 0;
if (!live || live.director.reduced || !visible || document.hidden || asleepRef.current) return;
// Never replay time spent hidden: clamp the step like the app does.
const dt = last ? Math.min((now - last) / 1000, 0.1) : 1 / 30;
if (now - last >= 1000 / 31) {
last = now;
live.director.step(dt);
paint();
}
frame = requestAnimationFrame(tick);
};
const schedule = () => {
if (!frame && live && !live.director.reduced && visible && !document.hidden && !asleepRef.current) {
last = 0;
frame = requestAnimationFrame(tick);
}
};
wakeRef.current = schedule;
loadWhaleReference().then((reference) => {
if (cancelled) return;
const director = new reference.acting.Director({ seed: 11, reduced: motion.matches });
live = { reference, director };
setReady(true);
paint();
schedule();
let turn = 0;
let step = 0;
let timer: ReturnType<typeof setTimeout> | undefined;
const autoplay = (delay: number) => {
clearTimeout(timer);
if (!perform || motion.matches) return;
timer = setTimeout(() => {
const { cue, seconds } = WHALE_PERFORMANCE[step % WHALE_PERFORMANCE.length];
step += 1;
cueWhale(cue);
autoplay(seconds * 1000);
}, delay);
};
const apply = (cue: WhaleCue) => {
if (cue.presence === "Done") turn += 1;
director.set(
cue.presence,
cue.kind ? { kind: cue.kind, observed: true, parallel: cue.parallel } : null,
cue.presence === "Done"
? { freshness: "Live", turnId: `site-${turn}`, status: "completed" }
: { freshness: "Live" },
);
setState(cue.label);
paint();
schedule();
// A reader's choice holds the stage, then the session starts over.
if (cue.chosen) { step = 0; autoplay(CHOSEN_HOLD_MS); }
};
cleanups.push(onWhaleCue(apply));
autoplay(1200);
cleanups.push(() => clearTimeout(timer));
const onMotion = () => { director.setReduced(motion.matches); paint(); schedule(); autoplay(1200); };
motion.addEventListener("change", onMotion);
cleanups.push(() => motion.removeEventListener("change", onMotion));
}).catch(() => { /* The static pose stays in place. */ });
const onVisibility = () => schedule();
document.addEventListener("visibilitychange", onVisibility);
cleanups.push(() => document.removeEventListener("visibilitychange", onVisibility));
if (typeof IntersectionObserver !== "undefined") {
const io = new IntersectionObserver((entries) => {
visible = entries.some((entry) => entry.isIntersecting);
schedule();
});
io.observe(canvas);
cleanups.push(() => io.disconnect());
}
const resize = new ResizeObserver(() => paint());
resize.observe(canvas);
cleanups.push(() => resize.disconnect());
if (follow) {
// The whale glances toward the pointer anywhere in the hero, not only
// over its own box. Passive: scrolling and clicks are untouched.
const move = (event: PointerEvent) => {
if (!live || motion.matches) return;
const box = canvas.getBoundingClientRect();
const fit = (Math.min(box.width, box.height) / 124) * INSET;
if (!fit) return;
live.reference.habitat.observe(
(event.clientX - box.left - box.width / 2) / fit,
(event.clientY - box.top - box.height / 2) / fit,
);
};
const leave = () => live?.reference.habitat.leave();
window.addEventListener("pointermove", move, { passive: true });
document.documentElement.addEventListener("pointerleave", leave, { passive: true });
cleanups.push(() => {
window.removeEventListener("pointermove", move);
document.documentElement.removeEventListener("pointerleave", leave);
});
}
return () => {
cancelled = true;
if (frame) cancelAnimationFrame(frame);
cleanups.forEach((cleanup) => cleanup());
};
}, [follow, perform]);
useEffect(() => {
asleepRef.current = asleep;
if (!asleep) wakeRef.current();
}, [asleep]);
return (
<div className={`whale-live ${className}`.trim()} data-ready={ready || undefined}>
<WhalePose pose="rest" priority className="whale-live-poster" />
<canvas ref={canvasRef} className="whale-live-canvas" aria-hidden="true" />
{perform && (
// Decorative narration: hidden from assistive technology so the loop
// never announces itself every few seconds.
<p className="whale-live-state" data-state={state} aria-hidden="true">
<span className="whale-live-dot" />
{labels[state]}
</p>
)}
</div>
);
}
-2
View File
@@ -1,7 +1,5 @@
import type { LocalizedText } from "./vocabulary";
export const HOME_MAKERS = ["DeepSeek", "Anthropic", "OpenAI", "Google", "xAI", "Moonshot/Kimi", "Qwen", "Z.ai", "MiniMax", "Mistral"];
export const HOME_OVERVIEW = {
modelsCount: { en: "{providers} providers and {models} models in the current source.", zh: "当前源码包含 {providers} 个提供商和 {models} 个模型。" },
modelsAny: { en: "Any OpenAI-compatible endpoint", zh: "任何 OpenAI 兼容端点" },
+3 -10
View File
@@ -1,20 +1,13 @@
export const PARTNER_PROMO = {
locales: ["en"],
href: "https://www.orcarouter.ai/redeem/1a6250545fdc4e0aaccb0a4470159e67",
image: {
src: "/codewhale-x-orcarouter.webp",
width: 1440,
height: 810,
alt: {
en: "Codewhale × OrcaRouter: The whales are teaming up. +30% DeepSeek v4.1 Flash usage.",
zh: "Codewhale × OrcaRouter:双鲸携手,DeepSeek v4.1 Flash 用量增加 30%。",
},
},
endsAt: "2026-11-21T12:29:57Z",
title: "Codewhale × OrcaRouter",
body: {
en: "Get 30% extra DeepSeek V4.1 Flash credit on each OrcaRouter top-up. One claim per developer, open until November 21, 2026.",
zh: "在 OrcaRouter 每笔充值可额外获得 30% 的 DeepSeek V4.1 Flash 额度。每位开发者限领 1 份,报名截至 2026 年 11 月 21 日。",
},
bar: {
en: "30% extra DeepSeek V4.1 Flash credit on each OrcaRouter top-up, until November 21.",
zh: "每笔 OrcaRouter 充值额外获得 30% 的 DeepSeek V4.1 Flash 额度,截至 11 月 21 日。",
@@ -24,7 +17,7 @@ export const PARTNER_PROMO = {
referralTag: { en: "Referral link", zh: "推荐链接" },
cta: { en: "Claim on OrcaRouter", zh: "在 OrcaRouter 领取" },
disclosure: {
en: "Codewhale earns a referral fee from OrcaRouter.",
zh: "Codewhale 会从 OrcaRouter 获得推荐佣金。",
en: "This link is part of a referral campaign; Codewhale may earn a referral reward on top-ups made through it.",
zh: "此链接属于推荐活动;通过此链接充值时,Codewhale 可能获得推荐奖励。",
},
};
-21
View File
@@ -1,21 +0,0 @@
export const RELEASE_HIGHLIGHTS = {
version: "0.10.3",
comingHeading: { en: "Coming in v{version}", zh: "v{version} 即将推出" },
newHeading: { en: "New in v{version}", zh: "v{version} 新功能" },
comingLede: { en: "Merged on main and not in a published release yet. Build main to try it now.", zh: "已合并到 main,尚未包含在已发布版本中。现在可从 main 构建试用。" },
changelogLink: { en: "Read the changelog", zh: "查看更新日志" },
items: [
{ title: "Steer mid-turn", body: { en: "Ctrl+Enter closes the in-flight model request and continues the same turn with your message. Tab and Shift+Tab change mode and permissions while the agent works; the change applies at its next step.", zh: "Ctrl+Enter 会立即结束正在进行的模型请求,并带着你的消息继续同一轮。智能体工作时也可以用 Tab 和 Shift+Tab 切换模式和权限,改动会在它的下一步生效。" } },
{ title: "MCP", body: { en: "MCP servers now connect through the official MCP SDK (2.4) behind Codewhale's Rust broker. Newer servers are discovered with server/discover; older stdio and HTTP servers fall back to the legacy handshake.", zh: "MCP 服务器现在通过官方 MCP SDK(2.4)连接,由 Codewhale 的 Rust 代理把关。较新的服务器通过 server/discover 发现;较旧的 stdio 和 HTTP 服务器回退到旧版握手。" } },
{ title: "MCP fixes", body: { en: "Older servers that reject the discovery probe still connect. A server that refuses sign-in shows its real 401 and the /mcp login hint, and one whose command cannot start says why.", zh: "拒绝发现探测的旧服务器仍可连接。拒绝登录的服务器会显示真实的 401 和 /mcp login 提示;命令无法启动的服务器会说明原因。" } },
{ title: "DeepSeek Harness plugins", body: { en: "Reviewed DSH plugins run in the Native extension host. Tool output is checked against its declared schema, and unsupported options are refused instead of ignored.", zh: "经过审核的 DSH 插件在 Native 扩展宿主中运行。工具输出按声明的 schema 校验,不支持的选项会被拒绝而不是被忽略。" } },
{ title: "Computer Use", body: { en: "No longer bundled in the binary. Install it from the codewhale marketplace, which points at its own release; every decision still needs your approval.", zh: "不再内置于二进制文件中。可从 codewhale 插件市场安装,市场指向它的独立发布版本;每个决定仍需你批准。" } },
{ title: "Opper and Runcrate", body: { en: "Two more bundled providers in /provider: Opper, an EU-hosted OpenAI-compatible gateway (OPPER_API_KEY), and Runcrate (RUNCRATE_API_KEY). Each provider's live model listing is the model authority.", zh: "/provider 新增两个内置提供商:Opper(欧盟托管的 OpenAI 兼容网关,OPPER_API_KEY)和 Runcrate(RUNCRATE_API_KEY)。模型列表以各提供商的实时列表为准。" } },
{ title: "Recall after compaction", body: { en: "Two read-only tools, context_archive_search and context_archive_get, let the model search and read what compaction replaced, with bounded results and credential masking.", zh: "两个只读工具 context_archive_search 和 context_archive_get 让模型搜索并读取被压缩替换的内容,结果有上限,凭据会被遮蔽。" } },
{ title: "Goals ask before guessing", body: { en: "When a /goal needs an answer only you can give, the model is told to yield and ask instead of inventing one. This is prompt guidance, not enforcement.", zh: "当 /goal 需要只有你才能给出的答案时,模型会被引导交还控制权并提问,而不是自行编造。这是提示引导,并非强制。" } },
{ title: "Image-heavy sessions", body: { en: "Older images are left out of an outbound request with a receipt, and an HTTP 413 is retried once with a smaller body. Saved images stay intact.", zh: "较早的图片会从发出的请求中省略并留下记录;遇到 HTTP 413 时会用更小的请求体重试一次。已保存的图片保持不变。" } },
{ title: "Instruction budgets", body: { en: "Global and project instructions get separate budgets, and every trim, skip or refusal shows in /context report.", zh: "全局指令和项目指令分别拥有各自的预算,每次截断、跳过或拒绝都会显示在 /context report 中。" } },
{ title: "codewhale uninstall", body: { en: "Previews the release files the installer recorded, on Unix and Windows; --yes removes only those. Package-managed copies print their own removal command. Your data stays unless you add --purge-data. Windows removal is compile-checked but not yet exercised on a Windows host.", zh: "在 Unix 和 Windows 上预览安装程序记录的发布文件;--yes 只删除这些文件。包管理器安装的版本会给出各自的卸载命令。除非加上 --purge-data,否则你的数据会保留。Windows 上的删除已通过编译检查,但尚未在 Windows 主机上实际运行。" } },
{ title: "/rc", body: { en: "codewhale rc attaches to the hosted control plane for web Chat again: it accepts the exact released runtime identity instead of only the retired 0.9.13 one.", zh: "codewhale rc 可以再次连接托管控制平面以使用网页 Chat:它接受确切的已发布运行时身份,而不再只接受已退役的 0.9.13。" } },
],
};