taleem-pam
v1.0.0
Published
Plain App Model (PAM) is a minimal architectural pattern for building app-like web pages using plain HTML and JavaScript.
Downloads
90
Readme
Plain App Model (PAM)
Plain App Model (PAM) is a minimal architectural pattern for building app-like web pages using plain HTML and JavaScript.
PAM is not a framework. It is a small set of rules and primitives for managing state, time, and rendering explicitly.
Core Idea
- State lives at the top level
- Time comes from an external clock (which can be replaced by Howler etc)
- A single render loop is always running
- Rendering is explicit and deterministic
API
App Lifecycle
init()
destroy()State
statePassive Clock (Howler-compatible)
timer.play()
timer.pause()
timer.seek(t)
timer.now()Render Loop
renderLoop.start(loopFn)
renderLoop.draw(state)Draw Function
draw(state)Complete Copy‑Paste Demo (Recommended)
Below is a fully working PAM app in a single HTML file.
It includes:
- External clock (
Timer) - Single eternal render loop
- Play / stop controls
- Scrubbable timeline
- Explicit state + deterministic rendering
This is intentionally not minimal to the point of being unclear. The CSS is included so the demo is immediately usable and readable.
You can copy this file, open it in a browser, and start experimenting.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>PAM – Minimal Example</title>
<style>
body {
margin: 0;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: system-ui, sans-serif;
}
#time {
font-size: 6rem;
font-weight: 800;
margin-bottom: 2rem;
}
.controls {
display: flex;
gap: 1rem;
align-items: center;
}
input[type="range"] {
width: 300px;
}
</style>
</head>
<body>
<div id="time">0.0</div>
<div class="controls">
<button id="play-btn">Play</button>
<button id="stop-btn">Stop</button>
<input id="scrub" type="range" min="0" max="30" step="0.1" />
</div>
<script type="module">
import { renderLoop } from "./src/renderLoop.js"
import { Timer } from "./src/Timer.js"
const state = {
currentTime: 0,
duration: 30
}
const timer = new Timer()
const timeEl = document.getElementById("time")
const playBtn = document.getElementById("play-btn")
const stopBtn = document.getElementById("stop-btn")
const scrubEl = document.getElementById("scrub")
scrubEl.min = 0
scrubEl.max = state.duration
scrubEl.step = 0.1
function draw(state) {
const t = state.currentTime
timeEl.textContent = t.toFixed(1)
scrubEl.value = t
}
renderLoop.setDraw(draw)
state.currentTime = timer.now()
scrubEl.value = state.currentTime
renderLoop.start(() => {
state.currentTime = timer.now()
renderLoop.draw(state)
})
playBtn.onclick = () => {
timer.play()
}
stopBtn.onclick = () => {
timer.pause()
timer.seek(0)
}
scrubEl.oninput = e => {
timer.seek(+e.target.value)
timer.pause()
}
</script>
</body>
</html>