[object Object]

← back to Quadrille Showroom

Slice 1: open-angle slider (qh_open_angle, 45° default, 20-90°, live re-rake) + middle board opens at measured 45° on boot; bump density max to 50

37affeb4205ee6d43b0205b7b4566007787330d4 · 2026-06-28 16:20:08 -0700 · Steve

Files touched

Diff

commit 37affeb4205ee6d43b0205b7b4566007787330d4
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Jun 28 16:20:08 2026 -0700

    Slice 1: open-angle slider (qh_open_angle, 45° default, 20-90°, live re-rake) + middle board opens at measured 45° on boot; bump density max to 50
---
 public/js/showroom.js | 46 +++++++++++++++++++++++++++++++++++++++++-----
 public/showroom.html  |  5 ++++-
 2 files changed, 45 insertions(+), 6 deletions(-)

diff --git a/public/js/showroom.js b/public/js/showroom.js
index 7524ebe..4dbf7c9 100644
--- a/public/js/showroom.js
+++ b/public/js/showroom.js
@@ -12,7 +12,7 @@ const CONFIG = {
   wing: {
     height: 1.8288,         // 6 ft tall (real sample-board height)
     boardWidthM: 0.762,     // 30" wide (real sample-board width)
-    totalCount: 20,         // boards in the arc at once — packed slivers; Peruse pages the 883
+    totalCount: 50,         // boards in the arc at once — packed slivers; Peruse pages the 883
     wallMargin: 0.3,
     animSpeed: 0.06,
     panelOpen: 1.35,
@@ -71,6 +71,16 @@ let VIEW_OPEN = (function () {
   return (v >= 0 && v <= 90 ? v : 8) * Math.PI / 180;
 })();
 
+// OPEN-ANGLE SLIDER (Slice-1 spec) — the resting MIDDLE board's open angle on boot.
+// Measured in DEGREES the board has swung off its packed closed-rake toward open-flat
+// (0°=fully closed/raked, rake°=fully open/flat-to-centre). Default 45°, range 20°–90°,
+// persisted to localStorage('qh_open_angle'). updateBooks converts this to a per-board
+// flip fraction (deg / board-rake-deg) so the open hero rakes to exactly this angle live.
+let OPEN_ANGLE_DEG = (function () {
+  const v = parseFloat(localStorage.getItem('qh_open_angle'));
+  return (v >= 20 && v <= 90) ? v : 45;
+})();
+
 // REVEAL SLIDER (Steve's explicit ask) — how much of each closed board's pattern shows
 // in the packed arc. Drives the per-board closed RAKE angle in QHRack (smaller reveal =
 // wider sliver = more pattern per board / fewer boards; larger reveal = thinner slivers =
@@ -2062,7 +2072,7 @@ function onCanvasMouseMove(event) {
 // ============================================================
 const SPIN_YAW_SPEED   = 1.6;             // rad/s of yaw under held arrow
 const SPIN_PITCH_SPEED = 0.9;             // rad/s of pitch under held arrow
-const YAW_CLAMP        = 105 * Math.PI / 180;  // ±105° off wing-facing (-z) — never behind the wings
+const YAW_CLAMP        = 72 * Math.PI / 180;   // ±72° off wing-facing (-z) — the look-vector z stays negative so the leftmost/rightmost boards + side wall stay in frame; never an empty room, never behind the wings
 const PITCH_MIN        = -0.62;           // look down ~36° (to the sample table)
 const PITCH_MAX        =  0.30;           // look up ~17°
 const TARGET_DIST      = 2.0;             // distance the look-target sits ahead of the fixed camera
@@ -2159,10 +2169,17 @@ function updateBooks(dt) {
   // Lazy-load the open board's full design the moment it begins to swing open.
   if (openPivot && !openPivot.userData.imageLoaded) { openPivot.userData.imageLoaded = true; loadWingBook(openPivot); }
 
-  // A real focus opens fully flat (hero); the resting-open middle eases to a partial
-  // angle (0.62) so it reads as "open at an angle", not a flat selected hero.
+  // A real focus opens fully flat (hero); the resting-open middle eases to the
+  // slider-controlled OPEN_ANGLE_DEG (default 45°) so it reads as "open at an angle",
+  // not a flat selected hero. flip = openAngle / rake (both in radians), so the board's
+  // actual swing off closed equals OPEN_ANGLE_DEG; clamp to [0,1].
   const isRestingOnly = (openPivot && openPivot === restingOpenWing && openPivot !== focusedWing);
-  const moved = QHRack.flipUpdate(wingBoards, openPivot, dt, isRestingOnly ? 0.8 : 1);
+  let restFlip = 1;
+  if (isRestingOnly) {
+    const rake = openPivot.userData.rake || (73 * Math.PI / 180);
+    restFlip = Math.max(0, Math.min(1, (OPEN_ANGLE_DEG * Math.PI / 180) / rake));
+  }
+  const moved = QHRack.flipUpdate(wingBoards, openPivot, dt, restFlip);
   // SHADOW REBAKE BUDGET: don't rebake the (2 lights × 238-mesh) shadow maps EVERY
   // moving frame — that's the dominant nav cost. Rebake once when a flip STARTS (catch
   // the first frame of motion) and once when it SETTLES (motion→still), not in between.
@@ -2683,6 +2700,25 @@ function initHUD() {
     });
   }
 
+  // OPEN-ANGLE slider (Slice-1) — the resting MIDDLE board's open angle on boot.
+  // Live: updateBooks re-derives the resting board's flip from OPEN_ANGLE_DEG every
+  // frame, so dragging this re-rakes the open hero in real time. Persisted to
+  // localStorage('qh_open_angle'). Range 20°–90°, default 45°.
+  const opn = document.getElementById('open-range');
+  const opnVal = document.getElementById('open-val');
+  if (opn) {
+    opn.value = Math.round(OPEN_ANGLE_DEG);
+    if (opnVal) opnVal.textContent = opn.value + '°';
+    opn.addEventListener('input', () => {
+      OPEN_ANGLE_DEG = parseInt(opn.value) || 45;
+      if (opnVal) opnVal.textContent = OPEN_ANGLE_DEG + '°';
+      localStorage.setItem('qh_open_angle', OPEN_ANGLE_DEG);
+      // Nudge the resting board out of its settled state so flipUpdate re-eases to the
+      // new target this frame (flipUpdate only moves boards whose flip != target).
+      if (restingOpenWing && window._requestShadowUpdate) window._requestShadowUpdate(6);
+    });
+  }
+
   // ---- GUIDED MODE wiring (senior-first big buttons) ----
   const gPrev = document.getElementById('g-prev');
   const gNext = document.getElementById('g-next');
diff --git a/public/showroom.html b/public/showroom.html
index 3c0cd88..71be014 100644
--- a/public/showroom.html
+++ b/public/showroom.html
@@ -286,7 +286,7 @@
         <option value="book">Book</option>
       </select>
       <span id="density-wrap" title="Wings per wall">
-        <span>Boards</span><input type="range" id="density-range" min="10" max="24" step="2" value="18">
+        <span>Boards</span><input type="range" id="density-range" min="10" max="50" step="2" value="50">
       </span>
       <span id="density-wrap" title="Open-board angle (0°=flat, dead-on)">
         <span>Wing&deg;</span><input type="range" id="angle-range" min="0" max="90" step="5" value="8"><span id="angle-val">8&deg;</span>
@@ -294,6 +294,9 @@
       <span id="density-wrap" title="How much of each closed board's pattern shows — wider edges = fewer boards, thinner slivers = more boards in the arc">
         <span>Reveal</span><input type="range" id="reveal-range" min="0" max="100" step="5" value="70"><span id="reveal-val">70</span>
       </span>
+      <span id="density-wrap" title="Open angle of the resting middle board (45° = the boot hero rake)">
+        <span>Open&deg;</span><input type="range" id="open-range" min="20" max="90" step="5" value="45"><span id="open-val">45&deg;</span>
+      </span>
     </div>
 
     <!-- NOW VIEWING navigator — museum media-player strip (shown only on focus).

← c38484d auto-save: 2026-06-28T16:15:32 (1 files) — LIQUID-UI-SPEC.md  ·  back to Quadrille Showroom  ·  Slice1 fix-it: 72° yaw clamp (non-empty), 50 boards, ~36° re 2c946ef →