← 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 →