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 @@
-
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 `