Small web tools by Frazer Searle, served at tools.frrazers.com via Cloudflare Pages.
site/ is the deployed tree and the single source of truth — edit pages there directly.
site/
index.html landing page
mobile-buttons/ Roblox mobile button sheet generator
thumbnails/ Thumbforge — AI Roblox thumbnail studio
dataset/ Icons & Thumbnail Dataset download page
roblox-devproducts/ Dev Products MCP install page
roblox-analytics/ Analytics MCP install page
theme.css / theme.js shared frrazers.com design system + theme toggle
logo.svg, favicons/ shared branding assets
_redirects /install.* + /roblox-analytics/install.* → GitHub raw scripts
blank-template.png and jump-template.png are the source assets embedded as base64
inside site/mobile-buttons/index.html.
The Icons & Thumbnail Dataset page is built from C:\Users\Frazer\Desktop\GAME THUMBNAILS
(icons/ + thumbs/) by the scripts in tools/, each a self-contained
uv script — uv run tools/<name>.py:
build_zips.py icons/thumbs/both → dist-dataset/*.zip (Zip64, stored)
build_previews.py samples 72 icons + 12 thumbs → site/dataset/previews/
dataset-page.template.html page source of truth, with __PLACEHOLDER__ tokens
build_page.py template + previews + real zip sizes → site/dataset/index.html
serve.py local preview of site/ on :8788, /dataset/files/ → dist-dataset/
site/dataset/index.html is generated — edit the template and re-run build_page.py.
The archives total ~13 GB and are not deployable to Pages (25 MB per file, 20k file
cap), so they live in dist-dataset/ — outside site/, and gitignored — and are served
from R2 by a Pages Function.
functions/ holds the Pages Functions (beside site/, not inside it — functions/ sits
at the project root alongside pages_build_output_dir):
functions/dataset/files/[file].ts streams an archive from R2, logs the download to D1
functions/dataset/stats.ts GET /dataset/stats → per-file download counts as JSON
tools/analytics-schema.sql the D1 downloads table
tools/upload_r2.py multipart upload of the archives to R2
The route is /dataset/files/<name>.zip, which is exactly what tools/serve.py serves
locally — so the page's download links need no environment-specific rewriting.
Only un-ranged GETs are counted: download managers and resumed transfers issue a Range
header per chunk, and counting those would inflate the number several-fold. Range requests
are still served correctly (206 + Content-Range), they just don't tick the counter. The
log stores file, timestamp, and country — no IP, no user agent.
Needs an API token with Workers R2 Storage: Edit and D1: Edit — the original Pages-only token can't do either — plus R2 access keys for the upload.
D1 is already provisioned: frrazers-dataset-analytics
(76eabc3d-3f65-4092-86cd-54a9b61410c2, region WEUR) with the schema applied and its
database_id set in wrangler.jsonc. Recreating it is only necessary from scratch:
npx wrangler d1 create frrazers-dataset-analytics # put database_id in wrangler.jsonc
npx wrangler d1 execute frrazers-dataset-analytics --remote --file=tools/analytics-schema.sql
R2 must be enabled on the account first (Dashboard → R2 → Enable; needs a payment method
even inside the free tier). Until then the API returns code: 10042 and the S3 endpoint
rejects the TLS handshake outright. Then:
npx wrangler r2 bucket create frrazers-dataset
export R2_ACCESS_KEY_ID=... # R2 → API → Manage API tokens → Object Read & Write
export R2_SECRET_ACCESS_KEY=...
uv run tools/upload_r2.py # ~13 GB, multipart, re-runnable (skips what's there)
npx wrangler pages deploy # reads wrangler.jsonc, uploads site/ + functions/
Reading the counts:
curl https://tools.frrazers.com/dataset/stats
npx wrangler d1 execute frrazers-dataset-analytics --remote \
--command="SELECT file, COUNT(*) FROM downloads GROUP BY file"
npx wrangler d1 execute frrazers-dataset-analytics --local --file=tools/analytics-schema.sql
npx wrangler r2 object put frrazers-dataset/roblox-icons.zip --file=<some.zip> --local
npx wrangler pages dev --port 8789
Local R2/D1 are simulated on disk under .wrangler/, so don't push the real 13 GB
through them — a small stand-in archive is enough to exercise the function.
All pages share the frrazers.com design system (paper/ink palette, Stack Sans Headline,
automatic light/dark with a toggle). Tokens live in site/theme.css; pages remap their
local variables onto those tokens.
npx wrangler pages deploy # wrangler.jsonc supplies the project, site/, and bindings