← back to Greenland Onboard
public/full-preview.html
90 lines
<!doctype html><html lang="en"><head><script>
// Credential-safe fetch guard (fleet drop-in) — if this page is opened with
// credentials in the URL (a saved bookmark / Chrome-remembered basic-auth:
// https://user:pass@host/…), the browser poisons document.baseURI, so any
// relative or root-relative fetch('/api/…') throws "Request cannot be constructed
// from a URL that includes credentials" and callers silently fall to an empty
// state. Resolve every non-absolute request URL against the credential-free
// location instead of baseURI. Placed first so it wraps window.fetch before any
// app script runs. Ref: creds-in-url-fetch-guard-fleet-pattern.
(function () {
var _fetch = window.fetch.bind(window);
var cleanBase = function () { return location.origin + location.pathname; };
window.fetch = function (input, init) {
try {
if (typeof input === 'string' && !/^[a-z]+:\/\//i.test(input) && input.indexOf('//') !== 0) {
input = new URL(input, cleanBase()).href;
} else if (input instanceof Request && !/^[a-z]+:\/\//i.test(input.url)) {
input = new Request(new URL(input.url, cleanBase()).href, input);
}
} catch (_) { /* fall through to native */ }
return _fetch(input, init);
};
})();
</script><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Phillipe Romano — Full Line (staging preview)</title>
<style>
:root{--cols:6}
*{box-sizing:border-box}
body{margin:0;font:14px/1.4 -apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:#faf8f5;color:#1a1a1a}
header{position:sticky;top:0;z-index:5;background:#fff;border-bottom:1px solid #e6e0d8;padding:12px 18px;display:flex;gap:14px;align-items:center;flex-wrap:wrap}
h1{font:600 18px/1 'Georgia',serif;margin:0;letter-spacing:.02em}
.note{color:#8a7f72;font-size:12px}
select,input[type=range]{font:inherit}
.wrap{padding:16px 18px}
.mat{margin:6px 0 22px}
.mat h2{font:600 14px/1 sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#6b5f52;border-bottom:1px solid #e6e0d8;padding-bottom:6px;margin:0 0 12px}
.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:12px}
.card{background:#fff;border:1px solid #eae3da;border-radius:4px;overflow:hidden;display:flex;flex-direction:column}
.card img{width:100%;aspect-ratio:1/1;object-fit:cover;background:#f0ece6}
.card .b{padding:8px 9px;font-size:12px}
.sku{font-weight:600;letter-spacing:.02em}
.city{color:#a8895f;font-style:italic}
.color{color:#555}
.mfr{color:#b3a596;font-size:11px;margin-top:3px;word-break:break-word}
.count{color:#8a7f72;font-weight:400}
</style></head><body>
<header>
<h1>Phillipe Romano — Full Line</h1>
<span class="note" id="tot">loading…</span>
<label>Material <select id="mat"><option value="">All</option></select></label>
<label>Density <input type="range" id="dens" min="3" max="10" value="6"></label>
<span class="note">staging preview · images localizing · DRAFT/quote-only · nothing live</span>
</header>
<div class="wrap" id="wrap"></div>
<script>
const dens=document.getElementById('dens');
dens.oninput=()=>document.documentElement.style.setProperty('--cols',dens.value);
let DATA=[];
fetch('/enriched-preview.json').then(r=>r.json()).then(d=>{
DATA=d;
document.getElementById('tot').textContent=`${d.length} SKUs · ${new Set(d.map(x=>x.collectionCode)).size} collections · ${new Set(d.map(x=>x.city)).size} cities`;
const mats=[...new Set(d.map(x=>x.material))].sort();
const sel=document.getElementById('mat');
for(const m of mats){const o=document.createElement('option');o.value=o.textContent=m;sel.appendChild(o);}
sel.onchange=render; render();
});
function render(){
const f=document.getElementById('mat').value;
const rows=f?DATA.filter(x=>x.material===f):DATA;
const byMat={};
for(const r of rows)(byMat[r.material]=byMat[r.material]||[]).push(r);
const wrap=document.getElementById('wrap'); wrap.innerHTML='';
for(const m of Object.keys(byMat).sort()){
const sec=document.createElement('div'); sec.className='mat';
sec.innerHTML=`<h2>${m} <span class="count">(${byMat[m].length})</span></h2>`;
const g=document.createElement('div'); g.className='grid';
for(const r of byMat[m]){
const c=document.createElement('div'); c.className='card';
c.innerHTML=`<img loading="lazy" src="${r.img}" alt="">
<div class="b"><div class="sku">${r.dwSku}</div>
<div class="city">${(r.title||'').split(' | ')[0]}</div>
<div class="color"><span style="display:inline-block;width:11px;height:11px;border-radius:2px;vertical-align:middle;margin-right:5px;background:${r.hex||'#eee'}"></span>${r.color||''} · ${r.hex||''}</div>
<div class="mfr">${(r.tags||[]).join(' · ')}</div></div>`;
g.appendChild(c);
}
sec.appendChild(g); wrap.appendChild(sec);
}
}
</script></body></html>