feat(frontend): ImageVariant component and WASM encoder workers

Add client-side multi-format image encoding via Web Workers using @jsquash and @discourse/jxl libraries. ImageVariant Svelte component renders <picture> elements with format-aware fallback. Vite configured for WASM asset inclusion and encoder dependency exclusion. Portfolio page updated to use multi-format URLs.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-04 01:07:00 +01:00
co-authored by Sisyphus
parent 0da7498ad7
commit 584c02ac6c
16 changed files with 178 additions and 60 deletions
+11
View File
@@ -0,0 +1,11 @@
import { init, default as encode } from '@jsquash/avif/encode.js';
self.onmessage = async (e: MessageEvent<{ imageData: ImageData; quality: number }>) => {
try {
await init({ locateFile: (path: string) => `/${path}` });
const encoded = await encode(e.data.imageData, { quality: e.data.quality });
self.postMessage({ encoded, format: 'avif' }, { transfer: [encoded as Transferable] });
} catch (err) {
self.postMessage({ error: (err as Error).message, format: 'avif' });
}
};
+11
View File
@@ -0,0 +1,11 @@
import { init, default as encode } from '@jsquash/jpeg/encode.js';
self.onmessage = async (e: MessageEvent<{ imageData: ImageData; quality: number }>) => {
try {
await init({ locateFile: (path: string) => `/${path}` });
const encoded = await encode(e.data.imageData, { quality: e.data.quality });
self.postMessage({ encoded, format: 'jpg' }, { transfer: [encoded as Transferable] });
} catch (err) {
self.postMessage({ error: (err as Error).message, format: 'jpg' });
}
};
+11
View File
@@ -0,0 +1,11 @@
import { init, default as encode } from '@discourse/jxl/encode.js';
self.onmessage = async (e: MessageEvent<{ imageData: ImageData; quality: number }>) => {
try {
await init({ locateFile: (path: string) => `/${path}` });
const encoded = await encode(e.data.imageData, { effort: 4, quality: e.data.quality });
self.postMessage({ encoded, format: 'jxl' }, { transfer: [encoded as Transferable] });
} catch (err) {
self.postMessage({ error: (err as Error).message, format: 'jxl' });
}
};
+11
View File
@@ -0,0 +1,11 @@
import { init, default as encode } from '@jsquash/webp/encode.js';
self.onmessage = async (e: MessageEvent<{ imageData: ImageData; quality: number }>) => {
try {
await init({ locateFile: (path: string) => `/${path}` });
const encoded = await encode(e.data.imageData, { quality: e.data.quality });
self.postMessage({ encoded, format: 'webp' }, { transfer: [encoded as Transferable] });
} catch (err) {
self.postMessage({ error: (err as Error).message, format: 'webp' });
}
};