← back to Quadrille Showroom
auto-save: 2026-07-02T11:47:40 (2 files) — public/js/rack.js public/js/showroom.js
e7f3b0f0a5753ac435adb24d29d88b824b1dbe36 · 2026-07-02 11:47:44 -0700 · Steve Abrams
Files touched
M public/js/rack.jsM public/js/showroom.js
Diff
commit e7f3b0f0a5753ac435adb24d29d88b824b1dbe36
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 2 11:47:44 2026 -0700
auto-save: 2026-07-02T11:47:40 (2 files) — public/js/rack.js public/js/showroom.js
---
public/js/rack.js | 43 +++++++++++++++++++++++++++++++++++++------
public/js/showroom.js | 15 +++++++++++++--
2 files changed, 50 insertions(+), 8 deletions(-)
diff --git a/public/js/rack.js b/public/js/rack.js
index 6782ec1..a25aa83 100644
--- a/public/js/rack.js
+++ b/public/js/rack.js
@@ -51,6 +51,15 @@
// The continuous `indexOffset` (Chunk I carousel) shifts every board by the same
// fractional number of slots so the rack conveyor-slides as one piece.
function arcAngleFor(i, n, cfg) {
+ // LINEAR mode (Steve 2026-07-02): NO semicircular arc — the wings hang on a
+ // STRAIGHT rail centred against the back wall, one per peg-hole, 2" apart.
+ // Here "angle" degenerates to the board's X position on that straight rail so
+ // railPointFor/faceCentreYaw stay the only consumers (call sites unchanged).
+ if (cfg && cfg.layout === 'linear') {
+ const pitch = (cfg.pegPitchM || 0.0508) * (cfg.sliverPitchScale || 1); // 2" default
+ const run = (n > 1 ? (n - 1) * pitch : 0);
+ return -run / 2 + (i + indexOffset) * pitch; // centred on x=0
+ }
const span = cfg.arcSpanDeg * Math.PI / 180;
const pitch = (n > 1 ? span / (n - 1) : span) * (cfg.sliverPitchScale || 1);
const centre = (typeof cfg.arcCentreDeg === 'number' ? cfg.arcCentreDeg : 90) * Math.PI / 180;
@@ -67,6 +76,12 @@
// World-space rail point for a given arc angle. The board hangs from this point.
function railPointFor(angle, cfg) {
+ // LINEAR mode: `angle` IS the board's X on the straight back-wall rail; Z is
+ // fixed just in front of the back wall, the rail runs flat (tangentY = 0).
+ if (cfg && cfg.layout === 'linear') {
+ const z = (typeof cfg.backWallZ === 'number') ? cfg.backWallZ : -2.87;
+ return { x: angle, z, tangentY: 0 };
+ }
const R = cfg.arcRadius, cx = cfg.arcCenter.x, cz = cfg.arcCenter.z;
const x = cx + Math.cos(angle) * R;
const z = cz + Math.sin(angle) * R;
@@ -79,6 +94,9 @@
// (cx-x, cz-z); its Three.js Y-rotation (atan2(dx, dz)) orients the +Z face
// of the plane to point that way.
function faceCentreYaw(angle, cfg) {
+ // LINEAR mode: every board faces the viewer dead-on (+Z), so the open hero is
+ // flat to the room and the closed neighbours rake off that shared front pose.
+ if (cfg && cfg.layout === 'linear') return 0;
const R = cfg.arcRadius, cx = cfg.arcCenter.x, cz = cfg.arcCenter.z;
const x = cx + Math.cos(angle) * R, z = cz + Math.sin(angle) * R;
return Math.atan2(cx - x, cz - z);
@@ -95,7 +113,12 @@
const angle = arcAngleFor(i, n, cfg);
const rp = railPointFor(angle, cfg);
const openYaw = faceCentreYaw(angle, cfg);
- const rake = revealToRake(reveal);
+ const linear = !!(cfg && cfg.layout === 'linear');
+ // LINEAR pegboard: a gentler rake so the wings lie NEAR-FLAT against the back
+ // wall (each showing a ~2" sliver of the next). They lean FORWARD off the wall
+ // (closedYaw uses −rake below) so a front board occludes its left neighbour —
+ // never raking back THROUGH the wall (the arc's 73° rake did, clipping them).
+ const rake = revealToRake(reveal) * (linear ? 0.34 : 1);
// BASE rail position (no open-forward offset). flipUpdate adds the forward push for
// the open hero ON TOP of this base, so the next relayout (carousel shift) re-reads a
@@ -104,10 +127,16 @@
// Inward unit normal toward the arc centre (cx,cz) — the direction a board pushes when
// it opens so its FACE comes forward toward the room centre / viewer, clearing the
// raked neighbour slivers + frames out from in front of its pattern.
- const cx = cfg.arcCenter.x, cz = cfg.arcCenter.z;
- let inx = cx - rp.x, inz = cz - rp.z;
- const ilen = Math.hypot(inx, inz) || 1;
- ud.inX = inx / ilen; ud.inZ = inz / ilen;
+ if (cfg && cfg.layout === 'linear') {
+ // Straight rail: the hero comes forward off the back wall toward the viewer
+ // (+Z), no lateral drift — so it stays over its own peg slot as it opens.
+ ud.inX = 0; ud.inZ = 1;
+ } else {
+ const cx = cfg.arcCenter.x, cz = cfg.arcCenter.z;
+ let inx = cx - rp.x, inz = cz - rp.z;
+ const ilen = Math.hypot(inx, inz) || 1;
+ ud.inX = inx / ilen; ud.inZ = inz / ilen;
+ }
// Half the board face width (face sits +halfWidth from the left hinge) — used by
// flipUpdate to re-centre the open hero's face on the slot centre.
ud.faceHalf = (cfg && cfg.boardWidthM ? cfg.boardWidthM : 0.762) / 2;
@@ -116,7 +145,9 @@
ud.arcAngle = angle;
ud.openYaw = openYaw;
ud.rake = rake;
- ud.closedYaw = openYaw + rake; // raked CCW about the hinge → packed
+ // Arc rakes CCW (+) into the fan; the straight rail rakes FORWARD (−) off the
+ // wall so packed neighbours occlude to a sliver without clipping the back wall.
+ ud.closedYaw = openYaw + (linear ? -rake : rake);
if (typeof ud.flip !== 'number') ud.flip = 0; // 0 closed → 1 open
pivot.rotation.y = ud.closedYaw + (ud.openYaw - ud.closedYaw) * ud.flip;
}
diff --git a/public/js/showroom.js b/public/js/showroom.js
index 4a08208..1b4af41 100644
--- a/public/js/showroom.js
+++ b/public/js/showroom.js
@@ -33,7 +33,15 @@ const CONFIG = {
arcSpanDeg: 96,
arcCentreDeg: -90, // sweep centred on -Z → boards sit in FRONT of the viewer
arcCenter: { x: 0.0, z: 0.5 }, // arc centre just BEHIND the centred viewer → concave bank faces them
- sliverPitchScale: 1.0 // multiplier on the angular pitch (reveal slider can thin)
+ sliverPitchScale: 1.0, // multiplier on the pitch (reveal slider can thin)
+ // ---- STRAIGHT BACK-WALL RAIL (Steve 2026-07-02) ----
+ // Steve killed the semicircular "dry-cleaner" arc: the wings now hang on a
+ // STRAIGHT brass rail centred against the back wall, one wing per peg-hole,
+ // 2" (0.0508m) apart, packed so each closed wing shows a ~2" colour sliver;
+ // click one and it swings forward off the wall to the dead-on open hero.
+ layout: 'linear',
+ pegPitchM: 0.0508, // 2" between peg-holes
+ backWallZ: -(6.10 / 2) + 0.18 // just in front of the back wall (z = -3.05 + 0.18)
},
// Camera FIXED at room centre, eye height ~1.6m, facing the wing bank (toward arc centre).
// startPos.z pulled back to +0.7 (Steve 2026-06-30: the centred eye at z=0 sat ~2.4m from
@@ -1973,7 +1981,10 @@ async function loadProducts() {
// mirrored sample book centred on the spawn's forward axis. The avatar spawns dead-centre,
// stationary, facing it — NO auto-fly. [ ] / N-P page the catalog.
if (BOOK_MODE) {
- if (currentWallGroup) currentWallGroup.visible = false; // no arc in the book view
+ // Steve 2026-07-02: KEEP the straight back-wall wing bank visible on load — all the
+ // other wings hang as ~2" colour slivers across the back wall — AND keep the centred
+ // open book in front (the "open boards on both sides" he likes). Was hidden before.
+ if (currentWallGroup) currentWallGroup.visible = true;
buildCenterBook();
setCenterBook(Math.floor((products.slice(0, windowSize).length) / 2));
document.body.classList.add('book-mode');
← a156052 5x sweep 1: keep bottom-corner panels clear of the centre pa
·
back to Quadrille Showroom
·
chore: macstudio3 migration — reconcile from mac2 + repoint 17535f5 →