[object Object]

← back to Dw Photo Capture

Add per-field 🎤 mic buttons on every Add-item field (dictate straight into Mfr#/Vendor/VID/Color/Name/Price) + prominent Voice-fill button

eae81b8a081b3c86571ff17a8193e9258b469fd0 · 2026-07-06 19:57:04 -0700 · Steve Abrams

Files touched

Diff

commit eae81b8a081b3c86571ff17a8193e9258b469fd0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jul 6 19:57:04 2026 -0700

    Add per-field 🎤 mic buttons on every Add-item field (dictate straight into Mfr#/Vendor/VID/Color/Name/Price) + prominent Voice-fill button
---
 public/index.html | 38 +++++++++++++++++++++++++++++++++-----
 1 file changed, 33 insertions(+), 5 deletions(-)

diff --git a/public/index.html b/public/index.html
index 8d1a65e..8ddb839 100644
--- a/public/index.html
+++ b/public/index.html
@@ -159,6 +159,12 @@
   .add-field input,.add-field select{width:100%;box-sizing:border-box;background:#0f0e0b;border:1px solid var(--line);border-radius:8px;padding:9px;color:var(--ink);font-size:15px}
   .add-row{display:flex;gap:10px}
   .add-field input.need,.add-field select.need{border-color:#b8902f;box-shadow:0 0 0 2px rgba(184,144,47,.35)}
+  .in-mic{display:flex;gap:6px;align-items:stretch}.in-mic input,.in-mic select{flex:1;min-width:0}
+  .mic-btn{flex:none;width:42px;border:1px solid var(--line);border-radius:8px;background:#151310;color:#e7ddc7;font-size:17px;cursor:pointer;line-height:1}
+  .mic-btn:active{transform:scale(.94)}.mic-btn.on{background:#7a2b2b;border-color:#b84a4a;color:#fff;animation:micpulse 1s infinite}
+  @keyframes micpulse{50%{background:#a53a3a}}
+  .add-voice{display:block;width:100%;box-sizing:border-box;margin:0 0 12px;padding:11px;border:1px solid var(--line);border-radius:10px;background:#151310;color:#e7ddc7;font-size:14px;font-weight:600;cursor:pointer}
+  .add-voice:active{transform:scale(.99)}
   .add-spec{display:flex;justify-content:space-between;gap:12px;font-size:12.5px;padding:4px 0;border-bottom:1px solid rgba(255,255,255,.05)}
   .add-spec span{color:#9a917d}.add-spec b{color:var(--ink);text-align:right}
   /* onload chooser — three white pills on black */
@@ -387,11 +393,11 @@
     <div class="samp-pat" style="margin-bottom:10px">Add new item</div>
     <button class="fb-tile" id="addPhotoTile" style="margin-bottom:12px;flex:none;width:100%"><div class="fb-thumb" id="addThumb" style="aspect-ratio:auto;height:64px;width:64px">📷</div><span>Photo (optional)</span></button>
     <input type="file" accept="image/*" capture="environment" id="addPhotoInput" hidden>
-    <button class="home-skip" id="addVoice" style="opacity:.85;color:var(--ink);margin:0 0 8px">🎤 Voice-fill what's missing</button>
-    <div class="add-field"><label>Mfr # / SKU</label><input id="addMfr" autocapitalize="characters" placeholder="e.g. TR2581"></div>
-    <div class="add-field"><label>Vendor</label><select id="addVendor"><option value="">Loading…</option></select></div>
-    <div class="add-row"><div class="add-field"><label>VID</label><input id="addVid" placeholder="THI"></div><div class="add-field"><label>Color</label><input id="addColor"></div></div>
-    <div class="add-row"><div class="add-field"><label>Pattern name</label><input id="addName"></div><div class="add-field"><label>Price</label><input id="addPrice" type="number" inputmode="decimal"></div></div>
+    <button class="add-voice" id="addVoice">🎤 Voice-fill everything that's missing</button>
+    <div class="add-field"><label>Mfr # / SKU</label><div class="in-mic"><input id="addMfr" autocapitalize="characters" placeholder="e.g. TR2581"><button class="mic-btn" data-mic="addMfr" data-kind="sku" title="Speak the SKU / mfr number">🎤</button></div></div>
+    <div class="add-field"><label>Vendor</label><div class="in-mic"><select id="addVendor"><option value="">Loading…</option></select><button class="mic-btn" data-mic="addVendor" data-kind="vendor" title="Speak the vendor name">🎤</button></div></div>
+    <div class="add-row"><div class="add-field"><label>VID</label><div class="in-mic"><input id="addVid" placeholder="THI"><button class="mic-btn" data-mic="addVid" data-kind="text" title="Speak the VID">🎤</button></div></div><div class="add-field"><label>Color</label><div class="in-mic"><input id="addColor"><button class="mic-btn" data-mic="addColor" data-kind="text" title="Speak the color">🎤</button></div></div></div>
+    <div class="add-row"><div class="add-field"><label>Pattern name</label><div class="in-mic"><input id="addName"><button class="mic-btn" data-mic="addName" data-kind="text" title="Speak the pattern name">🎤</button></div></div><div class="add-field"><label>Price</label><div class="in-mic"><input id="addPrice" type="number" inputmode="decimal"><button class="mic-btn" data-mic="addPrice" data-kind="num" title="Speak the price">🎤</button></div></div></div>
     <div id="addSpecs"></div>
     <button class="samp-print" id="addPreview">👁 Preview</button>
     <div class="samp-note" id="addNote"></div>
@@ -1471,6 +1477,28 @@ $('#addVoice').addEventListener('click',addPromptMissing);   // manual re-trigge
 $('#addMfr').addEventListener('input',()=>$('#addMfr').classList.remove('need'));
 $('#addVendor').addEventListener('change',()=>$('#addVendor').classList.remove('need'));
 
+// Per-field mic: tap the 🎤 on any field and speak straight into THAT field. kind tunes the parse.
+let _micLive=null;
+function dictateInto(fieldId, kind, btn){
+  const SR=window.SpeechRecognition||window.webkitSpeechRecognition;
+  if(!SR){ toast('Voice not supported here — use your keyboard’s mic 🎙️'); $('#'+fieldId).focus(); return; }
+  if(_micLive){ try{_micLive.stop();}catch(e){} }   // only one mic at a time
+  const r=new SR(); _micLive=r; r.lang='en-US'; r.interimResults=false;
+  const off=()=>{ btn.classList.remove('on'); _micLive=null; };
+  btn.classList.add('on'); toast('🎤 Listening…');
+  r.onend=off; r.onerror=off;
+  r.onresult=async e=>{ const t=((e.results[0][0]||{}).transcript||'').trim(); if(!t)return;
+    const el=$('#'+fieldId);
+    if(kind==='vendor'){ await loadVendors(); const up=t.toUpperCase(); const opt=[...el.options].find(o=>o.value&&(o.value.toUpperCase().includes(up.split(' ')[0])||up.includes(o.value.toUpperCase())));
+      if(opt){ el.value=opt.value; if(opt.dataset.vid&&!$('#addVid').value)$('#addVid').value=opt.dataset.vid; el.classList.remove('need'); toast('🎤 “'+t+'” → '+opt.value); } else toast('No vendor match for “'+t+'”'); }
+    else if(kind==='sku'){ const c=(t.toUpperCase().match(/[A-Z]{1,4}[- ]?\d{2,6}/)||t.match(/\d{3,6}/)||[])[0]; el.value=(c||t).toUpperCase().replace(/\s/g,''); el.classList.remove('need'); toast('🎤 “'+t+'”'); }
+    else if(kind==='num'){ const n=(t.match(/[\d.]+/)||[])[0]; if(n)el.value=n; toast('🎤 “'+t+'”'); }
+    else { el.value=t; el.classList.remove('need'); toast('🎤 “'+t+'”'); }
+  };
+  try{ r.start(); }catch(e){ off(); }
+}
+document.querySelectorAll('#addModal .mic-btn').forEach(b=>b.addEventListener('click',ev=>{ ev.preventDefault(); dictateInto(b.dataset.mic, b.dataset.kind, b); }));
+
 // ── onload chooser: Add New SKU · Update SKU · Check Sample In ──
 function routeHome(act){ $('#homeScreen').hidden=true;
   if(act==='add') openAddModal();

← 2857339 FileMaker sticker setup: STEP 1 now requires placing web_pri  ·  back to Dw Photo Capture  ·  Add 3 photo slots to Add-item (Front·pattern / Back·label / c0cf2c5 →