From 5e79712d66949c5747b008cb03ccf468ee8a5af0 Mon Sep 17 00:00:00 2001 From: "Claude (Anthropic)" Date: Wed, 27 May 2026 21:35:31 +0000 Subject: [PATCH 1/4] feat(demo): click any tile to view image at target display size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a modal that pops up when you click a tile. The modal renders the encoded image at its actual target width (capped to viewport when needed) — letting you see what the user would actually see at that display size, separate from how the tile shrinks the image to fit the grid. The modal makes the canon mechanism visible: - For target-binding tiles: the proxy encoded at target × 1.5; the browser downscales from there to target for display. That downscale IS the artifact filter — same mechanism that yields soft blur instead of blocking artifacts. - For source-binding tiles: explains that source × 1.5 capped to prevent manufacturing pixels from no signal; the browser upscales modestly from the encode to the target display. - For equal-binding tiles: no scaling work at the encoder. UX: - Click tile or Enter/Space when tile is focused → open modal - Click backdrop, X button, or Esc → close - Tiles get role=button + tabindex for accessibility - Hover state on tiles signals they're clickable - Hint added to legend: 'click any tile to view at target size' No behavioral change to the proxy or smoke test — just the demo page. --- src/demo-page.ts | 216 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) diff --git a/src/demo-page.ts b/src/demo-page.ts index 4fd4fd5..449ed7a 100644 --- a/src/demo-page.ts +++ b/src/demo-page.ts @@ -65,7 +65,11 @@ export const DEMO_PAGE_HTML = ` .tile { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; display: flex; flex-direction: column; + cursor: pointer; transition: border-color 0.15s, transform 0.15s; } + .tile:hover { border-color: var(--accent); transform: translateY(-1px); } + .tile.loading, .tile.error { cursor: default; } + .tile.loading:hover, .tile.error:hover { border-color: var(--border); transform: none; } .tile-header { padding: 10px 12px; border-bottom: 1px solid var(--border); background: var(--panel-2); font-size: 13px; font-weight: 600; @@ -89,6 +93,74 @@ export const DEMO_PAGE_HTML = ` } .file-size.ok { color: var(--accent-2); } .file-size.high { color: var(--warn); } + + /* Modal */ + .modal-backdrop { + position: fixed; inset: 0; + background: rgba(0,0,0,0.85); + display: none; + z-index: 100; + overflow-y: auto; + padding: 24px; + } + .modal-backdrop.open { display: flex; align-items: flex-start; justify-content: center; } + .modal { + background: var(--panel); border: 1px solid var(--border); + border-radius: 12px; + max-width: calc(100vw - 48px); + width: auto; + margin: auto; + box-shadow: 0 20px 60px rgba(0,0,0,0.5); + display: flex; flex-direction: column; + } + .modal-header { + display: flex; align-items: center; justify-content: space-between; + padding: 14px 18px; + border-bottom: 1px solid var(--border); + gap: 16px; + } + .modal-title { font-size: 15px; font-weight: 600; } + .modal-title .target { color: var(--accent); } + .modal-title .display-info { color: var(--text-dim); font-weight: 400; font-size: 12px; margin-left: 8px; } + .modal-close { + background: var(--panel-2); border: 1px solid var(--border); + color: var(--text); width: 32px; height: 32px; + border-radius: 6px; cursor: pointer; font-size: 18px; + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; + } + .modal-close:hover { background: var(--border); } + .modal-image-wrap { + background: #000; display: flex; align-items: center; justify-content: center; + padding: 0; max-height: calc(100vh - 240px); + overflow: auto; + } + .modal-image-wrap img { + display: block; height: auto; + /* width is set inline by JS to match target (or viewport cap) */ + } + .modal-meta { + padding: 14px 18px; + font-family: var(--mono); font-size: 12px; + color: var(--text-dim); + display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 8px 24px; + border-top: 1px solid var(--border); + } + .modal-meta .row { display: flex; justify-content: space-between; gap: 8px; } + .modal-meta .row strong { color: var(--text); font-weight: 500; } + .modal-explanation { + padding: 14px 18px; + font-size: 12px; + color: var(--text-dim); + border-top: 1px solid var(--border); + line-height: 1.6; + } + .modal-explanation code { + background: var(--panel-2); padding: 1px 5px; + border-radius: 3px; font-size: 11px; + color: var(--text); + } details { margin-top: 24px; } summary { cursor: pointer; color: var(--text-dim); font-size: 13px; padding: 8px 0; } details[open] summary { color: var(--text); } @@ -165,10 +237,26 @@ export const DEMO_PAGE_HTML = `
+ +
target × 1.5 binds (normal case) source × 1.5 binds (tiny-source cap) source equals target + → click any tile to view at target size
@@ -289,6 +377,22 @@ async function loadGrid() { tile.classList.remove('loading'); tile.classList.add(bindingClass(binding)); + // Store metadata for the modal + tile.dataset.target = String(target); + tile.dataset.path = path; + tile.dataset.sourceW = sourceW || ''; + tile.dataset.sourceH = sourceH || ''; + tile.dataset.encodeW = encodeW || ''; + tile.dataset.encodeH = encodeH || ''; + tile.dataset.binding = binding; + tile.dataset.quality = quality || ''; + tile.dataset.format = format; + tile.dataset.cache = cache; + tile.dataset.size = String(result.size); + tile.setAttribute('role', 'button'); + tile.setAttribute('tabindex', '0'); + tile.setAttribute('aria-label', 'Open ' + target + 'px preview'); + const img = document.createElement('img'); img.src = path; img.alt = 'target ' + target; @@ -322,6 +426,118 @@ function escapeHtml(s) { }[c])); } +// Modal +const modalBackdrop = document.getElementById('modal-backdrop'); +const modalTitle = document.getElementById('modal-title'); +const modalImageWrap = document.getElementById('modal-image-wrap'); +const modalMeta = document.getElementById('modal-meta'); +const modalExplanation = document.getElementById('modal-explanation'); +const modalCloseBtn = document.getElementById('modal-close'); + +function openModalForTile(tile) { + if (tile.classList.contains('loading') || tile.classList.contains('error')) return; + const d = tile.dataset; + const target = parseInt(d.target, 10); + const encodeW = parseInt(d.encodeW || '0', 10); + const encodeH = parseInt(d.encodeH || '0', 10); + const sourceW = d.sourceW; + const sourceH = d.sourceH; + const binding = d.binding; + const quality = d.quality; + const format = d.format; + const cache = d.cache; + const size = parseInt(d.size || '0', 10); + const path = d.path; + + // Cap display width to viewport (with a margin for modal padding/scrollbars) + const viewportCap = window.innerWidth - 80; + const displayWidth = Math.min(target, viewportCap); + const isClamped = displayWidth < target; + + modalTitle.innerHTML = + 'target ' + target + 'px' + + '' + + (isClamped + ? 'displayed at ' + displayWidth + 'px (your viewport is narrower than target)' + : 'displayed at ' + target + 'px (1:1 with target)') + + ''; + + // Render the image at target width — the browser does the final downscale + // from the encode dimensions (encodeW × encodeH) to this display size. + // That downscale IS the artifact-filter canon describes. + const img = document.createElement('img'); + img.src = path; + img.alt = 'target ' + target + ' preview'; + img.style.width = displayWidth + 'px'; + modalImageWrap.innerHTML = ''; + modalImageWrap.appendChild(img); + + modalMeta.innerHTML = + '
source' + (sourceW || '?') + ' × ' + (sourceH || '?') + '
' + + '
encode' + (encodeW || '?') + ' × ' + (encodeH || '?') + '
' + + '
display' + displayWidth + 'px wide
' + + '
binds' + (binding || '—') + '
' + + '
qualityq=' + (quality || '?') + '
' + + '
format' + (format || '?') + '
' + + '
size' + formatBytes(size) + '
' + + '
cache' + (cache || '—') + '
'; + + // Explanation tailored to which term bound + let explain = ''; + if (binding === 'target') { + explain = + 'The proxy encoded this image at ' + encodeW + '×' + encodeH + ' ' + + '(target × 1.5, mod-16 aligned). Your browser is downscaling that to ' + displayWidth + 'px ' + + 'for display — that downscale is the artifact filter, the same mechanism canon describes for ' + + '"control the character of the loss."'; + } else if (binding === 'source') { + explain = + 'Source is small enough that source × 1.5 = ' + encodeW + 'px binds instead of ' + + 'target × 1.5. The proxy encoded at the modest overshoot, and your browser is upscaling ' + + 'from ' + encodeW + 'px to ' + displayWidth + 'px for display. Without the source × 1.5 ' + + 'cap, this would have manufactured pixels from no signal.'; + } else if (binding === 'equal') { + explain = + 'Source dimensions already match the target. No scaling at the encoder; the only work is ' + + 'format conversion and quality adjustment.'; + } + modalExplanation.innerHTML = explain; + + modalBackdrop.classList.add('open'); + // Focus close button for keyboard accessibility + modalCloseBtn.focus(); +} + +function closeModal() { + modalBackdrop.classList.remove('open'); + // Free the image so it doesn't stay in memory + modalImageWrap.innerHTML = ''; +} + +// Tile click → open modal +grid.addEventListener('click', (e) => { + const tile = e.target.closest('.tile'); + if (tile) openModalForTile(tile); +}); +grid.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + const tile = e.target.closest('.tile'); + if (tile) { + e.preventDefault(); + openModalForTile(tile); + } + } +}); + +// Close handlers +modalCloseBtn.addEventListener('click', closeModal); +modalBackdrop.addEventListener('click', (e) => { + if (e.target === modalBackdrop) closeModal(); +}); +document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && modalBackdrop.classList.contains('open')) closeModal(); +}); + reloadBtn.addEventListener('click', loadGrid); sourceSelect.addEventListener('change', () => { customUrl.value = ''; loadGrid(); }); customUrl.addEventListener('change', loadGrid); From 6ff3c609ea449cfd4f882529e97aa295b8a41c08 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 27 May 2026 21:40:57 +0000 Subject: [PATCH 2/4] fix(demo): scope legend bullet to items and label scaling direction --- src/demo-page.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/demo-page.ts b/src/demo-page.ts index 449ed7a..bd602b6 100644 --- a/src/demo-page.ts +++ b/src/demo-page.ts @@ -174,7 +174,9 @@ export const DEMO_PAGE_HTML = ` display: flex; gap: 16px; flex-wrap: wrap; font-size: 11px; color: var(--text-dim); margin-top: 12px; } - .legend span::before { + .legend .target::before, + .legend .source::before, + .legend .equal::before { content: "■"; margin-right: 4px; } .legend .target::before { color: var(--accent); } @@ -491,11 +493,14 @@ function openModalForTile(tile) { 'for display — that downscale is the artifact filter, the same mechanism canon describes for ' + '"control the character of the loss."'; } else if (binding === 'source') { + const scaleVerb = encodeW < displayWidth ? 'upscaling' : encodeW > displayWidth ? 'downscaling' : 'rendering 1:1'; + const scaleClause = encodeW === displayWidth + ? 'and your browser is rendering 1:1 at ' + displayWidth + 'px' + : 'and your browser is ' + scaleVerb + ' from ' + encodeW + 'px to ' + displayWidth + 'px for display'; explain = 'Source is small enough that source × 1.5 = ' + encodeW + 'px binds instead of ' + - 'target × 1.5. The proxy encoded at the modest overshoot, and your browser is upscaling ' + - 'from ' + encodeW + 'px to ' + displayWidth + 'px for display. Without the source × 1.5 ' + - 'cap, this would have manufactured pixels from no signal.'; + 'target × 1.5. The proxy encoded at the modest overshoot, ' + scaleClause + '. ' + + 'Without the source × 1.5 cap, this would have manufactured pixels from no signal.'; } else if (binding === 'equal') { explain = 'Source dimensions already match the target. No scaling at the encoder; the only work is ' + From c516ecf530b44dfa2c16a42d74dffa6df22d4e80 Mon Sep 17 00:00:00 2001 From: "Claude (Anthropic)" Date: Wed, 27 May 2026 21:42:55 +0000 Subject: [PATCH 3/4] feat(demo): real test images, baseline tile, query-string fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three changes that work together to make the demo honest: 1. **Curated, verified test images.** Replaced random picsum.photos sources with Unsplash images I visually verified contain what the labels claim — a person reading a newspaper (real text on paper), a wall of open books (dense small text), tax forms with calculator (mixed text/objects), a portrait of a person (face, fabric pattern), a field of poppies (canon's 'confetti' case), a library (portrait detail). Plus Unsplash's ?w=N parameter is used for the source-near-target case (850×600) to exercise canon Example 2 precisely, and the small-thumbnail case (400×300) to exercise the source × 1.5 binding cleanly. 2. **Baseline tile.** A distinct tile above the grid shows the unmodified source through the proxy's passthrough path (no options applied means the worker streams origin bytes through unchanged). Displays source dimensions, original file size, and format. Click it to open in the modal at native size. Gives an honest comparison point — every transcoded tile below is what the proxy delivers relative to this. 3. **Query-string fix in URL parser.** The previous parser stripped query strings from the source URL because new URL(req.url).pathname doesn't include search. With Unsplash URLs like /image/w=800/https://images.unsplash.com/photo-X?w=2000, the ?w=2000 was being lost — the worker fetched the natural 5472×3648 instead of the requested 2000-wide version. Fixed parseProxyPath to accept a second 'search' argument and reattach it to the source URL. Added a test for the case. Worker now passes both pathname and search. Tests: 29 pass / 0 fail (was 28; added 1 for the search-reattach case). Typecheck clean. --- src/demo-page.ts | 224 +++++++++++++++++++++++++++++-- src/lib/parse-proxy-path.test.ts | 12 ++ src/lib/parse-proxy-path.ts | 12 +- src/worker.ts | 6 +- 4 files changed, 242 insertions(+), 12 deletions(-) diff --git a/src/demo-page.ts b/src/demo-page.ts index bd602b6..7db6f88 100644 --- a/src/demo-page.ts +++ b/src/demo-page.ts @@ -94,6 +94,57 @@ export const DEMO_PAGE_HTML = ` .file-size.ok { color: var(--accent-2); } .file-size.high { color: var(--warn); } + /* Baseline tile — the unmodified source for comparison */ + .baseline-wrap { margin-bottom: 20px; } + .baseline-tile { + background: var(--panel); border: 1px solid var(--border); + border-radius: 8px; overflow: hidden; + display: grid; grid-template-columns: 1fr 1fr; + cursor: pointer; transition: border-color 0.15s; + } + .baseline-tile:hover { border-color: var(--accent-2); } + .baseline-tile.loading, .baseline-tile.error { cursor: default; } + .baseline-tile.loading:hover, .baseline-tile.error:hover { border-color: var(--border); } + .baseline-tile .baseline-image { + background: #000; + display: flex; align-items: center; justify-content: center; + min-height: 200px; max-height: 360px; overflow: hidden; + } + .baseline-tile .baseline-image img { + max-width: 100%; max-height: 360px; display: block; object-fit: contain; + } + .baseline-tile .baseline-info { + padding: 18px 20px; + display: flex; flex-direction: column; gap: 10px; + } + .baseline-tile .baseline-label { + color: var(--accent-2); font-size: 11px; font-weight: 600; + text-transform: uppercase; letter-spacing: 0.08em; + } + .baseline-tile h2 { + margin: 0; font-size: 16px; font-weight: 600; + } + .baseline-tile .baseline-meta { + margin-top: 6px; + font-family: var(--mono); font-size: 11px; + color: var(--text-dim); + display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 6px 16px; + } + .baseline-tile .baseline-meta .row { display: flex; justify-content: space-between; gap: 8px; } + .baseline-tile .baseline-meta strong { color: var(--text); font-weight: 500; } + .baseline-tile .baseline-note { + font-size: 11px; color: var(--text-dim); line-height: 1.5; + border-top: 1px solid var(--border); padding-top: 10px; margin-top: 4px; + } + .baseline-tile.error .baseline-image { background: #2a1a1a; color: #ff8b8b; font-size: 12px; padding: 16px; text-align: center; } + .baseline-tile.loading .baseline-image::before { + content: "loading…"; color: var(--text-dim); font-size: 12px; + } + @media (max-width: 720px) { + .baseline-tile { grid-template-columns: 1fr; } + } + /* Modal */ .modal-backdrop { position: fixed; inset: 0; @@ -200,13 +251,16 @@ export const DEMO_PAGE_HTML = `
@@ -237,6 +291,8 @@ export const DEMO_PAGE_HTML = `
Loading source…
+
+