diff --git a/.gitignore b/.gitignore index 91fe18b..2d768c2 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,10 @@ cache/ packages/web/test-results/ packages/web/playwright-report/ +# blog images staged locally by the landing capture/render scripts, then uploaded to +# the community repo's blog-assets/ — never committed here (see lib/links.ts) +packages/landing/.blog-assets/ + # local design-doc symlink and agent instructions /design /AGENTS.md diff --git a/packages/landing/public/blog-assets/benchmark-light.svg b/packages/landing/public/blog-assets/benchmark-light.svg deleted file mode 100644 index 31c480c..0000000 --- a/packages/landing/public/blog-assets/benchmark-light.svg +++ /dev/null @@ -1,48 +0,0 @@ - -Accuracy · suite total · higher is better -Total cost (USD) · lower is better -Data analysis — 15 tasks, single run - -PenguinHarness - -66.67% -Claude Code - -53.33% -OpenAI Codex - -53.33% - -PenguinHarness - -$0.55 -Claude Code - -$38.48 -OpenAI Codex - -$19.41 -Coding — 40 tasks × 2 runs - -PenguinHarness - -71.25% -Claude Code - -86.25% -OpenAI Codex - -71.25% - -PenguinHarness - -$3.81 -Claude Code - -$146.97 -OpenAI Codex - -$220.08 -Each harness runs the model it is normally paired with: PenguinHarness on DeepSeek V4 Pro, Claude Code on Claude Opus 4.8, -OpenAI Codex on GPT-5.5. Accuracy, Tokens and cost are suite totals at official pricing. - diff --git a/packages/landing/public/blog-assets/databricks-pareto.png b/packages/landing/public/blog-assets/databricks-pareto.png deleted file mode 100644 index 80be831..0000000 Binary files a/packages/landing/public/blog-assets/databricks-pareto.png and /dev/null differ diff --git a/packages/landing/public/blog-assets/free-models-page-en-light.webp b/packages/landing/public/blog-assets/free-models-page-en-light.webp deleted file mode 100644 index 03c40f4..0000000 Binary files a/packages/landing/public/blog-assets/free-models-page-en-light.webp and /dev/null differ diff --git a/packages/landing/public/blog-assets/free-models-page-zh-light.webp b/packages/landing/public/blog-assets/free-models-page-zh-light.webp deleted file mode 100644 index 7954b82..0000000 Binary files a/packages/landing/public/blog-assets/free-models-page-zh-light.webp and /dev/null differ diff --git a/packages/landing/public/blog-assets/gemini-3-6-flash-evals.webp b/packages/landing/public/blog-assets/gemini-3-6-flash-evals.webp deleted file mode 100644 index 0dcbce1..0000000 Binary files a/packages/landing/public/blog-assets/gemini-3-6-flash-evals.webp and /dev/null differ diff --git a/packages/landing/public/blog-assets/rag-app-en-light.webp b/packages/landing/public/blog-assets/rag-app-en-light.webp deleted file mode 100644 index 0a2115b..0000000 Binary files a/packages/landing/public/blog-assets/rag-app-en-light.webp and /dev/null differ diff --git a/packages/landing/public/blog-assets/rag-app-zh-light.webp b/packages/landing/public/blog-assets/rag-app-zh-light.webp deleted file mode 100644 index b9dba57..0000000 Binary files a/packages/landing/public/blog-assets/rag-app-zh-light.webp and /dev/null differ diff --git a/packages/landing/scripts/capture-blog-shots.mjs b/packages/landing/scripts/capture-blog-shots.mjs index c3c7d97..0895f31 100644 --- a/packages/landing/scripts/capture-blog-shots.mjs +++ b/packages/landing/scripts/capture-blog-shots.mjs @@ -10,9 +10,15 @@ * dist -> provision one demo user per UI language (password rotated once so the * initial-password banner never shows) -> open /models, scroll the OpenRouter group * to the top of the viewport (Claude Opus 5 sits on its first card row, the free - * rows a few rows below) -> screenshot, light theme, zh + en, into - * public/blog-assets/ as free-models-page--light.webp (re-encoded to WebP - * inside Chromium, same as capture-shots.mjs). + * rows a few rows below) -> screenshot, light theme, zh + en, as + * free-models-page--light.webp (re-encoded to WebP inside Chromium, same as + * capture-shots.mjs). + * + * Output is a two-step flow, because blog images are not committed to this repo: + * 1. this script writes into the gitignored staging dir packages/landing/.blog-assets/; + * 2. upload the files it produced to the `blog-assets/` directory of the sibling + * `Prism-Shadow/penguin-harness-community` repo, which is what the posts load from + * (the renderer resolves /blog-assets/ there — see src/lib/links.ts). * * Prereqs: `pnpm --filter @prismshadow/penguin-{skills,core,server,web} build` and * Playwright's chromium. Run: `node scripts/capture-blog-shots.mjs` (or @@ -27,7 +33,8 @@ import { chromium } from "@playwright/test"; const HERE = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(HERE, "../../.."); -const OUT_DIR = path.resolve(HERE, "../public/blog-assets"); +// Gitignored staging dir: these images are hosted in the community repo, not committed here. +const OUT_DIR = path.resolve(HERE, "../.blog-assets"); const SRV_PORT = 8944; // Distinct from capture-shots.mjs (8940/8941) so both can run. // On loopback binds the App is canonically served on `localhost`; the 127.0.0.1 // counterpart is the Workspace-preview host, where /api deliberately answers 401 diff --git a/packages/landing/scripts/render-benchmark-svg.mjs b/packages/landing/scripts/render-benchmark-svg.mjs index 430733a..9edbfa4 100644 --- a/packages/landing/scripts/render-benchmark-svg.mjs +++ b/packages/landing/scripts/render-benchmark-svg.mjs @@ -2,9 +2,15 @@ * Renders the README / blog benchmark chart from the same data the landing page uses * (src/lib/benchmark-data.ts), so the static SVGs cannot drift from the site when the * numbers are refreshed. Emits: - * assets/readme/benchmark-light.svg - * assets/readme/benchmark-dark.svg - * packages/landing/public/blog-assets/benchmark-light.svg + * assets/readme/benchmark-light.svg (committed here, used by the README) + * assets/readme/benchmark-dark.svg (committed here, used by the README) + * packages/landing/.blog-assets/benchmark-light.svg + * + * That last one is a two-step flow: blog images are not committed to this repo, so the file + * lands in the gitignored staging dir packages/landing/.blog-assets/ and is then uploaded to + * the `blog-assets/` directory of the sibling `Prism-Shadow/penguin-harness-community` repo, + * which is what the posts load from (the renderer resolves /blog-assets/ there — see + * packages/landing/src/lib/links.ts). * * Two panels per suite (accuracy, cost), horizontal bars scaled linearly from zero — the * cost spread is ~70x, so the PenguinHarness bar is by far the shortest. That is the @@ -15,7 +21,7 @@ * * Run: node packages/landing/scripts/render-benchmark-svg.mjs */ -import { readFileSync, writeFileSync } from "node:fs"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -171,9 +177,11 @@ const bench = loadBench(); const targets = [ ["light", path.join(REPO, "assets/readme/benchmark-light.svg")], ["dark", path.join(REPO, "assets/readme/benchmark-dark.svg")], - ["light", path.join(LANDING, "public/blog-assets/benchmark-light.svg")], + ["light", path.join(LANDING, ".blog-assets/benchmark-light.svg")], ]; for (const [theme, file] of targets) { + // The blog staging dir is gitignored, so it does not exist in a fresh clone. + mkdirSync(path.dirname(file), { recursive: true }); writeFileSync(file, render(theme, bench)); console.log(`wrote ${path.relative(REPO, file)}`); } diff --git a/packages/landing/src/lib/links.ts b/packages/landing/src/lib/links.ts index 2692ef9..1f16fe3 100644 --- a/packages/landing/src/lib/links.ts +++ b/packages/landing/src/lib/links.ts @@ -25,19 +25,39 @@ export const INSTALL_CMD = "curl -fsSL https://penguin.ooo/install.sh | sh"; export const INSTALL_CMD_WINDOWS = "irm https://penguin.ooo/install.ps1 | iex"; /** - * Demo videos live in the sibling `penguin-harness-community` repo rather than in this - * one: they are ~9 MB each, and this repo's whole history is ~17 MB, so committing them - * here would triple what every contributor clones for assets only the marketing site - * shows. Served by raw.githubusercontent with `accept-ranges: bytes` (seeking works), - * `access-control-allow-origin: *` and a 5-minute cache. The `application/octet-stream` - * content type does not block playback — `nosniff` is only enforced for scripts and - * styles, and `