← back to Wallco Ai
public/collections/fliepaper-bugs.html
452 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>fliepaper × wallco — Gucci Resort Edition</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="description" content="Six luxury insect wallcoverings — scarab, luna moth, dragonfly, stag beetle, honeybee, firefly — in six Gucci-resort colorways. 36 SKUs from wallco.ai." />
<link rel="stylesheet" href="/marketplace/_layout.css" />
<style>
:root {
--fb-bg: #faf7f1;
--fb-fg: #1a1814;
--fb-muted: #6b665c;
--fb-line: rgba(0,0,0,.08);
--fb-card: #fff;
--fb-accent: #0F4F38;
--fb-cols: 3;
}
html[data-theme="dark"] {
--fb-bg: #0e0d0b;
--fb-fg: #f3eee2;
--fb-muted: #a39c8b;
--fb-line: rgba(255,255,255,.10);
--fb-card: #191714;
--fb-accent: #B8914A;
}
html,body { background: var(--fb-bg); color: var(--fb-fg); margin:0; padding:0;
font-family: ui-sans-serif, system-ui, "Helvetica Neue", Arial, sans-serif;
transition: background 200ms ease, color 200ms ease;
}
a { color: inherit; }
/* ─────── HERO (Gucci-style) ─────── */
.fb-hero {
position: relative;
height: min(96vh, 1080px);
min-height: 540px;
overflow: hidden;
background: #14110d;
}
.fb-hero-img {
position: absolute; inset:0;
background-size: cover; background-position: center;
transition: opacity 800ms ease;
opacity: 0;
}
.fb-hero-img.is-active { opacity: 1; }
.fb-hero-fallback {
position: absolute; inset:0;
background:
radial-gradient(ellipse at 30% 20%, rgba(184,145,74,.30), transparent 55%),
radial-gradient(ellipse at 70% 80%, rgba(15,79,56,.45), transparent 55%),
linear-gradient(135deg, #0F4F38 0%, #1A1F3C 50%, #3D1F52 100%);
}
.fb-hero-overlay {
position: absolute; inset:0;
background: linear-gradient(180deg, rgba(0,0,0,.20) 0%, rgba(0,0,0,0) 35%, rgba(0,0,0,.55) 100%);
pointer-events: none;
}
/* logo UL */
.fb-logo {
position: absolute; top: 20px; left: 28px; z-index: 5;
color: #fff; font-weight: 700; letter-spacing: .12em; font-size: 14px;
text-decoration: none; mix-blend-mode: difference;
}
/* hamburger UR ≡ MENU */
.fb-menu {
position: absolute; top: 18px; right: 24px; z-index: 5;
display: inline-flex; align-items: center; gap: 8px;
color: #fff; cursor: pointer; padding: 8px 4px;
background: transparent; border: 0;
font: 600 12px/1 ui-sans-serif, system-ui, sans-serif;
letter-spacing: .22em; text-transform: uppercase;
mix-blend-mode: difference;
}
.fb-menu .bars { display:inline-flex; flex-direction:column; gap:4px; }
.fb-menu .bars span { width: 22px; height: 1px; background: currentColor; display:block; }
/* dark/light toggle */
.fb-theme-toggle {
position: absolute; top: 20px; right: 110px; z-index: 5;
background: transparent; border: 0; cursor: pointer;
color: #fff; mix-blend-mode: difference;
font-size: 18px; padding: 6px 8px;
}
/* wordmark BELOW the photo (i.e. centered, low in the hero) */
.fb-wordmark {
position: absolute; left:0; right:0; bottom: 22%;
z-index: 4; text-align: center; color: #fff;
text-shadow: 0 2px 28px rgba(0,0,0,.45);
pointer-events: none;
}
.fb-wordmark .brand {
font-family: "Times New Roman", Didot, Georgia, serif;
font-size: clamp(48px, 9vw, 132px);
font-weight: 400; letter-spacing: .02em; line-height: 1;
}
.fb-wordmark .tag {
margin-top: 14px; font-style: italic; font-size: clamp(14px, 1.6vw, 20px);
letter-spacing: .18em; text-transform: uppercase; opacity: .92;
}
/* white Enter pill — centered bottom of hero */
.fb-enter {
position: absolute; left: 50%; bottom: 6%; transform: translateX(-50%);
z-index: 5;
background: #fff; color: #111;
padding: 14px 56px; border-radius: 4px;
font: 600 13px/1 ui-sans-serif, system-ui, sans-serif;
letter-spacing: .22em; text-transform: uppercase;
text-decoration: none;
transition: transform 200ms ease, box-shadow 200ms ease;
}
.fb-enter:hover { transform: translateX(-50%) translateY(-2px); box-shadow: 0 8px 20px rgba(0,0,0,.20); }
/* ─────── BODY ─────── */
.fb-main { max-width: 1480px; margin: 0 auto; padding: 56px 28px 100px; }
.fb-intro {
text-align: center; max-width: 720px; margin: 0 auto 36px;
}
.fb-intro h2 {
font-family: "Times New Roman", Didot, Georgia, serif; font-weight: 400;
font-size: clamp(28px, 3.6vw, 44px); margin: 0 0 12px;
}
.fb-intro p { color: var(--fb-muted); line-height: 1.55; margin: 0; }
/* color-dot row */
.fb-palette {
display: flex; gap: 18px; justify-content: center; align-items: center;
margin: 28px auto 48px; flex-wrap: wrap;
}
.fb-palette .swatch {
display: flex; flex-direction: column; align-items:center; gap: 8px;
cursor: pointer; min-width: 84px;
}
.fb-palette .dot {
width: 44px; height: 44px; border-radius: 50%;
border: 1px solid var(--fb-line);
box-shadow: inset 0 0 0 3px var(--fb-bg);
}
.fb-palette .swatch.is-active .dot { box-shadow: inset 0 0 0 3px var(--fb-bg), 0 0 0 2px var(--fb-fg); }
.fb-palette .swatch .name { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--fb-muted); }
/* controls (sort + density) */
.fb-controls {
display: flex; gap: 14px; flex-wrap: wrap; align-items: center;
margin: 0 0 28px; padding: 14px 16px;
background: var(--fb-card);
border: 1px solid var(--fb-line); border-radius: 12px;
}
.fb-controls label { font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--fb-muted); }
.fb-controls select {
padding: 8px 14px; border-radius: 999px; border: 1px solid var(--fb-line);
background: var(--fb-bg); color: var(--fb-fg); font-size: 14px;
}
.fb-controls .pill {
margin-left: auto; font-size: 12px; color: var(--fb-muted);
letter-spacing: .08em; text-transform: uppercase;
}
/* pattern grid */
.fb-grid {
display: grid; gap: 26px;
grid-template-columns: repeat(var(--fb-cols), 1fr);
}
@media (max-width: 980px) { :root { --fb-cols: 2; } }
@media (max-width: 600px) { :root { --fb-cols: 1; } }
.fb-card {
background: var(--fb-card); border: 1px solid var(--fb-line);
border-radius: 14px; overflow: hidden;
display: flex; flex-direction: column;
transition: transform 180ms ease, box-shadow 180ms ease;
}
.fb-card:hover { transform: translateY(-3px); box-shadow: 0 12px 28px rgba(0,0,0,.10); }
.fb-card .pat-img {
aspect-ratio: 1 / 1.15;
background-size: cover; background-position: center;
background-color: #1a1814; position: relative;
}
.fb-card .pat-pending {
position: absolute; top: 12px; left: 12px;
background: rgba(0,0,0,.55); color: #fff;
font: 600 10px/1 ui-sans-serif, system-ui;
letter-spacing: .2em; text-transform: uppercase;
padding: 6px 10px; border-radius: 999px;
}
.fb-card .body { padding: 16px 18px 18px; }
.fb-card .name {
font-family: "Times New Roman", Didot, Georgia, serif; font-size: 22px;
margin: 0 0 4px;
}
.fb-card .bug-sub { color: var(--fb-muted); font-size: 13px; margin: 0 0 12px;
letter-spacing: .04em; text-transform: capitalize; }
.fb-card .chips { display: flex; gap: 6px; flex-wrap: wrap; }
.fb-card .chip {
width: 22px; height: 22px; border-radius: 50%;
border: 1px solid var(--fb-line);
cursor: pointer; position: relative;
}
.fb-card .chip.is-missing::after {
content: ""; position: absolute; inset: 4px;
border: 1px dashed rgba(0,0,0,.30); border-radius: 50%;
}
.fb-card .meta-row {
display: flex; gap: 10px; justify-content: space-between;
align-items: center; margin-top: 14px;
font-size: 12px; color: var(--fb-muted);
}
.fb-footer {
margin-top: 80px; padding: 28px 0; border-top: 1px solid var(--fb-line);
text-align: center; color: var(--fb-muted); font-size: 12px; letter-spacing: .12em;
text-transform: uppercase;
}
.fb-footer a { color: inherit; text-decoration: underline; text-underline-offset: 4px; }
</style>
</head>
<body data-theme="light">
<!-- ───────────── HERO ───────────── -->
<section class="fb-hero" id="fb-hero">
<div class="fb-hero-fallback" id="fb-hero-fallback"></div>
<a href="/" class="fb-logo">wallco.ai</a>
<button class="fb-theme-toggle" id="fb-theme" title="Toggle light/dark">◐</button>
<button class="fb-menu" type="button" onclick="document.dispatchEvent(new Event('wallco-open-menu'))" aria-label="Open menu">
<span class="bars"><span></span><span></span><span></span></span>
MENU
</button>
<div class="fb-hero-overlay"></div>
<div class="fb-wordmark">
<div class="brand">fliepaper × wallco</div>
<div class="tag">Gucci Resort Edition · 6 patterns · 6 colorways · 36 SKUs</div>
</div>
<a class="fb-enter" href="#collection">Enter</a>
</section>
<!-- ───────────── BODY ───────────── -->
<main class="fb-main" id="collection">
<section class="fb-intro">
<h2>Six bugs. Six Gucci-resort colorways.</h2>
<p>Scarab, luna moth, dragonfly, stag beetle, honeybee, firefly — six luxury insect wallcoverings rendered in six Gucci-coordinated colorways: emerald, papyrus, midnight, antique horsebit, deep plum, oxblood. Designed in-house by Wallco House Studio. Every pattern generated through proprietary Wallco AI.</p>
</section>
<!-- Color dot row -->
<div class="fb-palette" id="fb-palette"></div>
<!-- Sort + density -->
<div class="fb-controls">
<label for="fb-sort">Sort</label>
<select id="fb-sort">
<option value="newest">Newest</option>
<option value="color">Color</option>
<option value="pattern">Pattern A→Z</option>
<option value="sku">SKU A→Z</option>
</select>
<label for="fb-density">Density</label>
<input type="range" id="fb-density" min="1" max="4" value="3" step="1" />
<span class="pill" id="fb-meta">loading…</span>
</div>
<div class="fb-grid" id="fb-grid"></div>
</main>
<footer class="fb-footer">
<a href="/">wallco.ai</a> · Fliepaper Bugs — Gucci Resort Edition · <span id="fb-status-count">0/36</span> SKUs generated
</footer>
<script>
const STORAGE_KEY = 'wallco.fliepaper-bugs.prefs';
const FB = { spec: null, cells: [], colorwayFilter: null };
function loadPrefs() {
try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); } catch { return {}; }
}
function savePrefs() {
const p = {
sort: document.getElementById('fb-sort').value,
density: document.getElementById('fb-density').value,
theme: document.body.dataset.theme,
};
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(p)); } catch {}
}
function applyDensity() {
const v = parseInt(document.getElementById('fb-density').value, 10);
// 1→1col, 2→2col, 3→3col, 4→4col
document.documentElement.style.setProperty('--fb-cols', v);
}
function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
document.body.dataset.theme = theme;
document.getElementById('fb-theme').textContent = theme === 'dark' ? '☀' : '◐';
}
function placeholderPoster(hex, label) {
// hsl-gradient style placeholder — never a 404, never a broken image.
// Two-stop gradient seeded from the colorway hex so each pattern×colorway
// pair gets a distinct placeholder.
const seed = label.split('').reduce((s,c)=>s+c.charCodeAt(0),0);
const hueShift = (seed % 60) - 30;
return `linear-gradient(135deg,
${hex} 0%,
color-mix(in oklab, ${hex} 65%, #000 35%) 100%)`;
}
function escHtml(s){ return String(s||'').replace(/[&<>"]/g, c => ({ '&':'&','<':'<','>':'>','"':'"' }[c])); }
async function fetchCollection() {
try {
const r = await fetch('/api/collections/fliepaper-bugs');
if (!r.ok) throw new Error('http ' + r.status);
const j = await r.json();
FB.spec = j.spec;
FB.cells = j.cells || [];
renderPalette();
renderGrid();
updateMeta();
} catch (e) {
document.getElementById('fb-meta').textContent = 'load error: ' + e.message;
}
}
function renderPalette() {
const el = document.getElementById('fb-palette');
el.innerHTML = '';
for (const c of FB.spec.palette) {
const w = document.createElement('div');
w.className = 'swatch' + (FB.colorwayFilter === c.slug ? ' is-active' : '');
w.innerHTML = `
<div class="dot" style="background:${c.hex}"></div>
<div class="name">${escHtml(c.name)}</div>
`;
w.addEventListener('click', () => {
FB.colorwayFilter = (FB.colorwayFilter === c.slug) ? null : c.slug;
renderPalette();
renderGrid();
});
el.appendChild(w);
}
}
function sortPatterns(patterns) {
const mode = document.getElementById('fb-sort').value;
const arr = patterns.slice();
if (mode === 'pattern') arr.sort((a,b) => a.name.localeCompare(b.name));
else if (mode === 'sku') arr.sort((a,b) => a.slug.localeCompare(b.slug));
else if (mode === 'color') {
// group by dominant colorway present — placeholder until art lands, sort by first-cell hex
arr.sort((a,b) => a.slug.localeCompare(b.slug));
}
// 'newest' = spec order (the order yolo-runner generates in)
return arr;
}
function renderGrid() {
const grid = document.getElementById('fb-grid');
const patterns = sortPatterns(FB.spec.patterns);
grid.innerHTML = '';
for (const p of patterns) {
const cellsForPat = FB.cells.filter(c => c.pattern_slug === p.slug);
const visibleCells = FB.colorwayFilter
? cellsForPat.filter(c => c.colorway_slug === FB.colorwayFilter)
: cellsForPat;
if (FB.colorwayFilter && visibleCells.length === 0) continue;
// Hero image for the card — prefer a generated cell that matches the
// active colorway filter, else first GENERATED cell, else placeholder.
const heroCell = (FB.colorwayFilter
? visibleCells.find(c => c.status === 'GENERATED')
: null)
|| cellsForPat.find(c => c.status === 'GENERATED')
|| cellsForPat[0];
const heroHex = (heroCell && heroCell.colorway_hex) || '#1A1F3C';
const heroIsReal = heroCell && heroCell.status === 'GENERATED' && heroCell.thumbnail_url;
const card = document.createElement('article');
card.className = 'fb-card';
card.innerHTML = `
<div class="pat-img" style="${heroIsReal
? `background-image:url('${heroCell.thumbnail_url}')`
: `background-image:${placeholderPoster(heroHex, p.slug)}`}">
${heroIsReal ? '' : `<span class="pat-pending">Pending</span>`}
</div>
<div class="body">
<h3 class="name">${escHtml(p.name)}</h3>
<div class="bug-sub">${escHtml(p.bug)} · ${escHtml(p.scale)}</div>
<div class="chips" data-pat="${p.slug}"></div>
<div class="meta-row">
<span>${visibleCells.filter(c => c.status === 'GENERATED').length}/${visibleCells.length} colorways live</span>
<span>${p.composition.split(';')[0]}</span>
</div>
</div>
`;
// chip row — one per colorway
const chips = card.querySelector('.chips');
for (const c of FB.spec.palette) {
const cell = cellsForPat.find(x => x.colorway_slug === c.slug);
const span = document.createElement('span');
span.className = 'chip' + (cell?.status === 'GENERATED' ? '' : ' is-missing');
span.style.background = c.hex;
span.title = `${c.name} — ${cell?.status || 'PENDING'}`;
span.addEventListener('click', () => {
FB.colorwayFilter = (FB.colorwayFilter === c.slug) ? null : c.slug;
renderPalette();
renderGrid();
});
chips.appendChild(span);
}
grid.appendChild(card);
}
}
function updateMeta() {
const generated = FB.cells.filter(c => c.status === 'GENERATED').length;
const total = FB.cells.length;
document.getElementById('fb-meta').textContent =
`${generated}/${total} SKUs generated · ${FB.spec.patterns.length} patterns · ${FB.spec.palette.length} colorways`;
document.getElementById('fb-status-count').textContent = `${generated}/${total}`;
}
// ─── Init ───
(function init() {
const prefs = loadPrefs();
if (prefs.sort) document.getElementById('fb-sort').value = prefs.sort;
if (prefs.density) document.getElementById('fb-density').value = prefs.density;
applyDensity();
applyTheme(prefs.theme === 'dark' ? 'dark' : 'light');
document.getElementById('fb-sort').addEventListener('change', () => { savePrefs(); renderGrid(); });
document.getElementById('fb-density').addEventListener('input', () => { applyDensity(); savePrefs(); });
document.getElementById('fb-theme').addEventListener('click', () => {
applyTheme(document.body.dataset.theme === 'dark' ? 'light' : 'dark');
savePrefs();
});
fetchCollection();
// Re-poll every 30s so newly generated cells swap in without reload.
setInterval(fetchCollection, 30000);
})();
</script>
</body>
</html>