diff --git a/packages/web/assets/app.css b/packages/web/assets/app.css index ef4d32b..5600ec5 100644 --- a/packages/web/assets/app.css +++ b/packages/web/assets/app.css @@ -143,3 +143,77 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--text); font-size: 0.9rem; margin-top: 12px; } + +/* PD calibration wizard */ +.calibrate-btn { + margin-top: 8px; + width: 100%; + font-size: 12px; + padding: 6px 10px; + border-color: var(--accent2); + color: var(--accent2); + background: #0d1528; +} +.calibrate-btn.active { + background: #1a2744; + border-color: var(--accent); + color: var(--accent); +} +.cal-info { + margin-top: 8px; + padding: 8px 10px; + background: #0d1528; + border: 1px solid var(--line); + border-radius: 8px; + font-size: 11px; + line-height: 1.5; +} +.cal-info.hidden { display: none; } +.cal-info .cal-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 4px; +} +.cal-info .cal-label { color: var(--muted); } +.cal-info .cal-value { color: var(--accent); font-weight: 600; } +.cal-info .cal-hint { + color: var(--muted); + font-size: 10px; + margin-top: 4px; + font-style: italic; +} +.cal-info .cal-actions { + display: flex; + gap: 6px; + margin-top: 8px; +} +.cal-info .cal-actions .btn { + font-size: 10px; + padding: 4px 8px; + flex: 1; +} +/* Calibration card overlay on stage */ +#cal-overlay { + position: absolute; + border: 2px dashed var(--accent); + border-radius: 8px; + background: rgba(245,197,24,0.08); + pointer-events: none; + display: none; + z-index: 3; + transition: none; +} +#cal-overlay.visible { display: block; } +#cal-overlay .cal-card-label { + position: absolute; + bottom: -22px; + left: 50%; + transform: translateX(-50%); + font-size: 10px; + color: var(--accent); + background: #000a; + padding: 2px 6px; + border-radius: 4px; + white-space: nowrap; +} diff --git a/packages/web/assets/app.js b/packages/web/assets/app.js index 06f0731..5cf2da4 100644 --- a/packages/web/assets/app.js +++ b/packages/web/assets/app.js @@ -37,6 +37,13 @@ try { } let sessionId = null; +// --- PD Calibration Wizard state --- +let calMode = false; +let cardPx = 200; // credit card width in pixels (adjustable) +const CARD_WIDTH_MM = 85.6; // standard ISO/IEC 7810 ID-1 credit card +const CARD_HEIGHT_MM = 54.0; +const CARD_ASPECT = CARD_WIDTH_MM / CARD_HEIGHT_MM; + function isStaticDemo() { return !!(globalThis.BeeARStatic && globalThis.BeeARStatic.detectStatic()); } @@ -856,6 +863,102 @@ function renderGallery() { // UI element event binding configuration document.getElementById("btn-cam").onclick = startCamera; + +// --- PD Calibration Wizard --- +function measurePdPx() { + const lx = face.left[0] * canvas.width; + const ly = face.left[1] * canvas.height; + const rx = face.right[0] * canvas.width; + const ry = face.right[1] * canvas.height; + return Math.hypot(rx - lx, ry - ly) || 100; +} + +function pdToFitScale(frameWidthMm, pdMmVal, pdPxVal) { + const pd = Math.max(50, Math.min(80, Number(pdMmVal) || 64)); + const pdPixels = Number(pdPxVal) || 100; + return (Number(frameWidthMm) || 140) / pd * pdPixels * 1.15; +} + +function computePdFromCard(cardPxWidth, pupilPx) { + if (!cardPxWidth || cardPxWidth <= 0) return null; + return Math.round(pupilPx / (cardPxWidth / CARD_WIDTH_MM)); +} + +function toggleCalibration() { + calMode = !calMode; + const btn = document.getElementById("btn-calibrate"); + const info = document.getElementById("cal-info"); + const overlay = document.getElementById("cal-overlay"); + if (calMode) { + btn.classList.add("active"); + btn.textContent = "📏 Exit Calibration"; + info.classList.remove("hidden"); + if (mode === "idle") startDemo(); + updateCalInfo(); + } else { + btn.classList.remove("active"); + btn.textContent = "📏 Calibrate PD"; + info.classList.add("hidden"); + if (overlay) overlay.classList.remove("visible"); + } +} + +function updateCalInfo() { + if (!calMode) return; + const pdPx = measurePdPx(); + const pxPerMm = cardPx / CARD_WIDTH_MM; + const estimatedPd = computePdFromCard(cardPx, pdPx) || pdMm; + const info = document.getElementById("cal-info"); + if (!info) return; + info.innerHTML = + '
Card width' + cardPx + 'px
' + + '
Scale' + pxPerMm.toFixed(2) + ' px/mm
' + + '
PD (pixels)' + pdPx.toFixed(0) + 'px
' + + '
Estimated PD' + estimatedPd + 'mm
' + + '

Hold a credit card (85.6×54mm) to your forehead. Adjust the slider until the outline matches your card.

' + + '
' + + '' + + '' + + '
'; + document.getElementById("cal-apply").onclick = function() { + pdMm = Math.max(50, Math.min(80, estimatedPd)); + document.getElementById("pd").value = pdMm; + document.getElementById("pd-val").textContent = String(pdMm); + updateMeta(); + toggleCalibration(); + }; + document.getElementById("cal-cancel").onclick = toggleCalibration; + drawCardCalibrationOverlay(); +} + +function drawCardCalibrationOverlay() { + var overlay = document.getElementById("cal-overlay"); + if (!overlay) { + overlay = document.createElement("div"); + overlay.id = "cal-overlay"; + var lbl = document.createElement("span"); + lbl.className = "cal-card-label"; + lbl.textContent = "Credit card ref (85.6×54mm)"; + overlay.appendChild(lbl); + var stage = document.querySelector(".stage"); + if (stage) stage.appendChild(overlay); + } + if (!calMode) { overlay.classList.remove("visible"); return; } + overlay.classList.add("visible"); + var stageEl = document.querySelector(".stage"); + if (!stageEl) return; + var cRect = canvas.getBoundingClientRect(); + var sRect = stageEl.getBoundingClientRect(); + var cardH = Math.round(cardPx / CARD_ASPECT); + var midX = (face.left[0] + face.right[0]) / 2 * cRect.width; + var midY = Math.min(face.left[1], face.right[1]) * cRect.height - cardH * 0.8; + overlay.style.left = (cRect.left - sRect.left + midX - cardPx / 2) + "px"; + overlay.style.top = (cRect.top - sRect.top + Math.max(0, midY)) + "px"; + overlay.style.width = cardPx + "px"; + overlay.style.height = cardH + "px"; +} + +document.getElementById("btn-calibrate").onclick = toggleCalibration; document.getElementById("btn-demo").onclick = startDemo; const btnDemoNext = document.getElementById("btn-demo-next"); if (btnDemoNext) btnDemoNext.onclick = nextDemoFace; diff --git a/packages/web/index.html b/packages/web/index.html index 711d756..23e9ab9 100644 --- a/packages/web/index.html +++ b/packages/web/index.html @@ -82,6 +82,8 @@

Catalog

Calibrate pupil distance for better fit scale

+ +
Select a SKU