[object Object]

← back to Corvette Dashboard Viewer

Live SKU ticker below engine (image+sku+palette, slide-in motion, processed counter) + glovebox starts collapsed; /api/new-items returns sku+image

c4c8d1acfebc2b1273208387349b06580209bb31 · 2026-07-28 09:00:31 -0700 · Steve Abrams

Files touched

Diff

commit c4c8d1acfebc2b1273208387349b06580209bb31
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 28 09:00:31 2026 -0700

    Live SKU ticker below engine (image+sku+palette, slide-in motion, processed counter) + glovebox starts collapsed; /api/new-items returns sku+image
---
 public/index.html | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 server.js         |  2 +-
 2 files changed, 71 insertions(+), 4 deletions(-)

diff --git a/public/index.html b/public/index.html
index 3d2412d..86e13af 100644
--- a/public/index.html
+++ b/public/index.html
@@ -117,6 +117,27 @@ svg .arc-red{stroke:var(--red); stroke-width:5; fill:none; opacity:.85}
 .odo{font-family:'Share Tech Mono',monospace; font-size:9px; color:#5f636c; margin-top:6px; letter-spacing:1px}
 .card .sku{font-family:'Share Tech Mono',monospace; color:var(--amber); letter-spacing:.5px;
   text-shadow:0 0 8px rgba(255,179,0,.35)}
+/* ===== live SKU ticker (below the engine) ===== */
+.live-head{display:flex; align-items:center; gap:10px; margin:4px 2px 10px}
+.live-head h2{font-family:'Racing Sans One',cursive; font-size:14px; letter-spacing:2px; color:#cfd3da; margin:0}
+.live-dot{width:9px; height:9px; border-radius:50%; background:#3fbf6f; box-shadow:0 0 8px #3fbf6f; animation:pulse 1.3s ease-in-out infinite}
+@keyframes pulse{0%,100%{opacity:1; transform:scale(1)} 50%{opacity:.3; transform:scale(.6)}}
+.live-cnt{font-family:'Share Tech Mono',monospace; color:var(--amber); font-size:12px; margin-left:auto}
+.live-list{display:flex; flex-direction:column; gap:6px; min-height:56px}
+.live-row{display:flex; align-items:center; gap:10px; background:linear-gradient(180deg,#17171a,#0e0e10);
+  border:1px solid #2b2b31; border-left:3px solid var(--amber); border-radius:9px; padding:6px 10px;
+  animation:slidein .5s cubic-bezier(.16,1,.3,1)}
+@keyframes slidein{from{opacity:0; transform:translateY(-16px)} to{opacity:1; transform:translateY(0)}}
+.live-row img{width:38px; height:38px; border-radius:6px; object-fit:cover; background:#000; flex:none; border:1px solid #333}
+.live-row .lr-sku{font-family:'Share Tech Mono',monospace; color:var(--amber); font-size:13px; flex:none; min-width:108px; text-shadow:0 0 8px rgba(255,179,0,.4)}
+.live-row .lr-t{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; color:var(--cream)}
+.live-row .lr-t small{color:#8b8f98; display:block; font-size:10px; letter-spacing:.5px}
+.live-row .lr-pal{display:flex; height:22px; width:92px; border-radius:4px; overflow:hidden; flex:none; border:1px solid #000}
+.live-row .lr-pal span{flex:1}
+/* collapsible glovebox */
+.feed-head{cursor:pointer; user-select:none}
+.feed-head .car{display:inline-block; color:var(--amber)}
+.feed.collapsed{display:none}
 footer{text-align:center; margin-top:22px; font-size:10px; letter-spacing:3px; color:#5f636c}
 </style>
 </head>
@@ -157,11 +178,20 @@ footer{text-align:center; margin-top:22px; font-size:10px; letter-spacing:3px; c
 
   <div class="stitch"></div>
 
-  <div class="feed-head">
-    <h2>▸ GLOVEBOX FEED</h2>
+  <div class="live-head">
+    <span class="live-dot"></span>
+    <h2>LIVE · SKUS UPDATING</h2>
+    <span class="live-cnt" id="liveCnt">connecting…</span>
+  </div>
+  <div class="live-list" id="liveList"></div>
+
+  <div class="stitch"></div>
+
+  <div class="feed-head" id="feedHead">
+    <h2><span class="car" id="feedCar">▸</span> GLOVEBOX FEED</h2>
     <span class="cnt" id="feedCnt"></span>
   </div>
-  <div class="feed" id="feed"></div>
+  <div class="feed collapsed" id="feed"></div>
 
   <footer>DESIGNER WALLCOVERINGS · TK-135 · POWERED PER SINGLE ROLL</footer>
 </div>
@@ -259,6 +289,43 @@ function render(){
     feed.appendChild(card);
   });
 }
+
+// ---- collapsible glovebox (starts collapsed) ----
+document.getElementById('feedHead').onclick=()=>{
+  const f=document.getElementById('feed'), c=document.getElementById('feedCar');
+  const open=f.classList.toggle('collapsed')===false;   // toggle returns true when class NOW present (collapsed)
+  c.textContent=open?'▾':'▸';
+};
+
+// ---- LIVE ticker: SKUs as the TK-135 apply-job updates them ----
+// Polls /api/new-items (progress-log tail). Prepends genuinely-new SKUs with a
+// slide-in so the console visibly MOVES; the "updated" counter ticks up in real time.
+const seenLive=new Set();
+async function pollLive(){
+  try{
+    const r=await fetch('/api/new-items?limit=12'); if(!r.ok)return;
+    const d=await r.json();
+    const cnt=document.getElementById('liveCnt');
+    if(typeof d.processed==='number') cnt.textContent=d.processed.toLocaleString()+' updated';
+    const list=document.getElementById('liveList');
+    // records are newest-first; iterate oldest→newest so the newest ends up on top
+    for(const rec of [...d.records].reverse()){
+      if(seenLive.has(rec.gid))continue;
+      seenLive.add(rec.gid);
+      const pal=(rec.palette||[]).map(c=>`<span style="background:${c.hex}"></span>`).join('')||'<span style="background:#333"></span>';
+      const img=rec.image?`<img src="${rec.image}" loading="lazy" onerror="this.style.visibility='hidden'">`:'<img alt="">';
+      const row=document.createElement('div'); row.className='live-row';
+      row.innerHTML=`${img}<span class="lr-sku">${rec.sku||rec.gid}</span>`+
+        `<span class="lr-t">${rec.title||''}<small>${rec.vendor||''}</small></span>`+
+        `<span class="lr-pal">${pal}</span>`;
+      if(rec.href){row.style.cursor='pointer';row.onclick=()=>window.open(rec.href,'_blank','noopener');}
+      list.insertBefore(row,list.firstChild);
+      while(list.children.length>10) list.removeChild(list.lastChild);
+    }
+  }catch(e){/* transient; next tick retries */}
+}
+setInterval(pollLive,2500);
+pollLive();
 </script>
 </body>
 </html>
diff --git a/server.js b/server.js
index 4d951da..a48a3f8 100644
--- a/server.js
+++ b/server.js
@@ -95,7 +95,7 @@ http.createServer((req, res) => {
       select regexp_replace(shopify_id,'.*/','') as gid, title, vendor, handle, image_url,
              coalesce(nullif(dw_sku,''),nullif(mfr_sku,''),nullif(variant_sku,'')) as sku,
              (select json_agg(v) from (select distinct trim(x) v from unnest(string_to_array(tags,',')) x
-                where trim(x) !~ '[:/.]' and trim(x) !~ '^[0-9]' and length(trim(x)) between 2 and 22
+                where trim(x) !~ '[:/.{}"]' and trim(x) !~ '^[0-9]' and length(trim(x)) between 2 and 22
                 limit 8) s) as tags
       from shopify_products where shopify_id in (${inList})) t`;
     execFile('psql', ['-h', PGHOST, '-d', PGDB, '-tAc', sql], { maxBuffer: 16 * 1024 * 1024 }, (err, stdout) => {

← c665d54 auto-save: 2026-07-28T08:58:52 (2 files) — server.js public/  ·  back to Corvette Dashboard Viewer  ·  live new-arrivals viewer (new.html + /api/new-items): newest 21a046b →