← back to Fentucci Viewer

public/index.html

100 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>Fentucci Naturals — Google Live Status</title>
<!-- href-drill:exempt reason="Google-live status readout — single status surface, not a browse catalog" (Steve 2026-08-23, exclusion-marker verdict) -->
<style>
:root{--cols:5;--card:1px solid #e4e0d8}
*{box-sizing:border-box}
body{margin:0;font:14px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;color:#23201b;background:#faf8f4}
header{position:sticky;top:0;z-index:5;background:#fff;border-bottom:1px solid #e4e0d8;padding:14px 20px;display:flex;flex-wrap:wrap;gap:16px;align-items:center}
h1{font-size:18px;margin:0;font-weight:600;letter-spacing:.02em}
.sub{color:#8a8478;font-size:12px}
.controls{margin-left:auto;display:flex;gap:14px;align-items:center;flex-wrap:wrap}
label{font-size:12px;color:#6b6559;display:flex;gap:6px;align-items:center}
select,input[type=range]{font:inherit}
.badge{display:inline-block;padding:2px 8px;border-radius:20px;font-size:11px;font-weight:600}
.live{background:#e6f4ea;color:#1c7c3e}.off{background:#fbe9e7;color:#b3261e}
.stat{font-weight:600}
main{padding:18px 20px}
.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:14px}
.card{border:var(--card);border-radius:10px;overflow:hidden;background:#fff;display:flex;flex-direction:column}
.card img{width:100%;aspect-ratio:1/1;object-fit:cover;background:#f0ece4;display:block}
.card .body{padding:9px 10px;display:flex;flex-direction:column;gap:5px}
.card .t{font-weight:600;font-size:13px;line-height:1.25}
.card .row{display:flex;justify-content:space-between;align-items:center;gap:8px}
.price{font-weight:700}
.mini{font-size:11px;color:#8a8478}
.densebar{display:flex;gap:8px;align-items:center}
</style></head>
<body>
<header>
  <div><h1>Fentucci Naturals</h1><div class="sub">Google Merchant live status · <span id="count"></span></div></div>
  <div class="controls">
    <span class="badge live">On Google <span class="stat" id="onN">–</span></span>
    <span class="badge off">Off <span class="stat" id="offN">–</span></span>
    <label>Sort
      <select id="sort">
        <option value="title">Title A→Z</option>
        <option value="google">On Google first</option>
        <option value="offgoogle">Off Google first</option>
        <option value="price">Sample price ↑</option>
      </select>
    </label>
    <label class="densebar">Density <input type="range" id="dens" min="3" max="8" value="5"></label>
  </div>
</header>
<main><div class="grid" id="grid"></div></main>
<script>
const S="https://designer-laboratory-sandbox.myshopify.com";
let items=[];
const grid=document.getElementById("grid");
function ph(t){const h=[...t].reduce((a,c)=>a+c.charCodeAt(0),0)%360;return `data:image/svg+xml,`+encodeURIComponent(`<svg xmlns='http://www.w3.org/2000/svg' width='300' height='300'><rect width='300' height='300' fill='hsl(${h} 30% 82%)'/><text x='150' y='155' font-family='sans-serif' font-size='16' fill='hsl(${h} 40% 40%)' text-anchor='middle'>${t.slice(0,18)}</text></svg>`);}
function render(){
  const s=document.getElementById("sort").value;
  let a=[...items];
  if(s==="title")a.sort((x,y)=>x.title.localeCompare(y.title));
  if(s==="google")a.sort((x,y)=>(y.onG-x.onG)||x.title.localeCompare(y.title));
  if(s==="offgoogle")a.sort((x,y)=>(x.onG-y.onG)||x.title.localeCompare(y.title));
  if(s==="price")a.sort((x,y)=>(parseFloat(x.sample||0))-(parseFloat(y.sample||0)));
  grid.innerHTML=a.map(p=>`<div class="card">
    <a rel="noopener noreferrer" href="${S}/products/${p.handle}" target="_blank"><img loading="lazy" src="${p.img||ph(p.title)}" onerror="this.src='${ph(p.title)}'" alt=""></a>
    <div class="body">
      <div class="t">${p.title.replace(/ \\| Fentucci.*$/,"")}</div>
      <div class="row"><span class="price">$${p.sample||"—"}</span><span class="badge ${p.onG?"live":"off"}">${p.onG?"On Google":"Off"}</span></div>
      <div class="mini">sample offer${p.roll?` · roll $${p.roll}`:""}</div>
    </div></div>`).join("");
}
fetch("products.json").then(r=>r.json()).then(d=>{
  items=d;
  document.getElementById("count").textContent=d.length+" products";
  document.getElementById("onN").textContent=d.filter(x=>x.onG).length;
  document.getElementById("offN").textContent=d.filter(x=>!x.onG).length;
  render();
});
document.getElementById("sort").onchange=render;
document.getElementById("dens").oninput=e=>document.documentElement.style.setProperty("--cols",e.target.value);
</script></body></html>