← back to Quadrille Showroom
showroom P1: remove angled/orbit/topdown, boot=walk-through-center, macro=2.5ft, carousel=continuous parade, compare=user-choosable two designs
bc32b4a9069910878fbc736938298c1abf40790c · 2026-06-30 19:38:07 -0700 · Steve Abrams
Files touched
M public/js/showroom.jsM public/js/versions.jsM public/js/viewmodes.js
Diff
commit bc32b4a9069910878fbc736938298c1abf40790c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jun 30 19:38:07 2026 -0700
showroom P1: remove angled/orbit/topdown, boot=walk-through-center, macro=2.5ft, carousel=continuous parade, compare=user-choosable two designs
---
public/js/showroom.js | 22 +++++++
public/js/versions.js | 4 +-
public/js/viewmodes.js | 165 +++++++++++++++++--------------------------------
3 files changed, 81 insertions(+), 110 deletions(-)
diff --git a/public/js/showroom.js b/public/js/showroom.js
index 6a58d1a..116d41d 100644
--- a/public/js/showroom.js
+++ b/public/js/showroom.js
@@ -3816,6 +3816,28 @@ function smoothCameraTo(pos, target, onComplete, fov) {
};
}
+// Automated "walk through the CENTER" of the showroom on boot (Steve 2026-06-30).
+// A slow forward dolly down the middle of the room, on the shared cameraAnim tween
+// (eases inside the main render loop). walk.enter() parked us at the front looking
+// at the board wall; this glides us up the centre to just in front of the arc, then
+// hands back to free Explore (WASD). Cheap + interruptible (cameraAnim auto-clears
+// on complete; a click stops any tour).
+function _qhStartCenterWalk() {
+ if (cameraAnim) return;
+ const backZ = -CONFIG.room.depth / 2;
+ cameraAnim = {
+ startPos: camera.position.clone(),
+ endPos: new THREE.Vector3(0, 1.62, backZ + 2.4), // centre of room, eye-level, near the boards
+ startTarget: controls.target.clone(),
+ endTarget: new THREE.Vector3(0, 1.4, backZ + 0.2), // looking straight down the centre at the board wall
+ startFov: camera.fov, endFov: camera.fov,
+ progress: 0,
+ duration: 7.0, // slow, cinematic centre walk
+ onComplete: null // leaves the user in Explore among the boards
+ };
+}
+window._qhStartCenterWalk = _qhStartCenterWalk;
+
function updateSampleTray() {
const tray = document.getElementById('sample-tray'), items = document.getElementById('tray-items');
document.getElementById('tray-count').textContent = sampleTray.length;
diff --git a/public/js/versions.js b/public/js/versions.js
index 3692f1b..54ab949 100644
--- a/public/js/versions.js
+++ b/public/js/versions.js
@@ -239,7 +239,7 @@ function boot() {
// the intended radial fan of swatches laid on the table (the fan mesh is the
// next-pass build; this is the camera, not the pinwheel yet).
setTheme('warm');
- setMode('topdown');
+ setMode('gallery'); // (top-down mode retired 2026-06-30 — gallery is the closest overview)
},
elements: [
{ n: 1, label: 'Top-down plan over the consultation table', anchor: { named: 'nook' } },
@@ -257,7 +257,7 @@ function boot() {
setTheme('boutique');
setWingDeg(20);
focusMiddle();
- setMode('orbit');
+ setMode('carousel'); // (orbit mode retired 2026-06-30 — carousel is the closest rotating preview)
},
elements: [
{ n: 1, label: 'Orbit around the hero board', anchor: { board: -1 } },
diff --git a/public/js/viewmodes.js b/public/js/viewmodes.js
index 0285042..8a42c0f 100644
--- a/public/js/viewmodes.js
+++ b/public/js/viewmodes.js
@@ -23,6 +23,7 @@ function boot() {
const WING = QH.CONFIG.wing;
const backZ = -ROOM.depth / 2 + 0.2; // wing-wall plane
const boardCY = WING.height / 2 + 0.04; // vertical centre of a 6-ft board
+ const MACRO_DIST = 0.762; // macro camera distance = 2.5 ft (Steve 2026-06-30)
// ---- state ----
let currentMode = null; // active view-mode key
@@ -33,6 +34,8 @@ function boot() {
let _carouselWG = null; // cached wall group (avoid full scene.traverse per frame)
let _carouselShadowAccum = 0; // throttle carousel shadow rebakes
let macroPhase = 0; // macro slow drift phase
+ let compareB = -1; // compare: user-chosen SECOND board index (-1 = auto next)
+ let compareKeyHandler = null; // compare: keydown listener to cycle the 2nd design
let compareSetup = false; // compare mode: two boards staged
let savedFocusIndex = 0; // remember which board to return to
@@ -73,6 +76,24 @@ function boot() {
}
}
+ // Compare-mode render: show boards i + j open, frame the pair, name both in the banner.
+ function renderCompare(i, j) {
+ QH.revertWalls();
+ const boards = QH.wingBoards;
+ boards.forEach((b, k) => { b.visible = (k === i || k === j); if (b.userData.wallDrop) b.userData.wallDrop.visible = false; });
+ [i, j].forEach(k => {
+ const ud = boards[k] && boards[k].userData; if (!ud) return;
+ ud.panelClosed = false;
+ if (ud.leafL) ud.leafL.rotation.y = 0.10;
+ if (ud.leafR) ud.leafR.rotation.y = -0.10;
+ });
+ const a = boardWorld(i), b = boardWorld(j);
+ const midX = (a.x + b.x) / 2;
+ park(new THREE.Vector3(midX, boardCY, backZ + 2.9), new THREE.Vector3(midX, boardCY, backZ), 44);
+ const nameOf = (k) => { const p = boards[k] && boards[k].userData && boards[k].userData.product; return p ? (p.pattern_name || p.name || ('Board ' + (k + 1))) : ('Board ' + (k + 1)); };
+ setBanner('Comparing “' + nameOf(i) + '” vs “' + nameOf(j) + '” · ◀ ▶ change the second design');
+ }
+
const MODES = {
hero: {
label: 'Hero', hint: 'Flat, dead-on — the gallery hero panel.', guided: true,
@@ -90,57 +111,6 @@ function boot() {
}
},
- angled: {
- label: 'Angled', hint: 'Board opens to a ~65° V — the open-book look.', guided: true,
- enter() {
- ensureFocused();
- setWingDeg(60);
- // Step a touch closer + lower so the V reads dimensionally.
- const c = boardWorld(curIdx());
- const pos = new THREE.Vector3(c.x + 0.35, c.y - 0.05, c.z + 1.7);
- const tgt = new THREE.Vector3(c.x, c.y, c.z);
- park(pos, tgt, 38);
- },
- exit() { setWingDeg(8); }
- },
-
- orbit: {
- label: 'Orbit', hint: 'Drag to orbit around the focused board.', guided: true,
- enter() {
- // Open the focused board to present its design, but keep neighbours visible so
- // the orbit reads as moving *around* a board in the room (richer than a lone slab).
- const idx = curIdx();
- QH.restoreBoards();
- if (!QH.focusedWing || QH.focusedWing.userData.index !== idx) { if (QH.wingBoards[idx]) QH.focusOnWing(QH.wingBoards[idx]); }
- QH.restoreBoards(); // un-isolate again (focusOnWing isolated)
- if (QH.focusedWing) QH.focusedWing.userData.panelClosed = false;
- setWingDeg(20);
- QH.revertWalls();
- freeCam();
- // Turn orbit controls ON for this mode regardless of Explore.
- const ctl = QH.controls;
- ctl.enableRotate = true; ctl.enableZoom = true; ctl.enablePan = false;
- const c = boardWorld(curIdx());
- ctl.target.copy(c);
- ctl.minDistance = 1.2; ctl.maxDistance = 5.5;
- QH.camera.position.set(c.x, c.y + 0.1, c.z + 2.6);
- QH.setTargetFov(40); QH.camera.fov = 40; QH.camera.updateProjectionMatrix();
- orbitAuto = true; orbitSpin = 0;
- },
- frame(dt) {
- if (!orbitAuto) return; // user drag cancels auto-spin
- const ctl = QH.controls, cam = QH.camera, c = ctl.target;
- orbitSpin += dt * 0.32;
- const r = 2.6, h = c.y + 0.12;
- cam.position.set(c.x + Math.sin(orbitSpin) * r, h, c.z + Math.cos(orbitSpin) * r);
- cam.lookAt(c);
- },
- exit() {
- orbitAuto = false;
- QH.controls.minDistance = 1.0; QH.controls.maxDistance = 8.0;
- }
- },
-
gallery: {
label: 'Gallery', hint: 'Pull back to see the whole wall of boards.', guided: false,
enter() {
@@ -156,29 +126,6 @@ function boot() {
}
},
- topdown: {
- label: 'Top-Down', hint: 'High overhead plan view of the showroom.', guided: false,
- enter() {
- QH.restoreBoards();
- if (QH.focusedWing) QH.unfocusWing(true);
- QH.revertWalls();
- setWingDeg(0);
- // The room has a real ceiling, so a camera ABOVE it would just see the ceiling.
- // Instead hide the ceiling for this mode and shoot from straight above the room
- // centre, tilted slightly toward the board wall — a true overhead plan that
- // reads the floor, furniture footprint, and the rail of boards.
- setCeilingVisible(false);
- // Camera tucked just under where the ceiling was, at the FRONT of the room,
- // looking down-and-in across the whole floor to the board wall — a high
- // establishing/plan angle that actually reads inside a closed box (a straight
- // top-down would only see wall tops). The floor + furniture + rail all show.
- const pos = new THREE.Vector3(0, ROOM.height - 0.15, ROOM.depth / 2 - 0.3);
- const tgt = new THREE.Vector3(0, 0.0, backZ + 0.5);
- park(pos, tgt, 70);
- },
- exit() { setCeilingVisible(true); }
- },
-
walk: {
label: 'Walk-Through', hint: 'First-person — W A S D to walk the room.', guided: false,
enter() {
@@ -224,7 +171,11 @@ function boot() {
if (!_carouselWG) _carouselWG = findWallGroup();
const wg = _carouselWG;
if (wg) {
- carouselT += dt * 0.22; wg.rotation.y = Math.sin(carouselT) * 0.5;
+ // CONTINUOUS parade (Steve 2026-06-30) — boards rotate steadily past like a
+ // real turntable, instead of the old Math.sin() rock (which just wobbled the
+ // arc back and forth ±0.5 rad and never actually paraded a new board to front).
+ wg.rotation.y += dt * 0.18;
+ if (wg.rotation.y > Math.PI * 2) wg.rotation.y -= Math.PI * 2;
_carouselShadowAccum += dt;
if (_carouselShadowAccum > 0.16) { _carouselShadowAccum = 0; if (QH.requestShadowUpdate) QH.requestShadowUpdate(1); }
}
@@ -238,11 +189,12 @@ function boot() {
ensureFocused();
setWingDeg(2);
const c = boardWorld(curIdx());
- // Very close, tight FOV — read the weave/print. Slight off-centre so the
- // grazing light catches the surface.
- const pos = new THREE.Vector3(c.x - 0.12, c.y + 0.15, c.z + 0.62);
+ // Zoom in to exactly 2.5 FEET from the board (Steve 2026-06-30) — 0.762 m —
+ // tight FOV to read the weave/print. Slight off-centre so grazing light catches
+ // the surface. MACRO_DIST is reused by the parallax drift so it stays at 2.5 ft.
+ const pos = new THREE.Vector3(c.x - 0.12, c.y + 0.15, c.z + MACRO_DIST);
const tgt = new THREE.Vector3(c.x - 0.05, c.y + 0.1, c.z);
- park(pos, tgt, 26);
+ park(pos, tgt, 30);
macroPhase = 0;
},
frame(dt) {
@@ -259,36 +211,27 @@ function boot() {
},
compare: {
- label: 'Compare', hint: 'Two boards side-by-side for A/B.', guided: false,
+ label: 'Compare', hint: 'Two designs side-by-side — ◀ ▶ change the second one.', guided: false,
enter() {
- // Stage two adjacent boards open + visible; frame both.
- QH.revertWalls();
+ // A = the current board; B = a user-CHOOSABLE second design (Steve 2026-06-30).
+ // Previously compare force-picked i+1 with no way to change it — confusing. Now
+ // ◀ ▶ (or [ ]) cycle the second design; the banner names both so you know what
+ // you're looking at.
+ const len = QH.wingBoards.length;
const i = curIdx();
- const j = (i + 1) % QH.wingBoards.length;
- // Show only these two.
- QH.wingBoards.forEach((b, k) => { b.visible = (k === i || k === j); if (b.userData.wallDrop) b.userData.wallDrop.visible = false; });
- // Open both leaves a touch so each presents its design.
- [i, j].forEach(k => {
- const ud = QH.wingBoards[k].userData;
- ud.panelClosed = false;
- // mark as a "compare-open" so the showroom's updateBooks won't fight it:
- // we set the leaf rotation directly here each enter; small static splay.
- if (ud.leafL) ud.leafL.rotation.y = 0.10;
- if (ud.leafR) ud.leafR.rotation.y = -0.10;
- });
- QH.focusOnWing && (QH._noop = 0);
- // Frame the pair: camera centred between the two boards, pulled back.
- const a = boardWorld(i), b = boardWorld(j);
- const midX = (a.x + b.x) / 2;
- const pos = new THREE.Vector3(midX, boardCY, backZ + 2.9);
- const tgt = new THREE.Vector3(midX, boardCY, backZ);
- park(pos, tgt, 44);
+ if (compareB < 0 || compareB === i || compareB >= len) compareB = (i + 1) % len;
+ renderCompare(i, compareB);
+ compareKeyHandler = (e) => {
+ if (!compareSetup) return;
+ if (e.code === 'ArrowRight' || e.key === ']') { do { compareB = (compareB + 1) % len; } while (compareB === i); renderCompare(i, compareB); e.preventDefault(); }
+ else if (e.code === 'ArrowLeft' || e.key === '[') { do { compareB = (compareB - 1 + len) % len; } while (compareB === i); renderCompare(i, compareB); e.preventDefault(); }
+ };
+ window.addEventListener('keydown', compareKeyHandler);
compareSetup = true;
- // Update the title to say what's being compared.
- setBanner('Comparing two designs side-by-side — Next steps both forward.');
},
exit() {
compareSetup = false;
+ if (compareKeyHandler) { window.removeEventListener('keydown', compareKeyHandler); compareKeyHandler = null; }
QH.restoreBoards();
clearBanner();
}
@@ -312,7 +255,7 @@ function boot() {
}
};
- const MODE_ORDER = ['hero','angled','orbit','gallery','topdown','walk','carousel','macro','compare','room'];
+ const MODE_ORDER = ['walk','hero','gallery','carousel','macro','compare','room'];
// Helper to drive the showroom's open-board angle (the Wing° slider value).
function setWingDeg(deg) {
@@ -506,11 +449,17 @@ function boot() {
// settled — except never auto-restore the free-camera modes on boot (they'd land
// on an empty room for a returning senior). Restore only the board-presentation
// modes; otherwise fall back to Hero.
+ // Boot = WALK-THROUGH the CENTER of the showroom (Steve 2026-06-30): first-person
+ // walk down the middle of the room past the boards, with an automated slow dolly
+ // (Peruse) so it moves on its own until the user takes WASD control. A returning
+ // user's saved board-presentation mode still restores; free-cam modes never do.
const savedMode = localStorage.getItem('qh_viewmode');
- const safeBootModes = { hero: 1, angled: 1, macro: 1, room: 1 };
- setMode('hero', true); // boot = Hero, guided focus already set the middle board
- if (savedMode && MODES[savedMode] && safeBootModes[savedMode] && savedMode !== 'hero') {
- setTimeout(() => setMode(savedMode, true), 1200);
+ const safeBootModes = { walk: 1, hero: 1, macro: 1, room: 1 };
+ const bootMode = (savedMode && MODES[savedMode] && safeBootModes[savedMode]) ? savedMode : 'walk';
+ setMode(bootMode, true);
+ if (bootMode === 'walk') {
+ // Kick the automated center walk-through a beat after the scene settles.
+ setTimeout(() => { if (currentMode === 'walk' && window._qhStartCenterWalk) window._qhStartCenterWalk(); }, 1400);
}
// Expose for the test harness + for showroom guided buttons to re-assert the view.
← 2b90e02 spec: 5 room types for showroom room-preview
·
back to Quadrille Showroom
·
auto-save: 2026-06-30T20:43:13 (9 files) — public/js/showroo 02a9c21 →