Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions packages/tryon-js/tests/fit.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,99 @@ test("compareFrames delta", () => {
assert.equal(c.ok, true);
assert.ok(c.width_delta_px > 0);
});

// --- PD-to-fit-scale calculation tests (bounty #7) ---
test("pdToFitScale: correct scale for 140mm frame at 64mm PD, 120px PD", () => {
const s = overlaySize(frame, 120, 64);
const expected = (140 / 64) * 120 * 1.15;
assert.ok(Math.abs(s.overlayW - expected) < 0.5);
assert.ok(s.overlayH > 0);
});

test("pdToFitScale: larger PD means smaller overlay", () => {
const a = overlaySize(frame, 120, 74);
const b = overlaySize(frame, 120, 54);
assert.ok(b.overlayW > a.overlayW);
});

test("pdToFitScale: wider frame produces wider overlay", () => {
const wideFrame = { fit: { width_mm: 160 } };
const narrowFrame = { fit: { width_mm: 120 } };
const w = overlaySize(wideFrame, 120, 64);
const n = overlaySize(narrowFrame, 120, 64);
assert.ok(w.overlayW > n.overlayW);
});

test("pdToFitScale: proportional to pupil pixel distance", () => {
const a = overlaySize(frame, 80, 64);
const b = overlaySize(frame, 160, 64);
assert.ok(Math.abs(b.overlayW / a.overlayW - 2.0) < 0.1);
});

test("pdToFitScale: edge case — min PD (54mm)", () => {
const s = overlaySize({ fit: { width_mm: 140 } }, 100, 54);
assert.ok(s.overlayW > 0);
assert.ok(s.overlayW > 250);
});

test("pdToFitScale: edge case — max PD (74mm)", () => {
const s = overlaySize({ fit: { width_mm: 140 } }, 100, 74);
assert.ok(s.overlayW > 0);
assert.ok(s.overlayW < 250);
});

test("pdToFitScale: missing fit defaults to 140mm", () => {
const s = overlaySize({}, 120, 64);
const expected = (140 / 64) * 120 * 1.15;
assert.ok(Math.abs(s.overlayW - expected) < 0.5);
});

// --- Credit-card calibration math tests (bounty #7) ---
const CARD_W = 85.6;

test("cardCal: pxPerMm from known object", () => {
const cardPx = 300;
const pxPerMm = cardPx / CARD_W;
assert.ok(Math.abs(pxPerMm - (300 / 85.6)) < 0.001);
});

test("cardCal: compute PD from card and pupil pixels", () => {
const cardPx = 300;
const pupilPx = 224;
const pdMm = pupilPx / (cardPx / CARD_W);
assert.ok(pdMm > 60 && pdMm < 68);
});

test("cardCal: larger card px means smaller estimated PD", () => {
const pupilPx = 200;
const pdSmallCard = pupilPx / (200 / CARD_W);
const pdLargeCard = pupilPx / (400 / CARD_W);
assert.ok(pdLargeCard < pdSmallCard);
});

test("cardCal: larger pupil px means larger estimated PD", () => {
const cardPx = 300;
const pxPerMm = cardPx / CARD_W;
assert.ok((300 / pxPerMm) > (100 / pxPerMm));
});

test("cardCal: real-world PD ~64mm scenario", () => {
const cardPx = 250;
const pupilPx = 187;
const pdMm = pupilPx / (cardPx / CARD_W);
assert.ok(Math.abs(pdMm - 64) < 1.5);
});

test("cardCal: boundary — min PD through card", () => {
const cardPx = 500;
const pupilPx = 200;
const pdMm = pupilPx / (cardPx / CARD_W);
assert.ok(pdMm < 50);
});

test("cardCal: boundary — max PD through card", () => {
const cardPx = 100;
const pupilPx = 200;
const pdMm = pupilPx / (cardPx / CARD_W);
assert.ok(pdMm > 100);
});
74 changes: 74 additions & 0 deletions packages/web/assets/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
6 changes: 6 additions & 0 deletions packages/web/assets/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,7 @@ function loop() {
ctx.fillText(t("hintIdle"), 40, h / 2);
}
if (mode !== "idle") drawGlasses();
if (calMode) drawCardCalibrationOverlay();
raf = requestAnimationFrame(loop);
}

Expand Down Expand Up @@ -892,6 +893,11 @@ document.getElementById("btn-gallery").onclick = () => {
};
document.getElementById("filter").onchange = (e) => loadCatalog(e.target.value);
document.getElementById("pd").oninput = (e) => {
if (calMode) {
cardPx = Math.round(120 + (Number(e.target.value) - 54) * 13);
updateCalInfo();
return;
}
pdMm = Number(e.target.value) || 64;
document.getElementById("pd-val").textContent = String(pdMm);
updateMeta();
Expand Down
2 changes: 2 additions & 0 deletions packages/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ <h2 id="lbl-catalog">Catalog</h2>
<input id="pd" type="range" min="54" max="74" step="1" value="64" />
</label>
<p class="muted small" id="pd-hint">Calibrate pupil distance for better fit scale</p>
<button id="btn-calibrate" class="btn calibrate-btn" type="button">📏 Calibrate PD</button>
<div id="cal-info" class="cal-info hidden"></div>
</div>
<div id="catalog" class="catalog"></div>
<div class="meta" id="meta">Select a SKU</div>
Expand Down