diff --git a/website/scripts/generate-placeholders.ts b/website/scripts/generate-placeholders.ts index 61304c1..3c17893 100644 --- a/website/scripts/generate-placeholders.ts +++ b/website/scripts/generate-placeholders.ts @@ -2,9 +2,14 @@ * Generate pixelated placeholder images for the website. * * Scans website/public/ for all image files (png, jpg, jpeg, webp) and - * generates a tiny 32px-wide version of each. When displayed at full size - * with CSS `image-rendering: pixelated` (nearest-neighbor / point sampling), - * these produce a crisp mosaic effect instead of a blurry upscale. + * generates a tiny 32px-wide version into src/assets/placeholders/. + * When displayed at full size with CSS `image-rendering: pixelated` + * (nearest-neighbor / point sampling), these produce a crisp mosaic effect. + * + * Output goes to src/assets/placeholders/ (NOT public/) so that Vite's + * asset pipeline processes them. Since all placeholders are < 4KB, Vite + * automatically inlines them as base64 data URIs via assetsInlineLimit, + * eliminating extra HTTP requests. * * Skips files that already have the `placeholder-` prefix. * Skips regeneration if the placeholder is newer than the source image. @@ -17,11 +22,14 @@ import path from "node:path"; import fs from "node:fs"; const PUBLIC_DIR = path.resolve(import.meta.dirname, "../public"); +const OUTPUT_DIR = path.resolve(import.meta.dirname, "../src/assets/placeholders"); const PLACEHOLDER_PREFIX = "placeholder-"; const PLACEHOLDER_WIDTH = 32; const IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".webp"]); async function main() { + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + const entries = fs.readdirSync(PUBLIC_DIR); const images = entries.filter((name) => { @@ -41,7 +49,7 @@ async function main() { const inputPath = path.join(PUBLIC_DIR, name); const ext = path.extname(name); const base = path.basename(name, ext); - const outputPath = path.join(PUBLIC_DIR, `${PLACEHOLDER_PREFIX}${base}${ext}`); + const outputPath = path.join(OUTPUT_DIR, `${PLACEHOLDER_PREFIX}${base}${ext}`); // Skip if placeholder already exists and is newer than source if (fs.existsSync(outputPath)) { diff --git a/website/src/assets/placeholders/placeholder-favicon-16.png b/website/src/assets/placeholders/placeholder-favicon-16.png new file mode 100644 index 0000000..0d386bd Binary files /dev/null and b/website/src/assets/placeholders/placeholder-favicon-16.png differ diff --git a/website/src/assets/placeholders/placeholder-favicon-32.png b/website/src/assets/placeholders/placeholder-favicon-32.png new file mode 100644 index 0000000..425b1ad Binary files /dev/null and b/website/src/assets/placeholders/placeholder-favicon-32.png differ diff --git a/website/src/assets/placeholders/placeholder-og-image.png b/website/src/assets/placeholders/placeholder-og-image.png new file mode 100644 index 0000000..5cfdbea Binary files /dev/null and b/website/src/assets/placeholders/placeholder-og-image.png differ diff --git a/website/src/assets/placeholders/placeholder-screenshot@2x.png b/website/src/assets/placeholders/placeholder-screenshot@2x.png new file mode 100644 index 0000000..c4e1c3f Binary files /dev/null and b/website/src/assets/placeholders/placeholder-screenshot@2x.png differ diff --git a/website/src/components/markdown.tsx b/website/src/components/markdown.tsx index 02f4aee..a234484 100644 --- a/website/src/components/markdown.tsx +++ b/website/src/components/markdown.tsx @@ -434,6 +434,20 @@ export function PixelatedImage({ style, }: { src: string; + /** + * URL of the tiny pixelated placeholder image. Use a static import so Vite + * inlines it as a base64 data URI (all placeholders are < 4KB, well under + * Vite's default assetsInlineLimit of 4096 bytes). This makes the + * placeholder available synchronously on first render with zero HTTP + * requests. Do NOT use dynamic imports or public/ paths — dynamic imports + * add a microtask delay, and public/ files bypass Vite's asset pipeline. + * + * @example + * ```tsx + * import placeholderScreenshot from "../assets/placeholders/placeholder-screenshot@2x.png"; + * + * ``` + */ placeholder: string; alt: string; width: number; @@ -504,6 +518,133 @@ export function PixelatedImage({ ); } +/* ========================================================================= + Lazy video with pixelated poster placeholder + Same visual pattern as PixelatedImage but for