implement PPTY

This commit is contained in:
do butterflies cry? 2026-02-03 20:26:22 +10:00
parent a13032c76e
commit d76ac04531
12 changed files with 499 additions and 118 deletions

View file

@ -1,24 +1,59 @@
import { Smc } from "./smc/smc.js"
import { Smc, fetchShader } from "./smc/lib.js";
import { PPTY } from "./ppty/lib.js";
main();
async function fetchShader(uri, delegate) {
const res = await fetch(uri);
if (res.ok)
return await res.text();
this.raiseError(
SmcErr.FETCH_SHADER,
`Failed to load shader source ${url}: ${res.status} ${res.json()}`);
return ""
function endBoot(root) {
const style = getComputedStyle(root);
const paddingLeft = parseFloat(style.paddingLeft);
const paddingRight = parseFloat(style.paddingRight);
const contentWidth = root.clientWidth - paddingLeft - paddingRight;
const paddingTop = parseFloat(style.paddingTop);
const paddingBottom = parseFloat(style.paddingBottom);
const contentHeight = root.clientHeight - paddingTop - paddingBottom;
root.style.width = `${contentWidth}px`;
root.style.height = `${contentHeight}px`;
const fade = root.animate(
[{ opacity: 0 }],
{
duration: 400,
delay: 3000,
fill: "forwards",
}
);
fade.onfinish = () => root.remove();
}
function main() {
const canvas = document.querySelector("#gl-canvas");
const boot = document.querySelector("#boot-ppty")
new PPTY(
boot,
[
{
cps: 12,
promptDelay: 0.5,
commandDelay: 0.7,
outputDelay: 1,
blinkTime: 0.6,
},
{
cps: 10,
promptDelay: 0,
commandDelay: 1.5,
outputDelay: 1.2,
blinkTime: 0.6,
}
])
.onfinish(endBoot)
.run();
const canvas = document.querySelector("#bg-canvas");
canvas.setAttribute('width', window.innerWidth);
canvas.setAttribute('height', window.innerHeight);
fetchShader("../shaders/segfault.glsl")
.then(frag =>
new Smc(canvas)

133
www/js/main.js.bak Normal file
View file

@ -0,0 +1,133 @@
import { Smc } from "./smc/lib.js"
main();
async function fetchShader(uri, delegate) {
const res = await fetch(uri);
if (res.ok)
return await res.text();
this.raiseError(
SmcErr.FETCH_SHADER,
`Failed to load shader source ${url}: ${res.status} ${res.json()}`);
return ""
}
function animDelta(anim) {
const timing = anim.effect.getComputedTiming();
return (timing.delay ?? 0) + (timing.duration ?? 0) + (timing.endDelay ?? 0);
}
function main() {
const cps = 12; // chars per second
const promptDelay = 0.5;
const commandDelay = 5;
const outputDelay = 5;
const blinkTime = 600; // x2 then x1000
// WARNING: delay does not account for runtime
// WARNING: it assumes all computations are down instantaneously
var delay = 0;
const boot = document.querySelector("#boot-ppty");
// WARNING: ensure boot != null
boot
.querySelectorAll(".ppty-block")
.forEach((block, index, blocks) => {
const prompt = block.querySelector(".ppty-prompt");
const command = block.querySelector(".ppty-command");
const output = block.querySelector(".ppty-output");
// WARNING: ensure prompt|command|output != null
const promptAnim = prompt.animate(
[{ visibility: "visible" }],
{
delay: delay + promptDelay * 1000,
fill: "forwards",
},
);
delay = animDelta(promptAnim);
const showCursor = () => { command.style.borderRightColor = cursorColor; return true; };
const hideCursor = () => { command.style.borderRightColor = "transparent"; return false; };
const startCursorBlink = () => setInterval(() => {
cursorVisible = cursorVisible ? hideCursor() : showCursor();
}, blinkTime);
const cursorColor = command.style.borderRightColor;
var cursorVisible = true;
var blinkId = null;
setTimeout(() => {
command.style.visibility = "visible";
blinkId = startCursorBlink();
}, delay);
// WARNING: ensure command.textContent != null
const commandLen = command.textContent.trim().length
const commandAnim = command.animate(
[
{
width: "0ch",
visibility: "visible",
},
{
width: `${commandLen}ch`,
visibility: "visible",
}
],
{
duration: commandLen / cps * 1000,
delay: delay + commandDelay * 1000,
easing: `steps(${commandLen}, end)`,
fill: "forwards",
}
);
// pause the cursor while typing
setTimeout(() => {
clearInterval(blinkId);
showCursor();
cursorVisible = true;
}, delay + commandDelay * 1000);
delay = animDelta(commandAnim);
setTimeout(() => {
blinkId = startCursorBlink();
}, delay);
setTimeout(() => {
clearInterval(blinkId);
hideCursor();
}, delay + outputDelay * 1000);
// unhide output
output.animate(
[{ visibility: "visible" }],
{
delay: delay + outputDelay * 1000,
fill: "forwards",
}
);
delay += outputDelay * 1000;
// hide the cursor after output displays
setTimeout(() => command.style.borderRightColor = "transparent", delay);
});
const canvas = document.querySelector("#bg-canvas");
canvas.setAttribute('width', window.innerWidth);
canvas.setAttribute('height', window.innerHeight);
fetchShader("../shaders/segfault.glsl")
.then(frag =>
new Smc(canvas)
.setMaxFps(30)
.setProgram(builder =>
builder
.addFragmentShader(frag))
.run()
);
}

2
www/js/ppty/lib.js Normal file
View file

@ -0,0 +1,2 @@
import { PPTY } from "./ppty.js";
export { PPTY };

130
www/js/ppty/ppty.js Normal file
View file

@ -0,0 +1,130 @@
export { PPTY };
function animDelta(anim) {
const timing = anim.effect.getComputedTiming();
return (timing.delay ?? 0) + (timing.duration ?? 0) + (timing.endDelay ?? 0);
}
class PPTY {
#root;
#blockOptions;
#startCallback = _ => { };
#finishCallback = _ => { };
constructor(root, blockOptions) {
this.#root = root;
this.#blockOptions = blockOptions;
}
onstart(handler) {
this.#startCallback = handler;
return this;
}
onfinish(handler) {
this.#finishCallback = handler;
return this;
}
run() {
var delay = 0;
this.#startCallback(this.#root);
this.#root
.querySelectorAll(".ppty-block")
.forEach((block, index, blocks) => {
const prompt = block.querySelector(".ppty-prompt");
const command = block.querySelector(".ppty-command");
const output = block.querySelector(".ppty-output");
const options = this.#blockOptions[index];
const cps = options["cps"]; // chars per second
const promptDelay = options["promptDelay"] * 1000;
const commandDelay = options["commandDelay"] * 1000;
const outputDelay = options["outputDelay"] * 1000;
const blinkTime = options["blinkTime"] * 2 * 1000; // x2 then x1000
// WARNING: ensure prompt|command|output != null
const promptAnim = prompt.animate(
[{ visibility: "visible" }],
{
delay: delay + promptDelay,
fill: "forwards",
},
);
delay = animDelta(promptAnim);
const showCursor = () => { command.style.borderRightColor = cursorColor; return true; };
const hideCursor = () => { command.style.borderRightColor = "transparent"; return false; };
const startCursorBlink = () => setInterval(() => {
cursorVisible = cursorVisible ? hideCursor() : showCursor();
}, blinkTime);
const cursorColor = command.style.borderRightColor;
var cursorVisible = true;
var blinkId = null;
setTimeout(() => {
command.style.visibility = "visible";
blinkId = startCursorBlink();
}, delay);
// WARNING: ensure command.textContent != null
const commandLen = command.textContent.trim().length
const commandAnim = command.animate(
[
{
width: "0ch",
visibility: "visible",
},
{
width: `${commandLen}ch`,
visibility: "visible",
}
],
{
duration: commandLen / cps * 1000,
delay: delay + commandDelay,
easing: `steps(${commandLen}, end)`,
fill: "forwards",
}
);
// pause the cursor while typing
setTimeout(() => {
clearInterval(blinkId);
showCursor();
cursorVisible = true;
}, delay + commandDelay);
delay = animDelta(commandAnim);
setTimeout(() => {
blinkId = startCursorBlink();
}, delay);
// delay until output is visible
delay += outputDelay;
setTimeout(() => {
clearInterval(blinkId);
hideCursor();
}, delay);
// unhide output
output.animate(
[{ visibility: "visible" }],
{
delay: delay,
fill: "forwards",
}
);
// hide the cursor after output displays and run the callback
setTimeout(() => {
command.style.borderRightColor = "transparent";
if (index == blocks.length - 1)
this.#finishCallback(this.#root);
}, delay);
});
}
}

View file

@ -1,4 +1,14 @@
import { Smc } from "./smc.js";
import { SmcErr } from "./errors.js";
export { Smc, SmcErr };
export { Smc, SmcErr, fetchShader };
async function fetchShader(uri, delegate) {
const res = await fetch(uri);
if (res.ok)
return await res.text();
this.raiseError(
SmcErr.FETCH_SHADER,
`Failed to load shader source ${url}: ${res.status} ${res.json()}`);
return ""
}

View file

@ -164,10 +164,11 @@ class Smc {
var delta = time - this.#prevTimeMs;
this.render(time, delta);
setTimeout(
() => requestAnimationFrame(this.renderLoop),
Math.max(0, delta - this.#minDeltaTimeMs)
);
requestAnimationFrame(this.renderLoop);
// setTimeout(
// () => requestAnimationFrame(this.renderLoop),
// Math.max(0, delta - this.#minDeltaTimeMs)
// );
this.#prevTimeMs = time;
}