[object Object]

← 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 →