diff --git a/README.md b/README.md index f600188..4d42b63 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,12 @@ Python builds with no network and no npm installation. npm/Chromium are needed o Use six slides as examples, not a fixed narrative. Copy the package to a deck workspace before editing. Adapt test inventory when slide/node counts change. Work Sans is optional example typography. Channel-specific preferences and authority remain outside this global skill. +## Explanatory mechanism example + +For architecture decomposition, modular products and inspect/reassemble interactions, first read [the mechanism reference](references/explanatory-mechanisms.md). It explains when to choose the pattern, what was observed in the Anime.js showcase, and what must not be inferred or copied. + +`assets/mechanism.html` is an original editable SVG example. Build it with `python3 scripts/build-mechanism.py`, then open `dist/mechanism-demo-0.2.0.html`. All four state controls and all three components are covered by `scripts/test-mechanism.mjs`; `npm test` runs both the original deck and this new example. + ## Licenses and scope Original code/documentation: MIT, RootAtSkic and Hermes Agent. Reveal.js and Anime.js: MIT; Work Sans: SIL OFL 1.1. Notices are preserved alongside vendor bytes and embedded in the output. No copyrighted showcase artwork was scraped into the distributable. Research documents disclose unavailable pages and link-only references. See `references/examples.md` for attribution and reuse decisions. diff --git a/SKILL.md b/SKILL.md index 003297d..ae8ed6c 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,7 +1,7 @@ --- name: presentation-html description: Build offline HTML decks with deliberate motion. -version: 0.1.0 +version: 0.2.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: 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. + ## How to Run Copy this complete package into a new working directory. Never edit an installed skill to create a client deck. Use `terminal` with that directory as `workdir`: diff --git a/assets/mechanism.html b/assets/mechanism.html new file mode 100644 index 0000000..90fbf82 --- /dev/null +++ b/assets/mechanism.html @@ -0,0 +1,31 @@ + +One system, three responsibilities +
+
Explanatory motion · original SVG example

One system, three responsibilities.

Keep the same parts in view. Separate them to explain their roles, inspect a component, then recombine the system.

+
+ +Assembled · a single system +StorageRetain the outcome +ProcessingApply the rules +InterfaceReceive the intent +Explode to reveal all labels · select a part for its responsibility +
Assembled systemThree stable components share one purpose. Explode the assembly to reveal each responsibility.
+

Original abstract 2D assembly, not a reproduction of the Anime.js mechanism. No third-party artwork. Motion respects reduced-motion preferences. Keyboard: Tab, Enter and Space.

+
diff --git a/dist/mechanism-demo-0.2.0.html b/dist/mechanism-demo-0.2.0.html new file mode 100644 index 0000000..19c3dc0 --- /dev/null +++ b/dist/mechanism-demo-0.2.0.html @@ -0,0 +1,143 @@ + +One system, three responsibilities +
+
Explanatory motion · original SVG example

One system, three responsibilities.

Keep the same parts in view. Separate them to explain their roles, inspect a component, then recombine the system.

+
+ +Assembled · a single system +StorageRetain the outcome +ProcessingApply the rules +InterfaceReceive the intent +Explode to reveal all labels · select a part for its responsibility +
Assembled systemThree stable components share one purpose. Explode the assembly to reveal each responsibility.
+

Original abstract 2D assembly, not a reproduction of the Anime.js mechanism. No third-party artwork. Motion respects reduced-motion preferences. Keyboard: Tab, Enter and Space.

