Files
px-icon/examples/index.html
T
mark 0c2785ca3b fix: sliders in demo require reload to rerender
CSS custom property changes (--pxicon-ap-size, --pxicon-mode) aren't
observed by MutationObserver/ResizeObserver, so a live-mounted <px-icon>
never rechecked its config. Add a pxicon:refresh document event + exported
refreshAll() helper; demo calls it from its control handlers.
2026-08-08 15:56:58 +02:00

252 lines
9.7 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>px-icon demo</title>
<style>
:root {
--bg: #1b1712;
--panel-bg: #2a241c;
--ink: #e8dcc4;
--accent: #c9a15a;
--ok: #7fae6a;
}
* { box-sizing: border-box; }
body {
margin: 0;
padding: 24px;
background: var(--bg);
color: var(--ink);
font-family: ui-monospace, "SF Mono", Consolas, monospace;
font-size: 13px;
}
h1 { font-size: 15px; font-weight: 600; margin: 28px 0 4px; }
h1:first-child { margin-top: 0; }
.sub { color: #948a76; margin: 0 0 16px; line-height: 1.5; max-width: 640px; }
.controls {
display: flex; gap: 24px; flex-wrap: wrap; align-items: center;
margin-bottom: 16px; padding: 12px 16px; background: var(--panel-bg);
border: 1px solid #3a3327;
}
.control { display: flex; align-items: center; gap: 8px; }
.control label { color: #b8ac93; }
input[type="range"] { width: 120px; }
.control .val { color: var(--accent); min-width: 40px; display: inline-block; }
select {
background: var(--bg); color: var(--ink); border: 1px solid #3a3327;
padding: 3px 6px; font-family: inherit;
}
button {
background: var(--bg); color: var(--ink); border: 1px solid var(--accent);
padding: 6px 12px; font-family: inherit; cursor: pointer;
}
button:hover { background: #241f18; }
.icon-row {
display: flex; gap: 20px; align-items: flex-end; flex-wrap: wrap;
margin-bottom: 8px; padding: 16px; background: var(--panel-bg);
border: 1px solid #3a3327;
}
.icon-wrap { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.icon-wrap px-icon { width: 64px; height: 64px; }
.icon-label { color: #948a76; font-size: 11px; }
.log {
background: #14110d; border: 1px solid #3a3327; padding: 10px 12px;
max-height: 180px; overflow-y: auto; line-height: 1.6; margin-top: 8px;
}
.log .ok { color: var(--ok); }
.log .t { color: #6b6152; }
code { color: var(--accent); }
</style>
</head>
<body>
<h1>px-icon</h1>
<p class="sub">
Standard &lt;svg&gt; markup, authored inline as normal. The library rasterizes each one onto a shared
art-pixel grid into its shadow DOM, while the original &lt;svg&gt; stays untouched in the light DOM.
</p>
<div class="controls">
<div class="control">
<label for="apSize">art-pixel size (px)</label>
<input type="range" id="apSize" min="2" max="20" value="6">
<span class="val" id="apSizeVal">6</span>
</div>
<div class="control">
<label for="mode">downsample</label>
<select id="mode">
<option value="average">area average</option>
<option value="point">hard point sample</option>
</select>
</div>
</div>
<div class="icon-row" id="mainRow">
<div class="icon-wrap">
<px-icon><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
<polygon points="60,50 140,50 170,90 100,180 30,90" fill="#8e1f3a" stroke="#3a0d17" stroke-width="4"/>
<polygon points="60,50 140,50 100,80" fill="#c9385c"/>
<polygon points="60,50 30,90 100,80" fill="#a02648"/>
<polygon points="140,50 170,90 100,80" fill="#a02648"/>
<polygon points="30,90 100,80 100,180" fill="#6e1730"/>
<polygon points="170,90 100,80 100,180" fill="#5c1329"/>
</svg></px-icon>
<div class="icon-label">ruby (own set)</div>
</div>
<div class="icon-wrap">
<px-icon><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
<rect x="10" y="10" width="180" height="180" rx="14" fill="#7a5a34" stroke="#3f2c18" stroke-width="6"/>
<rect x="26" y="26" width="148" height="148" fill="none" stroke="#c9a15a" stroke-width="3" stroke-dasharray="10 8"/>
</svg></px-icon>
<div class="icon-label">box (own set)</div>
</div>
<div class="icon-wrap">
<px-icon><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
<defs>
<linearGradient id="pxg1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#e8c887"/>
<stop offset="45%" stop-color="#c9a15a"/>
<stop offset="100%" stop-color="#6b4423"/>
</linearGradient>
</defs>
<circle cx="100" cy="100" r="90" fill="url(#pxg1)"/>
</svg></px-icon>
<div class="icon-label">gradient (own set)</div>
</div>
<div class="icon-wrap">
<px-icon><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#4a90d9" stroke="#1f5b96" stroke-width="1" d="M12 2 2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg></px-icon>
<div class="icon-label">layers (pasted-in external set)</div>
</div>
</div>
<h1>datastar morph safety</h1>
<p class="sub">
This icon lives in a container Idiomorph can morph, the same engine Datastar uses for server-driven
DOM patches. The buttons below simulate the server re-sending HTML for this fragment: once identical,
once with a different icon. The raster survives both without flashing back to raw SVG, because the
morph only ever touches the light-dom &lt;svg&gt;, never the shadow-dom canvas.
</p>
<div class="icon-row" id="morphRow">
<div class="icon-wrap">
<div id="morphTarget"><px-icon><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
<polygon points="60,50 140,50 170,90 100,180 30,90" fill="#8e1f3a" stroke="#3a0d17" stroke-width="4"/>
<polygon points="60,50 140,50 100,80" fill="#c9385c"/>
<polygon points="60,50 30,90 100,80" fill="#a02648"/>
<polygon points="140,50 170,90 100,80" fill="#a02648"/>
<polygon points="30,90 100,80 100,180" fill="#6e1730"/>
<polygon points="170,90 100,80 100,180" fill="#5c1329"/>
</svg></px-icon></div>
<div class="icon-label">morph target</div>
</div>
</div>
<div class="controls">
<button id="morphSame">simulate server morph (identical markup)</button>
<button id="morphSwap">simulate server morph (swap to different icon)</button>
</div>
<div class="log" id="morphLog"></div>
<h1>progressive enhancement</h1>
<p class="sub">
With a debug delay set on the render pipeline, the raw &lt;svg&gt; (via the shadow-root &lt;slot&gt;) stays
visible the whole time rasterization is genuinely in flight, then the canvas swaps in when it's ready.
No JS, or a slow one, degrades to a plain icon rather than nothing.
</p>
<div class="controls">
<div class="control">
<label for="peDelay">artificial render delay (ms)</label>
<input type="range" id="peDelay" min="0" max="3000" step="250" value="0">
<span class="val" id="peDelayVal">0</span>
</div>
<button id="peRerender">re-mount icon to see the delay</button>
</div>
<div class="icon-row" id="peRow">
<div class="icon-wrap" id="peWrap">
<px-icon><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#4a90d9" stroke="#1f5b96" stroke-width="1" d="M12 2 2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg></px-icon>
</div>
</div>
<script type="module">
import { refreshAll } from '../px-icon.js';
const apSizeInput = document.getElementById('apSize');
const apSizeVal = document.getElementById('apSizeVal');
const modeSelect = document.getElementById('mode');
function applyConfig() {
const ap = apSizeInput.value;
apSizeVal.textContent = ap;
document.documentElement.style.setProperty('--pxicon-ap-size', ap + 'px');
document.documentElement.style.setProperty('--pxicon-mode', modeSelect.value);
refreshAll();
}
apSizeInput.addEventListener('input', applyConfig);
modeSelect.addEventListener('change', applyConfig);
applyConfig();
// progressive enhancement delay control
const peDelay = document.getElementById('peDelay');
const peDelayVal = document.getElementById('peDelayVal');
const peWrap = document.getElementById('peWrap');
const peRerender = document.getElementById('peRerender');
const peIconMarkup = peWrap.innerHTML;
peDelay.addEventListener('input', () => {
peDelayVal.textContent = peDelay.value;
document.documentElement.style.setProperty('--pxicon-debug-delay', peDelay.value);
});
peRerender.addEventListener('click', () => {
// re-mount so connectedCallback runs again with the current delay
peWrap.innerHTML = '';
requestAnimationFrame(() => { peWrap.innerHTML = peIconMarkup; });
});
</script>
<script src="https://unpkg.com/idiomorph@0.7.4/dist/idiomorph.min.js"></script>
<script type="module">
const morphTarget = document.getElementById('morphTarget');
const log = document.getElementById('morphLog');
const originalMarkup = morphTarget.innerHTML;
const swappedMarkup = `<px-icon><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#4a90d9" stroke="#1f5b96" stroke-width="1" d="M12 2 2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg></px-icon>`;
function logLine(text, ok) {
const line = document.createElement('div');
const t = new Date().toLocaleTimeString();
line.innerHTML = `<span class="t">${t}</span> ${ok ? '<span class="ok">&#10003;</span> ' : ''}${text}`;
log.prepend(line);
}
function currentCanvas() {
const el = morphTarget.querySelector('px-icon');
return el && el.shadowRoot ? el.shadowRoot.querySelector('canvas') : null;
}
document.getElementById('morphSame').addEventListener('click', () => {
const before = currentCanvas();
Idiomorph.morph(morphTarget, `<div id="morphTarget">${originalMarkup}</div>`);
const after = currentCanvas();
logLine(
before && after === before
? 'identical morph applied, same &lt;canvas&gt; node survived untouched'
: 'canvas node changed (unexpected, check console)',
before && after === before
);
});
document.getElementById('morphSwap').addEventListener('click', () => {
Idiomorph.morph(morphTarget, `<div id="morphTarget">${swappedMarkup}</div>`);
logLine('server sent a different icon, light-dom &lt;svg&gt; morphed, watch it re-rasterize above', true);
});
logLine('ready. click a button to simulate a server-driven morph.', false);
</script>
</body>
</html>