← back to Pattern Vault
whimsical-compare/public/faces.html
64 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tema e Variazioni — Our Faces</title>
<style>
:root { --cols: 4; }
* { box-sizing: border-box; }
body { margin: 0; font-family: -apple-system, "Segoe UI", Roboto, sans-serif; background: #f3f1ec; color: #1c1a17; }
header { position: sticky; top: 0; z-index: 5; background: #fdfcf9; border-bottom: 1px solid #e2dccf;
padding: 16px 24px; display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }
header h1 { margin: 0; font-size: 20px; font-weight: 600; letter-spacing: .4px; }
header .sub { color: #8a8172; font-size: 12px; }
.controls { margin-left: auto; display: flex; align-items: center; gap: 14px; }
.controls label { font-size: 12px; color: #6b6355; display: flex; align-items: center; gap: 6px; }
.note { padding: 10px 24px; background: #fbf4e6; border-bottom: 1px solid #ecdfc4; font-size: 12px; color: #7a6a2f; }
.wrap { padding: 24px; }
.grid { display: grid; grid-template-columns: repeat(var(--cols), 1fr); gap: 20px; }
.tile { background: #fff; border: 1px solid #e2dccf; border-radius: 12px; overflow: hidden; }
.tile img { width: 100%; aspect-ratio: 1/1; object-fit: cover; display: block; background: #eee; }
.tile .cap { padding: 9px 12px; }
.tile .cap b { font-size: 13px; } .tile .cap .d { font-size: 11px; color: #8a8172; margin-top: 2px; }
.wallprev { margin: 8px 0 0; }
.pending { display: flex; align-items: center; justify-content: center; aspect-ratio: 1/1;
background: repeating-linear-gradient(45deg,#eee,#eee 10px,#e6e1d6 10px,#e6e1d6 20px); color: #999; font-size: 12px; }
</style>
</head>
<body>
<header>
<div><h1>Tema e Variazioni — <span style="color:#8a8172">Our Faces</span></h1>
<div class="sub">20 original b&w portrait-medallion tiles · whimsical variations · our own face</div></div>
<div class="controls">
<label>Density <input type="range" id="density" min="2" max="6" value="4"></label>
<label><input type="checkbox" id="wall"> show as wall repeat</label>
</div>
</header>
<div class="note">Original homage to the <b>theme-and-variations portrait-plate format</b> — our own invented face and variations. Not a reproduction of Fornasetti's design or their model.</div>
<div class="wrap"><div class="grid" id="grid"></div></div>
<script>
const $ = s => document.querySelector(s);
let DATA = { designs: [] }, WALL = false;
function tile(d) {
const img = d.file
? (WALL ? `<div class="wallprev" style="aspect-ratio:1/1;background:url('${d.file}');background-size:33%;background-repeat:repeat"></div>`
: `<img loading="lazy" src="${d.file}" alt="${d.name}">`)
: `<div class="pending">rendering…</div>`;
return `<div class="tile">${img}<div class="cap"><b>${d.n}. ${d.name}</b><div class="d">${d.desc}</div></div></div>`;
}
function render() { $('#grid').innerHTML = DATA.designs.map(tile).join(''); }
async function load() {
const r = await fetch('/api/faces'); DATA = await r.json();
// pad to 20 slots so pending tiles show
const have = new Set(DATA.designs.map(d => d.n));
for (let i = 1; i <= 20; i++) if (!have.has(i)) DATA.designs.push({ n: i, name: 'variation ' + i, desc: '', file: null });
DATA.designs.sort((a, b) => a.n - b.n); render();
}
$('#density').oninput = e => document.documentElement.style.setProperty('--cols', e.target.value);
$('#wall').onchange = e => { WALL = e.target.checked; render(); };
load(); setInterval(load, 12000);
</script>
</body>
</html>