← back to Prestige Car Wash
slideshow: /slideshow deck + Present button (deck falls back /portal→/ to parse the marketing deliverable)
c94229a0ad654dfed80d315a801b5f7e54963535 · 2026-07-26 08:47:24 -0700 · Steve Abrams
Files touched
M public/index.htmlA public/slideshow.html
Diff
commit c94229a0ad654dfed80d315a801b5f7e54963535
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jul 26 08:47:24 2026 -0700
slideshow: /slideshow deck + Present button (deck falls back /portal→/ to parse the marketing deliverable)
---
public/index.html | 1 +
public/slideshow.html | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 128 insertions(+)
diff --git a/public/index.html b/public/index.html
index fbdf420..8532ed6 100644
--- a/public/index.html
+++ b/public/index.html
@@ -144,5 +144,6 @@ fetch('/api/places').then(r=>r.json()).then(p=>{
`<div class="card"><div class="media" style="aspect-ratio:16/9"><video src="${v.src}" ${v.poster?`poster="${v.poster}"`:''} muted loop autoplay playsinline style="width:100%;height:100%;object-fit:cover"></video></div>${v.label?`<div class="body"><div class="blurb" style="margin:0">${v.label}</div></div>`:''}</div>`).join('');
});
</script>
+<a href="/slideshow" style="position:fixed;bottom:20px;right:20px;z-index:60;background:#c6a765;color:#12100e;font-family:system-ui,-apple-system,sans-serif;font-size:11px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;padding:11px 18px;border-radius:4px;text-decoration:none;box-shadow:0 8px 24px rgba(0,0,0,.45)">▶ Present</a>
</body>
</html>
diff --git a/public/slideshow.html b/public/slideshow.html
new file mode 100644
index 0000000..8b22147
--- /dev/null
+++ b/public/slideshow.html
@@ -0,0 +1,127 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Slideshow · Consulting Deliverable</title>
+<!-- Canonical consulting Slideshow. Source of truth: ~/.claude/skills/consulting/assets/slideshow.html
+ Copied verbatim into each portal's public/slideshow.html; served (gated) at /slideshow.
+ Self-contained, zero deps: fetches the portal's own /portal deliverable and turns each
+ <section id> into a full-screen slide (kicker + headline + subhead). Keyboard/click/dot nav. -->
+<style>
+ :root{--bg:#0c1016;--panel:#12100e;--gold:#c6a765;--ink:#eef1f6;--dim:rgba(238,241,246,.66);--line:rgba(198,167,101,.28)}
+ *{margin:0;padding:0;box-sizing:border-box}
+ html,body{height:100%}
+ body{background:var(--bg);color:var(--ink);font-family:'Jost',system-ui,-apple-system,sans-serif;overflow:hidden}
+ .disp{font-family:'Cormorant Garamond',Georgia,serif}
+ #deck{position:fixed;inset:0}
+ .slide{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;padding:0 clamp(28px,9vw,140px);opacity:0;transform:translateY(24px);transition:opacity .5s ease,transform .5s ease;pointer-events:none}
+ .slide.on{opacity:1;transform:none;pointer-events:auto}
+ .kicker{font-size:clamp(11px,1.4vw,14px);letter-spacing:.36em;text-transform:uppercase;color:var(--gold);margin-bottom:22px}
+ .head{font-family:'Cormorant Garamond',Georgia,serif;font-weight:400;font-size:clamp(34px,6vw,86px);line-height:1.04;max-width:20ch}
+ .head span{color:var(--gold)}
+ .sub{margin-top:26px;max-width:60ch;font-size:clamp(15px,1.9vw,23px);line-height:1.5;color:var(--dim)}
+ .cover .head{font-size:clamp(48px,9vw,120px)}
+ .cover .meta{margin-top:34px;font-size:13px;letter-spacing:.22em;text-transform:uppercase;color:var(--dim)}
+ /* chrome */
+ .bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:rgba(255,255,255,.06);z-index:20}
+ .bar i{display:block;height:100%;background:var(--gold);width:0;transition:width .4s ease}
+ .count{position:fixed;bottom:16px;right:22px;font-size:12px;letter-spacing:.14em;color:var(--dim);z-index:20;font-variant-numeric:tabular-nums}
+ .dots{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);display:flex;gap:7px;z-index:20;max-width:70vw;flex-wrap:wrap;justify-content:center}
+ .dots b{width:7px;height:7px;border-radius:50%;background:rgba(255,255,255,.22);cursor:pointer;transition:.25s}
+ .dots b.on{background:var(--gold);transform:scale(1.35)}
+ .hint{position:fixed;top:16px;left:22px;font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--dim);z-index:20}
+ .exit{position:fixed;top:14px;right:22px;z-index:20;color:var(--gold);text-decoration:none;font-size:12px;letter-spacing:.14em;text-transform:uppercase;border:1px solid var(--line);padding:7px 13px;border-radius:3px}
+ .exit:hover{background:var(--gold);color:var(--bg)}
+ .nav{position:fixed;top:0;bottom:0;width:22%;z-index:15;cursor:pointer}
+ .nav.l{left:0}.nav.r{right:0}
+ .err{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;text-align:center;color:var(--dim);padding:40px}
+ @media(prefers-reduced-motion:reduce){.slide{transition:none}}
+</style>
+<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;1,400&family=Jost:wght@300;400;500&display=swap" rel="stylesheet">
+</head>
+<body>
+<div class="hint">← → or space · Esc to exit</div>
+<a class="exit" href="/portal">✕ Deliverable</a>
+<div id="deck"><div class="err" id="err">Building the deck…</div></div>
+<div class="nav l" id="navL"></div><div class="nav r" id="navR"></div>
+<div class="dots" id="dots"></div>
+<div class="count" id="count"></div>
+<div class="bar"><i id="prog"></i></div>
+<script>
+(function(){
+ var deck=document.getElementById('deck'), slides=[], idx=0;
+ function esc(s){var d=document.createElement('div');d.textContent=s==null?'':String(s);return d.innerHTML;}
+ function txt(el){return el?el.textContent.replace(/\s+/g,' ').trim():'';}
+
+ // The deliverable lives at /portal on template-based portals, or at / on the
+ // originals (car-wash, fantasea). Try /portal, fall back to /.
+ var deliverableUrl='/portal';
+ function load(url){return fetch(url).then(function(r){if(!r.ok)return Promise.reject(r.status);deliverableUrl=url;return r.text();});}
+ load('/portal').catch(function(){return load('/');}).then(function(html){
+ var exit=document.querySelector('.exit'); if(exit) exit.href=deliverableUrl;
+ var doc=new DOMParser().parseFromString(html,'text/html');
+ var out=[];
+ // Cover slide from the deliverable's hero (h1 + first hero paragraph) or title.
+ var h1=doc.querySelector('.hero h1, h1');
+ var kick=doc.querySelector('.hero .kick, .kick, .eyebrow');
+ out.push({cover:true, kicker:txt(kick)||'Consulting Deliverable', head:(h1?h1.innerHTML:document.title), sub:txt(doc.querySelector('.hero p'))});
+ // One slide per section.
+ var secs=doc.querySelectorAll('section[id]');
+ if(!secs.length) secs=doc.querySelectorAll('section');
+ secs.forEach(function(s){
+ var k=s.querySelector(':scope > .eyebrow') || s.querySelector('.eyebrow');
+ var h=s.querySelector(':scope > h2, :scope h2, h2, h3');
+ var p=s.querySelector('.lead2, .lead, p');
+ var head=txt(h); if(!head) return;
+ out.push({kicker:txt(k), head:head, sub:txt(p)});
+ });
+ // Closing CTA slide.
+ out.push({cover:true, kicker:'The next step', head:'Pick a direction —<br>and we build it.', sub:'Every section above is one click from execution. Tell us where to start.'});
+ render(out);
+ }).catch(function(e){
+ document.getElementById('err').innerHTML='Could not load the deliverable to build the deck (/portal → '+esc(e)+').<br>Open the <a href="/portal" style="color:#c6a765">full deliverable</a> instead.';
+ });
+
+ function render(items){
+ deck.innerHTML='';
+ items.forEach(function(it,i){
+ var el=document.createElement('div');
+ el.className='slide'+(it.cover?' cover':'');
+ el.innerHTML=(it.kicker?'<div class="kicker">'+esc(it.kicker)+'</div>':'')
+ +'<h1 class="head disp">'+(it.cover?it.head:esc(it.head))+'</h1>'
+ +(it.sub?'<div class="sub">'+esc(it.sub)+'</div>':'')
+ +(it.cover&&i===0?'<div class="meta">Confidential strategy brief · press → to begin</div>':'');
+ deck.appendChild(el);
+ slides.push(el);
+ });
+ var dots=document.getElementById('dots');
+ slides.forEach(function(_,i){var b=document.createElement('b');b.onclick=function(){go(i);};dots.appendChild(b);});
+ go(0);
+ }
+ function go(n){
+ if(!slides.length) return;
+ idx=Math.max(0,Math.min(slides.length-1,n));
+ slides.forEach(function(s,i){s.classList.toggle('on',i===idx);});
+ var db=document.querySelectorAll('.dots b');
+ db.forEach(function(b,i){b.classList.toggle('on',i===idx);});
+ document.getElementById('count').textContent=(idx+1)+' / '+slides.length;
+ document.getElementById('prog').style.width=((idx)/(slides.length-1||1)*100)+'%';
+ }
+ function next(){go(idx+1);} function prev(){go(idx-1);}
+ document.getElementById('navR').onclick=next;
+ document.getElementById('navL').onclick=prev;
+ document.addEventListener('keydown',function(e){
+ if(e.key==='ArrowRight'||e.key===' '||e.key==='PageDown'){e.preventDefault();next();}
+ else if(e.key==='ArrowLeft'||e.key==='PageUp'){e.preventDefault();prev();}
+ else if(e.key==='Home'){go(0);} else if(e.key==='End'){go(slides.length-1);}
+ else if(e.key==='Escape'){location.href=deliverableUrl;}
+ });
+ // touch swipe
+ var x0=null;
+ document.addEventListener('touchstart',function(e){x0=e.touches[0].clientX;},{passive:true});
+ document.addEventListener('touchend',function(e){if(x0==null)return;var dx=e.changedTouches[0].clientX-x0;if(Math.abs(dx)>50){dx<0?next():prev();}x0=null;},{passive:true});
+})();
+</script>
+</body>
+</html>
← 2f4ad36 Membership: 'launching soon' call-out + interest capture via
·
back to Prestige Car Wash
·
slideshow: sync sanitized deck (XSS-safe cover); render veri 57d8ba0 →