Drive the real presnly.min.js running in a sandboxed iframe. Every JSON-RPC message you see is the actual wire format the Presnly macOS app exchanges with PPP-enabled decks.
<!doctype html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui; background: #1a1a2e; color: #fff; }
[data-ppp-slide] { display: none; height: 100vh; align-items: center;
justify-content: center; flex-direction: column; gap: 1rem; }
[data-ppp-slide].active { display: flex; }
h1 { font-size: 2.5rem; }
h2 { font-size: 1.8rem; }
p { opacity: 0.7; }
</style>
</head>
<body>
<div data-ppp-slide class="active"
data-ppp-section="Welcome"
data-ppp-notes="Welcome the audience.">
<h1>Hello, PPP!</h1>
<p>A Presnly-compatible presentation</p>
</div>
<div data-ppp-slide hidden
data-ppp-section="Demo"
data-ppp-notes="Show the protocol in action.">
<h2>Slide 2</h2>
<p>Navigate using the controls below</p>
</div>
<div data-ppp-slide hidden
data-ppp-notes="Explain how easy it is.">
<h2>Slide 3</h2>
<p>Just data attributes + one script tag</p>
</div>
<div data-ppp-slide hidden data-ppp-section="End">
<h1>The End</h1>
</div>
<script src="/ppp/presnly.min.js"></script>
<script>
// Drive the SDK from the parent page via postMessage. The iframe
// owns the deck DOM and the Presnly instance; the parent owns the
// controls and the JSON-RPC log.
const presnly = new Presnly({ adapter: 'vanilla' })
let parentReady = false
const pending = []
const flush = () => { while (pending.length) parent.postMessage(pending.shift(), '*') }
presnly.onSend((msg) => {
const m = { __ppp: true, message: msg }
if (parentReady) parent.postMessage(m, '*')
else pending.push(m)
})
window.addEventListener('message', (e) => {
if (e.source !== parent) return
if (e.data && e.data.__pppReady) { parentReady = true; flush(); return }
if (e.data && e.data.__pppDeliver && window.__ppp_receive) {
window.__ppp_receive(e.data.__pppDeliver)
}
})
// The transport surface: shim a transport that routes through
// postMessage. We assign it via the same prototype trick the
// smoke test uses, so the deck author doesn't need to know about
// this; they'd just <script src> the SDK and call .connect().
const proto = Object.getPrototypeOf(presnly)
const orig = proto._resolveTransport
proto._resolveTransport = function () {
return {
isConnected: true,
send(msg) {
const m = { __ppp: true, message: msg }
if (parentReady) parent.postMessage(m, '*')
else pending.push(m)
},
onMessage(handler) {
window.__ppp_receive = (data) => handler(typeof data === 'string' ? JSON.parse(data) : data)
return () => { delete window.__ppp_receive }
},
close() {},
}
}
try { presnly.connect() } finally { proto._resolveTransport = orig }
</script>
</body>
</html>Click a control to send a real PPP message