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 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 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');}