+
diff --git a/dist/mechanism-manifest.json b/dist/mechanism-manifest.json new file mode 100644 index 0000000..9cf8f5e --- /dev/null +++ b/dist/mechanism-manifest.json @@ -0,0 +1,7 @@ +{ + "file": "mechanism-demo-0.2.0.html", + "sha256": "558fe5c1d0a684edaf42fcbf825a586ee62a8335154ecdb34a96b42b498af3e4", + "interactive_components": 3, + "controls": 4, + "anime": "4.5.0" +} diff --git a/evidence/mechanism-assembled.png b/evidence/mechanism-assembled.png new file mode 100644 index 0000000..0a9dd03 Binary files /dev/null and b/evidence/mechanism-assembled.png differ diff --git a/evidence/mechanism-exploded.png b/evidence/mechanism-exploded.png new file mode 100644 index 0000000..07c8f18 Binary files /dev/null and b/evidence/mechanism-exploded.png differ diff --git a/evidence/mechanism-inspected.png b/evidence/mechanism-inspected.png new file mode 100644 index 0000000..dd118d9 Binary files /dev/null and b/evidence/mechanism-inspected.png differ diff --git a/evidence/mechanism-test-report.json b/evidence/mechanism-test-report.json new file mode 100644 index 0000000..413fc79 --- /dev/null +++ b/evidence/mechanism-test-report.json @@ -0,0 +1,27 @@ +{ + "sha256": "558fe5c1d0a684edaf42fcbf825a586ee62a8335154ecdb34a96b42b498af3e4", + "runs": [ + { + "reducedMotion": "no-preference", + "components": 3, + "componentActions": 9, + "controls": 4, + "interruption": true, + "print": true, + "clipped": [], + "errors": [], + "network": [] + }, + { + "reducedMotion": "reduce", + "components": 3, + "componentActions": 9, + "controls": 4, + "interruption": true, + "print": true, + "clipped": [], + "errors": [], + "network": [] + } + ] +} diff --git a/evidence/mechanism-visual-review.md b/evidence/mechanism-visual-review.md new file mode 100644 index 0000000..691591a --- /dev/null +++ b/evidence/mechanism-visual-review.md @@ -0,0 +1,7 @@ +# Mechanism visual review + +Self-review, not independent QA. Inspected assembled, exploded and selected states at 1280×900. The exploded layout has three readable components, clear spacing, a separate responsibility detail panel and visible keyboard focus. The first assembled render exposed fragments of rear labels; corrected by hiding rear text in the assembled state while preserving component identities and accessible names. Rebuilt, reran both normal/reduced-motion suites and re-inspected the corrected assembled image. + +The unchanged six-slide starter also passed its full browser regression after fixing a test race: wait for both fullscreen state and the asynchronous fullscreenchange label update. This changes the assertion timing, not application behavior. + +Exact mechanism artifact identity and tested scope are in mechanism-test-report.json. No remote CI, cross-browser or screen-reader certification is claimed. diff --git a/package.json b/package.json index 51d2170..fdefe1e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "presentation-html-skill", "version": "0.1.0", "private": true, - "scripts": {"build":"python3 scripts/build.py", "test":"npm run build && node scripts/test.mjs"}, + "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"} } diff --git a/references/examples.md b/references/examples.md index 94fd2fa..fc39fd2 100644 --- a/references/examples.md +++ b/references/examples.md @@ -11,6 +11,10 @@ Inspected 2026-10-21. “Established” identifies official showcase material or | Anime.js v4 logo animation / Julian Garnier | [v4.5.0 example](https://github.com/juliangarnier/anime/tree/v4.5.0/examples/animejs-v4-logo-animation) | Timeline orchestration produces staged hierarchy | MIT code does not imply trademark endorsement. Logo not copied. Original three-step process timeline, slide 4. | | Inventing on Principle / Bret Victor | [author link](https://worrydream.com/InventingOnPrinciple/) redirected to [Vimeo](https://vimeo.com/906418692) | Reference candidate for direct manipulation and explaining ideas through working demonstrations | No reuse license established; link only, no video or slide copying. Only redirect/page availability was verified here, not a new full-video review. Not claimed as a Reveal/Anime implementation. | +## Explanatory assembly reference + +Read [explanatory mechanisms](explanatory-mechanisms.md) when choosing an assemble/explode/inspect/reassemble pattern. It records the inspected Anime.js toolbox and modularity recordings, attribution and reuse limits. `assets/mechanism.html` is the original runnable counterpart; showcase artwork remains link-only. + ## Original reusable examples in assets `assets/starter.html` and `assets/app.js` are MIT original code, not reproductions of showcase layouts: diff --git a/references/explanatory-mechanisms.md b/references/explanatory-mechanisms.md new file mode 100644 index 0000000..88436e3 --- /dev/null +++ b/references/explanatory-mechanisms.md @@ -0,0 +1,53 @@ +# Explanatory mechanisms: assemble, explode, inspect, reassemble + +## When to read this reference + +Read **before storyboarding or coding** when an audience needs to understand how parts form a whole: architecture decomposition, product internals, modularity, ownership boundaries, or a user request for a mechanical/exploded-view animation. Also read when a simple stagger feels decorative rather than explanatory. Do not load it merely to add motion to a factual table. + +## Evidence and source boundaries + +The following Anime.js showcase recordings were inspected on 2026-10-21 via Awwwards after the live Anime.js homepage timed out. These are observations of recorded visuals, **not verification of live scroll/input behavior, implementation source, or the 3D rendering engine**. + +| Reference | Observed lesson | Use when | +|---|---|---| +| [The complete animator’s toolbox](https://www.awwwards.com/inspiration/the-complete-animator-s-toolbox-anime-js) | A shaded mechanical assembly opens into an exploded arrangement and a technical drawing with callouts. Preserve object identity while changing explanatory depth. | Explain internals of a system already introduced as a whole. | +| [Lightweight and modular](https://www.awwwards.com/inspiration/lightweight-and-modular-anime-js) | Separated parts become a compact assembly beside a bundle-size display. Motion connects modular composition to an outcome. | Explain optional modules, assembly, or contribution to a total. | +| [Scroll scrubber](https://www.awwwards.com/inspiration/scroll-scrubber-anime-js) | Navigation reference listed on the showcase; live interaction was not tested in this research. | Investigate reversible progress control; verify actual input behavior separately before adopting it. | +| [Anime.js showcase](https://www.awwwards.com/sites/anime-js) | Credits Julian Garnier; records Site of the Day, May 6, 2025. | Recover attribution and the recording catalog, not implementation evidence. | + +No license to redistribute the showcase artwork or recordings was established. Keep these as links and observations; do not package downloaded videos, screenshots, logo paths, or reconstructed branded art as reusable skill assets. The MIT library license is not permission to copy the showcase's artwork. + +## Original reusable asset + +- Editable source: [`assets/mechanism.html`](../assets/mechanism.html). +- Offline builder: [`scripts/build-mechanism.py`](../scripts/build-mechanism.py). +- Runnable output: `dist/mechanism-demo-0.2.0.html`. +- Browser acceptance suite: `scripts/test-mechanism.mjs`. + +This is an **original abstract 2D SVG assembly**, not a reproduction of the photographed/shaded mechanism and not a 3D renderer. Interface, Processing and Storage keep their DOM identity as they separate and recombine. Four native buttons expose assemble → explode → inspect → reassemble. All three components support click, Enter and Space; their details explain responsibility boundaries. Original source is covered by the package MIT license. Anime.js and font notices are embedded in the build. + +Use `terminal` in the copied package directory: + +```text +python3 scripts/build-mechanism.py +node scripts/test-mechanism.mjs +``` + +Reuse the component groups, stable coordinates, state transitions and lifecycle cleanup together—not only the animation call. Set `CHROMIUM_EXECUTABLE` if the package's Playwright browser is not installed. Never copy machine-local browser paths into the shared skill. + +## Adaptation contract + +1. Write the explanation first: whole → parts → responsibility → whole. Use the original assembly only where its three responsibilities actually fit the subject. +2. Maintain stable component IDs, color and relative order. Do not replace the object with an unrelated diagram mid-transition. +3. Use callouts outside moving geometry for lengthy details. Assembled overlap is intentional; exploded labels must all be readable. +4. Interruption is a supported action: settle the previous animation before starting another. Repeated clicks must converge on the last selected state. +5. Prefer explicit state buttons for a talk. If a scrubber is justified, add a keyboard-accessible range input and test reversible progress; **the included example has no scrubber**. +6. On reduced-motion changes, page departure and print, settle motion. Print expands all components. On Reveal `slidechanged`, explicitly settle the outgoing mechanism; do not let Reveal Auto-Animate and Anime own the same transform. +7. Reveal owns presentation steps and sizing; Anime coordinates part movement. The bundled mechanism is standalone so it can be studied without Reveal. When integrating, preserve the host deck's fullscreen, navigation, offline packaging and QA contract. +8. For genuine perspective, occlusion and camera orbit, select and validate a separate 3D layer. Do not claim Anime alone renders 3D from an observed video. + +## Acceptance and limits + +Test all four controls, all three parts by mouse/Enter/Space, intermediate motion, reverse/repeated input, reduced motion and runtime preference changes, print expansion, offline requests, console errors and exploded text bounds. Inspect assembled, exploded and selected screenshots personally. Hash the exact tested output. + +The standalone demo is not a full slide deck. Its tests do not prove Reveal integration, live scrubbing, Safari/Firefox, screen-reader output, projector readability or a native 3D implementation. Run the full deck suite after embedding it. diff --git a/references/research.md b/references/research.md index 93b0fc3..169187c 100644 --- a/references/research.md +++ b/references/research.md @@ -32,6 +32,10 @@ Use Reveal only for slide navigation and sizing; use Anime only for bounded cont The approved prior channel deck supplied preferences, not reusable proprietary content: editable diagrams, calm motion, Work Sans, visible fullscreen, offline packaging, exhaustive node testing. Neither its fifteen slides nor its 46-node acceptance scope is imposed on future decks. The historical artifact's embedded Anime bundle identifies itself as 3.2.1 despite prior context describing 3.2.2; the new package does not reuse that bundle. +## Explanatory mechanism follow-up + +For the subsequently inspected Awwwards recordings of the Anime.js homepage, see [explanatory mechanisms](explanatory-mechanisms.md). Read it before selecting mechanical assembly or exploded-view motion. Recorded visuals establish the explanatory sequence, not the rendering engine or live input behavior. + ## Failed candidates and limits `revealjs.com/examples/` and `slides.com/news/introducing-auto-animate` returned 404; neither is cited as a successful source. Browser Use could not start its isolated daemon; local Playwright Chromium was used for real rendering and interaction QA instead. Anime site availability is an explicit research caveat, not hidden by invented page summaries. diff --git a/scripts/build-mechanism.py b/scripts/build-mechanism.py new file mode 100644 index 0000000..f35b355 --- /dev/null +++ b/scripts/build-mechanism.py @@ -0,0 +1,27 @@ +"""Build the original assembly example offline from licensed local assets.""" +from pathlib import Path +import base64 +import hashlib +import json +import re + +root = Path(__file__).resolve().parents[1] +for entry in json.loads((root / 'vendor-manifest.json').read_text()): + assert hashlib.sha256((root / entry['path']).read_bytes()).hexdigest() == entry['sha256'] +vendor = root / 'assets/vendor' +text = (root / 'assets/mechanism.html').read_text() +js = re.sub(r'//# sourceMappingURL=.*', '', (vendor / 'anime.umd.min.js').read_text()) +text = text.replace('/* ANIME_JS */', js) +fonts = '' +for file, weight in [('work-sans.woff2', 400), ('work-sans-bold.woff2', 700)]: + data = base64.b64encode((vendor / file).read_bytes()).decode() + fonts += f"@font-face{{font-family:'Work Sans';font-weight:{weight};src:url(data:font/woff2;base64,{data}) format('woff2');}}" +text = text.replace('/* FONT_CSS */', fonts) +notices = '\n'.join((vendor / f).read_text() for f in ['anime-LICENSE.md', 'work-sans-LICENSE']) +text = text.replace('', '') +out = root / 'dist/mechanism-demo-0.2.0.html' +out.parent.mkdir(exist_ok=True) +out.write_text(text) +report = {'file': out.name, 'sha256': hashlib.sha256(out.read_bytes()).hexdigest(), 'interactive_components': 3, 'controls': 4, 'anime': '4.5.0'} +(root / 'dist/mechanism-manifest.json').write_text(json.dumps(report, indent=2) + '\n') +print(json.dumps(report)) diff --git a/scripts/test-mechanism.mjs b/scripts/test-mechanism.mjs new file mode 100644 index 0000000..bc2c08f --- /dev/null +++ b/scripts/test-mechanism.mjs @@ -0,0 +1,25 @@ +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,'..'),file=path.join(root,'dist/mechanism-demo-0.2.0.html'); +const browser=await chromium.launch({headless:true,...(process.env.CHROMIUM_EXECUTABLE?{executablePath:process.env.CHROMIUM_EXECUTABLE}:{})}); +const report={sha256:crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'),runs:[]}; +fs.mkdirSync(path.join(root,'evidence'),{recursive:true}); +try{for(const reducedMotion of ['no-preference','reduce']){ + const ctx=await browser.newContext({offline:true,reducedMotion,viewport:{width:1280,height:900}}),p=await ctx.newPage(),errors=[],network=[]; + p.on('pageerror',e=>errors.push(String(e)));p.on('console',m=>{if(m.type()==='error')errors.push(m.text());});p.on('request',r=>{if(/^https?:/.test(r.url()))network.push(r.url());}); + await p.goto(pathToFileURL(file).href);await p.evaluate(()=>document.fonts.ready); + const state=()=>p.evaluate(()=>mechanismQA.state());const settled=()=>p.waitForFunction(()=>!mechanismQA.state().active); + assert.deepEqual((await state()).positions,[460,520,580]); + if(reducedMotion==='no-preference')await p.screenshot({path:path.join(root,'evidence/mechanism-assembled.png')}); + await p.locator('#explode').click();if(reducedMotion==='no-preference'){await p.waitForTimeout(150);const s=await state();assert.ok(s.positions[0]<460&&s.positions[0]>200,'actual intermediate motion');}await settled();assert.deepEqual((await state()).positions,[200,520,840]); + const clipped=await p.evaluate(()=>{const r=document.querySelector('svg').getBoundingClientRect();return [...document.querySelectorAll('svg text')].filter(e=>{const b=e.getBoundingClientRect();return b.leftr.right||b.topr.bottom;}).map(e=>e.textContent);});assert.deepEqual(clipped,[]); + if(reducedMotion==='no-preference')await p.screenshot({path:path.join(root,'evidence/mechanism-exploded.png')}); + for(let i=0;i<3;i++){const n=p.locator(`[data-index="${i}"]`);for(const action of ['click','Enter','Space']){if(action==='click')await n.click();else{await n.focus();await p.keyboard.press(action);}await settled();assert.equal((await state()).selected,i);assert.equal(await n.getAttribute('aria-pressed'),'true');assert.equal(await p.locator('#detail-title').textContent(),['Interface','Processing','Storage'][i]);}} + if(reducedMotion==='no-preference')await p.screenshot({path:path.join(root,'evidence/mechanism-inspected.png')}); + for(const id of ['reassemble','assemble']){await p.locator('#'+id).click();await settled();assert.deepEqual((await state()).positions,[460,520,580]);assert.equal((await state()).selected,-1);} + await p.locator('#inspect').focus();await p.keyboard.press('Enter');await settled();assert.equal((await state()).selected,1); + await p.evaluate(()=>{for(const id of ['assemble','explode','reassemble','explode','assemble'])document.getElementById(id).click();});await settled();assert.deepEqual((await state()).positions,[460,520,580]); + await p.locator('#explode').click();await p.emulateMedia({reducedMotion:'reduce'});await settled();assert.deepEqual((await state()).positions,[200,520,840]); + await p.locator('#assemble').click();await p.emulateMedia({media:'print'});await p.evaluate(()=>dispatchEvent(new Event('beforeprint')));assert.deepEqual((await state()).positions,[200,520,840]);assert.equal(await p.locator('.controls').isVisible(),false); + assert.deepEqual(errors,[]);assert.deepEqual(network,[]);report.runs.push({reducedMotion,components:3,componentActions:9,controls:4,interruption:true,print:true,clipped,errors,network});await ctx.close(); +}fs.writeFileSync(path.join(root,'evidence/mechanism-test-report.json'),JSON.stringify(report,null,2)+'\n');console.log(JSON.stringify(report,null,2));}finally{await browser.close();} diff --git a/scripts/test.mjs b/scripts/test.mjs index 9bf43e1..91465db 100644 --- a/scripts/test.mjs +++ b/scripts/test.mjs @@ -26,7 +26,7 @@ for(const reducedMotion of ['no-preference','reduce']){ } assert.equal(testedNodes,6);await page.evaluate(()=>Reveal.slide(0));await page.keyboard.press('ArrowRight');assert.equal(await page.evaluate(()=>Reveal.getIndices().h),1);await page.keyboard.press('ArrowLeft');assert.equal(await page.evaluate(()=>Reveal.getIndices().h),0); await page.keyboard.press('Escape');assert.equal(await page.evaluate(()=>Reveal.isOverview()),true);await page.keyboard.press('Escape'); - await page.locator('#fullscreen').click();await page.waitForFunction(()=>!!document.fullscreenElement);assert.equal(await page.locator('#fullscreen').getAttribute('aria-pressed'),'true');await page.keyboard.press('f');await page.waitForFunction(()=>!document.fullscreenElement); + await page.locator('#fullscreen').click();await page.waitForFunction(()=>!!document.fullscreenElement && document.querySelector('#fullscreen').getAttribute('aria-pressed')==='true');assert.equal(await page.locator('#fullscreen').getAttribute('aria-pressed'),'true');await page.keyboard.press('f');await page.waitForFunction(()=>!document.fullscreenElement); await page.keyboard.press('f');await page.waitForFunction(()=>!!document.fullscreenElement);await page.locator('#fullscreen').click();await page.waitForFunction(()=>!document.fullscreenElement); await page.evaluate(()=>Reveal.slide(3));await page.waitForFunction(()=>location.hash.includes('sequence'));await page.reload();await page.waitForFunction(()=>window.deckReady);assert.equal(await page.evaluate(()=>Reveal.getIndices().h),3); if(reducedMotion==='reduce'){assert.equal(await page.evaluate(()=>presentationQA.active()),0);assert.equal(await page.locator('#motion').getAttribute('aria-pressed'),'false');}