@jokerized/decksmith 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ var o="decksmith-deck";function d(n){return typeof n=="object"&&n!==null&&n.channel===o&&typeof n.type=="string"}var p=5e3,a=class extends HTMLElement{static observedAttributes=["deck"];#t=null;#n=null;#e=0;#r=[];#s=0;#o=!1;get stops(){return this.#r}get at(){return this.#s}get connected(){return this.#o}connectedCallback(){this.#t||this.#a()}disconnectedCallback(){this.#l()}attributeChangedCallback(t,i,e){t==="deck"&&i!==e&&this.isConnected&&(this.#l(),this.#a())}next(){this.#i({channel:o,type:"next"})}prev(){this.#i({channel:o,type:"prev"})}go(t){this.#i({channel:o,type:"go",at:t})}play(t=!0){this.#i({channel:o,type:"play",on:t})}#i(t){this.#t?.contentWindow?.postMessage(t,"*")}#a(){let t=this.getAttribute("deck");if(!t)return;let i;try{i=new URL("deck.html",new URL(t,location.href)).href}catch{this.dispatchEvent(new CustomEvent("ds-error",{detail:{reason:"bad-deck-url",message:`"${t}" is not a URL a deck can be loaded from.`}}));return}let e=document.createElement("iframe");e.src=i,e.setAttribute("allow","fullscreen; autoplay"),e.setAttribute("allowfullscreen",""),e.title=this.getAttribute("label")??"Deck",e.style.cssText="display:block;width:100%;height:100%;border:0",this.#t=e;let l=r=>{if(r.source!==e.contentWindow||!d(r.data))return;let s=r.data;s.type==="ready"?(this.#e&&clearTimeout(this.#e),this.#e=0,this.#o=!0,this.#r=s.stops,this.#s=s.at,this.dispatchEvent(new CustomEvent("ds-ready",{detail:{stops:s.stops,at:s.at}}))):s.type==="stop"&&(this.#s=s.at,this.dispatchEvent(new CustomEvent("ds-stop",{detail:s})))};this.#n=l,addEventListener("message",l),e.addEventListener("load",()=>{e.contentWindow?.postMessage({channel:o,type:"hello"},"*"),this.#e=setTimeout(()=>{this.#o||this.dispatchEvent(new CustomEvent("ds-error",{detail:{reason:"no-bridge",message:"The deck did not answer. It was probably built before this element existed \u2014 it still plays, and its own keyboard still works."}}))},p)}),this.style.display||(this.style.display="block"),this.appendChild(e)}#l(){this.#n&&removeEventListener("message",this.#n),this.#e&&clearTimeout(this.#e),this.#n=null,this.#e=0,this.#t?.remove(),this.#t=null,this.#o=!1,this.#r=[],this.#s=0}};function c(n="decksmith-player"){typeof customElements>"u"||customElements.get(n)||customElements.define(n,a)}c();
@@ -0,0 +1 @@
1
+ var o="decksmith-deck";function d(n){return typeof n=="object"&&n!==null&&n.channel===o&&typeof n.type=="string"}var c=5e3,a=class extends HTMLElement{static observedAttributes=["deck"];#t=null;#n=null;#e=0;#r=[];#s=0;#o=!1;get stops(){return this.#r}get at(){return this.#s}get connected(){return this.#o}connectedCallback(){this.#t||this.#a()}disconnectedCallback(){this.#l()}attributeChangedCallback(t,i,e){t==="deck"&&i!==e&&this.isConnected&&(this.#l(),this.#a())}next(){this.#i({channel:o,type:"next"})}prev(){this.#i({channel:o,type:"prev"})}go(t){this.#i({channel:o,type:"go",at:t})}play(t=!0){this.#i({channel:o,type:"play",on:t})}#i(t){this.#t?.contentWindow?.postMessage(t,"*")}#a(){let t=this.getAttribute("deck");if(!t)return;let i;try{i=new URL("deck.html",new URL(t,location.href)).href}catch{this.dispatchEvent(new CustomEvent("ds-error",{detail:{reason:"bad-deck-url",message:`"${t}" is not a URL a deck can be loaded from.`}}));return}let e=document.createElement("iframe");e.src=i,e.setAttribute("allow","fullscreen; autoplay"),e.setAttribute("allowfullscreen",""),e.title=this.getAttribute("label")??"Deck",e.style.cssText="display:block;width:100%;height:100%;border:0",this.#t=e;let l=r=>{if(r.source!==e.contentWindow||!d(r.data))return;let s=r.data;s.type==="ready"?(this.#e&&clearTimeout(this.#e),this.#e=0,this.#o=!0,this.#r=s.stops,this.#s=s.at,this.dispatchEvent(new CustomEvent("ds-ready",{detail:{stops:s.stops,at:s.at}}))):s.type==="stop"&&(this.#s=s.at,this.dispatchEvent(new CustomEvent("ds-stop",{detail:s})))};this.#n=l,addEventListener("message",l),e.addEventListener("load",()=>{e.contentWindow?.postMessage({channel:o,type:"hello"},"*"),this.#e=setTimeout(()=>{this.#o||this.dispatchEvent(new CustomEvent("ds-error",{detail:{reason:"no-bridge",message:"The deck did not answer. It was probably built before this element existed \u2014 it still plays, and its own keyboard still works."}}))},c)}),this.style.display||(this.style.display="block"),this.appendChild(e)}#l(){this.#n&&removeEventListener("message",this.#n),this.#e&&clearTimeout(this.#e),this.#n=null,this.#e=0,this.#t?.remove(),this.#t=null,this.#o=!1,this.#r=[],this.#s=0}};function u(n="decksmith-player"){typeof customElements>"u"||customElements.get(n)||customElements.define(n,a)}export{a as DecksmithPlayer,u as define};
@@ -1,4 +1,4 @@
1
- "use strict";(()=>{var P='script[type="application/decksmith-narration+json"]';function I(e){return typeof e=="number"&&Number.isFinite(e)?e:void 0}function X(e){let t=e,r=I(t?.start),n=I(t?.end);return r===void 0||n===void 0||n<r?null:{start:r,end:n,text:typeof t?.text=="string"?t.text:""}}var z=84;function L(e,t){let r=[],n="";for(let s of e)n===""?n=s:n.length+1+s.length<=t?n+=` ${s}`:(r.push(n),n=s);return n!==""&&r.push(n),r}function B(e,t=z){if(e.text.length<=t)return[e];let r=e.text.split(/\s+/).filter(Boolean),n=L(r,t).length,s=t;for(let c=Math.ceil(e.text.length/n);c<t;c++)if(L(r,c).length<=n){s=c;break}let l=L(r,s);if(l.length<2)return[e];let d=l.reduce((c,a)=>c+a.length,0),o=e.end-e.start,f=e.start;return l.map((c,a)=>{let p=a===l.length-1?e.end:f+o*c.length/d,h={start:f,end:p,text:c};return f=p,h})}function V(e){let t=e,r=I(t?.stop);if(r===void 0||r<0||!t?.audio||typeof t.audio!="string")return null;let n=Array.isArray(t.cues)?t.cues.map(X).filter(s=>s!==null):[];return n.sort((s,l)=>s.start-l.start),{stop:r,audio:t.audio,cues:n.flatMap(s=>B(s))}}function F(e){if(!e)return null;let t;try{t=JSON.parse(e)}catch{return null}let r=t,n=r?.scenes;if(!n||typeof n!="object"||Array.isArray(n))return null;let s={};for(let[l,d]of Object.entries(n)){if(!Array.isArray(d))continue;let o=d.map(V).filter(f=>f!==null);o.length>0&&(s[l]=o)}return Object.keys(s).length===0?null:{voice:typeof r?.voice=="string"?r.voice:"",dir:typeof r?.dir=="string"?r.dir:"",scenes:s}}function R(e,t){for(let r of e){if(t<r.start)return null;if(t<r.end)return r}return null}function H(e,t,r){return e?.scenes[t]?.find(n=>n.stop===r)??null}function q(e,t){let r=e.dir.replace(/\/+$/,"");return r===""||/^[a-z][a-z0-9+.-]*:|^\.{0,2}\//i.test(t.audio)?t.audio:`${r}/${t.audio}`}function D(...e){for(let t of e)if(typeof t=="number"&&Number.isFinite(t))return t}function W(e){let t=[];for(let r of e){let n=D(r.startTime);if(n===void 0)continue;let s=D(r.endTime)??Number.POSITIVE_INFINITY,l=t.length===0?0:t[t.length-1].slide+1,d=r.notes??"",o=[...new Set(r.fragments??[])].filter(p=>Number.isFinite(p)&&p>n&&p<=s).sort((p,h)=>p-h),[f,...c]=o,a=r.sceneId;t.push({t:f??n,slide:l,fragment:0,notes:d,sceneId:a});for(let[p,h]of c.entries())t.push({t:h,slide:l,fragment:p+1,notes:d,sceneId:a})}return t}var U=2.5;function J(e,t,r={}){let n=t-e;return r.reducedMotion===!0||n<=0||n>U?{animate:!1,durationMs:0}:{animate:!0,durationMs:n*1e3}}function Y(e){return e.fragment>0?`#${e.slide+1}.${e.fragment}`:`#${e.slide+1}`}function O(e){let t=/^#?(\d+)(?:\.(\d+))?$/.exec(e.trim());if(!t)return null;let r=Number(t[1]);return r<1?null:{slide:r-1,fragment:t[2]===void 0?0:Number(t[2])}}function $(e,t){let r=e.findIndex(n=>n.slide===t.slide&&n.fragment===t.fragment);return r>=0?r:e.findIndex(n=>n.slide===t.slide)}var G='script[type="application/hyperframes-slideshow+json"]';function K(e){let t=e.shadowRoot?.querySelector("iframe")??e.querySelector("iframe")??null;try{let r=t?.contentDocument,n=t?.contentWindow;return r&&n?{doc:r,timelines:n.__timelines??{}}:null}catch{return null}}function _(e,t,r){for(let n of t){let s=n.startTime??0,l=n.endTime??Number.POSITIVE_INFINITY,d=r>=s&&r<l,o=e.doc.getElementById(n.sceneId);o?.style&&(o.style.display=d?"":"none"),d&&e.timelines[n.sceneId]?.seek(Math.max(0,r-s))}}function Q(e){let t=e.querySelector(G);if(!t?.textContent)return[];try{let n=JSON.parse(t.textContent)?.slides;return Array.isArray(n)?n.filter(s=>typeof s?.sceneId=="string"):[]}catch{return[]}}function Z(e){return e.ready===!0?Promise.resolve():new Promise(t=>{let r=()=>{clearTimeout(n),t()},n=setTimeout(()=>{e.removeEventListener("ready",r),t()},5e3);e.addEventListener("ready",r,{once:!0})})}var ee=`
1
+ "use strict";(()=>{var H="decksmith-deck";function F(e){return typeof e=="object"&&e!==null&&e.channel===H&&typeof e.type=="string"}var q='script[type="application/decksmith-narration+json"]';function R(e){return typeof e=="number"&&Number.isFinite(e)?e:void 0}function Y(e){let r=e,n=R(r?.start),t=R(r?.end);return n===void 0||t===void 0||t<n?null:{start:n,end:t,text:typeof r?.text=="string"?r.text:""}}var J=84;function P(e,r){let n=[],t="";for(let o of e)t===""?t=o:t.length+1+o.length<=r?t+=` ${o}`:(n.push(t),t=o);return t!==""&&n.push(t),n}function U(e,r=J){if(e.text.length<=r)return[e];let n=e.text.split(/\s+/).filter(Boolean),t=P(n,r).length,o=r;for(let s=Math.ceil(e.text.length/t);s<r;s++)if(P(n,s).length<=t){o=s;break}let l=P(n,o);if(l.length<2)return[e];let c=l.reduce((s,a)=>s+a.length,0),u=e.end-e.start,h=e.start;return l.map((s,a)=>{let p=a===l.length-1?e.end:h+u*s.length/c,m={start:h,end:p,text:s};return h=p,m})}function K(e){let r=e,n=R(r?.stop);if(n===void 0||n<0||!r?.audio||typeof r.audio!="string")return null;let t=Array.isArray(r.cues)?r.cues.map(Y).filter(o=>o!==null):[];return t.sort((o,l)=>o.start-l.start),{stop:n,audio:r.audio,cues:t.flatMap(o=>U(o))}}function $(e){if(!e)return null;let r;try{r=JSON.parse(e)}catch{return null}let n=r,t=n?.scenes;if(!t||typeof t!="object"||Array.isArray(t))return null;let o={};for(let[l,c]of Object.entries(t)){if(!Array.isArray(c))continue;let u=c.map(K).filter(h=>h!==null);u.length>0&&(o[l]=u)}return Object.keys(o).length===0?null:{voice:typeof n?.voice=="string"?n.voice:"",dir:typeof n?.dir=="string"?n.dir:"",scenes:o}}function _(e,r){for(let n of e){if(r<n.start)return null;if(r<n.end)return n}return null}function V(e,r,n){return e?.scenes[r]?.find(t=>t.stop===n)??null}function j(e,r){let n=e.dir.replace(/\/+$/,"");return n===""||/^[a-z][a-z0-9+.-]*:|^\.{0,2}\//i.test(r.audio)?r.audio:`${n}/${r.audio}`}function z(...e){for(let r of e)if(typeof r=="number"&&Number.isFinite(r))return r}function Q(e){let r=[];for(let n of e){let t=z(n.startTime);if(t===void 0)continue;let o=z(n.endTime)??Number.POSITIVE_INFINITY,l=r.length===0?0:r[r.length-1].slide+1,c=n.notes??"",u=[...new Set(n.fragments??[])].filter(p=>Number.isFinite(p)&&p>t&&p<=o).sort((p,m)=>p-m),[h,...s]=u,a=n.sceneId;r.push({t:h??t,slide:l,fragment:0,notes:c,sceneId:a});for(let[p,m]of s.entries())r.push({t:m,slide:l,fragment:p+1,notes:c,sceneId:a})}return r}var Z=2.5;function ee(e,r,n={}){let t=r-e;return n.reducedMotion===!0||t<=0||t>Z?{animate:!1,durationMs:0}:{animate:!0,durationMs:t*1e3}}function te(e){return e.fragment>0?`#${e.slide+1}.${e.fragment}`:`#${e.slide+1}`}function B(e){let r=/^#?(\d+)(?:\.(\d+))?$/.exec(e.trim());if(!r)return null;let n=Number(r[1]);return n<1?null:{slide:n-1,fragment:r[2]===void 0?0:Number(r[2])}}function X(e,r){let n=e.findIndex(t=>t.slide===r.slide&&t.fragment===r.fragment);return n>=0?n:e.findIndex(t=>t.slide===r.slide)}var ne='script[type="application/hyperframes-slideshow+json"]';function re(e){let r=e.shadowRoot?.querySelector("iframe")??e.querySelector("iframe")??null;try{let n=r?.contentDocument,t=r?.contentWindow;return n&&t?{doc:n,get timelines(){return t.__timelines??{}}}:null}catch{return null}}function G(e,r,n){for(let t of r){let o=t.startTime??0,l=t.endTime??Number.POSITIVE_INFINITY,c=n>=o&&n<l,u=e.doc.getElementById(t.sceneId);u?.style&&(u.style.display=c?"":"none"),c&&e.timelines[t.sceneId]?.seek(Math.max(0,n-o))}}function oe(e){let r=e.querySelector(ne);if(!r?.textContent)return[];try{let t=JSON.parse(r.textContent)?.slides;return Array.isArray(t)?t.filter(o=>typeof o?.sceneId=="string"):[]}catch{return[]}}function se(e){return e.ready===!0?Promise.resolve():new Promise(r=>{let n=()=>{clearTimeout(t),r()},t=setTimeout(()=>{e.removeEventListener("ready",n),r()},5e3);e.addEventListener("ready",n,{once:!0})})}var ie=`
2
2
  .ds-chrome{position:fixed;inset:auto 0 0 0;z-index:2147483000;pointer-events:none;
3
3
  font:500 13px/1.5 ui-sans-serif,system-ui,sans-serif;color:#fff}
4
4
  .ds-bar{height:3px;background:rgba(255,255,255,.14)}
@@ -16,6 +16,31 @@
16
16
  border-left:9px solid currentColor;border-top:6px solid transparent;border-bottom:6px solid transparent}
17
17
  .ds-play[data-on="1"]::before{margin-left:0;width:8px;height:10px;border:0;
18
18
  background:linear-gradient(to right,currentColor 0 3px,transparent 3px 5px,currentColor 5px 8px)}
19
+ /* THE PLAYER-PAGE VIDEO, and everything it needs lives INSIDE .ds-chrome. That
20
+ buys two things without writing them twice: the click-to-advance handler
21
+ already skips anything the chrome contains, and pointer-events are already off
22
+ everywhere the overlay is not.
23
+
24
+ A LIGHTBOX RATHER THAN AN OVERLAY ALIGNED TO THE POSTER, deliberately. The
25
+ poster is an image inside the player's iframe, drawn at 1920x1080 and scaled
26
+ to the viewport by the player; putting a rectangle exactly over it means the
27
+ iframe's own rect times the player's scale, recomputed on resize, on
28
+ fullscreen (the "f" key), and again whenever .ds-cap shrinks the player to
29
+ make room for subtitles. That arithmetic is right at one window size and
30
+ silently wrong at every other, and no gate here opens deck.html to notice. The
31
+ lightbox is the same size wherever the poster is. */
32
+ .ds-video{position:absolute;right:106px;bottom:6px;height:26px;padding:0 12px;border:0;
33
+ border-radius:13px;background:rgba(255,255,255,.10);color:#fff;cursor:pointer;
34
+ pointer-events:auto;opacity:.55;font:inherit;line-height:26px;
35
+ transition:opacity .15s,background-color .15s}
36
+ .ds-video:hover{opacity:1;background:rgba(255,255,255,.2)}
37
+ .ds-video[hidden]{display:none}
38
+ .ds-film{position:fixed;inset:0;display:grid;place-items:center;
39
+ background:rgba(0,0,0,.88);pointer-events:auto}
40
+ .ds-film iframe{width:min(92vw,158vh);aspect-ratio:16/9;border:0;background:#000}
41
+ .ds-shut{position:absolute;top:16px;right:16px;width:34px;height:34px;padding:0;border:0;
42
+ border-radius:50%;background:rgba(255,255,255,.14);color:#fff;cursor:pointer;
43
+ font:inherit;line-height:34px}
19
44
  .ds-notes{position:absolute;left:0;right:0;bottom:0;max-height:38vh;overflow:auto;
20
45
  padding:20px 24px;background:rgba(10,10,10,.92);font-size:19px;line-height:1.6;
21
46
  white-space:pre-wrap;pointer-events:auto}
@@ -52,4 +77,4 @@
52
77
  text-align:center;color:#fff;font-weight:600;font-size:var(--ds-cap-font);
53
78
  line-height:1.35;text-wrap:pretty}
54
79
  .ds-subs[hidden]{visibility:hidden}
55
- `;function te(e){let t=e.createElement("style");t.textContent=ee,e.head.append(t);let r=e.createElement("div");return r.className="ds-chrome",r.innerHTML='<div class="ds-bar"><i></i></div><button class="ds-play" type="button" aria-label="Play the deck"></button><div class="ds-count"></div><div class="ds-flags"></div><div class="ds-subs" hidden></div><div class="ds-notes" hidden></div>',e.body.append(r),{chrome:r,fill:r.querySelector("i"),play:r.querySelector(".ds-play"),count:r.querySelector(".ds-count"),notes:r.querySelector(".ds-notes"),flags:r.querySelector(".ds-flags"),subs:r.querySelector(".ds-subs")}}var ne={at:()=>!1,toggleMute:()=>{},toggleSubtitles:()=>{},onEnded:()=>{},unlock:()=>{}};function re(e,t,r){let n=e.createElement("audio");n.preload="auto",e.body.append(n);let s=!1,l=!0,d=()=>e.documentElement.classList.toggle("ds-cap",l);d();let o=!1,f=!1,c=[],a=0,p=null,h=null,x=0,b=()=>{let u=[];o&&f?u.push("press any key for sound"):s&&u.push("muted"),l||u.push("subtitles off"),r.flags.textContent=u.join(" \xB7 ")},v=()=>{let u=l?R(c,n.currentTime):null;u!==p&&(p=u,r.subs.textContent=u?.text??"",r.subs.hidden=u===null)},S=()=>{v(),a=n.ended||n.paused&&n.currentTime>0?0:requestAnimationFrame(S)},M=()=>{a&&cancelAnimationFrame(a),a=0,n.pause(),n.removeAttribute("src"),n.load(),c=[],v()},k=()=>{};n.addEventListener("ended",()=>{x===x&&!o&&k()});let m=u=>{let T=++x;h=u,M();let i=H(t,u.sceneId,u.fragment);return i?(c=i.cues,n.muted=s,n.src=q(t,i),Promise.resolve(n.play()).then(()=>{T===x&&(o=!1,b())},()=>{T===x&&(o=!0,f=!0,a&&cancelAnimationFrame(a),a=0,c=[],v(),b())}),a===0&&(a=requestAnimationFrame(S)),!0):!1};return{at:m,onEnded:u=>{k=u},toggleMute:()=>{s=!s,n.muted=s,b()},toggleSubtitles:()=>{l=!l,d(),v(),b()},unlock:()=>{!o||!h||(o=!1,b(),m(h))}}}function se(e){let t=e;return!!t&&(t.isContentEditable||/^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName??""))}async function j(e){let t=Q(e),r=e.querySelector("hyperframes-player");if(t.length===0||!r)return;let n=W(t);if(n.length===0)return;let s=te(e),l=F(e.querySelector(P)?.textContent),d=l?re(e,l,s):ne,o=0,f=null,c=0,a=0,p=-1,h=i=>{r.seek(i),f&&_(f,t,i),c=i},x=(i,g,y)=>{let N=c,w=0,E=C=>{w===0&&(w=C);let A=Math.min(1,(C-w)/y);if(A<1){c=N+(g-N)*A,_(i,t,c),a=requestAnimationFrame(E);return}a=0,h(g)};a=requestAnimationFrame(E)},b=!1,v=0,S=()=>{v&&clearTimeout(v),v=0},M=()=>{b&&(o+1<n.length?m(o+1):k(!1))},k=i=>{b=i,S(),s.play.dataset.on=i?"1":"0",s.play.setAttribute("aria-label",i?"Pause the deck":"Play the deck"),i&&(p=-1,m(o,!0))},m=(i,g=!1)=>{o=Math.max(0,Math.min(n.length-1,i));let y=n[o];a&&cancelAnimationFrame(a),a=0;let N=typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches,w=J(c,y.t,{reducedMotion:N});w.animate&&!g&&f?x(f,y.t,w.durationMs):h(y.t);let E=!1;if(o!==p&&(p=o,E=d.at(y)),S(),b&&!E){let C=n[o+1],A=C?(C.t-y.t)*1e3:0;v=setTimeout(M,Math.min(8e3,Math.max(1500,A)))}history.replaceState(null,"",Y(y)),s.fill.style.transform=`scaleX(${(o+1)/n.length})`,s.count.textContent=`${y.slide+1} / ${n[n.length-1].slide+1}`,s.notes.textContent=y.notes};d.onEnded(M),s.play.addEventListener("click",i=>{i.stopPropagation(),k(!b)}),addEventListener("hashchange",()=>{let i=O(location.hash);if(!i)return;let g=$(n,i);g>=0&&g!==o&&m(g,!0)}),e.addEventListener("keydown",i=>{if(!(i.defaultPrevented||se(i.target))){switch(i.key){case"ArrowRight":case"PageDown":case" ":m(o+1);break;case"ArrowLeft":case"PageUp":m(o-1);break;case"Home":m(0,!0);break;case"End":m(n.length-1,!0);break;case"n":s.notes.hidden=!s.notes.hidden;break;case"m":d.toggleMute();break;case"s":d.toggleSubtitles();break;case"p":k(!b);break;case"f":{(e.fullscreenElement?e.exitFullscreen():e.documentElement.requestFullscreen()).catch(()=>{});break}default:return}i.preventDefault()}}),e.addEventListener("click",i=>{if(s.chrome.contains(i.target))return;let g=(e.documentElement.clientWidth||1)/3;i.clientX>g*2?m(o+1):i.clientX<g&&m(o-1)});let u=()=>d.unlock();if(e.addEventListener("keydown",u),e.addEventListener("click",u),await Z(r),typeof r.seek!="function")return;r.pause?.(),f=K(r),f?.doc.documentElement.classList.add("ds-live"),f||console.warn("[decksmith] composition unreachable; serve the deck over http, not file://");let T=O(location.hash);m(T?Math.max(0,$(n,T)):0,!0)}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>void j(document),{once:!0}):j(document));})();
80
+ `;function ae(e){let r=e.createElement("style");r.textContent=ie,e.head.append(r);let n=e.createElement("div");return n.className="ds-chrome",n.innerHTML='<div class="ds-bar"><i></i></div><button class="ds-play" type="button" aria-label="Play the deck"></button><button class="ds-video" type="button" hidden>Video</button><div class="ds-count"></div><div class="ds-flags"></div><div class="ds-subs" hidden></div><div class="ds-notes" hidden></div>',e.body.append(n),{chrome:n,fill:n.querySelector("i"),play:n.querySelector(".ds-play"),video:n.querySelector(".ds-video"),count:n.querySelector(".ds-count"),notes:n.querySelector(".ds-notes"),flags:n.querySelector(".ds-flags"),subs:n.querySelector(".ds-subs")}}var le={at:()=>!1,hush:()=>{},toggleMute:()=>{},toggleSubtitles:()=>{},onEnded:()=>{},unlock:()=>{}};function ce(e,r,n){let t=e.createElement("audio");t.preload="auto",e.body.append(t);let o=!1,l=!0,c=()=>e.documentElement.classList.toggle("ds-cap",l);c();let u=!1,h=!1,s=[],a=0,p=null,m=null,y=0,x=()=>{let d=[];u&&h?d.push("press any key for sound"):o&&d.push("muted"),l||d.push("subtitles off"),n.flags.textContent=d.join(" \xB7 ")},S=()=>{let d=l?_(s,t.currentTime):null;d!==p&&(p=d,n.subs.textContent=d?.text??"",n.subs.hidden=d===null)},v=()=>{S(),a=t.ended||t.paused&&t.currentTime>0?0:requestAnimationFrame(v)},k=()=>{a&&cancelAnimationFrame(a),a=0,t.pause(),t.removeAttribute("src"),t.load(),s=[],S()},C=()=>{};t.addEventListener("ended",()=>{y===y&&!u&&C()});let L=d=>{let w=++y;m=d,k();let E=V(r,d.sceneId,d.fragment);return E?(s=E.cues,t.muted=o,t.src=j(r,E),Promise.resolve(t.play()).then(()=>{w===y&&(u=!1,x())},()=>{w===y&&(u=!0,h=!0,a&&cancelAnimationFrame(a),a=0,s=[],S(),x())}),a===0&&(a=requestAnimationFrame(v)),!0):!1};return{at:L,hush:k,onEnded:d=>{C=d},toggleMute:()=>{o=!o,t.muted=o,x()},toggleSubtitles:()=>{l=!l,c(),S(),x()},unlock:()=>{!u||!m||(u=!1,x(),L(m))}}}var de='script[type="application/decksmith-video+json"]';function ue(e){if(!e)return{};let r={};try{let n=JSON.parse(e);for(let[t,o]of Object.entries(n?.scenes??{}))typeof o?.url=="string"&&/^https:\/\//i.test(o.url)&&(r[t]={url:o.url,title:typeof o.title=="string"?o.title:"Video"})}catch{return{}}return r}var pe={at:()=>{},toggle:()=>{},close:()=>!1};function fe(e,r,n,t){let o=null,l=null,c=()=>o?(o.remove(),o=null,!0):!1,u=()=>{if(!l||o)return;t(),o=e.createElement("div"),o.className="ds-film";let s=e.createElement("iframe");s.src=l.url,s.title=l.title,s.allow="fullscreen",s.setAttribute("referrerpolicy","strict-origin-when-cross-origin");let a=e.createElement("button");a.type="button",a.className="ds-shut",a.textContent="\u2715",a.setAttribute("aria-label","Close the video"),a.addEventListener("click",p=>{p.stopPropagation(),c()}),o.append(s,a),n.chrome.append(o),a.focus()},h=()=>{c()||u()};return n.video.addEventListener("click",s=>{s.stopPropagation(),h()}),{at:s=>{c(),l=r[s.sceneId]??null,n.video.hidden=l===null,l&&n.video.setAttribute("aria-label",`Play the video: ${l.title}`)},toggle:h,close:c}}function he(e){let r=e;return!!r&&(r.isContentEditable||/^(INPUT|TEXTAREA|SELECT)$/.test(r.tagName??""))}async function W(e){let r=oe(e),n=e.querySelector("hyperframes-player");if(r.length===0||!n)return;let t=Q(r);if(t.length===0)return;let o=ae(e),l=$(e.querySelector(q)?.textContent),c=l?ce(e,l,o):le,u=ue(e.querySelector(de)?.textContent),h=Object.keys(u).length===0?pe:fe(e,u,o,()=>{c.hush(),d(!1)}),s=0,a=null,p=0,m=0,y=-1,x=i=>{n.seek(i),a&&G(a,r,i),p=i},S=(i,f,g)=>{let T=p,N=0,A=M=>{N===0&&(N=M);let I=Math.min(1,(M-N)/g);if(I<1){p=T+(f-T)*I,G(i,r,p),m=requestAnimationFrame(A);return}m=0,x(f)};m=requestAnimationFrame(A)},v=!1,k=0,C=()=>{k&&clearTimeout(k),k=0},L=()=>{v&&(s+1<t.length?b(s+1):d(!1))},d=i=>{v=i,C(),o.play.dataset.on=i?"1":"0",o.play.setAttribute("aria-label",i?"Pause the deck":"Play the deck"),i&&(y=-1,b(s,!0))},w=null,E=i=>{w===null||parent===window||parent.postMessage(i,w)};addEventListener("message",i=>{if(i.source!==parent||parent===window||!F(i.data))return;let f=i.data;if(f.type==="hello"){w=i.origin,E({channel:H,type:"ready",stops:t.map((g,T)=>({i:T,label:`${g.slide+1} / ${t[t.length-1].slide+1}`,notes:g.notes})),at:s});return}w===i.origin&&(f.type==="next"?b(s+1):f.type==="prev"?b(s-1):f.type==="go"?b(f.at,!0):f.type==="play"&&d(f.on))});let b=(i,f=!1)=>{s=Math.max(0,Math.min(t.length-1,i));let g=t[s];m&&cancelAnimationFrame(m),m=0;let T=typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches,N=ee(p,g.t,{reducedMotion:T});N.animate&&!f&&a?S(a,g.t,N.durationMs):x(g.t);let A=!1;if(s!==y&&(y=s,A=c.at(g)),h.at(g),C(),v&&!A){let M=t[s+1],I=M?(M.t-g.t)*1e3:0;k=setTimeout(L,Math.min(8e3,Math.max(1500,I)))}history.replaceState(null,"",te(g)),E({channel:H,type:"stop",at:s,total:t.length,label:`${g.slide+1} / ${t[t.length-1].slide+1}`,notes:g.notes,playing:v}),o.fill.style.transform=`scaleX(${(s+1)/t.length})`,o.count.textContent=`${g.slide+1} / ${t[t.length-1].slide+1}`,o.notes.textContent=g.notes};c.onEnded(L),o.play.addEventListener("click",i=>{i.stopPropagation(),d(!v)}),addEventListener("hashchange",()=>{let i=B(location.hash);if(!i)return;let f=X(t,i);f>=0&&f!==s&&b(f,!0)}),e.addEventListener("keydown",i=>{if(!(i.defaultPrevented||he(i.target))){switch(i.key){case"ArrowRight":case"PageDown":case" ":b(s+1);break;case"ArrowLeft":case"PageUp":b(s-1);break;case"Home":b(0,!0);break;case"End":b(t.length-1,!0);break;case"n":o.notes.hidden=!o.notes.hidden;break;case"m":c.toggleMute();break;case"s":c.toggleSubtitles();break;case"p":d(!v);break;case"v":h.toggle();break;case"Escape":if(!h.close())return;break;case"f":{(e.fullscreenElement?e.exitFullscreen():e.documentElement.requestFullscreen()).catch(()=>{});break}default:return}i.preventDefault()}}),e.addEventListener("click",i=>{if(o.chrome.contains(i.target))return;let f=(e.documentElement.clientWidth||1)/3;i.clientX>f*2?b(s+1):i.clientX<f&&b(s-1)});let O=()=>c.unlock();if(e.addEventListener("keydown",O),e.addEventListener("click",O),await se(n),typeof n.seek!="function")return;n.pause?.(),a=re(n),a?.doc.documentElement.classList.add("ds-live"),a||console.warn("[decksmith] composition unreachable; serve the deck over http, not file://");let D=B(location.hash);b(D?Math.max(0,X(t,D)):0,!0)}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>void W(document),{once:!0}):W(document));})();
@@ -0,0 +1,243 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Embedding a DeckSmith deck</title>
7
+
8
+ <!--
9
+ THIS PAGE IS THE PROOF, NOT A TUTORIAL.
10
+
11
+ Everything below is what a third party writes. It loads one module, uses one
12
+ element, and knows exactly one thing about DeckSmith: where a deck is. It does
13
+ not know that hyperframes exists, that a deck carries a slideshow island, or
14
+ that there is a build behind any of it — which is the test the design was
15
+ chosen against.
16
+
17
+ TWO PLAYERS ARE ON THE PAGE ON PURPOSE. `customElements.define` is one
18
+ registry per document, and the rejected design — hosting the deck's own
19
+ vendored player in this page — throws on the second one. Driving one deck
20
+ while the other stays put is the cheapest demonstration that the boundary
21
+ landed in the right place.
22
+
23
+ DECK URLS COME FROM THE PAGE, NOT FROM A CONSTANT. The dev server gives every
24
+ job an unguessable id and serves its deck at /d/<id>/, so there is no fixed
25
+ URL a static page could point at. Paste one in, or pass ?a=&b=. Copied next
26
+ to a built deck and opened over any static server, ./deck/ is the default and
27
+ the fields can be ignored.
28
+ -->
29
+
30
+ <style>
31
+ :root { color-scheme: light dark; --fg:#111318; --dim:#5a6270; --line:#dfe3ea; --bg:#fff; --accent:#2f6df6; }
32
+ @media (prefers-color-scheme: dark) {
33
+ :root { --fg:#e8ecf3; --dim:#98a2b3; --line:#252a33; --bg:#0d1014; }
34
+ }
35
+ * { box-sizing: border-box; }
36
+ body { margin:0; background:var(--bg); color:var(--fg);
37
+ font:15px/1.5 ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif; }
38
+ main { max-width:1180px; margin:0 auto; padding:32px 24px 64px; }
39
+ h1 { font-size:22px; margin:0 0 4px; letter-spacing:-0.01em; }
40
+ .lede { color:var(--dim); margin:0 0 22px; max-width:64ch; }
41
+ pre { background:color-mix(in srgb, var(--fg) 5%, transparent); border:1px solid var(--line);
42
+ border-radius:10px; padding:14px 16px; overflow:auto; margin:0 0 26px;
43
+ font:13px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace; }
44
+
45
+ form { display:flex; flex-wrap:wrap; gap:8px; margin:0 0 26px; align-items:center; }
46
+ input { flex:1 1 300px; min-width:0; font:13px ui-monospace,Menlo,monospace; padding:7px 10px;
47
+ border:1px solid var(--line); border-radius:7px; background:var(--bg); color:var(--fg); }
48
+ button { font:inherit; font-size:13px; padding:6px 12px; border:1px solid var(--line);
49
+ background:var(--bg); color:var(--fg); border-radius:7px; cursor:pointer; }
50
+ button:hover:not(:disabled) { border-color:var(--accent); color:var(--accent); }
51
+ button:disabled { opacity:.45; cursor:default; }
52
+
53
+ .decks { display:grid; gap:26px; grid-template-columns:repeat(auto-fit,minmax(430px,1fr)); }
54
+ .card { border:1px solid var(--line); border-radius:12px; overflow:hidden; }
55
+ .bar { display:flex; align-items:center; gap:8px; padding:9px 12px; border-bottom:1px solid var(--line);
56
+ background:color-mix(in srgb, var(--fg) 4%, transparent); }
57
+ .bar .name { font:12px ui-monospace,Menlo,monospace; color:var(--dim); margin-right:auto;
58
+ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
59
+ .count { font:12px ui-monospace,Menlo,monospace; color:var(--dim); }
60
+
61
+ /* The element is a block that fills the box it is given. The deck inside keeps
62
+ its own aspect ratio, so give the box the deck's. */
63
+ decksmith-player { display:block; width:100%; aspect-ratio:16/9; background:#0b0d11; }
64
+
65
+ .jump { display:flex; flex-wrap:wrap; gap:4px; padding:10px 12px; border-top:1px solid var(--line);
66
+ max-height:112px; overflow:auto; }
67
+ .jump button { padding:2px 8px; font:11px ui-monospace,Menlo,monospace; }
68
+ .jump button[aria-current=true] { border-color:var(--accent); color:var(--accent); font-weight:600; }
69
+ .notes, .warn { padding:10px 12px; border-top:1px solid var(--line); font-size:13px; }
70
+ .notes { color:var(--dim); min-height:38px; }
71
+ .warn { color:#b4690e; display:none; }
72
+ </style>
73
+ </head>
74
+ <body>
75
+ <main>
76
+ <h1>Embedding a DeckSmith deck</h1>
77
+ <p class="lede">
78
+ Everything on this page is what a third party writes. One module, one
79
+ element, and one thing you have to know — where the deck is.
80
+ </p>
81
+ <p class="lede">
82
+ The numbered buttons are stops, not slides. A deck pauses several times
83
+ inside one slide, so the counter shows the deck’s own slide number and the
84
+ stop index side by side.
85
+ </p>
86
+
87
+ <pre>&lt;script type="module" src="/player.js"&gt;&lt;/script&gt;
88
+ &lt;decksmith-player deck="/d/&lt;id&gt;/"&gt;&lt;/decksmith-player&gt;</pre>
89
+
90
+ <form id="pick">
91
+ <input id="ua" placeholder="deck URL, e.g. /d/abc123/ or ./deck/" />
92
+ <input id="ub" placeholder="a second deck URL (optional)" />
93
+ <button type="submit">Load</button>
94
+ </form>
95
+
96
+ <div class="decks">
97
+ <div class="card">
98
+ <div class="bar">
99
+ <span class="name" id="n1">no deck</span>
100
+ <button data-act="prev" data-for="p1" disabled>Prev</button>
101
+ <button data-act="next" data-for="p1" disabled>Next</button>
102
+ <button data-act="play" data-for="p1" disabled>Play</button>
103
+ <span class="count" id="c1">–</span>
104
+ </div>
105
+ <decksmith-player id="p1"></decksmith-player>
106
+ <div class="jump" id="j1"></div>
107
+ <div class="notes" id="s1"></div>
108
+ <div class="warn" id="w1"></div>
109
+ </div>
110
+
111
+ <div class="card">
112
+ <div class="bar">
113
+ <span class="name" id="n2">no deck</span>
114
+ <button data-act="prev" data-for="p2" disabled>Prev</button>
115
+ <button data-act="next" data-for="p2" disabled>Next</button>
116
+ <button data-act="play" data-for="p2" disabled>Play</button>
117
+ <span class="count" id="c2">–</span>
118
+ </div>
119
+ <decksmith-player id="p2"></decksmith-player>
120
+ <div class="jump" id="j2"></div>
121
+ <div class="notes" id="s2"></div>
122
+ <div class="warn" id="w2"></div>
123
+ </div>
124
+ </div>
125
+
126
+ <p class="lede" style="margin-top:28px">
127
+ Drive one and the other stays where it is. Each deck is its own document, so
128
+ two on one page do not fight over a custom-element registry, a stylesheet or
129
+ a keyboard.
130
+ </p>
131
+ </main>
132
+
133
+ <script type="module" src="/player.js"></script>
134
+ <script>
135
+ /* A PLAIN SCRIPT, NOT A MODULE: nothing here imports anything. A consumer wires
136
+ the element with the DOM API they already have. */
137
+
138
+ function wire(id, name, count, jump, notes, warn) {
139
+ var el = document.getElementById(id);
140
+ var buttons = document.querySelectorAll('[data-for="' + id + '"]');
141
+
142
+ el.addEventListener("ds-ready", function (e) {
143
+ document.getElementById(name).textContent =
144
+ el.getAttribute("deck") + " — " + e.detail.stops.length + " stops";
145
+ buttons.forEach(function (b) { b.disabled = false; });
146
+ var box = document.getElementById(jump);
147
+ box.textContent = "";
148
+ e.detail.stops.forEach(function (stop) {
149
+ var b = document.createElement("button");
150
+ b.textContent = String(stop.i + 1);
151
+ b.title = stop.label;
152
+ b.addEventListener("click", function () { el.go(stop.i); });
153
+ box.appendChild(b);
154
+ });
155
+ mark(box, e.detail.at);
156
+ });
157
+
158
+ /* BOTH NUMBERS, because they disagree and the disagreement is the point. A
159
+ deck stops several times inside one slide — 46 stops across 15 slides in
160
+ the demo — so `label`, which is the deck's own "slide / slides" chrome,
161
+ does not match the jump list, which is one button per stop. Showing only
162
+ one of them makes clicking button 6 and reading "2 / 15" look like a bug. */
163
+ el.addEventListener("ds-stop", function (e) {
164
+ document.getElementById(count).textContent =
165
+ e.detail.label + " · stop " + (e.detail.at + 1) + "/" + e.detail.total;
166
+ document.getElementById(notes).textContent = e.detail.notes || "";
167
+ mark(document.getElementById(jump), e.detail.at);
168
+ var play = document.querySelector('[data-act=play][data-for="' + id + '"]');
169
+ if (play) play.textContent = e.detail.playing ? "Pause" : "Play";
170
+ });
171
+
172
+ /* TWO FAILURES, AND THEY ARE NOT THE SAME. `no-bridge` means the deck loaded
173
+ and did not answer — it was built before the bridge existed, it still
174
+ plays, and only these controls are dead. `bad-deck-url` means nothing
175
+ loaded at all. Reporting either as the other sends the reader looking in
176
+ the wrong place. */
177
+ el.addEventListener("ds-error", function (e) {
178
+ var box = document.getElementById(warn);
179
+ box.style.display = "block";
180
+ box.textContent =
181
+ e.detail.reason === "bad-deck-url"
182
+ ? e.detail.message + " Check the address — a deck URL is a directory, like /d/<id>/ or ./deck/."
183
+ : "This deck does not answer the player bridge (" + e.detail.reason +
184
+ "). It still plays, and its own keyboard still works — the controls above cannot drive it.";
185
+ buttons.forEach(function (b) { b.disabled = true; });
186
+ });
187
+ }
188
+
189
+ function mark(box, at) {
190
+ for (var i = 0; i < box.children.length; i++) {
191
+ box.children[i].setAttribute("aria-current", String(i === at));
192
+ }
193
+ }
194
+
195
+ function reset(id, name, count, jump, notes, warn) {
196
+ document.getElementById(name).textContent = "no deck";
197
+ document.getElementById(count).textContent = "–";
198
+ document.getElementById(jump).textContent = "";
199
+ document.getElementById(notes).textContent = "";
200
+ document.getElementById(warn).style.display = "none";
201
+ document.querySelectorAll('[data-for="' + id + '"]').forEach(function (b) { b.disabled = true; });
202
+ }
203
+
204
+ /* Setting `deck` on a live element re-mounts it, so loading a second deck is
205
+ one attribute write — no teardown for the consumer to remember. */
206
+ function load(id, url, parts) {
207
+ var el = document.getElementById(id);
208
+ if (!url) { el.removeAttribute("deck"); reset.apply(null, parts); return; }
209
+ reset.apply(null, parts);
210
+ el.setAttribute("deck", url);
211
+ }
212
+
213
+ document.querySelectorAll("[data-act]").forEach(function (b) {
214
+ b.addEventListener("click", function () {
215
+ var el = document.getElementById(b.dataset.for);
216
+ if (b.dataset.act === "next") el.next();
217
+ else if (b.dataset.act === "prev") el.prev();
218
+ else el.play(b.textContent === "Play");
219
+ });
220
+ });
221
+
222
+ var A = ["p1", "n1", "c1", "j1", "s1", "w1"];
223
+ var B = ["p2", "n2", "c2", "j2", "s2", "w2"];
224
+ wire.apply(null, A);
225
+ wire.apply(null, B);
226
+
227
+ var q = new URLSearchParams(location.search);
228
+ var a = q.get("a") || "";
229
+ var b = q.get("b") || "";
230
+ document.getElementById("ua").value = a;
231
+ document.getElementById("ub").value = b;
232
+
233
+ document.getElementById("pick").addEventListener("submit", function (e) {
234
+ e.preventDefault();
235
+ load("p1", document.getElementById("ua").value.trim(), A);
236
+ load("p2", document.getElementById("ub").value.trim(), B);
237
+ });
238
+
239
+ if (a) load("p1", a, A);
240
+ if (b) load("p2", b, B);
241
+ </script>
242
+ </body>
243
+ </html>