Presnly logoPresnly

Interactive Playground

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.

Presentation Source

presentation.htmlhtml
<!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>

Live Preview (slide 1 of 4)

PPP Controls

PPP Message Log

Click a control to send a real PPP message