← back to Quadrille Showroom
NOW VIEWING navigator HUD: museum media-player strip (swatch thumb + serif name + tracked colorway + 27x27 spec + Back/Peruse-play-pause/Next + 'N of N' brass progress). Vanilla CSS/HTML, brass-hairline museum glass per ART-DIRECTION; reuses gotoBoardOffset+togglePeruse, syncs play glyph to tour state. FPS 72/min51, errorCount 0. Figma/Magic MCP design phase tool-gated this session (noted)
a38c66c4ad345814af59c3e2a783c60e467f6cfa · 2026-06-27 12:16:56 -0700 · Steve
Files touched
M public/css/showroom.cssM public/js/showroom.jsM public/showroom.html
Diff
commit a38c66c4ad345814af59c3e2a783c60e467f6cfa
Author: Steve <steve@designerwallcoverings.com>
Date: Sat Jun 27 12:16:56 2026 -0700
NOW VIEWING navigator HUD: museum media-player strip (swatch thumb + serif name + tracked colorway + 27x27 spec + Back/Peruse-play-pause/Next + 'N of N' brass progress). Vanilla CSS/HTML, brass-hairline museum glass per ART-DIRECTION; reuses gotoBoardOffset+togglePeruse, syncs play glyph to tour state. FPS 72/min51, errorCount 0. Figma/Magic MCP design phase tool-gated this session (noted)
---
public/css/showroom.css | 92 +++++++++++++++++++++++++++++++++++++++++++++++++
public/js/showroom.js | 76 ++++++++++++++++++++++++++++++++++++++--
public/showroom.html | 21 +++++++++++
3 files changed, 186 insertions(+), 3 deletions(-)
diff --git a/public/css/showroom.css b/public/css/showroom.css
index 142347f..5490519 100644
--- a/public/css/showroom.css
+++ b/public/css/showroom.css
@@ -188,3 +188,95 @@ body { overflow: hidden; background: #0a0a0f; font-family: 'Segoe UI', system-ui
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(201,169,110,0.3); border-radius: 2px; }
+
+/* ============================================================
+ NOW VIEWING — museum media-player navigator strip
+ Shown only when a wingboard is focused. Brass-hairline + museum
+ glass; conforms to ART-DIRECTION.md (refined, calm, not a dashboard).
+ Sits bottom-centre, above the window/sort bar. Augments the detail
+ card's browse row with a player-style transport + N-of-N progress.
+ ============================================================ */
+#now-viewing {
+ position: fixed; left: 50%; bottom: 96px; transform: translateX(-50%);
+ z-index: 28; width: min(460px, 70vw);
+ background: linear-gradient(180deg, rgba(20,20,26,0.92), rgba(12,12,17,0.95));
+ border: 1px solid rgba(201,169,110,0.28);
+ border-radius: 14px; overflow: hidden;
+ box-shadow: 0 16px 44px -14px rgba(0,0,0,0.72), inset 0 1px 0 rgba(255,255,255,0.04);
+ backdrop-filter: blur(16px);
+ opacity: 1; transition: opacity .35s ease, transform .35s ease;
+}
+#now-viewing.hidden { opacity: 0; transform: translateX(-50%) translateY(8px); pointer-events: none; }
+
+/* Thin brass progress rail across the very top — the "track position" */
+.nv-progress { height: 2px; background: rgba(201,169,110,0.14); width: 100%; }
+.nv-progress-fill {
+ height: 100%; width: 0%;
+ background: linear-gradient(90deg, #b9933f, #e8cf93);
+ box-shadow: 0 0 6px rgba(201,169,110,0.5);
+ transition: width .45s cubic-bezier(.22,.61,.36,1);
+}
+
+.nv-body { display: flex; align-items: center; gap: 14px; padding: 11px 16px 12px; }
+
+/* Swatch thumbnail — small framed chip of the actual wallcovering */
+.nv-swatch {
+ width: 46px; height: 46px; flex-shrink: 0; border-radius: 7px; overflow: hidden;
+ background: #15151c; border: 1px solid rgba(201,169,110,0.35);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.5);
+}
+.nv-swatch img { width: 100%; height: 100%; object-fit: cover; display: block; }
+
+/* Meta block — serif name, tracked colorway + spec */
+.nv-meta { flex: 1; min-width: 0; }
+.nv-eyebrow {
+ font-size: 8.5px; letter-spacing: 2.6px; text-transform: uppercase;
+ color: #c9a96e; opacity: .82; margin-bottom: 3px;
+}
+.nv-name {
+ font-family: Georgia, 'Times New Roman', serif; font-size: 17px; font-weight: 600;
+ color: #f0e6cf; line-height: 1.1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+}
+.nv-sub {
+ display: flex; align-items: center; gap: 7px; margin-top: 3px;
+ font-size: 11px; color: #a7a299; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+}
+.nv-color { letter-spacing: 1.4px; text-transform: uppercase; font-size: 10px; color: #b6b0a4; }
+.nv-dot { color: rgba(201,169,110,0.5); }
+.nv-spec { font-variant-numeric: tabular-nums; color: #cfc7b8; }
+
+/* Transport — Back / Peruse play-pause / Next, brass hairline circles */
+.nv-transport { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
+.nv-btn {
+ width: 30px; height: 30px; border-radius: 50%;
+ border: 1px solid rgba(201,169,110,0.34);
+ background: rgba(201,169,110,0.07); color: #c9a96e;
+ font-size: 11px; cursor: pointer; line-height: 1;
+ display: flex; align-items: center; justify-content: center;
+ transition: background .18s, border-color .18s, transform .12s;
+}
+.nv-btn:hover { background: rgba(201,169,110,0.2); transform: translateY(-1px); }
+.nv-btn:active { transform: translateY(0); }
+.nv-play {
+ width: 38px; height: 38px; font-size: 13px;
+ background: linear-gradient(180deg, rgba(216,189,132,0.22), rgba(185,147,63,0.18));
+ border-color: rgba(201,169,110,0.5);
+}
+.nv-play.playing {
+ background: linear-gradient(180deg, #d8bd84, #b9933f); color: #1a1206;
+ box-shadow: 0 0 12px rgba(201,169,110,0.45);
+}
+
+/* N of 50 progress count — tabular, brass accent on the index */
+.nv-count {
+ display: flex; align-items: baseline; gap: 4px; flex-shrink: 0;
+ font-variant-numeric: tabular-nums; font-size: 11px; color: #837d72;
+ letter-spacing: .5px; padding-left: 2px;
+}
+.nv-count #nv-index { color: #e8cf93; font-size: 14px; font-weight: 600; }
+.nv-count .nv-of { font-size: 9px; letter-spacing: 1.5px; text-transform: uppercase; opacity: .7; }
+
+@media (max-width: 720px) {
+ #now-viewing { bottom: 150px; width: 88vw; }
+ .nv-count { display: none; }
+}
diff --git a/public/js/showroom.js b/public/js/showroom.js
index cc7682f..359635e 100644
--- a/public/js/showroom.js
+++ b/public/js/showroom.js
@@ -1263,6 +1263,7 @@ function unfocusWing(skipPanel) {
// skipPanel=true and the next cladWalls overwrites, so no neutral flicker.
if (!skipPanel) revertWalls();
if (!skipPanel) document.getElementById('wing-detail').classList.add('hidden');
+ if (!skipPanel) hideNowViewing(); // tuck the navigator strip away on a true exit
// Unlock first so smoothCameraTo can move
unlockControls();
@@ -1340,6 +1341,65 @@ function showWingDetail(product) {
// CTA label by mode
const cta = document.getElementById('btn-view-dw');
if (cta) cta.textContent = product.cta_mode === 'live' ? 'View on Designer Wallcoverings' : 'Request a Memo Sample';
+
+ // Feed the NOW VIEWING media-player strip in lock-step with the detail card.
+ updateNowViewing(product);
+}
+
+// NOW VIEWING navigator — populate the museum media-player strip from the focused
+// board: swatch thumb + serif pattern name + tracked colorway + 27×27 spec + the
+// global N-of-N progress (across the whole window, not just the visible boards).
+function updateNowViewing(product) {
+ const nv = document.getElementById('now-viewing');
+ if (!nv) return;
+ nv.classList.remove('hidden');
+
+ const { pattern, colorway } = splitPatternColor(product.pattern_name || product.name);
+ const nm = document.getElementById('nv-name');
+ if (nm) nm.textContent = pattern || 'Pattern';
+ // Colorway: real colorway > derived from title > collection name (never the literal
+ // placeholder word). Hide the separator dot if there's genuinely nothing to show.
+ const col = document.getElementById('nv-color');
+ const dot = document.querySelector('#now-viewing .nv-dot');
+ const cw = product.color || colorway || product.collection || '';
+ if (col) col.textContent = cw;
+ if (dot) dot.style.display = cw ? '' : 'none';
+
+ // Spec: width × repeat (China Seas straight-match = 27″ × 27″). Falls back gracefully.
+ const fmtIn = (n) => (n || n === 0) ? `${(Math.round(n * 10) / 10)}″` : '';
+ const w = product.width_inches, r = product.repeat_inches;
+ const specEl = document.getElementById('nv-spec');
+ if (specEl) specEl.textContent = (w && r) ? `${fmtIn(w)} × ${fmtIn(r)}` : (w ? fmtIn(w) : '27″ × 27″');
+
+ // Swatch thumb — the actual wallcovering image (not the room shot).
+ const thumb = document.getElementById('nv-thumb');
+ const src = product.image || product.rawImage || product.room;
+ if (thumb && src) thumb.src = src.charAt(0) === '/' ? src : ('/api/proxy/image?url=' + encodeURIComponent(src));
+
+ // N of N — global position across the active window/collection.
+ const idx = (typeof windowOffset === 'number' ? windowOffset : 0) +
+ (focusedWing && focusedWing.userData ? focusedWing.userData.index : 0) + 1;
+ const total = windowTotal || products.length || 0;
+ const iEl = document.getElementById('nv-index'); if (iEl) iEl.textContent = idx;
+ const tEl = document.getElementById('nv-total'); if (tEl) tEl.textContent = total || '—';
+ const fill = document.getElementById('nv-fill');
+ if (fill) fill.style.width = (total ? Math.max(2, Math.min(100, (idx / total) * 100)) : 2) + '%';
+
+ syncNowViewingPlay();
+}
+
+// Keep the strip's play/pause glyph in sync with the Peruse tour state.
+function syncNowViewingPlay() {
+ const pb = document.getElementById('nv-play');
+ if (!pb) return;
+ pb.classList.toggle('playing', !!peruseActive);
+ pb.innerHTML = peruseActive ? '❚❚' : '▷';
+ pb.title = peruseActive ? 'Pause the tour' : 'Peruse (auto-tour)';
+}
+
+function hideNowViewing() {
+ const nv = document.getElementById('now-viewing');
+ if (nv) nv.classList.add('hidden');
}
function updateOpenBtnState() {
@@ -1643,9 +1703,11 @@ function stopPeruse(keepFocus) {
function setPeruseBtn(on) {
const b = document.getElementById('btn-peruse');
- if (!b) return;
- b.classList.toggle('active', on);
- b.innerHTML = on ? '■ Stop Perusing' : '▷ Peruse Collection';
+ if (b) {
+ b.classList.toggle('active', on);
+ b.innerHTML = on ? '■ Stop Perusing' : '▷ Peruse Collection';
+ }
+ syncNowViewingPlay(); // keep the navigator strip's play glyph in lock-step
}
// ============================================================
@@ -1661,6 +1723,14 @@ function initHUD() {
document.getElementById('close-detail').addEventListener('click', () => unfocusWing());
document.getElementById('btn-fan-left').addEventListener('click', () => gotoBoardOffset(-1)); // Back
document.getElementById('btn-fan-right').addEventListener('click', () => gotoBoardOffset(1)); // Next
+
+ // NOW VIEWING navigator transport — reuses the same board-flip + Peruse engine.
+ const nvBack = document.getElementById('nv-back');
+ const nvNext = document.getElementById('nv-next');
+ const nvPlay = document.getElementById('nv-play');
+ if (nvBack) nvBack.addEventListener('click', () => { stopPeruse(true); gotoBoardOffset(-1); });
+ if (nvNext) nvNext.addEventListener('click', () => { stopPeruse(true); gotoBoardOffset(1); });
+ if (nvPlay) nvPlay.addEventListener('click', () => togglePeruse());
document.getElementById('btn-back-wing').addEventListener('click', () => unfocusWing());
document.getElementById('btn-music').addEventListener('click', () => document.getElementById('music-player').classList.toggle('hidden'));
diff --git a/public/showroom.html b/public/showroom.html
index 2c7599d..c1a5836 100644
--- a/public/showroom.html
+++ b/public/showroom.html
@@ -140,6 +140,27 @@
</span>
</div>
+ <!-- NOW VIEWING navigator — museum media-player strip (shown only on focus).
+ Brass-hairline + museum glass, conforms to ART-DIRECTION.md. Augments the
+ detail card's browse row: swatch · serif name · colorway · spec · transport. -->
+ <div id="now-viewing" class="hidden" aria-label="Now viewing navigator">
+ <div class="nv-progress"><div class="nv-progress-fill" id="nv-fill"></div></div>
+ <div class="nv-body">
+ <div class="nv-swatch"><img id="nv-thumb" alt=""></div>
+ <div class="nv-meta">
+ <div class="nv-eyebrow">Now Viewing</div>
+ <div class="nv-name" id="nv-name">Pattern</div>
+ <div class="nv-sub"><span id="nv-color" class="nv-color">Colorway</span><span class="nv-dot">·</span><span id="nv-spec" class="nv-spec">27″ × 27″</span></div>
+ </div>
+ <div class="nv-transport">
+ <button id="nv-back" class="nv-btn" title="Back (previous wingboard)" aria-label="Back">◀</button>
+ <button id="nv-play" class="nv-btn nv-play" title="Peruse (play / pause the tour)" aria-label="Peruse play-pause">▷</button>
+ <button id="nv-next" class="nv-btn" title="Next wingboard" aria-label="Next">▶</button>
+ </div>
+ <div class="nv-count"><span id="nv-index">1</span><span class="nv-of">of</span><span id="nv-total">50</span></div>
+ </div>
+ </div>
+
<div id="bottom-bar">
<span id="info-text">WASD to walk up to a wing · within 4 ft it opens to the full continuous design · Click to inspect · P to Peruse</span>
<span id="fps-counter">60 FPS</span>
← 1e55735 PJ hero framing: narrow FOV (55->30) on focus + back off pro
·
back to Quadrille Showroom
·
WALL realism: derive subtle normal+roughness maps from wall- bb93cda →