From: Cameron Otsuka Date: Wed, 26 Aug 2026 00:01:06 +0000 (-0700) Subject: limit image size breakpoints to ones we actually use, include rightsizing script... X-Git-Url: https://git.otsuka.systems/?a=commitdiff_plain;h=f701ef9637b35d3b9e1cefe1954ff504241f86cc;p=cotsuka.github.io limit image size breakpoints to ones we actually use, include rightsizing script, streamline package scripts --- diff --git a/astro.config.mjs b/astro.config.mjs index 46f3939..7131cc0 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -63,6 +63,7 @@ export default defineConfig({ integrations: [mdx(), sitemap()], image: { layout: 'constrained', + breakpoints: [640, 960, 1280], service: { entrypoint: 'astro/assets/services/sharp', config: { diff --git a/content/articles/american-ai-suzerainty/american-ai-suzerainty.png b/content/articles/american-ai-suzerainty/american-ai-suzerainty.png index 18648e6..5bb6718 100644 Binary files a/content/articles/american-ai-suzerainty/american-ai-suzerainty.png and b/content/articles/american-ai-suzerainty/american-ai-suzerainty.png differ diff --git a/content/articles/bitcoin-mempools-cleared-is-nobody-using-bitcoin/hashprice-index-2yr.png b/content/articles/bitcoin-mempools-cleared-is-nobody-using-bitcoin/hashprice-index-2yr.png index 06f3b8a..7a4990b 100644 Binary files a/content/articles/bitcoin-mempools-cleared-is-nobody-using-bitcoin/hashprice-index-2yr.png and b/content/articles/bitcoin-mempools-cleared-is-nobody-using-bitcoin/hashprice-index-2yr.png differ diff --git a/content/articles/bitcoin-wallets-emptied/bitcoin-wallets-emptied.png b/content/articles/bitcoin-wallets-emptied/bitcoin-wallets-emptied.png index 12ff66b..74f2886 100644 Binary files a/content/articles/bitcoin-wallets-emptied/bitcoin-wallets-emptied.png and b/content/articles/bitcoin-wallets-emptied/bitcoin-wallets-emptied.png differ diff --git a/content/articles/bitcoin-wallets-emptied/cumulative-losses-by-evidence-tier.png b/content/articles/bitcoin-wallets-emptied/cumulative-losses-by-evidence-tier.png index 37bbd73..f531352 100644 Binary files a/content/articles/bitcoin-wallets-emptied/cumulative-losses-by-evidence-tier.png and b/content/articles/bitcoin-wallets-emptied/cumulative-losses-by-evidence-tier.png differ diff --git a/content/articles/bitcoins-opt-in-constitution/bitcoins-opt-in-constitution.png b/content/articles/bitcoins-opt-in-constitution/bitcoins-opt-in-constitution.png index 53a7f6d..13e73f7 100644 Binary files a/content/articles/bitcoins-opt-in-constitution/bitcoins-opt-in-constitution.png and b/content/articles/bitcoins-opt-in-constitution/bitcoins-opt-in-constitution.png differ diff --git a/content/articles/blocking-your-ai-access-for-your-safety/blocking-your-ai-access-for-your-safety.png b/content/articles/blocking-your-ai-access-for-your-safety/blocking-your-ai-access-for-your-safety.png index 5e377de..420502d 100644 Binary files a/content/articles/blocking-your-ai-access-for-your-safety/blocking-your-ai-access-for-your-safety.png and b/content/articles/blocking-your-ai-access-for-your-safety/blocking-your-ai-access-for-your-safety.png differ diff --git a/content/articles/circles-float-problem/circles-float-problem.png b/content/articles/circles-float-problem/circles-float-problem.png index 8bca8c7..18c104c 100644 Binary files a/content/articles/circles-float-problem/circles-float-problem.png and b/content/articles/circles-float-problem/circles-float-problem.png differ diff --git a/content/articles/formal-verification-when-intention-is-not-enough/formal-verification-when-intention-is-not-enough.png b/content/articles/formal-verification-when-intention-is-not-enough/formal-verification-when-intention-is-not-enough.png index a8ca84c..b6dc5a1 100644 Binary files a/content/articles/formal-verification-when-intention-is-not-enough/formal-verification-when-intention-is-not-enough.png and b/content/articles/formal-verification-when-intention-is-not-enough/formal-verification-when-intention-is-not-enough.png differ diff --git a/content/articles/from-ai-tools-to-ai-loops/from-ai-tools-to-ai-loops.png b/content/articles/from-ai-tools-to-ai-loops/from-ai-tools-to-ai-loops.png index 0e5616e..aaa31fb 100644 Binary files a/content/articles/from-ai-tools-to-ai-loops/from-ai-tools-to-ai-loops.png and b/content/articles/from-ai-tools-to-ai-loops/from-ai-tools-to-ai-loops.png differ diff --git a/content/articles/glasswing-and-daybreak-you-are-not-secure/daybreak-hacker.png b/content/articles/glasswing-and-daybreak-you-are-not-secure/daybreak-hacker.png index a299974..0495ff1 100644 Binary files a/content/articles/glasswing-and-daybreak-you-are-not-secure/daybreak-hacker.png and b/content/articles/glasswing-and-daybreak-you-are-not-secure/daybreak-hacker.png differ diff --git a/content/articles/glasswing-and-daybreak-you-are-not-secure/firefox-security-bug-fixes-by-month.png b/content/articles/glasswing-and-daybreak-you-are-not-secure/firefox-security-bug-fixes-by-month.png index 0b9de39..fcdfa30 100644 Binary files a/content/articles/glasswing-and-daybreak-you-are-not-secure/firefox-security-bug-fixes-by-month.png and b/content/articles/glasswing-and-daybreak-you-are-not-secure/firefox-security-bug-fixes-by-month.png differ diff --git a/content/articles/necessity-is-the-mother-of-huaweis-invention/1-Overall-AI-Capability.png b/content/articles/necessity-is-the-mother-of-huaweis-invention/1-Overall-AI-Capability.png index 695fa38..11484e9 100644 Binary files a/content/articles/necessity-is-the-mother-of-huaweis-invention/1-Overall-AI-Capability.png and b/content/articles/necessity-is-the-mother-of-huaweis-invention/1-Overall-AI-Capability.png differ diff --git a/content/articles/necessity-is-the-mother-of-huaweis-invention/necessity-is-the-mother-of-huaweis-invention.png b/content/articles/necessity-is-the-mother-of-huaweis-invention/necessity-is-the-mother-of-huaweis-invention.png index d94cc47..8915979 100644 Binary files a/content/articles/necessity-is-the-mother-of-huaweis-invention/necessity-is-the-mother-of-huaweis-invention.png and b/content/articles/necessity-is-the-mother-of-huaweis-invention/necessity-is-the-mother-of-huaweis-invention.png differ diff --git a/content/articles/r1-vs-o1-ai-as-commodity-or-moat/input-output-pricing.jpg b/content/articles/r1-vs-o1-ai-as-commodity-or-moat/input-output-pricing.jpg index 1cd444e..346940b 100644 Binary files a/content/articles/r1-vs-o1-ai-as-commodity-or-moat/input-output-pricing.jpg and b/content/articles/r1-vs-o1-ai-as-commodity-or-moat/input-output-pricing.jpg differ diff --git a/content/articles/r1-vs-o1-ai-as-commodity-or-moat/r1-o1-benchmarks.jpg b/content/articles/r1-vs-o1-ai-as-commodity-or-moat/r1-o1-benchmarks.jpg index d1fd67e..ad964f1 100644 Binary files a/content/articles/r1-vs-o1-ai-as-commodity-or-moat/r1-o1-benchmarks.jpg and b/content/articles/r1-vs-o1-ai-as-commodity-or-moat/r1-o1-benchmarks.jpg differ diff --git a/content/articles/soft-wars-for-hard-infrastructure/let-me-in.png b/content/articles/soft-wars-for-hard-infrastructure/let-me-in.png index 9798563..00cea79 100644 Binary files a/content/articles/soft-wars-for-hard-infrastructure/let-me-in.png and b/content/articles/soft-wars-for-hard-infrastructure/let-me-in.png differ diff --git a/content/articles/soft-wars-for-hard-infrastructure/soft-wars-for-hard-infrastructure.png b/content/articles/soft-wars-for-hard-infrastructure/soft-wars-for-hard-infrastructure.png index 209d12f..feba654 100644 Binary files a/content/articles/soft-wars-for-hard-infrastructure/soft-wars-for-hard-infrastructure.png and b/content/articles/soft-wars-for-hard-infrastructure/soft-wars-for-hard-infrastructure.png differ diff --git a/content/articles/tokenmaxxing-as-management-theory/tokenmaxxing-as-management-theory.png b/content/articles/tokenmaxxing-as-management-theory/tokenmaxxing-as-management-theory.png index 8bf11fc..f0442db 100644 Binary files a/content/articles/tokenmaxxing-as-management-theory/tokenmaxxing-as-management-theory.png and b/content/articles/tokenmaxxing-as-management-theory/tokenmaxxing-as-management-theory.png differ diff --git a/content/articles/tpus-advance-on-nvidia/NVDA_2026-05-05.png b/content/articles/tpus-advance-on-nvidia/NVDA_2026-05-05.png index 03a973a..4ca6030 100644 Binary files a/content/articles/tpus-advance-on-nvidia/NVDA_2026-05-05.png and b/content/articles/tpus-advance-on-nvidia/NVDA_2026-05-05.png differ diff --git a/content/articles/tpus-advance-on-nvidia/tpus-advance-on-nvidia.png b/content/articles/tpus-advance-on-nvidia/tpus-advance-on-nvidia.png index 6bba81e..18f93b5 100644 Binary files a/content/articles/tpus-advance-on-nvidia/tpus-advance-on-nvidia.png and b/content/articles/tpus-advance-on-nvidia/tpus-advance-on-nvidia.png differ diff --git a/content/articles/why-ai-companies-are-hunting-for-data-nobody-else-has/why-ai-companies-are-hunting-for-data-nobody-else-has.png b/content/articles/why-ai-companies-are-hunting-for-data-nobody-else-has/why-ai-companies-are-hunting-for-data-nobody-else-has.png index c95c91c..f9eae8f 100644 Binary files a/content/articles/why-ai-companies-are-hunting-for-data-nobody-else-has/why-ai-companies-are-hunting-for-data-nobody-else-has.png and b/content/articles/why-ai-companies-are-hunting-for-data-nobody-else-has/why-ai-companies-are-hunting-for-data-nobody-else-has.png differ diff --git a/content/articles/wisconsin-wont-write-the-ai-put/database-of-emerging-large-load-tariffs.png b/content/articles/wisconsin-wont-write-the-ai-put/database-of-emerging-large-load-tariffs.png index 352936a..2bfef51 100644 Binary files a/content/articles/wisconsin-wont-write-the-ai-put/database-of-emerging-large-load-tariffs.png and b/content/articles/wisconsin-wont-write-the-ai-put/database-of-emerging-large-load-tariffs.png differ diff --git a/content/articles/wisconsin-wont-write-the-ai-put/wisconsin-wont-write-the-ai-put.png b/content/articles/wisconsin-wont-write-the-ai-put/wisconsin-wont-write-the-ai-put.png index 01f168a..7fc74c5 100644 Binary files a/content/articles/wisconsin-wont-write-the-ai-put/wisconsin-wont-write-the-ai-put.png and b/content/articles/wisconsin-wont-write-the-ai-put/wisconsin-wont-write-the-ai-put.png differ diff --git a/eslint.config.js b/eslint.config.js index 65f40b1..92226fe 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -7,6 +7,12 @@ export default [ ...tseslint.configs.recommended, ...astroPlugin.configs.recommended, { + languageOptions: { + globals: { + process: 'readonly', + console: 'readonly', + }, + }, ignores: [ 'dist/', 'node_modules/', diff --git a/package.json b/package.json index d9240d5..9fd374e 100644 --- a/package.json +++ b/package.json @@ -9,16 +9,14 @@ "preview": "bun x --bun astro preview", "astro": "bun x --bun astro", "index": "bun x --bun pagefind", - "lint": "bun x --bun eslint src/", + "check": "bun x --bun astro check && bun x --bun eslint src/ && bun x --bun prettier --check .", "lint:fix": "bun x --bun eslint src/ --fix", - "format": "bun x --bun prettier --check .", "format:fix": "bun x --bun prettier --write .", - "typecheck": "bun x --bun astro check", "test": "bun test", "prepare": "simple-git-hooks" }, "simple-git-hooks": { - "pre-commit": "bun x --bun lint-staged" + "pre-commit": "bun scripts/rightsize-images.mjs --staged && bun x --bun lint-staged && bun run check" }, "lint-staged": { "*.{ts,tsx,astro,js,mjs,cjs}": [ diff --git a/scripts/rightsize-images.mjs b/scripts/rightsize-images.mjs new file mode 100644 index 0000000..2aa4d17 --- /dev/null +++ b/scripts/rightsize-images.mjs @@ -0,0 +1,113 @@ +/** + * Right-sizes source images before they enter the repository. + * + * Any raster image wider than MAX_WIDTH is downscaled in place to MAX_WIDTH. + * This caps Astro's generated variant count at our breakpoints (2x of the + * 640px content column) so oversized originals never enter the srcset. + * + * Usage: + * bun scripts/rightsize-images.mjs Scan all of content/ + src/assets/ + * bun scripts/rightsize-images.mjs --staged Process only staged images (used by + * the pre-commit hook); resized files + * are re-staged automatically. + * + * Scope: content/ and src/assets/ (the images Astro processes). Files in + * public/ are served verbatim, so resizing them here would change what ships; + * handle those manually if needed. + * + * Quality: PNG stays lossless. JPEG/WebP are re-encoded at high quality — + * the only intended loss beyond resampling is negligible recompression. + */ +import { readdirSync, renameSync, statSync } from 'node:fs'; +import { join } from 'node:path'; +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; +import sharp from 'sharp'; + +const execFileAsync = promisify(execFile); + +const MAX_WIDTH = 1280; +const DIRECTORIES = ['content', 'src/assets']; +const EXTENSIONS = /\.(png|jpe?g|webp)$/i; + +const ENCODE_OPTIONS = { + jpeg: { quality: 95, mozjpeg: true }, + webp: { quality: 92 }, + png: {}, // lossless +}; + +const stagedMode = process.argv.includes('--staged'); + +async function stagedImages() { + const { stdout } = await execFileAsync('git', [ + 'diff', + '--cached', + '--name-only', + '--diff-filter=ACMR', + ]); + return stdout + .split('\n') + .map((line) => line.trim()) + .filter( + (file) => + file && + EXTENSIONS.test(file) && + DIRECTORIES.some((dir) => file.startsWith(`${dir}/`)), + ); +} + +function* scanAll() { + for (const dir of DIRECTORIES) { + let entries; + try { + entries = readdirSync(dir); + } catch { + continue; // directory does not exist yet + } + for (const entry of entries) { + const path = join(dir, entry); + if (statSync(path).isDirectory()) yield* scanDir(path); + else if (EXTENSIONS.test(entry)) yield path; + } + } +} + +function* scanDir(dir) { + for (const entry of readdirSync(dir)) { + const path = join(dir, entry); + if (statSync(path).isDirectory()) yield* scanDir(path); + else if (EXTENSIONS.test(entry)) yield path; + } +} + +const files = stagedMode ? await stagedImages() : [...scanAll()]; + +let scanned = 0; +const resized = []; + +for (const file of files) { + scanned++; + const { width, format } = await sharp(file).metadata(); + + if (!width || width <= MAX_WIDTH || !(format in ENCODE_OPTIONS)) continue; + + const tmp = `${file}.rightsize.tmp`; + await sharp(file) + .resize({ width: MAX_WIDTH }) + .toFormat(format, ENCODE_OPTIONS[format]) + .toFile(tmp); + renameSync(tmp, file); + + resized.push(file); + console.log(`resized ${file} -> ${MAX_WIDTH}px wide`); +} + +if (stagedMode && resized.length > 0) { + await execFileAsync('git', ['add', '--', ...resized]); +} + +console.log( + resized.length === 0 + ? `rightsize: all ${scanned} images within ${MAX_WIDTH}px` + : `rightsize: resized ${resized.length} of ${scanned} images`, +); diff --git a/src/components/ui/figure.astro b/src/components/ui/figure.astro index e90794e..3b5c6fe 100644 --- a/src/components/ui/figure.astro +++ b/src/components/ui/figure.astro @@ -9,7 +9,7 @@ interface Props { layout?: 'constrained' | 'full-width' | 'fixed' | 'none'; } -const { image, alt, formats = ['avif', 'webp'], layout } = Astro.props; +const { image, alt, formats = ['webp'], layout } = Astro.props; ---