← back to Quadrille Showroom
auto-save: 2026-06-27T12:39:11 (1 files) — public/js/showroom.js
b44c0aa44f988ffe3136e6e75105204ebc6ea7ea · 2026-06-27 12:39:13 -0700 · Steve Abrams
Files touched
Diff
commit b44c0aa44f988ffe3136e6e75105204ebc6ea7ea
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sat Jun 27 12:39:13 2026 -0700
auto-save: 2026-06-27T12:39:11 (1 files) — public/js/showroom.js
---
public/js/showroom.js | 54 +++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 52 insertions(+), 2 deletions(-)
diff --git a/public/js/showroom.js b/public/js/showroom.js
index 83a57c8..c45aa13 100644
--- a/public/js/showroom.js
+++ b/public/js/showroom.js
@@ -286,6 +286,48 @@ function contactShadowTexture() {
return _contactTex;
}
+// PER-BOARD LIGHTING — a subtle top-to-bottom luminance falloff + a faint specular
+// sheen band where the picture-light grazes the upper face of each 6-ft board, so the
+// board reads as a physical object in a lit room (not an evenly-lit decal). Baked once
+// into a shared RGBA gradient sprite that overlays the board face with NormalBlending
+// (translucent), so it works identically for the pool material AND the loadWingBook
+// material WITHOUT touching the swatch's colour math. Low alphas everywhere keep the
+// wallcovering colour true + legible — the swatch is never crushed into shadow.
+let _boardLightTex = null;
+function boardLightTexture() {
+ if (_boardLightTex) return _boardLightTex;
+ const c = document.createElement('canvas'); c.width = 16; c.height = 512; // vertical 1-D gradient
+ const ctx = c.getContext('2d');
+ // y=0 is the TOP of the board (picture-light grazes here), y=512 the bottom (floor side).
+ const g = ctx.createLinearGradient(0, 0, 0, 512);
+ // Faint warm SHEEN band near the top third where the picture-light catches the face.
+ g.addColorStop(0.00, 'rgba(255,248,232,0.10)'); // soft warm highlight at the very top
+ g.addColorStop(0.14, 'rgba(255,248,232,0.055)'); // sheen falls off
+ g.addColorStop(0.34, 'rgba(0,0,0,0.0)'); // neutral mid — swatch reads TRUE here
+ g.addColorStop(0.70, 'rgba(28,24,18,0.05)'); // gentle warm-charcoal shading begins
+ g.addColorStop(1.00, 'rgba(28,24,18,0.16)'); // modest falloff at the bottom (never crushed)
+ ctx.fillStyle = g; ctx.fillRect(0, 0, 16, 512);
+ _boardLightTex = new THREE.CanvasTexture(c);
+ _boardLightTex.minFilter = THREE.LinearFilter; _boardLightTex.magFilter = THREE.LinearFilter;
+ return _boardLightTex;
+}
+
+// One overlay quad sized to a leaf face, parented to the leaf so it swings with it.
+// Sits a hair in front of the face (toward +z local) so it never z-fights the swatch.
+function makeBoardLightOverlay(leafW, H, yC) {
+ const m = new THREE.Mesh(
+ new THREE.PlaneGeometry(leafW, H),
+ new THREE.MeshBasicMaterial({
+ map: boardLightTexture(), transparent: true, opacity: 1,
+ depthWrite: false, polygonOffset: true, polygonOffsetFactor: -1, polygonOffsetUnits: -1
+ })
+ );
+ m.position.set(0, yC, 0.0085); // just in front of the 14 mm-thick board face
+ m.renderOrder = 2; // draw after the swatch
+ m.userData.isBoardLight = true; // so it's never raycast-hit as a face
+ return m;
+}
+
// Drop a soft contact shadow on the floor. (cx,cz) = world centre; w,d = footprint;
// opacity scales the darkness. Returns the mesh so callers can group/parent it.
function addContactShadow(cx, cz, w, d, opacity) {
@@ -884,14 +926,22 @@ function buildWingWall() {
planeL.position.set(-leafW / 2, yC, 0);
planeL.castShadow = true; planeL.receiveShadow = true;
planeL.userData = { isWingFace: true, parentPivot: pivot, leaf: 'L' };
- leafL.add(planeL); pivot.add(leafL);
+ leafL.add(planeL);
+ // Per-board light gradient + sheen, parented to the LEAF FACE so it tracks the
+ // exact swing of the leaf (offset to the leaf-centre x like the face itself).
+ const litL = makeBoardLightOverlay(leafW, H, yC); litL.position.x = -leafW / 2;
+ leafL.add(litL);
+ pivot.add(leafL);
const leafR = new THREE.Group(); // right page, hinge at spine
const planeR = new THREE.Mesh(new THREE.BoxGeometry(leafW, H, THICK), poolMat);
planeR.position.set(leafW / 2, yC, 0);
planeR.castShadow = true; planeR.receiveShadow = true;
planeR.userData = { isWingFace: true, parentPivot: pivot, leaf: 'R' };
- leafR.add(planeR); pivot.add(leafR);
+ leafR.add(planeR);
+ const litR = makeBoardLightOverlay(leafW, H, yC); litR.position.x = leafW / 2;
+ leafR.add(litR);
+ pivot.add(leafR);
// Thin brushed-brass surround behind the board (stays put when leaves fan open).
// Four slim bars frame the 30" board face like a gallery mount.
← d2c6df4 PHOTOGRAPHIC DEPTH: subtle CSS vignette + soft top-down dire
·
back to Quadrille Showroom
·
PER-BOARD LIGHT GRADIENT: each 6-ft board face gets a baked 433800b →