# Enamel Studio — WebGL

Open `index.html` or double-click `Open.command`. SVG and PNG files stay local; no server, installation, framework, build step, or uploaded artwork is required. For local development: `python3 -m http.server 8765 --bind 127.0.0.1` from this folder.

## Native studio backport

- Native-derived enamel/metal shading, high-precision normals, specular antialiasing and corrected environment-map seam derivatives.
- Gold, silver, white enamel, black enamel and automatic base materials. Gold-region, all-enamel and selected-colour artwork metal; optional original metal colours.
- Glossy/satin backs, shared fine surface grain, polished sidewalls with narrow rounded fillets, and an outward-growing outer border. Internal edging remains zero.
- Region-following enamel waviness, overall curvature, automatic foreground relief and an adaptive indexed relief mesh. Debug mode shows the actual interpolated mesh relief.
- Cheezy blob engraving, city name above, CHEEZYPARK below and dot separators; clearance/component-based placement, shrinking and skipping around holes. Optional separate inscription.
- Native-derived intro spring, drag/flick physics, front/back views, spin and Reduce Motion. Vertical drag supplies a modest tilt.
- Foreshortened shadow, optional white preview background, transparent PNG export, and SVG/PNG collection import by picker or drag/drop.

Defaults: waviness 0.20, border 20, curvature 0.10, geometric relief 1.00, shading relief 1.00, gloss 1.00, thickness 0.05, light direction 0.35 and glossy back.

## Architecture and memory

`app.js` manages plain WebGL 2, interaction and browser image decoding. `prepare.js` runs distance transforms, region inference, texture baking, mesh construction and engraving in a disposable Web Worker. Arrays transfer to the main thread rather than being copied; terminating the worker releases its preparation state. A new request cancels the old worker, and stale asynchronous image loads cannot replace a newer selection.

Only the selected badge holds live GPU resources. The collection retains source files and small browser image previews, not a renderer for every item. Stationary views stop requesting animation frames; hidden pages pause rendering. Source assets and shared procedural lighting/grain require no bundled texture files.

At 1024², the Gotland preview uses approximately **16.6 MiB of badge GPU assets** and **2.7 MiB of shared GPU assets**, including mipmaps. The UI reports the actual mesh buffer allocation. These are resource estimates, not total browser memory: render targets, decoding, JavaScript allocations, driver overhead and transient preparation buffers are additional. WebGL uses RG16F front normals and R11F_G11F_B10F shared environment profiles. Explicit mip generation handles float/SNORM formats consistently across WebGL implementations.

The first Gotland preparation measured about **1.3 seconds** in the development Chromium session while an iOS archive was running. This is not a controlled benchmark or a claim of native loading parity. The browser rasterizer, JavaScript worker and GPU upload differ from the optimized Swift/Metal pipeline. Preparation controls rebuild resources; lighting, finish, curvature, waviness and relief strength only update uniforms.

## Import and visual limits

The browser renders self-contained SVG, including paths, groups, transforms, internal references, styles, gradients, filters, metadata and switch elements. Scripts/event handlers and foreignObject content are removed. External resources are rejected; text uses locally available fonts, so convert text to paths for portable artwork. Files are never modified on disk. PNG transparency determines the badge silhouette; an opaque background becomes part of the badge.

Foreground relief and materials use colour/topology heuristics, not semantic object recognition. They can misclassify unfamiliar artwork. The relief debug toggle and material palette expose those decisions. Browser fonts/rasterization, mesh tessellation, mip generation and engraving filtering can differ slightly from native Metal; this is a functional backport, not a pixel-identical renderer. Back engraving uses the same normal/coverage technique as the current native shader rather than separately tessellated letter geometry.

## Verification

- `node --check app.js`, `node --check prepare.js`, `node --check shaders.js`.
- `node tests/prepare.test.cjs`: exact distance field, outward border growth, preserved interior hole, finite indexed geometry, disabled engraving.
- Playwright with existing Chromium: Gotland front/back, material changes, white background, PNG import, transparent export, desktop/mobile layouts and direct `file://` opening. Montreal verifies hole-aware seal placement. An animation-frame counter verifies that rendering stops at rest. No browser or app dependency was added.
- Latest page loads have no app console errors or WebGL warnings. Initial float mipmap errors were corrected with explicit mip levels. Export alpha was inspected: transparent and opaque pixels are both present.

Safari/WebKit and device GPU frame time have not been benchmarked. The native iOS app and this standalone web studio have independent GPU-resource lifetimes.

## Files and provenance

`shaders.js` ports the project's Metal shader. `prepare.js` ports its preparation algorithms. `blob.js` embeds the project's Cheezy mascot vector. `sample.js` embeds the Gotland convenience vector, also supplied in `assets/gotland.svg`.

The Gotland vector was traced from the user-supplied emblem; original heraldic artwork by Vladimir A. Sagerlund / Riksarkivet Sverige. It is a convenience sample, not an official master. No generated photorealistic reference PNG, Minted code or Apple award textures are included.
