← back to Artmura Internal
Retrofit index.html to internal-line-viewer spec: mobile hamburger filter drawer, artmura.* localStorage namespace (was astek.*), auto-hide zero-value facet tables; E2E gate passed (chromium+webkit creds-in-URL, 16/16)
9b0b2f308b8f10ebafc901eb0f263b7a6ec77b77 · 2026-07-02 17:25:17 -0700 · Steve Abrams
Files touched
M public/curate.htmlM public/index.html
Diff
commit 9b0b2f308b8f10ebafc901eb0f263b7a6ec77b77
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 2 17:25:17 2026 -0700
Retrofit index.html to internal-line-viewer spec: mobile hamburger filter drawer, artmura.* localStorage namespace (was astek.*), auto-hide zero-value facet tables; E2E gate passed (chromium+webkit creds-in-URL, 16/16)
---
public/curate.html | 4 ++--
public/index.html | 46 +++++++++++++++++++++++++++++++++++-----------
2 files changed, 37 insertions(+), 13 deletions(-)
diff --git a/public/curate.html b/public/curate.html
index 361bbee..3e14a65 100644
--- a/public/curate.html
+++ b/public/curate.html
@@ -69,11 +69,11 @@ const PAGE=400;
const SEL=new Set(); // dw_sku set — the Phillipe Romano candidate list
const BY_SKU={}; // dw_sku -> product
function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));}
-const dens=localStorage.getItem('astek.cur.density')||'8';
+const dens=localStorage.getItem('artmura.cur.density')||'8';
$('#density').value=dens; $('#denv').textContent=dens;
document.documentElement.style.setProperty('--cols',dens);
$('#density').oninput=()=>{const v=$('#density').value;document.documentElement.style.setProperty('--cols',v);
- $('#denv').textContent=v;localStorage.setItem('astek.cur.density',v);};
+ $('#denv').textContent=v;localStorage.setItem('artmura.cur.density',v);};
function tile(p,idx){
const on=SEL.has(p.dw_sku);
diff --git a/public/index.html b/public/index.html
index 3aa5957..a9fdeb4 100644
--- a/public/index.html
+++ b/public/index.html
@@ -42,9 +42,20 @@ aside summary .cur{margin-left:auto;color:var(--acc);text-transform:none;letter-
.fmore{padding:3px 8px;font-size:11px;color:var(--mut);font-style:italic}
#clearf{display:none;margin-top:10px;width:100%}
main{flex:1;min-width:0}
+/* mobile: hamburger + full-screen filter drawer (sidebar no longer stacks above the grid) */
+#burger{display:none;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:6px;padding:6px 12px;cursor:pointer;font-size:13px;font-weight:600;white-space:nowrap}
+#fclose{display:none}
@media(max-width:880px){
#layout{display:block}
- aside{position:static;width:auto;max-height:none;border-right:0;border-bottom:1px solid var(--line)}
+ header{padding:8px 10px;gap:8px}
+ #burger{display:inline-flex;align-items:center;gap:6px}
+ .ctl:has(#density){display:none} /* grid is fixed 2-up on phones — slider is noise */
+ /* top:0!important beats the inline style.top the desktop sticky-layout JS sets */
+ aside{display:none;position:fixed;inset:0;top:0!important;z-index:40;width:auto;min-width:0;max-height:none!important;
+ overflow:auto;border-right:0;padding:12px 12px 40px;background:var(--bg)}
+ aside.open-mobile{display:block}
+ #fclose{display:block;position:sticky;top:0;z-index:2;width:100%;background:var(--acc);color:#062a24;border:none;border-radius:8px;padding:11px;font-size:14px;font-weight:700;cursor:pointer;margin-bottom:10px}
+ .grid:not(.list){grid-template-columns:repeat(2,1fr)}
}
/* ── grid — tight, 1pt stroke edges; text scales with density via --fs ── */
.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:8px;padding:10px 12px 60px}
@@ -128,6 +139,7 @@ body.listmode #listhead{display:grid}
<body>
<header>
<h1>Artmura · Catalog</h1>
+ <button id="burger" type="button" aria-label="Open filters">☰ Filters</button>
<span class="pill" id="total">…</span>
<span class="muted" id="meta"></span>
<div class="ctl"><label class="muted">Sort</label>
@@ -146,6 +158,7 @@ body.listmode #listhead{display:grid}
</header>
<div id="layout">
<aside aria-label="filters">
+ <button id="fclose" type="button">✕ Done — show products</button>
<input type="text" id="q" placeholder="Search pattern / sku / color…">
<input type="text" id="pattern" list="patternlist" placeholder="Pattern type-ahead…" title="type-ahead over every pattern in the catalog">
<datalist id="patternlist"></datalist>
@@ -174,7 +187,11 @@ const PAGE=120;
const BUCKET_HEX={white:'#f4f4f2',grey:'#9aa0aa',black:'#1c1e24',pink:'#f3a8c0',red:'#d0453e',orange:'#e08a3c',
brown:'#8a6242',gold:'#c9a75a',green:'#5f8f5c',blue:'#4f7fb5',purple:'#8a6fb5'};
function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));}
-// ── every data field is a left-panel table: collapsed on load, click value = filter ──
+// ── every data field the catalog carries is a left-panel table: collapsed on load,
+// click value = filter. Fields with zero values in the snapshot (Artmura today:
+// color/material/width/length/repeat/match are all null; no style field at all,
+// so no Styles table / Style sort) are auto-hidden by renderSide and re-appear
+// if the 15-min refresh enriches them.
const FIELDS=[
['book','Books'],['color_bucket','Colors'],['series','Patterns'],['color','Colorways'],
['material','Material'],['width','Width'],['length','Length'],['repeat','Repeat'],['match','Match'],
@@ -184,7 +201,7 @@ FIELDS.forEach(([k])=>FIL[k]='');
let FCOUNTS={}; // field -> [[value,count]…] computed once from ALL
const FCAP=40; // rows shown per table (top-by-count)
// persisted prefs (CLAUDE.md: sort + density persist in localStorage)
-$('#sort').value=localStorage.getItem('astek.sort')||'newest';
+$('#sort').value=localStorage.getItem('artmura.sort')||'newest';
// ── density also drives type scale: fewer/larger cards → bigger type, 20/row → smallest ──
function setDensity(v){
v=Number(v);
@@ -192,16 +209,23 @@ function setDensity(v){
document.documentElement.style.setProperty('--cols',v);
document.documentElement.style.setProperty('--fs',fs.toFixed(3));
}
-const dens=localStorage.getItem('astek.density')||'6';
+const dens=localStorage.getItem('artmura.density')||'6';
$('#density').value=dens; setDensity(dens);
-$('#density').oninput=()=>{setDensity($('#density').value);localStorage.setItem('astek.density',$('#density').value);};
+$('#density').oninput=()=>{setDensity($('#density').value);localStorage.setItem('artmura.density',$('#density').value);};
+// ── mobile: hamburger opens the filter sidebar as a full-screen drawer ──
+const asideEl=document.querySelector('aside');
+$('#burger').onclick=()=>{asideEl.classList.add('open-mobile');document.body.style.overflow='hidden';};
+$('#fclose').onclick=()=>{asideEl.classList.remove('open-mobile');document.body.style.overflow='';};
+window.matchMedia('(max-width:880px)').addEventListener('change',e=>{
+ if(!e.matches){asideEl.classList.remove('open-mobile');document.body.style.overflow='';}
+});
// ── card-field visibility toggles (image-only when all off) ──
-const FVIS=JSON.parse(localStorage.getItem('astek.fieldvis')||'{"vend":true,"ttl":true,"chips":true}');
+const FVIS=JSON.parse(localStorage.getItem('artmura.fieldvis')||'{"vend":true,"ttl":true,"chips":true}');
function applyFieldVis(){
for(const k of ['vend','ttl','chips'])document.body.classList.toggle('f-off-'+k,!FVIS[k]);
document.body.classList.toggle('imgonly',!FVIS.vend&&!FVIS.ttl&&!FVIS.chips);
document.querySelectorAll('#fieldsbox input[data-fv]').forEach(cb=>cb.checked=!!FVIS[cb.dataset.fv]);
- localStorage.setItem('astek.fieldvis',JSON.stringify(FVIS));
+ localStorage.setItem('artmura.fieldvis',JSON.stringify(FVIS));
}
document.querySelectorAll('#fieldsbox input[data-fv]').forEach(cb=>{
cb.onchange=()=>{FVIS[cb.dataset.fv]=cb.checked;applyFieldVis();};
@@ -310,7 +334,7 @@ function buildFacetCache(){
// left panel — one collapsed <details> table per field; open state survives re-render
function renderSide(){
const openNow=new Set([...document.querySelectorAll('#facetbox details[open]')].map(d=>d.dataset.f));
- $('#facetbox').innerHTML=FIELDS.map(([k,label])=>{
+ $('#facetbox').innerHTML=FIELDS.filter(([k])=>(FCOUNTS[k]||[]).length).map(([k,label])=>{
const rows=(FCOUNTS[k]||[]).slice(0,FCAP).map(([v,n])=>{
const dot=k==='color_bucket'?`<span class="dot" style="background:${BUCKET_HEX[v]||'#666'}"></span>`:'';
return `<div class="frow${FIL[k]===String(v)?' active':''}" onclick="pickF('${k}',this.dataset.v)" data-v="${esc(v)}">
@@ -325,7 +349,7 @@ function renderSide(){
function pickF(k,v){FIL[k]=(FIL[k]===v?'':v);applyFilters();}
window.pickF=pickF;
$('#clearf').onclick=()=>{FIELDS.forEach(([k])=>FIL[k]='');$('#q').value='';$('#pattern').value='';applyFilters();};
-$('#sort').onchange=()=>{localStorage.setItem('astek.sort',$('#sort').value);COLSORT.key=null;renderListHead();applyFilters();};
+$('#sort').onchange=()=>{localStorage.setItem('artmura.sort',$('#sort').value);COLSORT.key=null;renderListHead();applyFilters();};
let t; $('#q').oninput=()=>{clearTimeout(t);t=setTimeout(applyFilters,300);};
let tp; $('#pattern').oninput=()=>{clearTimeout(tp);tp=setTimeout(applyFilters,300);};
$('#more').onclick=renderMore;
@@ -351,8 +375,8 @@ function syncSticky(){
document.querySelector('aside').style.maxHeight=`calc(100vh - ${h}px)`;
}
addEventListener('resize',syncSticky);
-function applyView(){const list=localStorage.getItem('astek.view')==='list';grid.classList.toggle('list',list);document.body.classList.toggle('listmode',list);$('#view').textContent=list?'▦ Grid view':'▤ List view';renderListHead();}
-$('#view').onclick=()=>{localStorage.setItem('astek.view',localStorage.getItem('astek.view')==='list'?'grid':'list');applyView();};
+function applyView(){const list=localStorage.getItem('artmura.view')==='list';grid.classList.toggle('list',list);document.body.classList.toggle('listmode',list);$('#view').textContent=list?'▦ Grid view':'▤ List view';renderListHead();}
+$('#view').onclick=()=>{localStorage.setItem('artmura.view',localStorage.getItem('artmura.view')==='list'?'grid':'list');applyView();};
applyView();
addEventListener('scroll',()=>{if(shown<VIEW.length&&innerHeight+scrollY>=document.body.offsetHeight-600)renderMore();});
async function loadAll(){
← aa90c8f chore: addendum gate clean, v0.2.1 (session close)
·
back to Artmura Internal
·
vendor-requests: self-bootstrap vendor_requests table (fix ' 64bed3f →