[object Object]

← back to Dw Catalog 3d

D3: deep-link hash routing (#v=slug, #v=slug&c=i) — replaceState on every transition + restore on load

3448a2321fe74b41d0d5eb5fd7ae7b6492fedd42 · 2026-06-26 10:10:08 -0700 · Steve Studio

Files touched

Diff

commit 3448a2321fe74b41d0d5eb5fd7ae7b6492fedd42
Author: Steve Studio <steve@designerwallcoverings.com>
Date:   Fri Jun 26 10:10:08 2026 -0700

    D3: deep-link hash routing (#v=slug, #v=slug&c=i) — replaceState on every transition + restore on load
---
 public/index.html | 27 +++++++++++++++++++++++----
 1 file changed, 23 insertions(+), 4 deletions(-)

diff --git a/public/index.html b/public/index.html
index 6baaf07..a97b59b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -169,7 +169,7 @@ function renderVendors(){
       eg.position.copy(m.position); world.add(eg); y+=h+0.04; });
     if(match){ const lab=label(d.vendor); lab.position.set(cx,y+1.8,cz); lab.userData={kind:'vendor',d}; world.add(lab); }
   });
-  flyTo([58,46,74],[0,7,0]);
+  flyTo([58,46,74],[0,7,0]); syncHash();
 }
 
 // ===================== LEVEL 1 : COLLECTIONS =====================
@@ -200,7 +200,7 @@ async function drillVendor(d){
     new THREE.MeshStandardMaterial({color:0xC8A24B,emissive:0xC8A24B,emissiveIntensity:.25,metalness:.6,roughness:.3}));
   ped.position.y=0.7; world.add(ped);
   const vlab=label(d.vendor,[16,3.6,1],'#C8A24B'); vlab.position.set(0,3.4,0); world.add(vlab);
-  flyTo([0,R*1.5,R*1.9],[0,4,0]);
+  flyTo([0,R*1.5,R*1.9],[0,4,0]); syncHash();
 }
 
 // ===================== LEVEL 2 : PRODUCTS =====================
@@ -222,7 +222,7 @@ function drillCollection(cobj){
       undefined,()=>{m.material=fallback.clone();}); } else m.material=fallback.clone();
   });
   const r=Math.max(20,cols*gap*1.1);
-  flyTo([0,8,r],[0,8,0]);
+  flyTo([0,8,r],[0,8,0]); syncHash();
 }
 
 // ---- legends / breadcrumb ----
@@ -276,6 +276,24 @@ window.__vendor=()=>{ if(state.vData) drillVendor(state.vendor); };
 window.__drill=(slug)=>drillVendor(DATA.find(d=>d.slug===slug)||DATA.find(d=>d.slug));
 window.__coll=(i)=>{ if(state.vData) drillCollection(state.vData.collections[i||0]); };
 
+// ---- D3: deep-link hash routing (#v=<slug> | #v=<slug>&c=<i>) ----
+function syncHash(){
+  let h='';
+  if(state.vendor&&state.vendor.slug){ h='v='+encodeURIComponent(state.vendor.slug);
+    if(state.level===2&&state.coll&&state.vData){ const i=state.vData.collections.indexOf(state.coll);
+      if(i>=0) h+='&c='+i; } }
+  const nh=h?'#'+h:location.pathname+location.search; // bare path clears the hash
+  if(location.hash.replace(/^#/,'')!==h) history.replaceState(null,'',nh);
+}
+async function restoreFromHash(rawHash){
+  const m=new URLSearchParams((rawHash||location.hash).replace(/^#/,''));
+  const slug=m.get('v'); if(!slug) return; // nothing to restore → stay at L0
+  const d=DATA.find(x=>x.slug===slug); if(!d) return;
+  await drillVendor(d);                       // L1 (awaits the v/<slug>.json fetch)
+  const ci=m.get('c');
+  if(ci!=null&&state.vData&&state.vData.collections[+ci]) drillCollection(state.vData.collections[+ci]); // L2
+}
+
 // ---- interaction ----
 const ray=new THREE.Raycaster(), mouse=new THREE.Vector2(), tip=document.getElementById('tooltip');
 let downXY=null;
@@ -342,8 +360,9 @@ sinput.addEventListener('keydown',e=>{
 });
 document.addEventListener('click',e=>{ if(!document.getElementById('search').contains(e.target)) sresults.style.display='none'; });
 
+const __initialHash=location.hash;   // capture before renderVendors() clears it via syncHash
 fetch('scene.json?_='+Date.now()).then(r=>r.json()).then(d=>{
-  DATA=d.sort((a,b)=>(b.active+b.archived)-(a.active+a.archived)); renderVendors(); });
+  DATA=d.sort((a,b)=>(b.active+b.archived)-(a.active+a.archived)); renderVendors(); restoreFromHash(__initialHash); });
 
 addEventListener('resize',()=>{camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth,innerHeight);});
 (function loop(){requestAnimationFrame(loop);stepFly();controls.update();renderer.render(scene,camera);})();

← 4f30f3d Frontend: L2 Active/Staged/Draft status filter chips + L0 ve  ·  back to Dw Catalog 3d  ·  D1: document daily refresh (path B — standalone com.steve.ca 320cfff →