]> git.otsuka.systems Git - cotsuka.github.io/commitdiff
limit image size breakpoints to ones we actually use, include rightsizing script...
authorCameron Otsuka <cameron@otsuka.haus>
Wed, 26 Aug 2026 00:01:06 +0000 (17:01 -0700)
committerCameron Otsuka <cameron@otsuka.haus>
Wed, 26 Aug 2026 00:01:06 +0000 (17:01 -0700)
28 files changed:
astro.config.mjs
content/articles/american-ai-suzerainty/american-ai-suzerainty.png
content/articles/bitcoin-mempools-cleared-is-nobody-using-bitcoin/hashprice-index-2yr.png
content/articles/bitcoin-wallets-emptied/bitcoin-wallets-emptied.png
content/articles/bitcoin-wallets-emptied/cumulative-losses-by-evidence-tier.png
content/articles/bitcoins-opt-in-constitution/bitcoins-opt-in-constitution.png
content/articles/blocking-your-ai-access-for-your-safety/blocking-your-ai-access-for-your-safety.png
content/articles/circles-float-problem/circles-float-problem.png
content/articles/formal-verification-when-intention-is-not-enough/formal-verification-when-intention-is-not-enough.png
content/articles/from-ai-tools-to-ai-loops/from-ai-tools-to-ai-loops.png
content/articles/glasswing-and-daybreak-you-are-not-secure/daybreak-hacker.png
content/articles/glasswing-and-daybreak-you-are-not-secure/firefox-security-bug-fixes-by-month.png
content/articles/necessity-is-the-mother-of-huaweis-invention/1-Overall-AI-Capability.png
content/articles/necessity-is-the-mother-of-huaweis-invention/necessity-is-the-mother-of-huaweis-invention.png
content/articles/r1-vs-o1-ai-as-commodity-or-moat/input-output-pricing.jpg
content/articles/r1-vs-o1-ai-as-commodity-or-moat/r1-o1-benchmarks.jpg
content/articles/soft-wars-for-hard-infrastructure/let-me-in.png
content/articles/soft-wars-for-hard-infrastructure/soft-wars-for-hard-infrastructure.png
content/articles/tokenmaxxing-as-management-theory/tokenmaxxing-as-management-theory.png
content/articles/tpus-advance-on-nvidia/NVDA_2026-05-05.png
content/articles/tpus-advance-on-nvidia/tpus-advance-on-nvidia.png
content/articles/why-ai-companies-are-hunting-for-data-nobody-else-has/why-ai-companies-are-hunting-for-data-nobody-else-has.png
content/articles/wisconsin-wont-write-the-ai-put/database-of-emerging-large-load-tariffs.png
content/articles/wisconsin-wont-write-the-ai-put/wisconsin-wont-write-the-ai-put.png
eslint.config.js
package.json
scripts/rightsize-images.mjs [new file with mode: 0644]
src/components/ui/figure.astro

