← back to Quadrille Showroom
V10 Concierge Nook: boards now genuinely LIE on the table — share the table's exact perspective + 58deg rake + vanishing point (coplanar with the wood), counter-rotate ONLY the placard so labels stay readable, in-plane foreshortened cast shadows seat each board on the felt; placard color dot grounded on enrichment hue
27c72af4fce25ec0ecd1efbe71974725520f2f7b · 2026-06-28 09:35:31 -0700 · Steve
Files touched
A public/proto/shots/v10-concierge.pngM public/proto/v10-concierge.html
Diff
commit 27c72af4fce25ec0ecd1efbe71974725520f2f7b
Author: Steve <steve@designerwallcoverings.com>
Date: Sun Jun 28 09:35:31 2026 -0700
V10 Concierge Nook: boards now genuinely LIE on the table — share the table's exact perspective + 58deg rake + vanishing point (coplanar with the wood), counter-rotate ONLY the placard so labels stay readable, in-plane foreshortened cast shadows seat each board on the felt; placard color dot grounded on enrichment hue
---
public/proto/shots/v10-concierge.png | Bin 0 -> 295960 bytes
public/proto/v10-concierge.html | 81 +++++++++++++++++++++++++++++------
2 files changed, 68 insertions(+), 13 deletions(-)
diff --git a/public/proto/shots/v10-concierge.png b/public/proto/shots/v10-concierge.png
new file mode 100644
index 0000000..4aa48cb
Binary files /dev/null and b/public/proto/shots/v10-concierge.png differ
diff --git a/public/proto/v10-concierge.html b/public/proto/v10-concierge.html
index db1089b..c4d63ad 100644
--- a/public/proto/v10-concierge.html
+++ b/public/proto/v10-concierge.html
@@ -148,35 +148,73 @@
border-radius:50%; width:46px; height:46px; font-size:18px; flex:0 0 auto; transition:.18s; }
.ask button:hover{ background:var(--brass); }
- /* ===== Boards gliding onto the table ===== */
+ /* ===== Boards LYING ON the table (CONTRARIAN FIX) =====
+ The table surface is rotateX(58deg). The boards now share that same 3D
+ perspective + vanishing point — they're raked back ONTO the felt (rotateX
+ ~46deg, a touch flatter than the table so the placard stays readable), laid
+ out in receding rows that get smaller/dimmer toward the back, each casting a
+ contact shadow on the table. They are physical sample boards on a table, not
+ flat cards floating above a table photo. */
+ /* The stage shares the ROOM's exact perspective context so the boards converge
+ to the SAME vanishing point as the table (perspective 1400px, origin 50% 38%). */
.stage{ position:fixed; left:380px; right:0; top:0; bottom:0; z-index:10;
- display:flex; align-items:center; justify-content:center; pointer-events:none; }
+ display:flex; align-items:flex-end; justify-content:center; pointer-events:none;
+ perspective:1400px; perspective-origin:50% 38%; }
.boards{
pointer-events:auto;
- display:flex; gap:30px; flex-wrap:wrap; justify-content:center; align-content:center;
- max-width:1100px; max-height:88vh; overflow-y:auto; padding:40px 30px 20px;
- perspective:1200px;
+ display:flex; gap:30px 28px; flex-wrap:wrap; justify-content:center; align-content:flex-end;
+ /* clamped to roughly the felt-runner width so the boards sit ON the mat */
+ width:min(760px,82%); max-height:150vh; overflow-y:auto;
+ padding:9vh 30px 18vh;
+ transform-style:preserve-3d;
+ /* lay the tableau onto the table's ACTUAL plane: same 58deg rake, anchored to
+ the near table edge → genuinely coplanar with the wood, one shared VP. */
+ transform: rotateX(58deg);
+ transform-origin:50% 100%;
+ /* far rows fade into the table's shadow toward the vanishing point */
+ -webkit-mask-image:linear-gradient(180deg, transparent 0%, rgba(0,0,0,.5) 8%, #000 22%, #000 100%);
+ mask-image:linear-gradient(180deg, transparent 0%, rgba(0,0,0,.5) 8%, #000 22%, #000 100%);
}
.boards::-webkit-scrollbar{width:8px} .boards::-webkit-scrollbar-thumb{background:rgba(201,169,110,.4);border-radius:4px}
.board{
- width:210px; flex:0 0 auto;
+ width:200px; flex:0 0 auto;
+ transform-style:preserve-3d;
transform-origin:50% 100%;
- animation:glide .7s cubic-bezier(.18,.8,.28,1) backwards;
+ animation:glide .75s cubic-bezier(.18,.8,.28,1) backwards;
cursor:pointer;
}
+ /* boards slide IN from the near edge (toward the viewer) and settle onto the felt */
@keyframes glide{
- 0%{ opacity:0; transform:translateY(70px) rotateX(28deg) scale(.86); }
- 100%{ opacity:1; transform:translateY(0) rotateX(0) scale(1); }
+ 0%{ opacity:0; transform:translateZ(130px) scale(1.05); }
+ 100%{ opacity:1; transform:translateZ(0) scale(1); }
}
- /* brass-framed sample board with gallery placard */
+ /* brass frame lies FLAT on the felt (coplanar with the table). Its cast shadow is
+ a separate in-plane element (.board::after) — NOT a screen-space drop shadow. */
.board .frame{
position:relative; border-radius:4px;
padding:9px; background:linear-gradient(155deg,#d8c79c,var(--brass) 55%,#7e6526);
- box-shadow:0 22px 44px rgba(0,0,0,.5), 0 2px 0 rgba(255,255,255,.3) inset;
+ box-shadow:0 1px 0 rgba(255,255,255,.3) inset, 0 0 0 1px rgba(0,0,0,.25);
transition:transform .35s cubic-bezier(.2,.7,.3,1), box-shadow .35s;
}
- .board:hover .frame{ transform:translateY(-10px) rotateX(-4deg); box-shadow:0 34px 60px rgba(0,0,0,.6); }
+ /* IN-PLANE cast shadow: a dark, blurred quad lying on the felt, offset toward the
+ vanishing point (up-screen = "back" on the raked plane) and foreshortened. */
+ .board .frame::before{
+ content:""; position:absolute; inset:0; z-index:-1; border-radius:6px;
+ background:rgba(0,0,0,.5); filter:blur(14px);
+ transform:translateY(-10%) scaleY(.82); /* push back + foreshorten in-plane */
+ }
+ /* CONTRARIAN FIX — counter-rotate ONLY the placard back toward the viewer so the
+ label tips up and stays readable while the swatch lies flat on the table, just
+ like a real tented sample-board placard. */
+ .board .placard{
+ transform: rotateX(-46deg);
+ transform-origin:50% 0%;
+ box-shadow:0 10px 22px -6px rgba(0,0,0,.55);
+ }
+ /* hover = lift the whole board OFF the table toward the viewer (pick it up) */
+ .board:hover{ transform:translateZ(60px) rotateX(-30deg); z-index:5; }
+ .board:hover .frame::before{ background:rgba(0,0,0,.6); filter:blur(20px); transform:translateY(6%) scaleY(.9); }
.board .swatch{
width:100%; aspect-ratio:1/1; border-radius:2px; object-fit:cover;
background:var(--stone); display:block; box-shadow:0 0 0 1px rgba(0,0,0,.25) inset;
@@ -307,6 +345,8 @@
absolute backend URL is kept only for opening the file standalone.) */
const API = '/api/showroom/products?limit=200';
const API_FALLBACK = 'http://127.0.0.1:7690/api/showroom/products?limit=200';
+const ENRICH_URL = './enrichment.json'; // REAL hue / color_bucket / family per sku
+let ENRICH = {};
let PRODUCTS = [];
/* ---- Color lexicon: word -> {bucket, hex (for the chip swatch)} ---- */
@@ -572,8 +612,20 @@ function renderTray(){
});
}
-/* pick a representative swatch hex for a product from its color words */
+/* hsl(h 0-360,s,l) -> hex */
+function hslHex(h,s,l){ h=((h%360)+360)%360/360;
+ const f=n=>{const k=(n+h*12)%12,a=s*Math.min(l,1-l);
+ return l-a*Math.max(-1,Math.min(k-3,9-k,1));};
+ const to=v=>('0'+Math.round(Math.max(0,Math.min(1,v))*255).toString(16)).slice(-2);
+ return '#'+to(f(0))+to(f(8))+to(f(4)); }
+/* CONTRARIAN/enrichment — the placard dot is now grounded on the REAL enrichment
+ hue (pixel-derived upstream), not a guess from a colour word in the name. */
function swatchHex(p){
+ const e=ENRICH[p.sku];
+ if(e && typeof e.hue==='number'){
+ const muted=e.color_bucket==null||e.color_bucket==='neutral'||e.color_bucket==='grey';
+ return hslHex(e.hue, muted?0.12:0.5, 0.5);
+ }
const n=(p.pattern_name||'').toLowerCase();
for(const [w,[,hex]] of Object.entries(COLOR_WORDS)){ if(n.includes(w)) return hex; }
return '#cfc7b8';
@@ -631,6 +683,9 @@ async function boot(){
buildChips();
addMsg('assist', `Welcome — make yourself comfortable. I'm your design concierge for the
<em>China Seas</em> atelier. Tell me a colour, a mood, or a pattern you love, and I'll pull the boards.`);
+ // load REAL enrichment first so the placard dots are grounded, not name-guessed
+ try{ const er=await fetch(ENRICH_URL); const ej=await er.json();
+ ENRICH=(ej&&ej.enrich)?ej.enrich:(ej||{}); }catch(_){ ENRICH={}; }
try{
let r;
try{ r=await fetch(API); if(!r.ok) throw new Error(r.status); }
← cbee4bb V9 Walk-In Room: photo-real depth — soft contact shadows gro
·
back to Quadrille Showroom
·
Proto V6-V10: refresh final verification screenshots (0 cons 765e004 →