← back to Dw Photo Capture
Scan: brand-aware fast-lock + RED locked-in state
a045b5f6f556bff7f0d4df4824a4ba9033ff02fd · 2026-06-25 17:48:51 -0700 · Steve Abrams
- OCR now detects the vendor name printed on the swatch (Winfield Thybony,
Fentucci, Phillip Jeffries, Thibaut/Anna French, Schumacher) and boosts the
code whose prefix matches that brand, so the largest-font SKU wins.
- Emits topStrong: a known-prefix code, or brand-name-present + font-dominant
code, is safe to lock on the FIRST read instead of waiting for 2 frames.
- Live scanner locks fast on strong reads, turns the whole frame + status pill
RED ('locked in'), holds a beat, then resolves — 'once locked... go.'
Files touched
Diff
commit a045b5f6f556bff7f0d4df4824a4ba9033ff02fd
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jun 25 17:48:51 2026 -0700
Scan: brand-aware fast-lock + RED locked-in state
- OCR now detects the vendor name printed on the swatch (Winfield Thybony,
Fentucci, Phillip Jeffries, Thibaut/Anna French, Schumacher) and boosts the
code whose prefix matches that brand, so the largest-font SKU wins.
- Emits topStrong: a known-prefix code, or brand-name-present + font-dominant
code, is safe to lock on the FIRST read instead of waiting for 2 frames.
- Live scanner locks fast on strong reads, turns the whole frame + status pill
RED ('locked in'), holds a beat, then resolves — 'once locked... go.'
---
public/index.html | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/public/index.html b/public/index.html
index 4f50969..c61f5e5 100644
--- a/public/index.html
+++ b/public/index.html
@@ -870,7 +870,7 @@ async function openLiveScan(){
unlockAudio(); // we're inside the user's tap → unlock audio for the lock-in cue
try{ await startScanStream(); }
catch(e){ toast('Camera blocked — using snap'); return $('#scanInput').click(); }
- _lastTop=null;_lockHits=0; $('#scanStatus').textContent='Point at the printed SKU…';
+ _lastTop=null;_lockHits=0; $('#scanlive').classList.remove('locked'); $('#scanStatus').textContent='Point at the printed SKU…';
$('#scanFlip').textContent=(camFacing==='user'?'🔄 Back camera':'🔄 Front camera');
$('#scanlive').hidden=false; document.body.style.overflow='hidden';
scanTimer=setInterval(scanTick,600);
@@ -881,7 +881,7 @@ async function flipCam(){ // toggle back<->front, persist, re-acquire live + r
if(!$('#scanlive').hidden){ try{ await startScanStream(); }catch(e){ toast('Could not switch camera'); } }
else toast(camFacing==='user'?'📷 Front camera':'📷 Back camera');
}
-function closeLiveScan(){ if(scanTimer)clearInterval(scanTimer); scanTimer=null; if(scanStream){scanStream.getTracks().forEach(t=>t.stop());scanStream=null;} $('#scanlive').hidden=true; document.body.style.overflow=''; }
+function closeLiveScan(){ if(scanTimer)clearInterval(scanTimer); scanTimer=null; if(scanStream){scanStream.getTracks().forEach(t=>t.stop());scanStream=null;} $('#scanlive').hidden=true; $('#scanlive').classList.remove('locked'); document.body.style.overflow=''; }
async function ocrPost(dataUrl){ const r=await fetch('/api/ocr',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({dataUrl})}); return r.json(); }
function mirrorDataUrl(dataUrl){ return new Promise((res,rej)=>{ const im=new Image(); im.onload=()=>{ const c=document.createElement('canvas'); c.width=im.naturalWidth; c.height=im.naturalHeight; const x=c.getContext('2d'); x.translate(c.width,0); x.scale(-1,1); x.drawImage(im,0,0); res(c.toDataURL('image/jpeg',0.8)); }; im.onerror=rej; im.src=dataUrl; }); }
// front camera can deliver a MIRRORED frame (iOS) → backwards text reads as garbage. If a front-cam read finds nothing, retry flipped.
@@ -902,12 +902,19 @@ async function scanTick(){
const d=await ocrSmart(c.toDataURL('image/jpeg',0.7)); // front-cam mirror-retry built in
if(d&&d.top){
if(d.top===_lastTop) _lockHits++; else {_lastTop=d.top;_lockHits=1;}
- $('#scanStatus').textContent=(_lockHits>=2?'🔒 LOCKED · ':'👀 ')+d.top;
- if(_lockHits>=2){ // seen the same SKU twice → lock in, then multi-pass resolve
+ // FAST LOCK: a strong read (known vendor prefix, or the brand name is printed on the
+ // swatch and this is the biggest code) locks on the FIRST sighting; anything weaker
+ // waits for a 2nd matching frame so a shaky/garbled read can't false-lock.
+ const locked=_lockHits>=2 || (d.topStrong && _lockHits>=1);
+ const vtag=d.vendor ? d.vendor+' · ' : '';
+ $('#scanStatus').textContent=(locked?'🔒 LOCKED · ':'👀 ')+vtag+d.top;
+ if(locked){
+ $('#scanlive').classList.add('locked'); // turn the frame + pill RED — "locked in"
try{navigator.vibrate&&navigator.vibrate(70);}catch(e){}
playYoohoo(); // 🔊 audible "yoo-hoo" the instant the number locks
- closeLiveScan();
- applyScan(d); // tries every OCR candidate: TWIL sku/mfr/name → all-Shopify
+ if(scanTimer){clearInterval(scanTimer);scanTimer=null;} // freeze ticks on the red frame
+ const hit=d;
+ setTimeout(()=>{ closeLiveScan(); applyScan(hit); },420); // hold the red a beat, then GO
}
} else if(_lockHits<2){ $('#scanStatus').textContent='Point at the printed SKU…'; }
}catch(e){} finally{ scanBusy=false; }
← 89ca632 Scan: audible 'yoo-hoo' cue on code lock-in (v41)
·
back to Dw Photo Capture
·
Scan audio: outcome-based — yoo-hoo on resolve, 'uh-oh' on n 610fc30 →