index 46f3939480f15bf6401bb90f01083475002cfc89..7131cc0e28654529cbaabcc7a79af27ee30feb44 100644 (file)
@@ -63,6 +63,7 @@ export default defineConfig({
   integrations: [mdx(), sitemap()],
   image: {
     layout: 'constrained',
+    breakpoints: [640, 960, 1280],
     service: {
       entrypoint: 'astro/assets/services/sharp',
       config: {
index 18648e6c822bf08c4a3ba06b4148e6906eef9e9d..5bb67187cd39f3fe11dba3482cf0711c1f4a3e42 100644 (file)
Binary files a/content/articles/american-ai-suzerainty/american-ai-suzerainty.png and b/content/articles/american-ai-suzerainty/american-ai-suzerainty.png differ
index 06f3b8a13b64e05d8d17ef475f9bc0b54d947d2d..7a4990b8e265d9bd89482a4a1148934847bf8d4a 100644 (file)
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
index 12ff66b7ad141843b2511625de5c9b848b7abd0c..74f28868a0c1eb83a5358cd0c790d0f623f7467e 100644 (file)
Binary files a/content/articles/bitcoin-wallets-emptied/bitcoin-wallets-emptied.png and b/content/articles/bitcoin-wallets-emptied/bitcoin-wallets-emptied.png differ
index 37bbd73fa0d1541ca9ac242d71e02700f19f9f1d..f5313520a4875c223be98f6fb444ae86b0bd47a3 100644 (file)
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
index 53a7f6d91a334d23200c854fdb2bb9a6ce8fbeb6..13e73f7c08ec8fd9fd22ff03d12b1673b7151ff2 100644 (file)
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
index 5e377de5c788a679ce6f8ba2305de5835af68c95..420502d86c0df4185afc2552118446153885c500 100644 (file)
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
index 8bca8c70f61e581b9cf264f6ed93fd3587388be6..18c104c92b4b48605d579aa0a7f82bd1e96477b6 100644 (file)
Binary files a/content/articles/circles-float-problem/circles-float-problem.png and b/content/articles/circles-float-problem/circles-float-problem.png differ
index a8ca84c0cbe7d06da161a90a2f55fa330dfea99f..b6dc5a19258b9be8eaa9714adac6eea731afe61f 100644 (file)
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
index 0e5616e3de7c551a6c15146c6b5c54f130fca12f..aaa31fb9c5eb280c3703342bba3be89f413a5f58 100644 (file)
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
index a2999743dc29c669eb2be88e2fc12eb5a4604933..0495ff1cba882d7792dfa07b7414f8539dc58d36 100644 (file)
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
index 0b9de39fe142a462b0fe4567fbe3d00f6317e483..fcdfa30e5cd4e0d6aa76c6d824a6e17d7e328468 100644 (file)
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
index 695fa38a29183123cde056bd0be9b68b3e680e70..11484e940b287e641014baba2a0f432645c358b6 100644 (file)
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
index d94cc4745222d9001a0426af3c23da3a835097db..8915979f7bebc1c987bc72fc4c1dee20e97833eb 100644 (file)
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
index 1cd444ef06a1887621dde23bb69ec0b4f7558478..346940b98c61b1f0566107e9799d8be9c80c6019 100644 (file)
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
index d1fd67ec1bf748c24b244b6efd825ded1b147787..ad964f1c863bb362f74b1d898221a580ba69e7c8 100644 (file)
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
index 9798563b7e00e2a96f09e5d63cd4552f6e039b16..00cea7968596f2d464ce3fe52f48e5bb49789050 100644 (file)
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
index 209d12f94077df6e0b45b95ab6e3dcd3c59a55d0..feba654a1ca177b0108b003987baac6290afb521 100644 (file)
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
index 8bf11fc8877cb820a475a3171cd734224a4a72d0..f0442db7ec370ebb9a2469c3faf675b08f0359cc 100644 (file)
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
index 03a973a425744360b734635f68d65737ef8344ce..4ca6030279edee09c150d6b114aa5b20743318f3 100644 (file)
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
index 6bba81e3dbbe0681cf01617e587fd7e5dea70cde..18f93b5d7caef87e75c9657ed6ea284a9be9897e 100644 (file)
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
index c95c91c2e1aa49359787218bf5592b56a624a19f..f9eae8f2171baa194cdb1c89e74a1513eec5d7b9 100644 (file)
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
index 352936ae671df5cf242f1a25afd19e2efe143626..2bfef511a85f89f9e4195a052f32d0a7ec0aad95 100644 (file)
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
index 01f168a04d4d07a4085004e43bb91c5ea55195b1..7fc74c5a3f1488a669eba4361d6b8e44903853ee 100644 (file)
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
index 65f40b107f446beeb7ebcfbed9da19e164ae48a0..92226fe8fa991d0bd6d24e242a94a7d5dc8f5acc 100644 (file)
@@ -7,6 +7,12 @@ export default [
   ...tseslint.configs.recommended,
   ...astroPlugin.configs.recommended,
   {
+    languageOptions: {
+      globals: {
+        process: 'readonly',
+        console: 'readonly',
+      },
+    },
     ignores: [
       'dist/',
       'node_modules/',
index d9240d5e916470261db4d925754dd818350d7893..9fd374e08aa200540acfcaf7e9369066dc281e18 100644 (file)
@@ -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 (file)
index 0000000..2aa4d17
--- /dev/null
@@ -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`,
+);
index e90794e51975328e43b66bcab73a5a4729746d59..3b5c6fef0fcb859aac6346f09d44c517cb587c79 100644 (file)
@@ -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;
 ---
 
 <figure>