feat: add verified deterministic Anime v4 choreography
No files matched your search
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: presentation-html
|
||||
description: Build offline HTML decks with deliberate motion.
|
||||
version: 0.2.0
|
||||
version: 0.3.0
|
||||
author: RootAtSkic, Hermes Agent
|
||||
license: MIT
|
||||
platforms: [linux, macos, windows]
|
||||
@@ -30,6 +30,10 @@ Build editable, offline-first browser presentations using Reveal.js for navigati
|
||||
- Read [research](references/research.md), [example catalog](references/examples.md) and [QA contract](references/qa.md) before changing the starter.
|
||||
- Optional design/review skills may supplement this workflow; channel instructions remain channel-owned.
|
||||
|
||||
## Read on demand: choreography and deterministic motion
|
||||
|
||||
Before adapting animation recipes, read [choreography](references/choreography.md): motion briefs, named timeline beats, paused ownership, deterministic seeking, SVG drawing/motion paths and spatial stagger. The starter exposes seek/replay/settle for authoring and QA without adding presentation controls. [Catalog patterns](references/catalog-patterns.md) records the three inspected `animejs` skills, source pins, license limits and rejected v3 examples. Tests exercise our original v4 equivalents; upstream snippets are not automatically compatible.
|
||||
|
||||
## Read on demand: explanatory mechanisms
|
||||
|
||||
Before designing an exploded view, modular assembly, product-internals explanation or reversible inspect/reassemble interaction, read [explanatory mechanisms](references/explanatory-mechanisms.md). It separates inspected Anime.js/Awwwards visual references from unverified implementation claims, links the original runnable `assets/mechanism.html` example, and defines adaptation and QA checks. Use it when motion must explain how parts form a whole—not to decorate tables. Build with `python3 scripts/build-mechanism.py`; test with `node scripts/test-mechanism.mjs` in the copied package.
|
||||
@@ -47,7 +51,7 @@ npm test
|
||||
|
||||
The builder works entirely offline using vendored assets. Dependency/browser installation requires network once; deck playback and build do not. If a compatible Chromium is already installed, set `CHROMIUM_EXECUTABLE` to its actual executable when invoking `npm test`. Do not silently download a new runtime during an offline test.
|
||||
|
||||
Open `dist/presentation-html-demo-0.1.0.html` directly. The six-slide demo is a teaching example, not a mandated length. Source: `assets/starter.html`, `assets/app.js`, `scripts/build.py`. Tests: `scripts/test.mjs`.
|
||||
Open `dist/presentation-html-demo-0.3.0.html` directly. The six-slide demo is a teaching example, not a mandated length. Source: `assets/starter.html`, `assets/app.js`, `scripts/build.py`. Tests: `scripts/test.mjs`.
|
||||
|
||||
## Procedure
|
||||
|
||||
|
||||
@@ -1,22 +1,42 @@
|
||||
(() => {
|
||||
const {animate, createTimeline, stagger} = anime;
|
||||
const {createTimeline, stagger} = anime;
|
||||
const reduced = matchMedia('(prefers-reduced-motion: reduce)');
|
||||
const printMode = location.search.includes('print-pdf');
|
||||
const seen = new Set(); let active = []; let motion = !reduced.matches;
|
||||
const seen = new Set(), timelines = new Map(); let motion = !reduced.matches;
|
||||
const motionButton = document.querySelector('#motion');
|
||||
// Explicit paused instances: Reveal owns slide navigation; one clock owns motion.
|
||||
// Targets are dedicated motion wrappers, not positioned/rotated diagram parts.
|
||||
document.querySelectorAll('.slides>section').forEach(slide=>{
|
||||
const targets=[...slide.querySelectorAll('[data-motion]')];
|
||||
if(!targets.length)return;
|
||||
const tl=createTimeline({autoplay:false,defaults:{duration:300,ease:'outCubic'}});
|
||||
if(slide.id==='sequence') {
|
||||
['observe','interpret','act'].forEach((label,i)=>{
|
||||
tl.label(label,i*180).add(targets[i],{opacity:[0,1],translateY:[12,0]},label);
|
||||
});
|
||||
} else tl.add(targets,{opacity:[0,1],translateY:[12,0],delay:stagger(70),duration:400},0);
|
||||
tl.seek(tl.duration);tl.pause();timelines.set(slide,tl);
|
||||
});
|
||||
const allowed=()=>motion&&!reduced.matches&&!printMode&&!Reveal.isOverview();
|
||||
function settle() {
|
||||
active.forEach(a => { a.complete(); }); active = [];
|
||||
document.querySelectorAll('[data-motion]').forEach(e => {e.style.opacity='1';e.style.transform='none';});
|
||||
timelines.forEach(tl=>{tl.pause();tl.seek(tl.duration);});
|
||||
}
|
||||
function replay() {
|
||||
settle();const tl=timelines.get(Reveal.getCurrentSlide());
|
||||
if(!tl||!allowed())return false;
|
||||
tl.seek(0);tl.play();return true;
|
||||
}
|
||||
function seek(timeMs) {
|
||||
if(!Number.isFinite(timeMs))throw new TypeError('seek requires finite milliseconds');
|
||||
const tl=timelines.get(Reveal.getCurrentSlide());
|
||||
if(!tl)return false;
|
||||
tl.pause();tl.seek(allowed()?Math.max(0,Math.min(timeMs,tl.duration)):tl.duration);
|
||||
return true;
|
||||
}
|
||||
function run() {
|
||||
settle(); const slide=Reveal.getCurrentSlide();
|
||||
if (!slide || seen.has(slide.id) || !motion || reduced.matches || printMode) return;
|
||||
seen.add(slide.id); const targets=slide.querySelectorAll('[data-motion]');
|
||||
if (!targets.length) return;
|
||||
if(slide.id==='sequence') {
|
||||
const tl=createTimeline({defaults:{duration:300,ease:'outCubic'}});
|
||||
targets.forEach((e,i)=>tl.add(e,{opacity:[0,1],translateY:[12,0]},i*180));active.push(tl);
|
||||
} else active.push(animate(targets,{opacity:[0,1],translateY:[12,0],delay:stagger(70),duration:400,ease:'outCubic'}));
|
||||
settle();const slide=Reveal.getCurrentSlide();
|
||||
if(!slide||seen.has(slide.id)||!allowed())return;
|
||||
seen.add(slide.id);replay();
|
||||
}
|
||||
function motionLabel(){motionButton.textContent=motion&&!reduced.matches?'Motion on':'Motion off';motionButton.setAttribute('aria-pressed',String(motion&&!reduced.matches));}
|
||||
motionButton.onclick=()=>{motion=!motion;settle();motionLabel();};
|
||||
@@ -37,5 +57,7 @@
|
||||
run(); window.deckReady=true;
|
||||
});
|
||||
Reveal.on('slidechanged',()=>{try{localStorage.setItem('presentation-html-demo-slide',String(Reveal.getIndices().h));}catch{}run();});
|
||||
window.presentationQA={settle,reduced:()=>reduced.matches,active:()=>active.filter(a=>!a.completed).length};
|
||||
Reveal.on('overviewshown',settle);
|
||||
document.addEventListener('visibilitychange',()=>{if(document.hidden)settle();});
|
||||
window.presentationQA={settle,seek,replay,reduced:()=>reduced.matches,active:()=>[...timelines.values()].filter(tl=>!tl.paused&&!tl.completed).length};
|
||||
})();
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"file": "presentation-html-demo-0.1.0.html",
|
||||
"sha256": "c81f4f77250ee50973c97d12d22172d9e2cd6f97a1e699e960b491a0aa1581b0",
|
||||
"bytes": 362988,
|
||||
"file": "presentation-html-demo-0.3.0.html",
|
||||
"sha256": "f9e7ea2a327ea85c34db5c90332633a77bca647d256223918c6d79f1a0723015",
|
||||
"bytes": 363913,
|
||||
"slides": 6,
|
||||
"interactive_nodes": 6,
|
||||
"reveal": "6.0.2",
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
<!doctype html>
|
||||
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>From signal to decision — offline HTML starter</title>
|
||||
<style>.reveal .stretch,.reveal .r-stretch{max-width:none;max-height:none}.reveal pre.stretch code,.reveal pre.r-stretch code{box-sizing:border-box;height:100%;max-height:100%}.reveal .r-fit-text{white-space:nowrap;display:inline-block}.reveal .r-stack{grid-template-rows:100%;display:grid}.reveal .r-stack>*{grid-area:1/1;margin:auto}.reveal .r-vstack,.reveal .r-hstack{display:flex}.reveal .r-vstack img,.reveal .r-vstack video,.reveal .r-hstack img,.reveal .r-hstack video{object-fit:contain;min-width:0;min-height:0}.reveal .r-vstack{flex-direction:column;justify-content:center;align-items:center}.reveal .r-hstack{flex-direction:row;justify-content:center;align-items:center}.reveal .items-stretch{align-items:stretch}.reveal .items-start{align-items:flex-start}.reveal .items-center{align-items:center}.reveal .items-end{align-items:flex-end}.reveal .justify-between{justify-content:space-between}.reveal .justify-around{justify-content:space-around}.reveal .justify-start{justify-content:flex-start}.reveal .justify-center{justify-content:center}.reveal .justify-end{justify-content:flex-end}html.reveal-full-page{width:100%;height:100vh;height:calc(var(--vh,1vh) * 100);height:100dvh;overflow:hidden}.reveal-viewport{color:#000;--r-controls-spacing:12px;--r-overlay-header-height:40px;--r-overlay-margin:0px;--r-overlay-padding:6px;--r-overlay-gap:5px;background-color:#fff;height:100%;margin:0;line-height:1;position:relative;overflow:hidden}@media screen and (width<=1024px),(height<=768px){.reveal-viewport{--r-overlay-header-height:26px}}.reveal-viewport:fullscreen{width:100%!important;height:100%!important;top:0!important;left:0!important;transform:none!important}.reveal .fragment{transition:all .2s}.reveal .fragment:not(.custom){opacity:0;visibility:hidden;will-change:opacity}.reveal .fragment.visible{opacity:1;visibility:inherit}.reveal .fragment.disabled{transition:none}.reveal .fragment.grow{opacity:1;visibility:inherit}.reveal .fragment.grow.visible{transform:scale(1.3)}.reveal .fragment.shrink{opacity:1;visibility:inherit}.reveal .fragment.shrink.visible{transform:scale(.7)}.reveal .fragment.zoom-in{transform:scale(.1)}.reveal .fragment.zoom-in.visible{transform:none}.reveal .fragment.fade-out{opacity:1;visibility:inherit}.reveal .fragment.fade-out.visible{opacity:0;visibility:hidden}.reveal .fragment.semi-fade-out{opacity:1;visibility:inherit}.reveal .fragment.semi-fade-out.visible{opacity:.5;visibility:inherit}.reveal .fragment.strike{opacity:1;visibility:inherit}.reveal .fragment.strike.visible{text-decoration:line-through}.reveal .fragment.fade-up{transform:translateY(40px)}.reveal .fragment.fade-up.visible{transform:translate(0)}.reveal .fragment.fade-down{transform:translateY(-40px)}.reveal .fragment.fade-down.visible{transform:translate(0)}.reveal .fragment.fade-right{transform:translate(-40px)}.reveal .fragment.fade-right.visible{transform:translate(0)}.reveal .fragment.fade-left{transform:translate(40px)}.reveal .fragment.fade-left.visible{transform:translate(0)}.reveal .fragment.fade-in-then-out,.reveal .fragment.current-visible{opacity:0;visibility:hidden}.reveal .fragment.fade-in-then-out.current-fragment,.reveal .fragment.current-visible.current-fragment{opacity:1;visibility:inherit}.reveal .fragment.fade-in-then-semi-out{opacity:0;visibility:hidden}.reveal .fragment.fade-in-then-semi-out.visible{opacity:.5;visibility:inherit}.reveal .fragment.fade-in-then-semi-out.current-fragment,.reveal .fragment.highlight-red,.reveal .fragment.highlight-current-red,.reveal .fragment.highlight-green,.reveal .fragment.highlight-current-green,.reveal .fragment.highlight-blue,.reveal .fragment.highlight-current-blue{opacity:1;visibility:inherit}.reveal .fragment.highlight-red.visible{color:#ff2c2d}.reveal .fragment.highlight-green.visible{color:#17ff2e}.reveal .fragment.highlight-blue.visible{color:#1b91ff}.reveal .fragment.highlight-current-red.current-fragment{color:#ff2c2d}.reveal .fragment.highlight-current-green.current-fragment{color:#17ff2e}.reveal .fragment.highlight-current-blue.current-fragment{color:#1b91ff}.reveal:after{content:"";font-style:italic}.reveal iframe{z-index:1}.reveal a{position:relative}@keyframes bounce-right{0%,10%,25%,40%,50%{transform:translate(0)}20%{transform:translate(10px)}30%{transform:translate(-5px)}}@keyframes bounce-left{0%,10%,25%,40%,50%{transform:translate(0)}20%{transform:translate(-10px)}30%{transform:translate(5px)}}@keyframes bounce-down{0%,10%,25%,40%,50%{transform:translateY(0)}20%{transform:translateY(10px)}30%{transform:translateY(-5px)}}.reveal .controls{top:auto;bottom:var(--r-controls-spacing);right:var(--r-controls-spacing);z-index:11;color:#000;pointer-events:none;font-size:10px;display:none;position:absolute;left:auto}.reveal .controls button{cursor:pointer;color:currentColor;z-index:2;pointer-events:auto;font-size:inherit;visibility:hidden;opacity:0;-webkit-appearance:none;-webkit-tap-highlight-color:#0000;background-color:#0000;border:0;outline:0;padding:0;trLine truncated
|
||||
</style><style>
|
||||
@font-face{font-family:'Work Sans';font-weight:400;font-style:normal;font-display:block;src:url(data:font/woff2;base64,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 truncated
|
||||
:root{--r-background-color:#101e25;--r-main-color:#f4f3ed;--ink:#f4f3ed;--muted:#b8c4c7;--bg:#101e25;--accent:#c5f27a;--line:#57717c}*{box-sizing:border-box}body.reveal-viewport{background:var(--bg);color:var(--ink)}.reveal{font-family:'Work Sans',sans-serif;font-size:28px}.reveal .slides{text-align:left}.reveal .slides>section{height:700px;padding:45px 48px}.eyebrow{color:var(--accent);font-size:20px;letter-spacing:.15em;text-transform:uppercase;margin:0 0 24px}h1{font-size:86px;line-height:1.05;letter-spacing:-.045em;margin:24px 0}h2{font-size:55px;line-height:1.1;margin:0 0 30px;letter-spacing:-.025em}p{line-height:1.5;max-width:1000px}.muted{color:var(--muted)}.big{font-size:38px;max-width:900px}.accent{color:var(--accent)}.rule{border-top:2px solid var(--line);padding-top:20px;margin-top:42px}button{font:inherit;color:var(--ink);background:#20343e;border:2px solid var(--line);padding:10px 16px;cursor:pointer;min-height:44px}button:hover,button[aria-pressed=true]{border-color:var(--accent);color:var(--accent)}button:focus-visible,[role=button]:focus-visible{outline:4px solid var(--accent);outline-offset:5px}.toolbar{position:fixed;right:22px;top:14px;z-index:30;display:flex;gap:12px;font:16px 'Work Sans',sans-serif}.toolbar button{font-size:16px}.reveal .slide-number{font:18px 'Work Sans';background:transparent;color:var(--muted);left:25px;right:auto;bottom:22px}.reveal .controls{color:var(--accent)}svg{width:100%;height:370px;overflow:visible}svg text{font-family:inherit;fill:var(--ink);font-size:24px;text-anchor:middle;dominant-baseline:middle}svg .edge{stroke:var(--line);stroke-width:3;fill:none}svg rect{fill:#20343e;stroke:var(--line);stroke-width:2}svg [role=button]{cursor:pointer}svg [aria-pressed=true] rect{stroke:var(--accent)}svg .hub{fill:var(--accent)}svg .hub-text{fill:var(--bg);font-weight:700}.detail{margin:5px 0;font-size:24px;min-height:40px}.stages{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;margin-top:60px}.stage{border-top:3px solid var(--accent);padding-top:20px}.stage strong{font-size:36px}.stage p{font-size:25px}.dots{display:flex;gap:20px;margin:65px 0}.dot{width:70px;height:70px;background:var(--accent)}.ending{font-size:70px;line-height:1.12}.reveal .progress{color:var(--accent)}
|
||||
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important;scroll-behavior:auto!important}}
|
||||
@media print{.toolbar,.controls{display:none!important}.reveal .slides section [data-motion]{opacity:1!important;transform:none!important}}
|
||||
</style></head><body>
|
||||
<div class="toolbar"><button id="motion" aria-pressed="true">Motion on</button><button id="fullscreen" aria-pressed="false">Enter fullscreen</button></div>
|
||||
<div class="reveal"><div class="slides">
|
||||
<section id="signal"><p class="eyebrow">Original example · presentation-html</p><h1>From signal<br>to <span class="accent">decision.</span></h1><p class="big">Use structure to explain the system.<br>Use motion only to explain change.</p><p class="rule muted">Reveal.js 6.0.2 + Anime.js 4.5.0 · All assets embedded</p></section>
|
||||
<section id="story"><p class="eyebrow">01 / Narrative</p><h2>One question. One visual answer.</h2><div class="stages"><div class="stage" data-motion><strong>Context</strong><p>What changed, and why does it matter?</p></div><div class="stage" data-motion><strong>Mechanism</strong><p>What connects the evidence to the choice?</p></div><div class="stage" data-motion><strong>Decision</strong><p>What should the audience do next?</p></div></div><p class="rule muted">Choose the slide count from the story—not a template quota.</p></section>
|
||||
<section id="system"><p class="eyebrow">02 / Editable SVG + real interaction</p><h2>Keep the decision at the center.</h2>
|
||||
<svg viewBox="0 0 1100 370" role="group" aria-label="Six evidence sources feed one decision"><g aria-hidden="true"><path class="edge" d="M250 65L550 185M550 65V185M850 65L550 185M250 305L550 185M550 305V185M850 305L550 185"/></g>
|
||||
<rect class="hub" x="435" y="145" width="230" height="80"/><text class="hub-text" x="550" y="185">Decision</text>
|
||||
<g role="button" tabindex="0" aria-label="People" aria-pressed="false" data-node="0" data-detail="People: who owns the decision?"><rect x="140" y="33" width="220" height="64"/><text x="250" y="65">People</text></g>
|
||||
<g role="button" tabindex="0" aria-label="Process" aria-pressed="false" data-node="1" data-detail="Process: how does work move?"><rect x="440" y="33" width="220" height="64"/><text x="550" y="65">Process</text></g>
|
||||
<g role="button" tabindex="0" aria-label="Platform" aria-pressed="false" data-node="2" data-detail="Platform: what enables delivery?"><rect x="740" y="33" width="220" height="64"/><text x="850" y="65">Platform</text></g>
|
||||
<g role="button" tabindex="0" aria-label="Quality" aria-pressed="false" data-node="3" data-detail="Quality: what evidence proves fitness?"><rect x="140" y="273" width="220" height="64"/><text x="250" y="305">Quality</text></g>
|
||||
<g role="button" tabindex="0" aria-label="Cost" aria-pressed="false" data-node="4" data-detail="Cost: what constraint shapes the choice?"><rect x="440" y="273" width="220" height="64"/><text x="550" y="305">Cost</text></g>
|
||||
<g role="button" tabindex="0" aria-label="Risk" aria-pressed="false" data-node="5" data-detail="Risk: what could invalidate the plan?"><rect x="740" y="273" width="220" height="64"/><text x="850" y="305">Risk</text></g>
|
||||
</svg><p class="detail" id="detail" aria-live="polite">Select a source to reveal its role. Tab + Enter or Space also works.</p></section>
|
||||
<section id="sequence"><p class="eyebrow">03 / Anime.js timeline</p><h2>Reveal dependency—not decoration.</h2><div class="stages"><div class="stage" data-motion><strong>Observe</strong><p>Make the signal visible.</p></div><div class="stage" data-motion><strong>Interpret</strong><p>Connect evidence and context.</p></div><div class="stage" data-motion><strong>Act</strong><p>Name the next reversible step.</p></div></div><p class="rule muted">A short, bounded sequence. No loops. No hidden final content.</p></section>
|
||||
<section id="stagger"><p class="eyebrow">04 / Restrained stagger</p><h2>Make order perceptible.</h2><div class="dots" aria-hidden="true"><i class="dot" data-motion></i><i class="dot" data-motion></i><i class="dot" data-motion></i><i class="dot" data-motion></i><i class="dot" data-motion></i><i class="dot" data-motion></i></div><p class="big">The pattern is original. The technique is portable.</p><p class="muted">Reduced motion, print and revisit states show the complete result.</p></section>
|
||||
<section id="decision"><p class="eyebrow">05 / Delivery gate</p><h2 class="ending">Test the exact file.<br>Inspect <span class="accent">every slide.</span></h2><p class="big">Offline. Readable. Keyboard-operable.<br>Then publish the immutable artifact.</p><p class="rule muted">← → Navigate · Esc Overview · F Fullscreen · Tab Focus</p></section>
|
||||
</div></div><script>/*!
|
||||
* reveal.js
|
||||
* https://revealjs.com
|
||||
* MIT licensed
|
||||
*
|
||||
* Copyright (C) 2011-2026 Hakim El Hattab, https://hakim.se
|
||||
*/
|
||||
(function(e,t){typeof exports==`object`&&typeof module<`u`?module.exports=t():typeof define==`function`&&define.amd?define([],t):(e=typeof globalThis<`u`?globalThis:e||self,e.Reveal=t())})(this,function(){var e=(e,t)=>{for(let n in t)e[n]=t[n];return e},t=(e,t)=>Array.from(e.querySelectorAll(t)),n=(e,t,n)=>{n?e.classList.add(t):e.classList.remove(t)},r=e=>{if(typeof e==`string`){if(e===`null`)return null;if(e===`true`)return!0;if(e===`false`)return!1;if(e.match(/^-?[\d\.]+$/))return parseFloat(e)}return e},i=(e,t)=>{e.style.transform=t},a=(e,t)=>{let n=e.matches||e.matchesSelector||e.msMatchesSelector;return!!(n&&n.call(e,t))},o=(e,t)=>{if(e&&typeof e.closest==`function`)return e.closest(t);for(;e;){if(a(e,t))return e;e=e.parentElement}return null},s=e=>{e=e||document.documentElement;let t=e.requestFullscreen||e.webkitRequestFullscreen||e.webkitRequestFullScreen||e.mozRequestFullScreen||e.msRequestFullscreen;t&&t.apply(e)},c=(e,t,n,r=``)=>{let i=e.querySelectorAll(`.`+n);for(let t=0;t<i.length;t++){let n=i[t];if(n.parentNode===e)return n}let a=document.createElement(t);return a.className=n,a.innerHTML=r,e.appendChild(a),a},l=e=>{let t=document.createElement(`style`);return e&&e.length>0&&t.appendChild(document.createTextNode(e)),document.head.appendChild(t),t},u=()=>{let e={};location.search.replace(/[A-Z0-9]+?=([\w\.%-]*)/gi,t=>{let n=t.split(`=`).shift(),r=t.split(`=`).pop();return n&&r!==void 0&&(e[n]=r),t});for(let t in e){let n=e[t];e[t]=r(unescape(n))}return e.dependencies!==void 0&&delete e.dependencies,e},d=(e,t=0)=>{if(e){var n;let r,i=e.style.height;return e.style.height=`0px`,e.parentElement&&(e.parentElement.style.height=`auto`),r=t-(((n=e.parentElement)==null?void 0:n.offsetHeight)||0),e.style.height=i+`px`,e.parentElement&&e.parentElement.style.removeProperty(`height`),r}return t},f={mp4:`video/mp4`,m4a:`video/mp4`,ogv:`video/ogg`,mpeg:`video/mpeg`,webm:`video/webm`},p=(e=``)=>{let t=e.split(`.`).pop();return t?f[t]:void 0},m=(e=``)=>encodeURI(e).replace(/%5B/g,`[`).replace(/%5D/g,`]`).replace(/[!'()*]/g,e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`),h=navigator.userAgent,g=/(iphone|ipod|ipad|android)/gi.test(h)||navigator.platform===`MacIntel`&&navigator.maxTouchPoints>1;/chrome/i.test(h)&&/edge/i.test(h);var _=/android/gi.test(h),v=function(e){if(e){var t=function(e){return[].slice.call(e)},n=0,r=1,i=2,a=3,o=[],s=null,c=`requestAnimationFrame`in e?function(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{sync:!1};e.cancelAnimationFrame(s);var n=function(){return u(o.filter((function(e){return e.dirty&&e.active})))};if(t.sync)return n();s=e.requestAnimationFrame(n)}:function(){},l=function(e){return function(t){o.forEach((function(t){return t.dirty=e})),c(t)}},u=function(e){e.filter((function(e){return!e.styleComputed})).forEach((function(e){e.styleComputed=m(e)})),e.filter(h).forEach(g);var t=e.filter(p);t.forEach(f),t.forEach((function(e){g(e),d(e)})),t.forEach(_)},d=function(e){return e.dirty=n},f=function(e){e.availableWidth=e.element.parentNode.clientWidth,e.currentWidth=e.element.scrollWidth,e.previousFontSize=e.currentFontSize,e.currentFontSize=Math.min(Math.max(e.minSize,e.availableWidth/e.currentWidth*e.previousFontSize),e.maxSize),e.whiteSpace=e.multiLine&&e.currentFontSize===e.minSize?`normal`:`nowrap`},p=function(e){return e.dirty!==i||e.dirty===i&&e.element.parentNode.clientWidth!==e.availableWidth},m=function(t){var n=e.getComputedStyle(t.element,null);return t.currentFontSize=parseFloat(n.getPropertyValue(`font-size`)),t.display=n.getPropertyValue(`display`),t.whiteSpace=n.getPropertyValue(`white-space`),!0},h=function(e){var t=!1;return!e.preStyleTestCompleted&&(/inline-/.test(e.display)||(t=!0,e.display=`inline-block`),e.whiteSpace!==`nowrap`&&(t=!0,e.whiteSpace=`nowrap`),e.preStyleTestCompleted=!0,t)},g=function(e){e.element.style.whiteSpace=e.whiteSpace,e.element.style.display=e.display,e.element.style.fontSize=e.currentFontSize+`px`},_=function(e){e.element.dispatchEvent(new CustomEvent(`fit`,{detail:{oldValue:e.previousFontSize,newValue:e.currentFontSize,scaleFactor:e.currentFontSize/e.previousFontSize}}))},v=function(e,t){return function(n){e.dirty=t,e.active&&c(n)}},y=function(e){return function(){o=o.filter((function(t){return t.element!==e.element})),e.observeMutations&&e.observer.disconnect(),e.element.style.whiteSpace=e.originalStyle.whiteSpace,e.element.style.display=e.originalStyle.display,e.element.style.fontSize=e.originalStyle.fontSize}},b=function(e){return function(){e.active||(e.active=!0,c())}},x=function(e){return function(){return e.active=!1}},S=function(e){e.observeMutations&&(e.observer=new MutationObserver(v(e,r)),e.observer.observe(e.element,e.observeMutations))},ee={minSize:16,maxSize:512,multiLine:!0,observeMutations:`MutationObserver`in e&&{subtree:!0,childList:!0,characterData:!0}},C=null,w=function(){e.clearTimeout(C),C=e.setTimeout(l(i),E.observeWindowDelay)},te=[`resize`,`orientationchange`];return Object.defineProperty(ELine truncated
|
||||
<span class="slide-number-a">${e}</span>
|
||||
<span class="slide-number-delimiter">${t}</span>
|
||||
<span class="slide-number-b">${n}</span>
|
||||
</a>`:`<a href="${r}">
|
||||
<span class="slide-number-a">${e}</span>
|
||||
</a>`}destroy(){this.element.remove()}},re=class{constructor(e){this.Reveal=e,this.onInput=this.onInput.bind(this),this.onBlur=this.onBlur.bind(this),this.onKeyDown=this.onKeyDown.bind(this)}render(){this.element=document.createElement(`div`),this.element.className=`jump-to-slide`,this.jumpInput=document.createElement(`input`),this.jumpInput.type=`text`,this.jumpInput.className=`jump-to-slide-input`,this.jumpInput.placeholder=`Jump to slide`,this.jumpInput.addEventListener(`input`,this.onInput),this.jumpInput.addEventListener(`keydown`,this.onKeyDown),this.jumpInput.addEventListener(`blur`,this.onBlur),this.element.appendChild(this.jumpInput)}show(){this.indicesOnShow=this.Reveal.getIndices(),this.Reveal.getRevealElement().appendChild(this.element),this.jumpInput.focus()}hide(){this.isVisible()&&(this.element.remove(),this.jumpInput.value=``,clearTimeout(this.jumpTimeout),delete this.jumpTimeout)}isVisible(){return!!this.element.parentNode}jump(){clearTimeout(this.jumpTimeout),delete this.jumpTimeout;let e=this.jumpInput.value.trim(``),t;if(/^\d+$/.test(e)){let n=this.Reveal.getConfig().slideNumber;if(n===`c`||n===`c/t`){let n=this.Reveal.getSlides()[parseInt(e,10)-1];n&&(t=this.Reveal.getIndices(n))}}return t||(/^\d+\.\d+$/.test(e)&&(e=e.replace(`.`,`/`)),t=this.Reveal.location.getIndicesFromHash(e,{oneBasedIndex:!0})),!t&&/\S+/i.test(e)&&e.length>1&&(t=this.search(e)),t&&e!==``?(this.Reveal.slide(t.h,t.v,t.f),!0):(this.Reveal.slide(this.indicesOnShow.h,this.indicesOnShow.v,this.indicesOnShow.f),!1)}jumpAfter(e){clearTimeout(this.jumpTimeout),this.jumpTimeout=setTimeout(()=>this.jump(),e)}search(e){let t=RegExp(`\\b`+e.trim()+`\\b`,`i`),n=this.Reveal.getSlides().find(e=>t.test(e.innerText));return n?this.Reveal.getIndices(n):null}cancel(){this.Reveal.slide(this.indicesOnShow.h,this.indicesOnShow.v,this.indicesOnShow.f),this.hide()}confirm(){this.jump(),this.hide()}destroy(){this.jumpInput.removeEventListener(`input`,this.onInput),this.jumpInput.removeEventListener(`keydown`,this.onKeyDown),this.jumpInput.removeEventListener(`blur`,this.onBlur),this.element.remove()}onKeyDown(e){e.keyCode===13?this.confirm():e.keyCode===27&&(this.cancel(),e.stopImmediatePropagation())}onInput(e){this.jumpAfter(200)}onBlur(){setTimeout(()=>this.hide(),1)}},D=e=>{let t=e.match(/^#([0-9a-f]{3})$/i);if(t&&t[1]){let e=t[1];return{r:parseInt(e.charAt(0),16)*17,g:parseInt(e.charAt(1),16)*17,b:parseInt(e.charAt(2),16)*17}}let n=e.match(/^#([0-9a-f]{6})$/i);if(n&&n[1]){let e=n[1];return{r:parseInt(e.slice(0,2),16),g:parseInt(e.slice(2,4),16),b:parseInt(e.slice(4,6),16)}}let r=e.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);if(r)return{r:parseInt(r[1],10),g:parseInt(r[2],10),b:parseInt(r[3],10)};let i=e.match(/^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i);return i?{r:parseInt(i[1],10),g:parseInt(i[2],10),b:parseInt(i[3],10),a:parseFloat(i[4])}:null},ie=e=>(typeof e==`string`&&(e=D(e)),e?(e.r*299+e.g*587+e.b*114)/1e3:null),ae=class{constructor(e){this.Reveal=e}render(){this.element=document.createElement(`div`),this.element.className=`backgrounds`,this.Reveal.getRevealElement().appendChild(this.element)}create(){this.element.innerHTML=``,this.element.classList.add(`no-transition`),this.Reveal.getHorizontalSlides().forEach(e=>{let n=this.createBackground(e,this.element);t(e,`section`).forEach(e=>{this.createBackground(e,n),n.classList.add(`stack`)})}),this.Reveal.getConfig().parallaxBackgroundImage?(this.element.style.backgroundImage=`url("`+this.Reveal.getConfig().parallaxBackgroundImage+`")`,this.element.style.backgroundSize=this.Reveal.getConfig().parallaxBackgroundSize,this.element.style.backgroundRepeat=this.Reveal.getConfig().parallaxBackgroundRepeat,this.element.style.backgroundPosition=this.Reveal.getConfig().parallaxBackgroundPosition,setTimeout(()=>{this.Reveal.getRevealElement().classList.add(`has-parallax-background`)},1)):(this.element.style.backgroundImage=``,this.Reveal.getRevealElement().classList.remove(`has-parallax-background`))}createBackground(e,t){let n=document.createElement(`div`);n.className=`slide-background `+e.className.replace(/present|past|future/,``);let r=document.createElement(`div`);return r.className=`slide-background-content`,n.appendChild(r),t.appendChild(n),e.slideBackgroundElement=n,e.slideBackgroundContentElement=r,this.sync(e),n}sync(e){let t=e.slideBackgroundElement,n=e.slideBackgroundContentElement,r={background:e.getAttribute(`data-background`),backgroundSize:e.getAttribute(`data-background-size`),backgroundImage:e.getAttribute(`data-background-image`),backgroundVideo:e.getAttribute(`data-background-video`),backgroundIframe:e.getAttribute(`data-background-iframe`),backgroundColor:e.getAttribute(`data-background-color`),backgroundGradient:e.getAttribute(`data-background-gradient`),backgroundRepeat:e.getAttribute(`data-background-repeat`),backgroundPosition:e.getAttribute(`data-background-position`),backgroundTransition:e.getAttribute(`data-backLine truncated
|
||||
<button class="navigate-right" aria-label="${e?`previous slide`:`next slide`}"><div class="controls-arrow"></div></button>
|
||||
<button class="navigate-up" aria-label="above slide"><div class="controls-arrow"></div></button>
|
||||
<button class="navigate-down" aria-label="below slide"><div class="controls-arrow"></div></button>`,this.Reveal.getRevealElement().appendChild(this.element),this.controlsLeft=t(n,`.navigate-left`),this.controlsRight=t(n,`.navigate-right`),this.controlsUp=t(n,`.navigate-up`),this.controlsDown=t(n,`.navigate-down`),this.controlsPrev=t(n,`.navigate-prev`),this.controlsNext=t(n,`.navigate-next`),this.controlsFullscreen=t(n,`.enter-fullscreen`),this.controlsRightArrow=this.element.querySelector(`.navigate-right`),this.controlsLeftArrow=this.element.querySelector(`.navigate-left`),this.controlsDownArrow=this.element.querySelector(`.navigate-down`)}configure(e,t){let n=e.controls===`speaker`||e.controls===`speaker-only`;this.element.style.display=e.controls&&(!n||this.Reveal.isSpeakerNotes())?`block`:`none`,this.element.setAttribute(`data-controls-layout`,e.controlsLayout),this.element.setAttribute(`data-controls-back-arrows`,e.controlsBackArrows)}bind(){let e=[`touchstart`,`click`];_&&(e=[`touchend`]),e.forEach(e=>{this.controlsLeft.forEach(t=>t.addEventListener(e,this.onNavigateLeftClicked,!1)),this.controlsRight.forEach(t=>t.addEventListener(e,this.onNavigateRightClicked,!1)),this.controlsUp.forEach(t=>t.addEventListener(e,this.onNavigateUpClicked,!1)),this.controlsDown.forEach(t=>t.addEventListener(e,this.onNavigateDownClicked,!1)),this.controlsPrev.forEach(t=>t.addEventListener(e,this.onNavigatePrevClicked,!1)),this.controlsNext.forEach(t=>t.addEventListener(e,this.onNavigateNextClicked,!1)),this.controlsFullscreen.forEach(t=>t.addEventListener(e,this.onEnterFullscreen,!1))})}unbind(){[`touchstart`,`touchend`,`click`].forEach(e=>{this.controlsLeft.forEach(t=>t.removeEventListener(e,this.onNavigateLeftClicked,!1)),this.controlsRight.forEach(t=>t.removeEventListener(e,this.onNavigateRightClicked,!1)),this.controlsUp.forEach(t=>t.removeEventListener(e,this.onNavigateUpClicked,!1)),this.controlsDown.forEach(t=>t.removeEventListener(e,this.onNavigateDownClicked,!1)),this.controlsPrev.forEach(t=>t.removeEventListener(e,this.onNavigatePrevClicked,!1)),this.controlsNext.forEach(t=>t.removeEventListener(e,this.onNavigateNextClicked,!1)),this.controlsFullscreen.forEach(t=>t.removeEventListener(e,this.onEnterFullscreen,!1))})}update(){let e=this.Reveal.availableRoutes();[...this.controlsLeft,...this.controlsRight,...this.controlsUp,...this.controlsDown,...this.controlsPrev,...this.controlsNext].forEach(e=>{e.classList.remove(`enabled`,`fragmented`),e.setAttribute(`disabled`,`disabled`)}),e.left&&this.controlsLeft.forEach(e=>{e.classList.add(`enabled`),e.removeAttribute(`disabled`)}),e.right&&this.controlsRight.forEach(e=>{e.classList.add(`enabled`),e.removeAttribute(`disabled`)}),e.up&&this.controlsUp.forEach(e=>{e.classList.add(`enabled`),e.removeAttribute(`disabled`)}),e.down&&this.controlsDown.forEach(e=>{e.classList.add(`enabled`),e.removeAttribute(`disabled`)}),(e.left||e.up)&&this.controlsPrev.forEach(e=>{e.classList.add(`enabled`),e.removeAttribute(`disabled`)}),(e.right||e.down)&&this.controlsNext.forEach(e=>{e.classList.add(`enabled`),e.removeAttribute(`disabled`)});let t=this.Reveal.getCurrentSlide();if(t){let e=this.Reveal.fragments.availableRoutes();e.prev&&this.controlsPrev.forEach(e=>{e.classList.add(`fragmented`,`enabled`),e.removeAttribute(`disabled`)}),e.next&&this.controlsNext.forEach(e=>{e.classList.add(`fragmented`,`enabled`),e.removeAttribute(`disabled`)});let n=this.Reveal.isVerticalSlide(t),r=n&&t.parentElement&&t.parentElement.querySelectorAll(`:scope > section`).length>1;n&&r?(e.prev&&this.controlsUp.forEach(e=>{e.classList.add(`fragmented`,`enabled`),e.removeAttribute(`disabled`)}),e.next&&this.controlsDown.forEach(e=>{e.classList.add(`fragmented`,`enabled`),e.removeAttribute(`disabled`)})):(e.prev&&this.controlsLeft.forEach(e=>{e.classList.add(`fragmented`,`enabled`),e.removeAttribute(`disabled`)}),e.next&&this.controlsRight.forEach(e=>{e.classList.add(`fragmented`,`enabled`),e.removeAttribute(`disabled`)}))}if(this.Reveal.getConfig().controlsTutorial){let t=this.Reveal.getIndices();!this.Reveal.hasNavigatedVertically()&&e.down?this.controlsDownArrow.classList.add(`highlight`):(this.controlsDownArrow.classList.remove(`highlight`),this.Reveal.getConfig().rtl?!this.Reveal.hasNavigatedHorizontally()&&e.left&&t.v===0?this.controlsLeftArrow.classList.add(`highlight`):this.controlsLeftArrow.classList.remove(`highlight`):!this.Reveal.hasNavigatedHorizontally()&&e.right&&t.v===0?this.controlsRightArrow.classList.add(`highlight`):this.controlsRightArrow.classList.remove(`highlight`))}}destroy(){this.unbind(),this.element.remove()}onNavigateLeftClicked(e){e.preventDefault(),this.Reveal.onUserInput(),this.Reveal.getConfig().navigationMode===`linear`?this.Reveal.prev():this.Reveal.left()}onNavigateRightClicked(e){e.preventDefault(),this.Reveal.onUserInput(),this.Reveal.getConfig().navigationMode===`linear`?this.Reveal.next():this.Reveal.right()}onNavigateUpClicked(e){e.preventDefault(),this.Reveal.onULine truncated
|
||||
`+e))});let r=document.querySelector(`head`);r&&r.insertBefore(n,r.lastChild)},ye=class{constructor(e){this.Reveal=e,this.state=`idle`,this.registeredPlugins={},this.asyncDependencies=[]}load(e,t){return this.state=`loading`,e.forEach(this.registerPlugin.bind(this)),new Promise(e=>{let n=[],r=0;if(t.forEach(e=>{(!e.condition||e.condition())&&(e.async?this.asyncDependencies.push(e):n.push(e))}),n.length){r=n.length;let t=t=>{t&&typeof t.callback==`function`&&t.callback(),--r===0&&this.initPlugins().then(e)};n.forEach(e=>{typeof e.id==`string`?(this.registerPlugin(e),t(e)):typeof e.src==`string`?ve(e.src,()=>t(e)):(console.warn(`Unrecognized plugin format`,e),t())})}else this.initPlugins().then(e)})}initPlugins(){return new Promise(e=>{let t=Object.values(this.registeredPlugins),n=t.length;if(n===0)this.loadAsync().then(e);else{let r,i=()=>{--n===0?this.loadAsync().then(e):r()},a=0;r=()=>{let e=t[a++];if(typeof e.init==`function`){let t=e.init(this.Reveal);t&&typeof t.then==`function`?t.then(i):i()}else i()},r()}})}loadAsync(){return this.state=`loaded`,this.asyncDependencies.length&&this.asyncDependencies.forEach(e=>{ve(e.src,e.callback)}),Promise.resolve()}registerPlugin(e){arguments.length===2&&typeof arguments[0]==`string`?(e=arguments[1],e.id=arguments[0]):typeof e==`function`&&(e=e());let t=e.id;typeof t==`string`?this.registeredPlugins[t]===void 0?(this.registeredPlugins[t]=e,this.state===`loaded`&&typeof e.init==`function`&&e.init(this.Reveal)):console.warn(`reveal.js: "`+t+`" plugin has already been registered`):console.warn(`Unrecognized plugin format; can't find plugin.id`,e)}hasPlugin(e){return!!this.registeredPlugins[e]}getPlugin(e){return this.registeredPlugins[e]}getRegisteredPlugins(){return this.registeredPlugins}destroy(){Object.values(this.registeredPlugins).forEach(e=>{typeof e.destroy==`function`&&e.destroy()}),this.registeredPlugins={},this.asyncDependencies=[]}},be=class{constructor(e){this.Reveal=e,this.onSlidesClicked=this.onSlidesClicked.bind(this),this.iframeTriggerSelector=null,this.mediaTriggerSelector=`[data-preview-image], [data-preview-video]`,this.stateProps=[`previewIframe`,`previewImage`,`previewVideo`,`previewFit`],this.state={}}update(){this.iframeTriggerSelector=this.Reveal.getConfig().previewLinks?`a[href]:not([data-preview-link=false]), [data-preview-link]:not(a):not([data-preview-link=false])`:`[data-preview-link]:not([data-preview-link=false])`;let e=this.Reveal.getSlidesElement().querySelectorAll(this.iframeTriggerSelector).length>0,t=this.Reveal.getSlidesElement().querySelectorAll(this.mediaTriggerSelector).length>0;e||t?this.Reveal.getSlidesElement().addEventListener(`click`,this.onSlidesClicked,!1):this.Reveal.getSlidesElement().removeEventListener(`click`,this.onSlidesClicked,!1)}createOverlay(e){this.dom=document.createElement(`div`),this.dom.classList.add(`r-overlay`),this.dom.classList.add(e),this.viewport=document.createElement(`div`),this.viewport.classList.add(`r-overlay-viewport`),this.dom.appendChild(this.viewport),this.Reveal.getRevealElement().appendChild(this.dom)}previewIframe(e){if(typeof e!=`string`)return;let t;try{t=new URL(e,document.baseURI)}catch(e){return}if(t.protocol===`file:`&&window.location.protocol!==`file:`||![`http:`,`https:`,`file:`].includes(t.protocol)&&t.href!==`about:blank`)return;this.close(),this.state={previewIframe:e},this.createOverlay(`r-overlay-preview`),this.dom.dataset.state=`loading`,this.viewport.innerHTML=`<header class="r-overlay-header">
|
||||
<a class="r-overlay-header-button r-overlay-external" target="_blank" rel="noopener"><span class="icon"></span></a>
|
||||
<button class="r-overlay-header-button r-overlay-close"><span class="icon"></span></button>
|
||||
</header>
|
||||
<div class="r-overlay-spinner"></div>
|
||||
<div class="r-overlay-content">
|
||||
<iframe></iframe>
|
||||
<small class="r-overlay-content-inner">
|
||||
<span class="r-overlay-error x-frame-error">Unable to load iframe. This is likely due to the site's policy (x-frame-options).</span>
|
||||
</small>
|
||||
</div>`;let n=this.dom.querySelector(`iframe`);n.addEventListener(`load`,e=>{this.dom.dataset.state=`loaded`},!1),n.src=t.href,this.dom.querySelector(`.r-overlay-external`).href=t.href,this.dom.querySelector(`.r-overlay-close`).addEventListener(`click`,e=>{this.close(),e.preventDefault()},!1),this.dom.querySelector(`.r-overlay-external`).addEventListener(`click`,e=>{this.close()},!1),this.Reveal.dispatchEvent({type:`previewiframe`,data:{url:e}})}previewMedia(e,t,n){if(t!==`image`&&t!==`video`){console.warn(`Please specify a valid media type to preview (image|video)`);return}this.close(),n=n||`scale-down`,this.createOverlay(`r-overlay-preview`),this.dom.dataset.state=`loading`,this.dom.dataset.previewFit=n,this.viewport.innerHTML=`<header class="r-overlay-header">
|
||||
<button class="r-overlay-header-button r-overlay-close">Esc <span class="icon"></span></button>
|
||||
</header>
|
||||
<div class="r-overlay-spinner"></div>
|
||||
<div class="r-overlay-content"></div>`;let r=this.dom.querySelector(`.r-overlay-content`);if(t===`image`){this.state={previewImage:e,previewFit:n};let t=document.createElement(`img`,{});t.src=e,r.appendChild(t),t.addEventListener(`load`,()=>{this.dom.dataset.state=`loaded`},!1),t.addEventListener(`error`,()=>{this.dom.dataset.state=`error`,r.innerHTML=`<span class="r-overlay-error">Unable to load image.</span>`},!1),this.dom.style.cursor=`zoom-out`,this.dom.addEventListener(`click`,e=>{this.close()},!1),this.Reveal.dispatchEvent({type:`previewimage`,data:{url:e}})}else if(t===`video`){this.state={previewVideo:e,previewFit:n};let t=document.createElement(`video`);t.autoplay=this.dom.dataset.previewAutoplay!==`false`,t.controls=this.dom.dataset.previewControls!==`false`,t.loop=this.dom.dataset.previewLoop===`true`,t.muted=this.dom.dataset.previewMuted===`true`,t.playsInline=!0,t.src=e,r.appendChild(t),t.addEventListener(`loadeddata`,()=>{this.dom.dataset.state=`loaded`},!1),t.addEventListener(`error`,()=>{this.dom.dataset.state=`error`,r.innerHTML=`<span class="r-overlay-error">Unable to load video.</span>`},!1),this.Reveal.dispatchEvent({type:`previewvideo`,data:{url:e}})}else throw Error(`Please specify a valid media type to preview`);this.dom.querySelector(`.r-overlay-close`).addEventListener(`click`,e=>{this.close(),e.preventDefault()},!1)}previewImage(e,t){this.previewMedia(e,`image`,t)}previewVideo(e,t){this.previewMedia(e,`video`,t)}toggleHelp(e){typeof e==`boolean`?e?this.showHelp():this.close():this.dom?this.close():this.showHelp()}showHelp(){if(this.Reveal.getConfig().help){this.close(),this.createOverlay(`r-overlay-help`);let e=`<p class="title">Keyboard Shortcuts</p>`,t=this.Reveal.keyboard.getShortcuts(),n=this.Reveal.keyboard.getBindings();e+=`<table><th>KEY</th><th>ACTION</th>`;for(let n in t)e+=`<tr><td>${n}</td><td>${t[n]}</td></tr>`;for(let t in n)n[t].key&&n[t].description&&(e+=`<tr><td>${n[t].key}</td><td>${n[t].description}</td></tr>`);e+=`</table>`,this.viewport.innerHTML=`
|
||||
<header class="r-overlay-header">
|
||||
<button class="r-overlay-header-button r-overlay-close">Esc <span class="icon"></span></button>
|
||||
</header>
|
||||
<div class="r-overlay-content">
|
||||
<div class="r-overlay-help-content">${e}</div>
|
||||
</div>
|
||||
`,this.dom.querySelector(`.r-overlay-close`).addEventListener(`click`,e=>{this.close(),e.preventDefault()},!1),this.Reveal.dispatchEvent({type:`showhelp`})}}isOpen(){return!!this.dom}close(){return this.dom?(this.dom.remove(),this.dom=null,this.state={},this.Reveal.dispatchEvent({type:`closeoverlay`}),!0):!1}getState(){return this.state}setState(e){this.stateProps.every(t=>this.state[t]===e[t])||(e.previewIframe?this.previewIframe(e.previewIframe):e.previewImage?this.previewImage(e.previewImage,e.previewFit):e.previewVideo?this.previewVideo(e.previewVideo,e.previewFit):this.close())}onSlidesClicked(e){let t=e.target,n=t.closest(this.iframeTriggerSelector),r=t.closest(this.mediaTriggerSelector);if(n){if(e.metaKey||e.shiftKey||e.altKey)return;let t=n.getAttribute(`data-preview-link`),r=typeof t==`string`&&t.startsWith(`http`)?t:n.getAttribute(`href`);r&&(this.previewIframe(r),e.preventDefault())}else if(r){if(r.hasAttribute(`data-preview-image`)){let t=r.dataset.previewImage||r.getAttribute(`src`);t&&(this.previewImage(t,r.dataset.previewFit),e.preventDefault())}else if(r.hasAttribute(`data-preview-video`)){let t=r.dataset.previewVideo||r.getAttribute(`src`);if(!t){let e=r.querySelector(`source`);e&&(t=e.getAttribute(`src`))}t&&(this.previewVideo(t,r.dataset.previewFit),e.preventDefault())}}}destroy(){this.close()}},F=40,I=1.03,xe=class{constructor(e){this.Reveal=e,this.touchStartX=0,this.touchStartY=0,this.touchStartCount=0,this.touchCaptured=!1,this.activePointers=new Map,this.onPointerDown=this.onPointerDown.bind(this),this.onPointerMove=this.onPointerMove.bind(this),this.onPointerUp=this.onPointerUp.bind(this),this.onPointerCancel=this.onPointerCancel.bind(this),this.onTouchStart=this.onTouchStart.bind(this),this.onTouchMove=this.onTouchMove.bind(this),this.onTouchEnd=this.onTouchEnd.bind(this)}bind(){let e=this.Reveal.getRevealElement();`onpointerdown`in window?(e.addEventListener(`pointerdown`,this.onPointerDown,!1),e.addEventListener(`pointermove`,this.onPointerMove,!1),e.addEventListener(`pointerup`,this.onPointerUp,!1),e.addEventListener(`pointercancel`,this.onPointerCancel,!1)):(e.addEventListener(`touchstart`,this.onTouchStart,!1),e.addEventListener(`touchmove`,this.onTouchMove,!1),e.addEventListener(`touchend`,this.onTouchEnd,!1))}unbind(){let e=this.Reveal.getRevealElement();e.removeEventListener(`pointerdown`,this.onPointerDown,!1),e.removeEventListener(`pointermove`,this.onPointerMove,!1),e.removeEventListener(`pointerup`,this.onPointerUp,!1),e.removeEventListener(`pointercancel`,this.onPointerCancel,!1),e.removeEventListener(`touchstart`,this.onTouchStart,!1),e.removeEventListener(`touchmove`,this.onTouchMove,!1),e.removeEventListener(`touchend`,this.onTouchEnd,!1)}isSwipePrevented(e){if(a(e,`video[controls], audio[controls]`))return!0;for(;e&&typeof e.hasAttribute==`function`;){if(e.hasAttribute(`data-prevent-swipe`))return!0;e=e.parentNode}return!1}isViewportZoomed(){if(!window.visualViewport||typeof window.visualViewport.scale!=`number`)return!1;let e=window.visualViewport.scale;return this.visualViewportBaseScale=this.visualViewportBaseScale===void 0?e:Math.min(this.visualViewportBaseScale,e),e/this.visualViewportBaseScale>I}onTouchStart(e){if(this.touchCaptured=!1,this.isSwipePrevented(e.target))return!0;this.touchStartX=e.touches[0].clientX,this.touchStartY=e.touches[0].clientY,this.touchStartCount=e.touches.length}onTouchMove(e){if(this.isSwipePrevented(e.target))return!0;let t=this.Reveal.getConfig();if(this.touchCaptured)_&&e.preventDefault();else{this.Reveal.onUserInput(e);let n=e.touches[0].clientX,r=e.touches[0].clientY;if(e.touches.length===1&&this.touchStartCount!==2){let i=this.Reveal.availableRoutes({includeFragments:!0}),a=n-this.touchStartX,o=r-this.touchStartY;a>F&&Math.abs(a)>Math.abs(o)?(this.touchCaptured=!0,t.navigationMode===`linear`?t.rtl?this.Reveal.next():this.Reveal.prev():this.Reveal.left()):a<-40&&Math.abs(a)>Math.abs(o)?(this.touchCaptured=!0,t.navigationMode===`linear`?t.rtl?this.Reveal.prev():this.Reveal.next():this.Reveal.right()):o>F&&i.up?(this.touchCaptured=!0,t.navigationMode===`linear`?this.Reveal.prev():this.Reveal.up()):o<-40&&i.down&&(this.touchCaptured=!0,t.navigationMode===`linear`?this.Reveal.next():this.Reveal.down()),t.embedded?(this.touchCaptured||this.Reveal.isVerticalSlide())&&e.preventDefault():e.preventDefault()}}}onTouchEnd(e){this.touchCaptured&&!this.Reveal.slideContent.isAllowedToPlayAudio()&&this.Reveal.startEmbeddedContent(this.Reveal.getCurrentSlide()),this.touchCaptured=!1}getActiveTouches(){return Array.from(this.activePointers.values(),e=>({clientX:e.clientX,clientY:e.clientY}))}onPointerDown(e){e.pointerType===`touch`&&(this.activePointers.set(e.pointerId,e),e.touches=this.getActiveTouches(),this.onTouchStart(e))}onPointerMove(e){e.pointerType===`touch`&&(this.activePointers.set(e.pointerId,e),e.touches=this.getActiveTouches(),this.onTouchMove(e))}onPointerUp(e){e.pointerType===`touch`&&(this.activePointers.delete(e.pointerId),Line truncated
|
||||
`):null}destroy(){this.element.remove()}},we=class{constructor(e,t){this.diameter=100,this.diameter2=this.diameter/2,this.thickness=6,this.playing=!1,this.progress=0,this.progressOffset=1,this.container=e,this.progressCheck=t,this.canvas=document.createElement(`canvas`),this.canvas.className=`playback`,this.canvas.width=this.diameter,this.canvas.height=this.diameter,this.canvas.style.width=this.diameter2+`px`,this.canvas.style.height=this.diameter2+`px`,this.context=this.canvas.getContext(`2d`),this.container.appendChild(this.canvas),this.render()}setPlaying(e){let t=this.playing;this.playing=e,!t&&this.playing?this.animate():this.render()}animate(){let e=this.progress;this.progress=this.progressCheck(),e>.8&&this.progress<.2&&(this.progressOffset=this.progress),this.render(),this.playing&&requestAnimationFrame(this.animate.bind(this))}render(){let e=this.playing?this.progress:0,t=this.diameter2-this.thickness,n=this.diameter2,r=this.diameter2;this.progressOffset+=(1-this.progressOffset)*.1;let i=-Math.PI/2+Math.PI*2*e,a=-Math.PI/2+this.progressOffset*(Math.PI*2);this.context.save(),this.context.clearRect(0,0,this.diameter,this.diameter),this.context.beginPath(),this.context.arc(n,r,t+4,0,Math.PI*2,!1),this.context.fillStyle=`rgba( 0, 0, 0, 0.4 )`,this.context.fill(),this.context.beginPath(),this.context.arc(n,r,t,0,Math.PI*2,!1),this.context.lineWidth=this.thickness,this.context.strokeStyle=`rgba( 255, 255, 255, 0.2 )`,this.context.stroke(),this.playing&&(this.context.beginPath(),this.context.arc(n,r,t,a,i,!1),this.context.lineWidth=this.thickness,this.context.strokeStyle=`#fff`,this.context.stroke()),this.context.translate(n-14,r-14),this.playing?(this.context.fillStyle=`#fff`,this.context.fillRect(0,0,10,28),this.context.fillRect(18,0,10,28)):(this.context.beginPath(),this.context.translate(4,0),this.context.moveTo(0,0),this.context.lineTo(24,14),this.context.lineTo(0,28),this.context.fillStyle=`#fff`,this.context.fill()),this.context.restore()}on(e,t){this.canvas.addEventListener(e,t,!1)}off(e,t){this.canvas.removeEventListener(e,t,!1)}destroy(){this.playing=!1,this.canvas.parentNode&&this.container.removeChild(this.canvas)}},Te={width:960,height:700,margin:.04,minScale:.2,maxScale:2,controls:!0,controlsTutorial:!0,controlsLayout:`bottom-right`,controlsBackArrows:`faded`,progress:!0,slideNumber:!1,showSlideNumber:`all`,hashOneBasedIndex:!1,hash:!1,respondToHashChanges:!0,jumpToSlide:!0,history:!1,keyboard:!0,keyboardCondition:null,disableLayout:!1,overview:!0,center:!0,touch:!0,loop:!1,rtl:!1,navigationMode:`default`,shuffle:!1,fragments:!0,fragmentInURL:!0,embedded:!1,help:!0,pause:!0,showNotes:!1,showHiddenSlides:!1,autoPlayMedia:null,preloadIframes:null,mouseWheel:!1,previewLinks:!1,viewDistance:3,mobileViewDistance:2,display:`block`,hideInactiveCursor:!0,hideCursorTime:5e3,sortFragmentsOnSync:!0,autoAnimate:!0,autoAnimateMatcher:null,autoAnimateEasing:`ease`,autoAnimateDuration:1,autoAnimateUnmatched:!0,autoAnimateStyles:[`opacity`,`color`,`background-color`,`padding`,`font-size`,`line-height`,`letter-spacing`,`border-width`,`border-color`,`border-radius`,`outline`,`outline-offset`],autoSlide:0,autoSlideStoppable:!0,autoSlideMethod:null,defaultTiming:null,postMessage:!0,postMessageEvents:!1,focusBodyOnPageVisibilityChange:!0,transition:`slide`,transitionSpeed:`default`,backgroundTransition:`fade`,parallaxBackgroundImage:``,parallaxBackgroundSize:``,parallaxBackgroundRepeat:``,parallaxBackgroundPosition:``,parallaxBackgroundHorizontal:null,parallaxBackgroundVertical:null,view:null,scrollLayout:`full`,scrollSnap:`mandatory`,scrollProgress:`auto`,scrollActivationWidth:435,pdfMaxPagesPerSlide:1/0,pdfSeparateFragments:!0,pdfPageHeightOffset:-1,dependencies:[],plugins:[]},Ee=`6.0.2`;function z(a,s){arguments.length<2&&(s=arguments[0],a=document.querySelector(`.reveal`));let l={},f={},p=!1,m=!1,h,_,v,y,b={hasNavigatedHorizontally:!1,hasNavigatedVertically:!1},x=[],S=1,T={layout:``,overview:``},D={},ie=`idle`,O=0,k,se=0,ce=-1,A=!1,j=new ee(l),M=new ne(l),N=new re(l),ve=new oe(l),F=new ae(l),I=new le(l),L=new ue(l),R=new de(l),z=new fe(l),B=new pe(l),V=new me(l),H=new he(l),U=new ge(l),De=new _e(l),W=new ye(l),G=new be(l),K=new Se(l),Oe=new xe(l),q=new Ce(l);function ke(e){if(!a)throw`Unable to find presentation root (<div class="reveal">).`;if(p)throw`Reveal.js has already been initialized.`;if(p=!0,D.wrapper=a,D.slides=a.querySelector(`.slides`),!D.slides)throw`Unable to find slides container (<div class="slides">).`;return f=P(P(P(P(P({},Te),f),s),e),u()),/print-pdf/gi.test(window.location.search)&&(f.view=`print`),Ae(),window.addEventListener(`load`,Y,!1),W.load(f.plugins,f.dependencies).then(je),new Promise(e=>l.on(`ready`,e))}function Ae(){f.embedded===!0?D.viewport=o(a,`.reveal-viewport`)||a:(D.viewport=document.body,document.documentElement.classList.add(`reveal-full-page`)),D.viewport.classList.add(`reveal-viewport`)}function je(){p!==!1&&(m=!0,Ne(),Pe(),Ve(),ze(),Be(),vt(),He(),F.update(!0),Me(),VLine truncated
|
||||
* Anime.js - UMD minified bundle
|
||||
* @version v4.5.0
|
||||
* @license MIT
|
||||
* @copyright 2026 - Julian Garnier
|
||||
*/
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).anime={})}(this,function(t){"use strict";const e="undefined"!=typeof window,s=e?window:null,i=e?document:null,r={replace:0,none:1,blend:2},n=Symbol(),o=Symbol(),a=Symbol(),l=Symbol(),h=Symbol(),d=1e-11,c=1e12,u=1e3,p="",m=[],f=(()=>{const t=new Map;return t.set("x","translateX"),t.set("y","translateY"),t.set("z","translateZ"),t})(),g=["perspective","translateX","translateY","translateZ","rotate","rotateX","rotateY","rotateZ","scale","scaleX","scaleY","scaleZ","skew","skewX","skewY"],y=g.reduce((t,e)=>({...t,[e]:e+"("}),{}),_=()=>{},b=/\)\s*[-.\d]/,v=/(^#([\da-f]{3}){1,2}$)|(^#([\da-f]{4}){1,2}$)/i,T=/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i,x=/rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(-?\d+|-?\d*.\d+)\s*\)/i,S=/hsl\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*\)/i,w=/hsla\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)\s*\)/i,$=/[-+]?\d*\.?\d+(?:e[-+]?\d)?/gi,C=/^([-+]?\d*\.?\d+(?:e[-+]?\d+)?)([a-z]+|%)$/i,E=/([a-z])([A-Z])/g,k=/(\*=|\+=|-=)/,N=/var\(\s*(--[\w-]+)(?:\s*,\s*([^)]+))?\s*\)/,D={id:null,keyframes:null,playbackEase:null,playbackRate:1,frameRate:240,loop:0,reversed:!1,alternate:!1,autoplay:!0,persist:!1,duration:u,delay:0,loopDelay:0,ease:"out(2)",composition:r.replace,modifier:t=>t,onBegin:_,onBeforeUpdate:_,onUpdate:_,onLoop:_,onPause:_,onComplete:_,onRender:_},A={current:null,root:i},I={defaults:D,precision:4,timeScale:1,tickThreshold:200,editor:null},R={version:"4.5.0",engine:null};e&&(s.AnimeJS||(s.AnimeJS=[]),s.AnimeJS.push(R));const L=t=>t.replace(E,"$1-$2").toLowerCase(),B=(t,e)=>0===t.indexOf(e),P=Date.now,F=Array.isArray,M=t=>t&&t.constructor===Object,V=t=>"number"==typeof t&&!isNaN(t),O=t=>"string"==typeof t,z=t=>"function"==typeof t,H=t=>void 0===t,X=t=>H(t)||null===t,Y=t=>e&&t instanceof SVGElement,W=t=>v.test(t),U=t=>B(t,"rgb"),q=t=>B(t,"hsl"),j=t=>!I.defaults.hasOwnProperty(t),G=["opacity","rotate","overflow","color"],Z=t=>O(t)?parseFloat(t):t,Q=Math.pow,J=Math.sqrt,K=Math.sin,tt=Math.cos,et=Math.abs,st=Math.exp,it=Math.ceil,rt=Math.floor,nt=Math.asin,ot=Math.max,at=Math.atan2,lt=Math.PI,ht=Math.round,dt=(t,e,s)=>t<e?e:t>s?s:t,ct=(t,e)=>{if(e<0)return t;if(!e)return ht(t);const s=10**e;return ht(t*s)/s},ut=(t,e)=>F(e)?e.reduce((e,s)=>et(s-t)<et(e-t)?s:e):e?ht(t/e)*e:t,pt=(t,e,s)=>1===s?e:0===s?t:t+(e-t)*s,mt=t=>t===1/0?c:t===-1/0?-c:t,ft=t=>t<=d?d:mt(ct(t,11)),gt=t=>F(t)?[...t]:t,yt=(t,e)=>{const s={...t};for(let i in e){const r=t[i];s[i]=H(r)?e[i]:r}return s},_t=(t,e,s,i="_prev",r="_next")=>{let n=t._head,o=r;for(s&&(n=t._tail,o=i);n;){const t=n[o];e(n),n=t}},bt=(t,e,s="_prev",i="_next")=>{const r=e[s],n=e[i];r?r[i]=n:t._head=n,n?n[s]=r:t._tail=r,e[s]=null,e[i]=null},vt=(t,e,s,i="_prev",r="_next")=>{let n=t._tail;for(;n&&s&&s(n,e);)n=n[i];const o=n?n[r]:t._head;n?n[r]=e:t._head=e,o?o[i]=e:t._tail=e,e[i]=n,e[r]=o},Tt=t=>{let e=p;for(let s=0,i=g.length;s<i;s++){const i=g[s],r=t[i];if(void 0!==r){if("translateX"===i){const i=t.translateY;if(void 0!==i){const n=t.translateZ;void 0!==n?(e+=`translate3d(${r},${i},${n}) `,s+=2):(e+=`translate(${r},${i}) `,s+=1);continue}}if("scaleX"===i&&void 0===t.scale){const i=t.scaleY;if(void 0!==i){const n=t.scaleZ;void 0!==n?(e+=`scale3d(${r},${i},${n}) `,s+=2):(e+=`scale(${r},${i}) `,s+=1);continue}}e+=`${y[i]}${r}) `}"rotateZ"===i&&void 0!==t.rotate3d&&(e+=`rotate3d(${t.rotate3d}) `)}return void 0!==t.matrix&&(e+=`matrix(${t.matrix}) `),void 0!==t.matrix3d&&(e+=`matrix3d(${t.matrix3d}) `),e},xt=[];function St(t,e){if(!t)return null;const s=xt.length;t:for(let i=0;i<s;i++){const s=xt[i];if(s.detect&&!s.detect(t))continue;const r=s.targetAdapters;for(let s=0,i=r.length;s<i;s++){const i=r[s];if(i.detect(t)){const s=i.props[e];if(s&&(!s.gate||s.gate(t)))return s;break t}}}for(let i=0;i<s;i++){const s=xt[i];if(s.detect&&!s.detect(t))continue;const r=s.propertyResolvers;for(let s=0,i=r.length;s<i;s++){const i=r[s](t,e);if(i)return i}}return null}const wt=(t,e,s)=>(s<0&&(s+=1),s>1&&(s-=1),s<1/6?t+6*(e-t)*s:s<.5?e:s<2/3?t+(e-t)*(2/3-s)*6:t),$t=(t,e)=>H(t)?e:t,Ct=(t,e)=>{const s=t.match(N),i=e[o]?e:document.documentElement;let r=getComputedStyle(i)?.getPropertyValue(s[1]);return r&&r.trim()!==p||!s[2]||(r=s[2].trim()),r||0},Et=(t,e,s,i,r,n)=>{if(z(t)){if(!r){const r=t(e,s,i,n);return isNaN(+r)?r||0:+r}const o=()=>{const r=t(e,s,i,n);return isNaN(+r)?r||0:+r};return r.func=o,o()}if(O(t)&&B(t,"var(")){if(!r)return Ct(t,e);const s=()=>Ct(t,e);return r.func=s,s()}return t},kt=(t,e)=>t[o]?t[a]&&((t,e)=>{if(G.includes(e))return!1;if(t.getAttribute(e)||e in t){if("scale"===e){const e=t.parentNode;return e&&"filter"===e.tagName}return!0}})(t,e)?1:g.includes(e)||f.get(e)?3:B(e,"--")?4:e in t.style?2:e in t?0:1:0,Nt=(t,e,s)=>{const i=t.style[e];i&&s&&(s[e]=i);const r=i||getComputedStyle(t[Line truncated
|
||||
</script><script>(() => {
|
||||
const {createTimeline, stagger} = anime;
|
||||
const reduced = matchMedia('(prefers-reduced-motion: reduce)');
|
||||
const printMode = location.search.includes('print-pdf');
|
||||
const seen = new Set(), timelines = new Map(); let motion = !reduced.matches;
|
||||
const motionButton = document.querySelector('#motion');
|
||||
// Explicit paused instances: Reveal owns slide navigation; one clock owns motion.
|
||||
// Targets are dedicated motion wrappers, not positioned/rotated diagram parts.
|
||||
document.querySelectorAll('.slides>section').forEach(slide=>{
|
||||
const targets=[...slide.querySelectorAll('[data-motion]')];
|
||||
if(!targets.length)return;
|
||||
const tl=createTimeline({autoplay:false,defaults:{duration:300,ease:'outCubic'}});
|
||||
if(slide.id==='sequence') {
|
||||
['observe','interpret','act'].forEach((label,i)=>{
|
||||
tl.label(label,i*180).add(targets[i],{opacity:[0,1],translateY:[12,0]},label);
|
||||
});
|
||||
} else tl.add(targets,{opacity:[0,1],translateY:[12,0],delay:stagger(70),duration:400},0);
|
||||
tl.seek(tl.duration);tl.pause();timelines.set(slide,tl);
|
||||
});
|
||||
const allowed=()=>motion&&!reduced.matches&&!printMode&&!Reveal.isOverview();
|
||||
function settle() {
|
||||
timelines.forEach(tl=>{tl.pause();tl.seek(tl.duration);});
|
||||
}
|
||||
function replay() {
|
||||
settle();const tl=timelines.get(Reveal.getCurrentSlide());
|
||||
if(!tl||!allowed())return false;
|
||||
tl.seek(0);tl.play();return true;
|
||||
}
|
||||
function seek(timeMs) {
|
||||
if(!Number.isFinite(timeMs))throw new TypeError('seek requires finite milliseconds');
|
||||
const tl=timelines.get(Reveal.getCurrentSlide());
|
||||
if(!tl)return false;
|
||||
tl.pause();tl.seek(allowed()?Math.max(0,Math.min(timeMs,tl.duration)):tl.duration);
|
||||
return true;
|
||||
}
|
||||
function run() {
|
||||
settle();const slide=Reveal.getCurrentSlide();
|
||||
if(!slide||seen.has(slide.id)||!allowed())return;
|
||||
seen.add(slide.id);replay();
|
||||
}
|
||||
function motionLabel(){motionButton.textContent=motion&&!reduced.matches?'Motion on':'Motion off';motionButton.setAttribute('aria-pressed',String(motion&&!reduced.matches));}
|
||||
motionButton.onclick=()=>{motion=!motion;settle();motionLabel();};
|
||||
reduced.addEventListener('change',()=>{if(reduced.matches)settle();motionLabel();});motionLabel();
|
||||
addEventListener('beforeprint',settle);
|
||||
const fs=document.querySelector('#fullscreen');
|
||||
async function fullscreen(){try{if(document.fullscreenElement)await document.exitFullscreen();else await document.documentElement.requestFullscreen();}catch{fs.textContent='Fullscreen unavailable';}}
|
||||
fs.onclick=fullscreen;
|
||||
document.addEventListener('fullscreenchange',()=>{fs.textContent=document.fullscreenElement?'Exit fullscreen':'Enter fullscreen';fs.setAttribute('aria-pressed',String(!!document.fullscreenElement));});
|
||||
document.addEventListener('keydown',e=>{if(e.key.toLowerCase()==='f'&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&!e.target.matches('input,textarea,[contenteditable]')){e.preventDefault();e.stopPropagation();fullscreen();}},true);
|
||||
document.querySelectorAll('[data-node]').forEach(node=>{
|
||||
function select(){const was=node.getAttribute('aria-pressed')==='true';document.querySelectorAll('[data-node]').forEach(n=>n.setAttribute('aria-pressed','false'));node.setAttribute('aria-pressed',String(!was));document.querySelector('#detail').textContent=was?'Select a source to reveal its role. Tab + Enter or Space also works.':node.dataset.detail;}
|
||||
node.addEventListener('click',select);
|
||||
node.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();e.stopPropagation();select();}});
|
||||
});
|
||||
Reveal.initialize({width:1240,height:700,margin:.08,center:false,hash:true,transition:'none',backgroundTransition:'none',slideNumber:'c/t',view:'slide',keyboard:{70:null},autoAnimate:false}).then(()=>{
|
||||
if(!location.hash){try{const i=Number(localStorage.getItem('presentation-html-demo-slide'));if(Number.isInteger(i)&&i>=0&&i<Reveal.getTotalSlides())Reveal.slide(i);}catch{}}
|
||||
run(); window.deckReady=true;
|
||||
});
|
||||
Reveal.on('slidechanged',()=>{try{localStorage.setItem('presentation-html-demo-slide',String(Reveal.getIndices().h));}catch{}run();});
|
||||
Reveal.on('overviewshown',settle);
|
||||
document.addEventListener('visibilitychange',()=>{if(document.hidden)settle();});
|
||||
window.presentationQA={settle,seek,replay,reduced:()=>reduced.matches,active:()=>[...timelines.values()].filter(tl=>!tl.paused&&!tl.completed).length};
|
||||
})();
|
||||
</script>
|
||||
<!-- THIRD-PARTY NOTICES
|
||||
Copyright (C) 2011-2026 Hakim El Hattab, http://hakim.se, and reveal.js contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2025 Julian Garnier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans) WorkSans-Italic[wght].ttf: Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
—————————————————————————————-
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
—————————————————————————————-
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting — in part or in whole — any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
-->
|
||||
</body></html>
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"passed": true,
|
||||
"checks": [
|
||||
"deterministic seek",
|
||||
"finite input",
|
||||
"replay",
|
||||
"departure",
|
||||
"revisit",
|
||||
"overview",
|
||||
"reduced motion",
|
||||
"print",
|
||||
"SVG draw",
|
||||
"SVG motion path",
|
||||
"grid stagger"
|
||||
],
|
||||
"errors": [],
|
||||
"requests": []
|
||||
}
|
||||
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 68 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"file": "/opt/data/work/presentation-html-skill/dist/presentation-html-demo-0.1.0.html",
|
||||
"sha256": "c81f4f77250ee50973c97d12d22172d9e2cd6f97a1e699e960b491a0aa1581b0",
|
||||
"file": "/opt/data/channels/skills-public/repositories/presentation-html/dist/presentation-html-demo-0.3.0.html",
|
||||
"sha256": "f9e7ea2a327ea85c34db5c90332633a77bca647d256223918c6d79f1a0723015",
|
||||
"runs": [
|
||||
{
|
||||
"reducedMotion": "no-preference",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "presentation-html-skill",
|
||||
"version": "0.1.0",
|
||||
"version": "0.3.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "presentation-html-skill",
|
||||
"version": "0.1.0",
|
||||
"version": "0.3.0",
|
||||
"dependencies": {
|
||||
"@fontsource/work-sans": "5.2.8",
|
||||
"animejs": "4.5.0",
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
{
|
||||
"name": "presentation-html-skill",
|
||||
"version": "0.1.0",
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"scripts": {"build":"python3 scripts/build.py", "test":"npm run build && node scripts/test.mjs && python3 scripts/build-mechanism.py && node scripts/test-mechanism.mjs"},
|
||||
"devDependencies": {"@playwright/test":"1.64.1"},
|
||||
"dependencies": {"reveal.js":"6.0.2","animejs":"4.5.0","@fontsource/work-sans":"5.2.8"}
|
||||
"scripts": {
|
||||
"build": "python3 scripts/build.py",
|
||||
"test": "npm run build && node scripts/test.mjs && node scripts/test-motion.mjs && python3 scripts/build-mechanism.py && node scripts/test-mechanism.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.64.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"reveal.js": "6.0.2",
|
||||
"animejs": "4.5.0",
|
||||
"@fontsource/work-sans": "5.2.8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# Catalog patterns: adopt behavior, not incompatible code
|
||||
|
||||
The Hermes Skills Hub search field returned **three** results for `animejs` on 2026-10-23. All three source packages were downloaded and inspected locally. These are source-inspection verdicts, not claims that the peer applications were executed.
|
||||
|
||||
| Source | Relative verdict | Useful advantage | Limitation vs presentation-html |
|
||||
|---|---|---|---|
|
||||
| presentation-html baseline 563aaf4 | Reference | Offline Reveal + pinned Anime v4, accessible nodes, mechanism demo and browser QA | Entry timelines were disposable; no deterministic seek/replay regression suite or compact SVG/grid recipes |
|
||||
| [ClawHub animejs 1.0.0](https://clawhub.ai/skills/animejs) | Specialist: stronger clock contract | Explicit paused-instance registry and externally driven seeking | HyperFrames-specific; basic/timeline snippets use v3 `anime()` / `anime.timeline()` despite a v4 CDN URL; no portable Reveal deck |
|
||||
| [freshtechbro/claudedesignskills](https://github.com/freshtechbro/claudedesignskills/tree/1da73febff0c3e1dfefc07f8a5ef8f7d1dfdb6cd/plugins/individual/animejs/skills/animejs) | Specialist: broader choreography recipes | Timeline presets and spatial stagger guidance | Skill and Python timeline generator emit legacy v3 syntax; web hero/modal/loader presets are not a presentation narrative |
|
||||
| [bowtiedswan/animejs-skills](https://github.com/bowtiedswan/animejs-skills/tree/cf2bed8d6654a6cfd02d3ff11b74ff3a7a7631e2) | Specialist: broader v4 API reference | SVG drawing/motion paths, grid stagger, scope lifecycle and seek API coverage | Not an offline deck builder; unpinned CDN examples and some namespace claims must be checked against the installed UMD runtime |
|
||||
|
||||
## Adopted capabilities
|
||||
|
||||
| Capability | Best evidence | Native destination | Excluded | Acceptance gate |
|
||||
|---|---|---|---|---|
|
||||
| Explicit animation ownership and deterministic seeking | ClawHub SKILL.md contract | assets/app.js: paused timeline Map, seek/replay/settle | HyperFrames registry/runtime, GSAP replacement and wall-clock callbacks | Forward → backward → same-time DOM equality; active clock paused |
|
||||
| Meaningful named beats | Freshtechbro timeline guidance and timeline_builder.py | assets/app.js sequence labels; references/choreography.md | v3 generated code, modal/loader choreography as deck default | Real v4 playback plus replay/departure/revisit tests |
|
||||
| Vector flow and spatial order recipes | Bowtiedswan SKILL.md and references/examples.md | references/choreography.md + scripts/test-motion.mjs | Unpinned CDN, scroll-driven slide navigation, perpetual waves | SVG draw/path and six-cell grid execute offline against vendored 4.5.0 |
|
||||
| Cleanup under state changes | Explicit-clock contract plus reference lifecycle gaps | assets/app.js overview/visibility settling | Global engine pause affecting unrelated scenes; blanket transform removal | Overview/reduced-motion/print regression checks; final state retained |
|
||||
|
||||
Reveal.js 6.0.2, Anime.js 4.5.0, the dependency-free Python builder, native DOM/SVG and single-file offline delivery remain unchanged. No new runtime dependencies. Existing assembly/inspection behavior is retained rather than replaced. Accessible motion remains an enhancement; source availability does not establish a missing capability or justify removing existing QA.
|
||||
|
||||
## Provenance and reuse
|
||||
|
||||
- ClawHub source archive: `https://clawhub.ai/api/v1/download?slug=animejs&version=1.0.0`; package includes SKILL.md, skill-card.md and _meta.json. Registry metadata declares MIT-0; no license file was included. SKILL.md SHA-256: `1ca46135880d2475c7149d5e54fc41ce3d85d63809fddfdb4f4ded649120f8de`.
|
||||
- Freshtechbro commit: `1da73febff0c3e1dfefc07f8a5ef8f7d1dfdb6cd`; repository LICENSE is MIT (2025 Claude Skills Project). Inspected package path: `plugins/individual/animejs/skills/animejs`; bundled copies are not counted as additional independent skills.
|
||||
- Bowtiedswan commit: `cf2bed8d6654a6cfd02d3ff11b74ff3a7a7631e2`; no root LICENSE file was present. Treat as inspection-only, not permission to redistribute its package.
|
||||
|
||||
No peer source files, generators, artwork or installation scripts are redistributed or run. New examples are original implementations using the already-licensed Anime API. Runtime tests establish our recipes' compatibility, not correctness of every upstream example. HyperFrames itself was not installed or validated. Morphing, drag/scroll interaction and split text remain optional techniques, not newly claimed working features.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Anime v4 choreography recipes
|
||||
|
||||
Use these patterns to explain causality, spatial order or state change. Keep Reveal for navigation and the pinned Anime UMD bundle for motion. These are original examples, not copied peer implementations. Read this before adapting catalog examples with `anime(...)`, `anime.timeline(...)`, `easing`, `anime.path` or `anime.setDashoffset`: those are v3 APIs, not our v4 contract.
|
||||
|
||||
## Motion brief before code
|
||||
|
||||
For each slide record: stable objects, meaningful change, named beats, final readable state, previous/next behavior, and reduced-motion equivalent. Motion must carry information, not merely fade in a panel. A dependency can draw from cause to effect; a comparison can preserve object identity; an assembly can separate and rejoin parts. Keep a plain title legible while its explanatory diagram changes. Do not impose the sample's three beats or six slides on a real deck.
|
||||
|
||||
## One clock, explicit instances
|
||||
|
||||
Create timelines synchronously with `autoplay:false`. Register them by slide; initialize inactive slides to their final state. Start only the active slide's timeline. Avoid randomness, asynchronous DOM mutations and side-effect callbacks in seekable scenes. Wait for fonts/assets before constructing geometry-sensitive timelines. Layout changes require rebuilding that geometry, not assuming cached path measurements remain correct.
|
||||
|
||||
The starter provides `presentationQA.seek(milliseconds)`, `replay()`, `settle()` and `active()`. Seeking pauses, clamps to the finite timeline and must reproduce the same DOM state after forward/backward seeks. Replay is explicit: ordinary revisits remain complete. Reduced motion and overview reject replay and force the final state. Departure, overview, page hiding and print settle timelines. The API is a QA/authoring surface, not a request to bolt extra controls onto the delivered presentation.
|
||||
|
||||
Use dedicated motion wrappers. Do not overwrite a diagram part's structural `transform` or let Reveal Auto-Animate own the same properties. For an SPA/embedded deck, additionally detach event listeners and call `revert()` on its owned timelines when unmounting; the single-document starter does not implement component mounting.
|
||||
|
||||
## Semantic timeline positions
|
||||
|
||||
```js
|
||||
const tl = anime.createTimeline({autoplay:false,
|
||||
defaults:{duration:300, ease:'outCubic'}});
|
||||
tl.label('cause', 0)
|
||||
.add(cause, {opacity:[0,1]}, 'cause')
|
||||
.label('effect', 240)
|
||||
.add(effect, {opacity:[0,1]}, 'effect');
|
||||
```
|
||||
|
||||
Labels express narrative beats; explicit overlap is a decision, not arbitrary delay accumulation. Keep descriptions in normal HTML; never make an essential conclusion available only during a transition.
|
||||
|
||||
## SVG connections and motion paths
|
||||
|
||||
Use the UMD `anime.svg` namespace. Keep the path in an SVG coordinate space shared with the animated marker. A marker starting at the path origin avoids accidental double offsets.
|
||||
|
||||
```js
|
||||
const edge = document.querySelector('#flow-path');
|
||||
const trace = anime.animate(anime.svg.createDrawable(edge), {
|
||||
draw:['0 0','0 1'], duration:600, ease:'linear', autoplay:false
|
||||
});
|
||||
const flow = anime.animate(document.querySelector('#packet'), {
|
||||
...anime.svg.createMotionPath(edge), duration:600,
|
||||
ease:'linear', autoplay:false
|
||||
});
|
||||
```
|
||||
|
||||
Register **both** instances with the owner; settle them to full duration on departure/reduced motion/print. Animate a flow marker only when travel encodes direction or latency. Do not animate essential labels away. Avoid arbitrary path morphs: establish corresponding shapes, direction and final labels first. No automatic conversion of incompatible v3 `d` interpolation snippets.
|
||||
|
||||
## Spatial stagger
|
||||
|
||||
```js
|
||||
const pulse = anime.animate(cells, {
|
||||
opacity:[0.25,1], delay:anime.stagger(60,{grid:[3,2],from:'center'}),
|
||||
duration:240, ease:'linear', autoplay:false
|
||||
});
|
||||
```
|
||||
|
||||
The grid is `[columns, rows]`; supply targets in row-major visual order and match `columns * rows` to the actual node count. Spatial order must mean something (e.g. propagation from an origin), not decorate a list. Choose opacity/transform rather than animating layout width/height across many nodes. Restore full readability when motion is disabled.
|
||||
|
||||
## Acceptance
|
||||
|
||||
Run `npm test` through `terminal` in the copied package. `scripts/test-motion.mjs` covers deterministic seeking, replay, departure/revisit, overview, live reduced-motion changes, print, no network and SVG/grid primitives against the actual vendored v4 bundle. Inspect meaningful intermediate frames as well as final screenshots. Library existence is not compatibility proof; keep these checks when upgrading pins. Browser-based headless checks do not establish screen-reader, other-browser or performance acceptance.
|
||||
@@ -24,7 +24,7 @@ def build():
|
||||
s=s.replace('/* FONT_CSS */',fonts).replace('/* APP_JS */',(R/'assets/app.js').read_text())
|
||||
licenses='\n'.join((vendor/f).read_text() for f in ['reveal-LICENSE','anime-LICENSE.md','work-sans-LICENSE'])
|
||||
s=s.replace('<!-- LICENSES -->','<!-- THIRD-PARTY NOTICES\n'+licenses.replace('--','—')+'\n-->')
|
||||
out=R/'dist';out.mkdir(exist_ok=True);p=out/'presentation-html-demo-0.1.0.html';p.write_text(s)
|
||||
out=R/'dist';out.mkdir(exist_ok=True);p=out/'presentation-html-demo-0.3.0.html';p.write_text(s)
|
||||
report={'file':p.name,'sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'bytes':p.stat().st_size,'slides':6,'interactive_nodes':6,'reveal':'6.0.2','anime':'4.5.0'}
|
||||
(out/'manifest.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report))
|
||||
if __name__=='__main__':build()
|
||||
@@ -0,0 +1,61 @@
|
||||
import { chromium } from '@playwright/test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
const root=path.resolve(import.meta.dirname,'..');
|
||||
const browser=await chromium.launch({headless:true,...(process.env.CHROMIUM_EXECUTABLE?{executablePath:process.env.CHROMIUM_EXECUTABLE}:{})});
|
||||
try {
|
||||
const context=await browser.newContext({offline:true});
|
||||
const page=await context.newPage();const errors=[],requests=[];
|
||||
page.on('pageerror',e=>errors.push(String(e)));
|
||||
page.on('request',r=>{if(/^https?:/.test(r.url()))requests.push(r.url());});
|
||||
await page.goto(pathToFileURL(path.join(root,'dist/presentation-html-demo-0.3.0.html')).href);
|
||||
await page.waitForFunction(()=>window.deckReady);
|
||||
assert.equal(await page.evaluate(()=>typeof presentationQA.seek),'function');
|
||||
await page.evaluate(()=>Reveal.slide(3));
|
||||
const sample=()=>page.evaluate(()=>[...document.querySelectorAll('#sequence [data-motion]')].map(e=>({opacity:e.style.opacity,transform:e.style.transform})));
|
||||
await page.evaluate(()=>presentationQA.seek(240)); const first=await sample();
|
||||
await page.evaluate(()=>presentationQA.seek(600));await page.evaluate(()=>presentationQA.seek(240));
|
||||
assert.deepEqual(await sample(),first,'backward seek is deterministic');
|
||||
assert.equal(await page.evaluate(()=>presentationQA.active()),0,'seek pauses the clock');
|
||||
await page.evaluate(()=>presentationQA.seek(Infinity)).then(()=>assert.fail('nonfinite seek accepted'),()=>{});
|
||||
await page.evaluate(()=>presentationQA.replay());
|
||||
assert.equal(await page.evaluate(()=>presentationQA.active()),1);
|
||||
await page.evaluate(()=>Reveal.slide(4));
|
||||
assert.equal(await page.evaluate(()=>[...document.querySelectorAll('#sequence [data-motion]')].every(e=>+getComputedStyle(e).opacity===1)),true,'departure settles previous slide');
|
||||
await page.evaluate(()=>Reveal.slide(3));assert.equal(await page.evaluate(()=>presentationQA.active()),0,'revisit is complete');
|
||||
await page.evaluate(()=>presentationQA.replay());await page.keyboard.press('Escape');
|
||||
assert.equal(await page.evaluate(()=>presentationQA.active()),0,'overview settles');await page.keyboard.press('Escape');
|
||||
await page.evaluate(()=>presentationQA.replay());await page.emulateMedia({reducedMotion:'reduce'});
|
||||
await page.waitForFunction(()=>presentationQA.active()===0 && document.querySelector('#motion').getAttribute('aria-pressed')==='false');
|
||||
assert.equal(await page.evaluate(()=>presentationQA.active()),0);
|
||||
assert.equal(await page.evaluate(()=>presentationQA.replay()),false,'reduced motion forbids replay');
|
||||
await page.evaluate(()=>presentationQA.seek(0));
|
||||
assert.equal(await page.evaluate(()=>[...document.querySelectorAll('#sequence [data-motion]')].every(e=>+getComputedStyle(e).opacity===1)),true,'seek respects reduced motion');
|
||||
await page.emulateMedia({reducedMotion:'no-preference'});
|
||||
await page.evaluate(()=>presentationQA.replay());await page.evaluate(()=>dispatchEvent(new Event('beforeprint')));
|
||||
assert.equal(await page.evaluate(()=>presentationQA.active()),0);
|
||||
await page.setContent('<svg width="240" height="80" viewBox="0 0 240 80"><path id="flow-path" d="M0 30 L200 30" fill="none" stroke="black"/><circle id="packet" r="5"/></svg><div id="cells">'+ '<i style="display:inline-block;width:20px;height:20px">•</i>'.repeat(6)+'</div>');
|
||||
await page.addScriptTag({path:path.join(root,'assets/vendor/anime.umd.min.js')});
|
||||
const primitives=await page.evaluate(()=>{
|
||||
const edge=document.querySelector('#flow-path');
|
||||
const trace=anime.animate(anime.svg.createDrawable(edge),{draw:['0 0','0 1'],duration:600,ease:'linear',autoplay:false});
|
||||
const flow=anime.animate(document.querySelector('#packet'),{...anime.svg.createMotionPath(edge),duration:600,ease:'linear',autoplay:false});
|
||||
const pulse=anime.animate(document.querySelectorAll('#cells i'),{opacity:[.25,1],delay:anime.stagger(60,{grid:[3,2],from:'center'}),duration:240,ease:'linear',autoplay:false});
|
||||
const snapshot=()=>[edge.getAttribute('stroke-dasharray'),edge.getAttribute('stroke-dashoffset'),document.querySelector('#packet').getAttribute('style'),...Array.from(document.querySelectorAll('#cells i'),e=>e.style.opacity)];
|
||||
[trace,flow,pulse].forEach(a=>a.seek(200));const first=snapshot();
|
||||
[trace,flow,pulse].forEach(a=>a.seek(a.duration));const end=snapshot();
|
||||
[trace,flow,pulse].forEach(a=>a.seek(200));const second=snapshot();
|
||||
const x=new DOMMatrix(getComputedStyle(document.querySelector('#packet')).transform).m41;
|
||||
return {first,end,second,x,paused:[trace,flow,pulse].every(a=>a.paused)};
|
||||
});
|
||||
assert.deepEqual(primitives.first,primitives.second);assert.notDeepEqual(primitives.first,primitives.end);
|
||||
assert.notDeepEqual(primitives.first.slice(0,2),primitives.end.slice(0,2),'SVG draw changes dash attributes');
|
||||
assert.ok(primitives.first.slice(3).some(v=>+v<1),'grid has intermediate opacity');
|
||||
assert.ok(primitives.end.slice(3).every(v=>+v===1),'grid final state is visible');
|
||||
assert.equal(primitives.paused,true);assert.ok(primitives.x>50&&primitives.x<80,'motion path reaches one-third of 200px path: '+JSON.stringify(primitives));
|
||||
assert.deepEqual(errors,[]);assert.deepEqual(requests,[]);
|
||||
fs.writeFileSync(path.join(root,'evidence/motion-test-report.json'),JSON.stringify({passed:true,checks:['deterministic seek','finite input','replay','departure','revisit','overview','reduced motion','print','SVG draw','SVG motion path','grid stagger'],errors,requests},null,2)+'\n');
|
||||
console.log('Motion lifecycle and v4 SVG/grid recipes: PASS');
|
||||
} finally { await browser.close(); }
|
||||
@@ -1,6 +1,6 @@
|
||||
import { chromium } from '@playwright/test';
|
||||
import fs from 'node:fs';import path from 'node:path';import {pathToFileURL} from 'node:url';import assert from 'node:assert/strict';import crypto from 'node:crypto';
|
||||
const root=path.resolve(import.meta.dirname,'..');const file=process.env.DECK_PATH||path.join(root,'dist/presentation-html-demo-0.1.0.html');
|
||||
const root=path.resolve(import.meta.dirname,'..');const file=process.env.DECK_PATH||path.join(root,'dist/presentation-html-demo-0.3.0.html');
|
||||
const executablePath=process.env.CHROMIUM_EXECUTABLE;
|
||||
const browser=await chromium.launch({headless:true,...(executablePath?{executablePath}:{})});
|
||||
const report={file,sha256:crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'),runs:[]};
|
||||
|
||||