diff --git a/.env.example b/.env.example
index d7a0209..2b7f48f 100644
--- a/.env.example
+++ b/.env.example
@@ -1,17 +1,13 @@
-# These are auto-set by branch on Cloudflare Workers Builds (see astro.config.mjs):
-# main branch → Google Analytics ON, indexable
-# other branch → Google Analytics OFF, robots noindex
-# Only set them below to override that locally.
+# Analytics + indexing are auto-set by branch on Cloudflare Workers Builds
+# (see astro.config.mjs): main → Google Analytics ON + indexable; other branches
+# → GA OFF + robots noindex. You normally don't set anything here.
-# Google Analytics 4 measurement ID (e.g. G-XXXXXXXXXX). Leave empty to disable.
-# Analytics only loads after the visitor accepts cookies in the consent banner.
-PUBLIC_GA_ID=
+# Production Google Analytics 4 ID (e.g. G-XXXXXXXXXX). Set this as a BUILD
+# variable on the production Cloudflare Worker (Workers Builds → Settings →
+# Build → Variables and Secrets), NOT as a runtime variable. Leave unset to
+# disable analytics. GA loads only after the visitor accepts the cookie banner.
+SITE_GA_ID=
-# Set to 1 to emit robots noindex on every page. Auto-set to 1 for non-main
-# branch builds in CI; leave empty otherwise.
+# Force robots noindex on every page (auto-set to 1 for non-main branch builds
+# in CI; leave empty otherwise).
PUBLIC_NOINDEX=
-
-# Production Google Analytics 4 ID, injected by the deploy env (e.g. Cloudflare
-# build variable SITE_GA_ID). Consumed by astro.config.mjs on `main` builds.
-# Leave unset to disable analytics. Local dev: set PUBLIC_GA_ID above instead.
-SITE_GA_ID=
diff --git a/DEPLOYMENT-GIT.md b/DEPLOYMENT-GIT.md
deleted file mode 100644
index 1edfb01..0000000
--- a/DEPLOYMENT-GIT.md
+++ /dev/null
@@ -1,152 +0,0 @@
-# Deployment (Git-based auto-deploy, no GitHub Actions)
-
-This guide sets up **push-to-deploy** with Cloudflare Workers Builds — Cloudflare
-watches your GitHub branches directly and builds + deploys on every push. **No
-GitHub Actions, no `wrangler` in CI you maintain.**
-
-- Push to **`main`** → deploys **production** (`goodwebtools.com`).
-- Push to **`develop`** → deploys **staging** (`goodwebtools-staging.workers.dev`).
-
-> This is an alternative to the manual/CLI flow in [DEPLOYMENT.md](./DEPLOYMENT.md).
-> The architecture (static `dist/` + a Worker that streams models from R2) and the
-> one-time R2 model upload are identical — read those sections there first. This
-> doc only covers the two-branch git wiring.
-
----
-
-## How it maps
-
-Two Cloudflare **Worker** services, both connected to the **same** GitHub repo,
-each watching a different production branch:
-
-| Environment | Worker service | Production branch | Deploy command | URL |
-|-------------|----------------|-------------------|----------------|-----|
-| Production | `goodwebtools` | `main` | `npx wrangler deploy` | `goodwebtools.com` |
-| Staging | `goodwebtools-staging` | `develop` | `npx wrangler deploy --env staging` | `goodwebtools-staging.workers.dev` |
-
-The `staging` environment is defined in [`wrangler.jsonc`](./wrangler.jsonc)
-(`env.staging`, worker name `goodwebtools-staging`). It reuses the **same R2
-bucket** — the ML models are immutable and safe to share between environments.
-
-```
-git push origin main ─▶ Cloudflare Workers Builds ─▶ wrangler deploy ─▶ production
-git push origin develop ─▶ Cloudflare Workers Builds ─▶ wrangler deploy --env staging ─▶ staging
-```
-
----
-
-## One-time setup
-
-### 0. Prerequisites (shared with DEPLOYMENT.md)
-
-- Create the R2 bucket and upload the model assets **once** (see
- [DEPLOYMENT.md → Upload the ML model assets to R2](./DEPLOYMENT.md#upload-the-ml-model-assets-to-r2)).
- Both environments read from the same bucket, so you only do this once.
-
-### 1. Production Worker (branch `main`)
-
-Cloudflare dashboard → **Workers & Pages → Create → Workers → Import a repository**:
-
-1. Select the `slaveofcode/goodwebtools` repo.
-2. **Worker name:** `goodwebtools`
-3. **Production branch:** `main`
-4. **Build command:** `npm run build`
-5. **Deploy command:** `npx wrangler deploy`
-6. **Build variables & secrets** (these are read at *build* time by Vite):
- - `PUBLIC_GA_ID` = your GA4 id, e.g. `G-XXXXXXXXXX` (omit to disable analytics)
- - leave `PUBLIC_NOINDEX` **unset** (production must be indexable)
-7. Save & deploy. Then add the custom domain `goodwebtools.com` under the
- Worker's **Settings → Domains & Routes**.
-
-### 2. Staging Worker (branch `develop`)
-
-Repeat **Create → Import a repository** for the *same* repo, as a second Worker:
-
-1. Select the same repo.
-2. **Worker name:** `goodwebtools-staging`
-3. **Production branch:** `develop` ← the branch this Worker deploys
-4. **Build command:** `npm run build`
-5. **Deploy command:** `npx wrangler deploy --env staging`
-6. **Build variables & secrets:**
- - **do not** set `PUBLIC_GA_ID` (no analytics on staging)
- - set `PUBLIC_NOINDEX` = `1` (emits `noindex` so staging stays out of search)
-7. Save & deploy. It's reachable at `goodwebtools-staging.workers.dev` (or attach
- `staging.goodwebtools.com`).
-
-That's it. From now on every push to `main` or `develop` auto-builds and deploys
-the matching environment.
-
----
-
-## Why `PUBLIC_*` variables go in the *build* settings
-
-`PUBLIC_GA_ID` and `PUBLIC_NOINDEX` are consumed by `npm run build` (Vite inlines
-`import.meta.env.PUBLIC_*` into the static output) — they are **not** Worker
-runtime bindings. So they must be set as **Build variables** on each Worker's
-Workers Builds configuration, and they differ per environment:
-
-| Variable | Production (`main`) | Staging (`develop`) |
-|----------|---------------------|---------------------|
-| `PUBLIC_GA_ID` | your GA4 id | *(unset)* |
-| `PUBLIC_NOINDEX` | *(unset)* | `1` |
-
-Changing a build variable takes effect on the **next push/redeploy**.
-
----
-
-## Feature-branch previews (optional)
-
-Each Worker only auto-**deploys** its own production branch. Pushing any *other*
-branch to a connected Worker triggers a **preview build** instead (Cloudflare runs
-`npx wrangler versions upload`, giving a temporary versioned preview URL) without
-touching the live environment. You can turn this off per Worker under **Settings
-→ Builds → Non-production branches** if you don't want preview builds.
-
----
-
-## Promoting staging → production
-
-Because both environments build from the same commit graph, promotion is just a
-fast-forward merge and a push:
-
-```bash
-git checkout main
-git merge --ff-only develop
-git push origin main # ← triggers the production deploy
-git checkout develop
-```
-
----
-
-## Verifying a deploy
-
-- **Production indexable, staging not:**
- ```bash
- curl -s https://goodwebtools.com/ | grep -o ']*>'
- # → index, follow
- curl -s https://goodwebtools-staging.workers.dev/ | grep -o ']*>'
- # → noindex, nofollow
- ```
-- **Analytics only in production:** `PUBLIC_GA_ID` is present in the production
- HTML and absent in staging (and it still only loads after cookie consent).
-- **Models serve:** open a page that uses an AI tool and confirm `/models/…`
- returns `200` from R2 (see DEPLOYMENT.md troubleshooting).
-
----
-
-## Rollback
-
-Every deploy is a Worker **version**. In the dashboard → the Worker → **Deployments**,
-pick a previous version and **Rollback** — instant, no rebuild. Or push a revert
-commit to the branch to redeploy the previous state.
-
----
-
-## Notes
-
-- **Local manual deploys still work:** `npm run deploy` (production) and
- `npm run deploy:staging` (staging) run the same commands Cloudflare runs.
-- **`wrangler.jsonc` is the single source of truth** for both environments; the
- staging worker name and bindings live under `env.staging`.
-- **Secrets:** none are committed. `PUBLIC_*` build vars are configured in the
- Cloudflare dashboard per Worker; `.env`/`.dev.vars` stay local and gitignored.
diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md
index b97b0ca..56ec53c 100644
--- a/DEPLOYMENT.md
+++ b/DEPLOYMENT.md
@@ -1,175 +1,246 @@
# Deployment Guide
GoodWebTools is a **static Astro site** deployed to **Cloudflare Workers** (with
-Static Assets), plus a small Worker that streams the AI-tool model files from an
-**R2 bucket**. Everything runs client-side; the Worker only serves files.
+Static Assets), fronted by a small Worker that streams the AI-tool model files
+from an **R2 bucket**. Everything runs client-side; the Worker only serves files.
-> **Prefer push-to-deploy?** For a two-branch, git-based auto-deploy
-> (`main` → production, `develop` → staging) with **no GitHub Actions**, see
-> [DEPLOYMENT-GIT.md](./DEPLOYMENT-GIT.md). This guide covers the architecture,
-> the one-time R2 model upload (shared by both), and manual/CLI deploys.
+Deploys are **push-to-deploy** via Cloudflare Workers Builds — Cloudflare watches
+the GitHub branches and builds + deploys on every push. **No GitHub Actions.**
+
+- Push to **`main`** → **production** (`goodwebtools.com`)
+- Push to **`develop`** → **staging** (`goodwebtools-staging.workers.dev`)
## Architecture
- `npm run build` → static site in `dist/` (HTML, JS, CSS, WASM).
-- `worker/index.js` → serves `/models/*` from the R2 bucket `MODELS`, and
- delegates everything else to the static build (`ASSETS` binding).
-- `wrangler.jsonc` → wires it together (`main`, `assets`, `r2_buckets`).
-
-> **Workers, not Pages.** This project deploys **one Cloudflare Worker with
-> Static Assets** — *not* Cloudflare Pages. A Worker is required because a plain
-> static/Pages site can't stream the large ML model files at `/models/*`; the
-> Worker sits in front and routes `/models/*` to **R2** while serving everything
-> else from the bundled **Static Assets**. (The static-asset half plays the role
-> Pages would, but it's all a single Worker.)
+- `worker/index.js` → serves `/models/*` from the R2 bucket (`MODELS` binding),
+ and delegates everything else to the static build (`ASSETS` binding).
+- `wrangler.jsonc` → wires it together (worker name, `assets`, `r2_buckets`, and
+ the `env.staging` environment).
-### Deployment flow (build → deploy → runtime)
+> **Workers, not Pages.** This deploys **one Cloudflare Worker with Static
+> Assets** — *not* Cloudflare Pages. A Worker is required because a plain static
+> site can't stream the large ML model files at `/models/*`; the Worker routes
+> `/models/*` to **R2** and serves everything else from the bundled Static Assets.
```
- BUILD & DEPLOY — automatic on every push (Cloudflare Workers Builds; no GitHub Actions)
+ BUILD & DEPLOY — automatic on every push (Cloudflare Workers Builds)
┌─────────────┐ git push ┌────────────────────────┐ npm run build ┌────────────────┐
│ GitHub repo │ ──────────▶ │ Cloudflare Workers │ ───────────────▶ │ dist/ │
- │ main / │ (branch │ Builds (CI runner) │ + postbuild │ static site: │
- │ develop │ watched) │ │ prune │ HTML·JS·CSS· │
- └─────────────┘ │ then: wrangler deploy │ │ fonts·small │
- └───────────┬─────────────┘ │ wasm (mupdf, │
- │ deploys │ libarchive, │
- │ the Worker │ sqlite) │
- ▼ └───────┬────────┘
- ┌──────────────────────────────────────────────────────────┐ │ uploaded as
- │ Cloudflare Worker (worker/index.js) │◀─────────────┘ Static Assets
+ │ main / │ (branch │ Builds (CI runner) │ + postbuild │ static site │
+ │ develop │ watched) │ then: wrangler deploy │ prune │ │
+ └─────────────┘ └───────────┬─────────────┘ └───────┬────────┘
+ │ deploys the Worker │ uploaded as
+ ▼ │ Static Assets
+ ┌──────────────────────────────────────────────────────────┐◀─────────────┘
+ │ Cloudflare Worker (worker/index.js) │
│ binding ASSETS ─▶ dist/ · binding MODELS ─▶ R2 │
└──────────────────────────────────────────────────────────┘
- ▲
- │ read at runtime (NOT part of the git build)
+ ▲ read at runtime (NOT part of the git build)
┌──────────────────────────────────────────────────────────┐
│ R2 bucket "goodwebtools-models" — uploaded ONCE, by hand │
- │ /imgly /mediapipe /esrgan-slim /ort /lama /ffmpeg (~540 MB of ML models + wasm)
+ │ /imgly /mediapipe /esrgan-slim /ort /lama /ffmpeg (~540 MB)
└──────────────────────────────────────────────────────────┘
-
RUNTIME — every visitor request
-
- Browser ──▶ Cloudflare Worker (worker/index.js)
- │
- ├─ path starts with /models/ ─▶ env.MODELS.get(key) ─▶ R2 bucket (big models)
- │ hit → stream + immutable cache; miss → 404
- │
- └─ anything else ─▶ env.ASSETS.fetch() ─▶ dist/ static assets
- (HTML, JS, CSS, fonts, small wasm)
+ Browser ──▶ Worker ──┬─ /models/* ─▶ env.MODELS.get(key) ─▶ R2 (hit → stream + immutable cache; miss → 404)
+ └─ everything ─▶ env.ASSETS.fetch() ─▶ dist/ static assets
```
**What goes where**
| Content | Lives in | Served by | Deployed by |
|---------|----------|-----------|-------------|
-| HTML · JS · CSS · fonts · small wasm (mupdf, libarchive, sqlite) | Worker **Static Assets** (from `dist/`) | `ASSETS` binding | `git push` → Workers Builds |
-| The routing logic (`/models/*` → R2, else → assets) | The **Worker** (`worker/index.js`) | the Worker itself | `git push` → Workers Builds |
-| Large ML models + ORT/ffmpeg wasm (~540 MB) | **R2** bucket `goodwebtools-models` | `MODELS` binding (Worker streams) | **one-time manual** upload (`wrangler r2 object put`) |
+| HTML · JS · CSS · fonts · small wasm (mupdf, libarchive, sqlite) | Worker Static Assets (from `dist/`) | `ASSETS` binding | `git push` → Workers Builds |
+| Routing logic (`/models/*` → R2, else → assets) | The Worker (`worker/index.js`) | the Worker itself | `git push` → Workers Builds |
+| Large ML models + ORT/ffmpeg wasm (~540 MB) | **R2** bucket | `MODELS` binding | **one-time manual** upload (`npm run sync:r2`) |
+
+The models are the only piece **not** part of the git build — uploaded to R2 once
+(see below) and immutable, so a normal push never re-uploads ~540 MB.
-The models are the only piece that is **not** part of the git build — they're
-uploaded to R2 once (see below) and are immutable, so a normal push never
-re-uploads ~540 MB. For the two-branch git wiring (main → production,
-develop → staging), see [DEPLOYMENT-GIT.md](./DEPLOYMENT-GIT.md).
+## Environments
+
+Two Cloudflare **Worker** services, both connected to the **same** GitHub repo,
+each watching a different production branch:
+
+| Environment | Worker service | Branch | Deploy command | URL |
+|-------------|----------------|--------|----------------|-----|
+| Production | `goodwebtools` | `main` | `npx wrangler deploy` | `goodwebtools.com` |
+| Staging | `goodwebtools-staging` | `develop` | `npx wrangler deploy --env staging` | `goodwebtools-staging.workers.dev` |
+
+The `staging` environment is defined in [`wrangler.jsonc`](./wrangler.jsonc)
+(`env.staging`). Each environment has its **own** R2 bucket
+(`goodwebtools-models` / `goodwebtools-models-staging`).
+
+```
+git push origin main ─▶ Workers Builds ─▶ wrangler deploy ─▶ production
+git push origin develop ─▶ Workers Builds ─▶ wrangler deploy --env staging ─▶ staging
+```
## One-time setup
You need a Cloudflare account and the Wrangler CLI (pinned as a devDependency).
+### 1. Authenticate + create the R2 buckets
+
```bash
-npx wrangler login # authenticate
-npx wrangler r2 bucket create goodwebtools-models # production model bucket
-npx wrangler r2 bucket create goodwebtools-models-staging # staging model bucket
+npx wrangler login
+npx wrangler r2 bucket create goodwebtools-models # production models
+npx wrangler r2 bucket create goodwebtools-models-staging # staging models
```
-### Upload the ML model assets to R2
+### 2. Upload the ML model assets to R2
-The Phase-5 AI tools each need model + runtime files (**~540 MB total**) that are
-**not** committed — they live in R2 under `/models/`, mirroring the on-disk
-layout. `npm run stage:models` fetches/copies them all into `public/models/`:
+The AI tools need model + runtime files (**~540 MB total**) that are **not**
+committed — they live in R2 under `/models/`, mirroring the on-disk layout.
| Prefix | Tool(s) | Approx size |
|--------|---------|-------------|
-| `imgly/` | Background Remover, Portrait Blur (ISNet + ort) | ~211 MB |
-| `mediapipe/` | Face Blur (BlazeFace + tasks-vision WASM) | ~35 MB |
-| `esrgan-slim/` | Image Upscaler (ESRGAN weights) | ~4 MB |
+| `imgly/` | Background Remover, Portrait Blur | ~211 MB |
+| `mediapipe/` | Face Blur | ~35 MB |
+| `esrgan-slim/` | Image Upscaler | ~4 MB |
| `ort/` | Object Remover (onnxruntime-web WASM) | ~76 MB |
-| `lama/` | Object Remover (LaMa ONNX, fixed 512×512) | ~200 MB |
-| `ffmpeg/` | Video → GIF and other Media tools (ffmpeg.wasm **ESM** core) | ~31 MB |
+| `lama/` | Object Remover (LaMa ONNX) | ~200 MB |
+| `ffmpeg/` | Video/audio tools (ffmpeg.wasm) | ~31 MB |
-Stage, then sync the whole tree to R2 (keys keep their subfolders). `sync:r2`
-reads the bucket name(s) from `wrangler.jsonc`, so it targets whatever
-production/staging are configured to use:
+Stage them locally, then sync to R2 (`sync:r2` reads the bucket names from
+`wrangler.jsonc`, so it targets whatever prod/staging are configured to use):
```bash
npm run stage:models # → public/models/** (gitignored; downloads LaMa + face model)
-# Cloudflare auth first: set CLOUDFLARE_API_TOKEN, or run `npx wrangler login`
-npm run sync:r2 # upload to every configured bucket (prod + staging, deduped)
-# or target one environment:
-npm run sync:r2:prod # production bucket only
-npm run sync:r2:staging # staging bucket only
-# preview without uploading:
-npm run sync:r2 -- --dry-run
+# Cloudflare auth first: set CLOUDFLARE_API_TOKEN, or `npx wrangler login`
+npm run sync:r2 # upload to every configured bucket (prod + staging)
+npm run sync:r2:prod # …or just production
+npm run sync:r2:staging # …or just staging
+npm run sync:r2 -- --dry-run # preview without uploading
```
Uploads are idempotent (`wrangler r2 object put --remote`), so re-running retries
-any failures and re-staging + re-syncing updates a bumped model version. (You can
-also drag the folders into the bucket via the Cloudflare dashboard, or use
-`rclone`.) **Until the models are in R2, the AI tools 404 in prod.**
-
-> Production uses `goodwebtools-models` and staging uses
-> `goodwebtools-models-staging`, so `sync:r2` uploads to **both**. Create the
-> staging bucket once: `npx wrangler r2 bucket create goodwebtools-models-staging`.
+failures and re-staging + re-syncing updates a bumped model version. (You can
+also drag the folders into the bucket via the dashboard, or use `rclone`.)
+**Until the models are in R2, the AI tools 404.**
> **Local dev:** `npm run stage:models` also lets `npm run dev` serve the models
-> from `public/models/**`. Remove that folder before a *local* `wrangler deploy`
-> so ~540 MB isn't uploaded as static assets — production loads them from R2.
-> (CI never has it: `public/models/` is gitignored.)
+> from `public/models/**`. `public/models/` is gitignored, so CI never uploads it.
+
+### 3. Connect Workers Builds (one Worker per environment)
+
+In the Cloudflare dashboard → **Workers & Pages → Create → Workers → Import a
+repository**, create **two** Workers from the same repo:
-## Deploying
+| | Production | Staging |
+|--|-----------|---------|
+| **Worker name** | `goodwebtools` | `goodwebtools-staging` |
+| **Production branch** | `main` | `develop` |
+| **Build command** | `npm run build` | `npm run build` |
+| **Deploy command** | `npx wrangler deploy` | `npx wrangler deploy --env staging` |
-### Option A — Cloudflare Workers Builds (recommended, git-based)
+Then attach the custom domain (`goodwebtools.com`) to the production Worker under
+**Settings → Domains & Routes**. From now on, every push to `main`/`develop`
+auto-builds and deploys the matching environment.
-Connect the GitHub repo in the Cloudflare dashboard → Workers & Pages → Create →
-Import a repository, then set:
+### 4. Build variables
-- **Build command:** `npm run build`
-- **Deploy command:** `npx wrangler deploy`
-- **Production branch:** the branch you want live (currently the app lives on
- `develop` — set this to `develop`, or merge `develop → main` first).
+Analytics and indexing are **auto-gated by branch** in `astro.config.mjs` (using
+the `WORKERS_CI_BRANCH` Cloudflare injects), so you barely configure anything:
-Cloudflare rebuilds and deploys on every push to that branch.
+| | Production (`main`) | Staging / other branches |
+|--|--------------------|--------------------------|
+| Google Analytics | on — from `SITE_GA_ID` | off |
+| `robots` | `index, follow` | `noindex, nofollow` |
-### Option B — Manual
+The **only** build variable to set is `SITE_GA_ID` on the **production** Worker
+(Workers Builds → Settings → Build → Variables) — your GA4 id, e.g.
+`G-XXXXXXXXXX`. Leave it unset to disable analytics. Everything else is automatic;
+you do **not** set `PUBLIC_NOINDEX` by hand (the branch gate handles it). GA is
+also consent-gated — it only loads after the visitor accepts the cookie banner.
+
+> These are **build-time** variables (Vite inlines `import.meta.env.PUBLIC_*` into
+> the static output), so they must be **Build variables**, not Worker runtime
+> bindings. Changes take effect on the next push/redeploy.
+
+## Everyday deploys
+
+Just push — Workers Builds does the rest:
```bash
-npm run deploy # = npm run build && wrangler deploy
+git push origin develop # → staging
+git push origin main # → production
```
+**Manual/CLI** (runs the same commands Cloudflare runs) still works:
+
+```bash
+npm run deploy # production (= npm run build && wrangler deploy)
+npm run deploy:staging # staging (= … && wrangler deploy --env staging)
+```
+
+## Promoting staging → production
+
+Both environments build from the same commit graph, so promotion is a
+fast-forward merge and a push:
+
+```bash
+git checkout main
+git merge --ff-only develop
+git push origin main # ← triggers the production deploy
+git checkout develop
+```
+
+## Feature-branch previews
+
+Each Worker only auto-**deploys** its own production branch. Pushing any *other*
+branch triggers a **preview build** (Cloudflare runs `npx wrangler versions
+upload`, giving a temporary versioned preview URL) without touching the live
+environment. Toggle this per Worker under **Settings → Builds → Non-production
+branches**.
+
+## Rollback
+
+Every deploy is a Worker **version**. In the dashboard → the Worker →
+**Deployments**, pick a previous version and **Rollback** — instant, no rebuild.
+Or push a revert commit to the branch to redeploy the previous state.
+
+## Verifying a deploy
+
+```bash
+# Production indexable, staging not:
+curl -s https://goodwebtools.com/ | grep -o ']*>' # → index, follow
+curl -s https://goodwebtools-staging.workers.dev/ | grep -o ']*>' # → noindex, nofollow
+
+# Models serve from R2 (200):
+curl -sI https://goodwebtools.com/models/lama/lama_fp32.onnx | head -1 # → HTTP/2 200
+```
+
+Analytics: `SITE_GA_ID` is inlined in the production HTML and absent in staging.
+
## Notes & troubleshooting
-- **`wrangler deploy` needs the R2 bucket to exist** — create it first (above),
- or the deploy fails on the `MODELS` binding.
-- **Per-file asset limit is 25 MB.** The site's own WASM (mupdf ~10 MB,
- libarchive ~1 MB) is fine; large ML models go to R2 precisely to avoid this.
- onnxruntime-web's wasm gets emitted into `dist/_astro` at ~26 MB but is unused
- (ORT loads from `/models/ort/`); the `postbuild` step (`scripts/prune-dist.mjs`)
- removes it automatically so deploys stay under the limit.
-- **Model versions must match.** `@imgly/background-removal` and
- `@imgly/background-removal-data` are pinned to the same version; a mismatch
- causes "Resource … not found" at runtime.
-- **404s / routing:** Astro emits `dist/tools//index.html`; Cloudflare
- serves trailing-slash variants automatically and uses `dist/404.html`
+- **`wrangler deploy` needs the R2 bucket to exist** — create it first, or the
+ deploy fails on the `MODELS` binding.
+- **Per-file asset limit is 25 MB.** Site WASM (mupdf ~10 MB, libarchive ~1 MB)
+ is fine; large ML models go to R2 to avoid this. onnxruntime-web's ~26 MB wasm
+ is emitted into `dist/_astro` but unused (ORT loads from `/models/ort/`); the
+ `postbuild` step (`scripts/prune-dist.mjs`) removes it so deploys stay under the
+ limit.
+- **Node heap:** the asset-heavy build sets `--max-old-space-size=8192` in the
+ `build` script (via `cross-env`) so Cloudflare's build doesn't OOM.
+- **Peer deps:** `.npmrc` sets `legacy-peer-deps=true` so `npm ci` resolves the
+ tfjs/upscaler peer conflict on Cloudflare.
+- **Model versions must match.** `@imgly/background-removal` and its `-data`
+ package are pinned together; a mismatch causes "Resource … not found".
+- **404s / routing:** Astro emits `dist/tools//index.html`; Cloudflare serves
+ trailing-slash variants and uses `dist/404.html`
(`not_found_handling: "404-page"`).
-- **Secrets:** none are committed. Wrangler auth is via `wrangler login` or the
+- **Secrets:** none are committed. Wrangler auth is `wrangler login` or the
`CLOUDFLARE_API_TOKEN` env var; `.dev.vars`, `.env*`, and `.wrangler/` are
gitignored.
## Cost
-Cloudflare's free tier covers this comfortably: static assets have unlimited
+Cloudflare's free tier covers this comfortably: Static Assets have unlimited
bandwidth, and R2 has a generous free tier for storage + egress (models are
immutable and hard-cached, so they download once per visitor).
@@ -180,15 +251,11 @@ GoodWebTools is self-hostable on Cloudflare Workers. To run your own copy:
1. **Fork** this repo and clone it.
2. **Rename the deployment identifiers** in `wrangler.jsonc` (`name`,
`env.staging.name`) and the R2 bucket names to values you own.
-3. **Create the R2 buckets:**
- `npx wrangler r2 bucket create ` (and a `-staging` one).
+3. **Create the R2 buckets** (production + `-staging`, step 1 above).
4. **Point branding at your domain** in `src/config.ts` (`SITE_URL`, `REPO_URL`)
and `astro.config.mjs` (`site`); update `public/robots.txt`.
-5. **(Optional) analytics:** set `SITE_GA_ID` as a production build variable
- (Workers Builds → Settings → Build). Leave unset to disable.
-6. **Stage & upload the ML models to R2:** `npm run stage:models` then
- `npm run sync:r2` (see the section above).
-7. **Connect Workers Builds** to your fork (production branch `main`, build
- command `npm run build`) and push.
+5. **(Optional) analytics:** set `SITE_GA_ID` as a production build variable.
+6. **Stage & upload the models:** `npm run stage:models` then `npm run sync:r2`.
+7. **Connect Workers Builds** to your fork (production branch `main`) and push.
Nothing sends data anywhere except your own Cloudflare account.
diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro
index 232cd0d..98dfd2a 100644
--- a/src/layouts/Base.astro
+++ b/src/layouts/Base.astro
@@ -102,10 +102,18 @@ const jsonLdBlocks = [siteJsonLd, ...(Array.isArray(jsonLd) ? jsonLd : jsonLd ?
diff --git a/wrangler.jsonc b/wrangler.jsonc
index e35073d..2caaeb2 100644
--- a/wrangler.jsonc
+++ b/wrangler.jsonc
@@ -41,7 +41,7 @@
// Staging environment (deployed from the `develop` branch). Bindings and
// `assets` are NOT inherited by named environments, so they're repeated here.
// Deploy with: npx wrangler deploy --env staging
- // See DEPLOYMENT-GIT.md.
+ // See DEPLOYMENT.md.
"env": {
"staging": {
"name": "goodwebtools-staging",