@jokerized/decksmith 0.3.1 → 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.
- package/README.md +112 -1
- package/dist/cli.js +2814 -730
- package/dist/deck-player-element.js +1 -0
- package/dist/deck-player.js +1 -0
- package/dist/deck-runtime.js +27 -2
- package/dist/embed.html +243 -0
- package/dist/index.js +2888 -854
- package/dist/mcp.js +2865 -818
- package/dist/types/deck/player-element.d.ts +1 -0
- package/dist/types/deck/player.d.ts +37 -0
- package/dist/types/deck/protocol.d.ts +80 -0
- package/dist/types/deck/runtime.d.ts +36 -0
- package/dist/types/emit/archetypes/claim-figure.d.ts +0 -7
- package/dist/types/emit/kit.d.ts +33 -0
- package/dist/types/images/providers.d.ts +14 -5
- package/dist/types/index.d.ts +13 -0
- package/dist/types/mcp/tools.d.ts +20 -0
- package/dist/types/net/fetch.d.ts +81 -0
- package/dist/types/pack/media.d.ts +12 -0
- package/dist/types/plan/prompt.d.ts +2 -1
- package/dist/types/server/pipeline.d.ts +51 -8
- package/dist/types/server/upload.d.ts +29 -5
- package/dist/types/source/assets.d.ts +77 -4
- package/dist/types/source/harvest.d.ts +250 -0
- package/dist/types/source/readability.d.ts +120 -0
- package/dist/types/source/transcode.d.ts +83 -0
- package/dist/types/types.d.ts +21 -0
- package/package.json +1 -1
|
@@ -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};
|
package/dist/deck-runtime.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";(()=>{var
|
|
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
|
|
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));})();
|
package/dist/embed.html
ADDED
|
@@ -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><script type="module" src="/player.js"></script>
|
|
88
|
+
<decksmith-player deck="/d/<id>/"></decksmith-player></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>
|