← back to Prestige Car Wash

public/slideshow.html

144 lines

<!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():'';}
  // Cover heads keep the deliverable's <br>/<span> styling but are whitelist-sanitized:
  // never render raw innerHTML from a parsed deliverable (XSS-safe). Only text, <br>, <span>.
  function sanitizeHead(el){
    var out='', ns=el.childNodes, i;
    for(i=0;i<ns.length;i++){
      var n=ns[i];
      if(n.nodeType===3){ out+=esc(n.nodeValue); }
      else if(n.nodeType===1){
        var t=n.tagName.toLowerCase();
        if(t==='br'){ out+='<br>'; }
        else if(t==='span'){ out+='<span>'+esc(n.textContent)+'</span>'; }
        else { out+=esc(n.textContent); }
      }
    }
    return out;
  }

  // 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?sanitizeHead(h1):esc(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>