]> 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 46f3939..7131cc0 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 18648e6..5bb6718 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 06f3b8a..7a4990b 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 12ff66b..74f2886 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 37bbd73..f531352 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 53a7f6d..13e73f7 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 5e377de..420502d 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 8bca8c7..18c104c 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 a8ca84c..b6dc5a1 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 0e5616e..aaa31fb 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 a299974..0495ff1 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 0b9de39..fcdfa30 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 695fa38..11484e9 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 d94cc47..8915979 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 1cd444e..346940b 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 d1fd67e..ad964f1 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 9798563..00cea79 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 209d12f..feba654 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 8bf11fc..f0442db 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 03a973a..4ca6030 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 6bba81e..18f93b5 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 c95c91c..f9eae8f 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 352936a..2bfef51 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 01f168a..7fc74c5 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 65f40b1..92226fe 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 d9240d5..9fd374e 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 e90794e..3b5c6fe 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>