Widget runtime lab
Gate, mount, poke, watch the event stream.
dark
<style> .gb-wrap{display:flex;flex-direction:column;gap:14px;padding:16px;background:var(--z-surface);border:1px solid var(--z-border);border-radius:var(--z-radius)} .gb-row{display:flex;align-items:center;justify-content:space-between;gap:12px} .gb-title{font-weight:600;color:var(--z-fg)} .gb-readout{color:var(--z-muted)} .gb-starts{display:flex;gap:8px} .gb-start{min-height:44px;padding:8px 14px;border:1px solid var(--z-border);border-radius:calc(var(--z-radius) - 4px);background:var(--z-faint);color:var(--z-fg);cursor:pointer} .gb-start[aria-pressed="true"]{border-color:var(--z-primary);color:var(--z-primary-contrast);background:var(--z-primary)} .gb-slider{width:100%;min-height:44px} .gb-svg{width:100%;height:auto;display:block} .gb-bowl{fill:none;stroke:var(--z-border);stroke-width:3} .gb-bead{fill:var(--z-primary)} .gb-ghost{fill:var(--z-blue)} </style> <div class="gb-wrap"> <div class="gb-row"> <span class="gb-title">Rolling into the bowl</span> <span class="gb-readout z-readout" id="gb-out">a = 0.30</span> </div> <svg class="gb-svg" viewBox="0 0 300 180" preserveAspectRatio="xMidYMid meet" aria-hidden="true"> <path class="gb-bowl" data-zettel-anchor="bowl" d="M20 20 Q150 200 280 20"></path> <g id="gb-ghosts"></g> <circle class="gb-bead" id="gb-bead" data-zettel-anchor="ball" cx="150" cy="110" r="8"></circle> </svg> <label class="gb-row"><span>Damping</span></label> <input class="gb-slider" type="range" id="gb-alpha" data-zettel-anchor="damping-dial" min="0.02" max="1" step="0.02" value="0.3" aria-label="damping"> <div class="gb-starts" id="gb-starts" data-zettel-anchor="start-buttons"> <button class="gb-start" data-value="left">Left</button> <button class="gb-start" data-value="center">Center</button> <button class="gb-start" data-value="right">Right</button> </div> </div> <script> Zettel.state.define({ alpha: { type: "number", initial: 0.3, min: 0.02, max: 1, step: 0.02, label: "Damping", retain: 4 }, start: { type: "choice", initial: "center", options: ["left", "center", "right"], label: "Start" } }); var bead = document.getElementById("gb-bead"); var out = document.getElementById("gb-out"); var ghosts = document.getElementById("gb-ghosts"); var slider = document.getElementById("gb-alpha"); var starts = document.getElementById("gb-starts"); Zettel.control(slider, "alpha"); var buttons = starts.querySelectorAll(".gb-start"); for (var i = 0; i < buttons.length; i++) { Zettel.control(buttons[i], "start", { value: buttons[i].getAttribute("data-value") }); } function xFor(start) { return start === "left" ? 60 : start === "right" ? 240 : 150; } function render() { var alpha = Number(Zettel.state.get("alpha")); var start = Zettel.state.get("start"); var x = xFor(start); // Parabola y = a(x-150)^2 scaled into the bowl; more damping = shallower rest. var t = (x - 150) / 130; var y = 110 - (1 - alpha) * 70 * (1 - t * t); bead.setAttribute("cx", String(x)); bead.setAttribute("cy", String(y.toFixed(1))); out.textContent = "a = " + alpha.toFixed(2); for (var j = 0; j < buttons.length; j++) { buttons[j].setAttribute("aria-pressed", buttons[j].getAttribute("data-value") === start ? "true" : "false"); } // Ghost strata: prior committed damping values, fading (residue). var hist = Zettel.state.history("alpha"); var svgns = "http://www.w3.org/2000/svg"; ghosts.textContent = ""; for (var k = 0; k < hist.length - 1; k++) { var ga = Number(hist[k]); var gy = 110 - (1 - ga) * 70; var c = document.createElementNS(svgns, "circle"); c.setAttribute("class", "gb-ghost"); c.setAttribute("cx", "150"); c.setAttribute("cy", String(gy.toFixed(1))); c.setAttribute("r", "5"); c.setAttribute("opacity", String((0.2 + 0.2 * k).toFixed(2))); ghosts.appendChild(c); } } Zettel.state.on("alpha", render); Zettel.state.on("start", render); </script>
Gate
Mount
Load good fixture
Load broken fixture
Mount a widget to interact with it.
event log
—