← back to Fantasea Consulting
slideshow: sync sanitized deck; render verified 13 slides
740eb8276a9b35abc94bca33f9a383b79b49e6af · 2026-07-26 08:52:49 -0700 · Steve Abrams
Files touched
Diff
commit 740eb8276a9b35abc94bca33f9a383b79b49e6af
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jul 26 08:52:49 2026 -0700
slideshow: sync sanitized deck; render verified 13 slides
---
public/slideshow.html | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/public/slideshow.html b/public/slideshow.html
index 8b22147..0be5f04 100644
--- a/public/slideshow.html
+++ b/public/slideshow.html
@@ -53,6 +53,22 @@
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 /.
@@ -65,7 +81,7 @@
// 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'))});
+ 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');
← 9b4c3ad slideshow: /slideshow deck + Present button (parses the deli
·
back to Fantasea Consulting
·
chore: v1.2.0 (session close — LHF/SWOT/Slideshow) ec8946e →