implement PPTY
This commit is contained in:
parent
a13032c76e
commit
d76ac04531
12 changed files with 499 additions and 118 deletions
50
www/css/ppty.css
Normal file
50
www/css/ppty.css
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
/* =========================================================== *
|
||||||
|
* Pseudo-Pseudo-Terminal Effect *
|
||||||
|
/* =========================================================== */
|
||||||
|
|
||||||
|
.ppty {
|
||||||
|
background-color: var(--theme-bg);
|
||||||
|
border: 0.5ch solid var(--theme-tty-border);
|
||||||
|
|
||||||
|
margin: auto auto;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
/* display: block; */
|
||||||
|
/* flex-direction: column; */
|
||||||
|
/* justify-content: start; */
|
||||||
|
/* align-content: center; */
|
||||||
|
/* align-items: start; */
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ppty-block {
|
||||||
|
color: var(--theme-tty-output);
|
||||||
|
|
||||||
|
visibility: hidden;
|
||||||
|
white-space: nowrap; /* only break on <br/> */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ppty-prompt {
|
||||||
|
color: var(--theme-tty-prompt);
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ppty-command {
|
||||||
|
color: var(--theme-tty-command);
|
||||||
|
|
||||||
|
width: 0px;
|
||||||
|
border-right: 1ch solid var(--theme-tty-output);
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ppty-output {
|
||||||
|
}
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
.gl-canvas-bg {
|
|
||||||
display:block;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,27 @@
|
||||||
body {
|
body {
|
||||||
background-color: var(--theme-bg);
|
background-color: var(--theme-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#bg-canvas {
|
||||||
|
display:block;
|
||||||
|
position: fixed;
|
||||||
|
|
||||||
|
inset: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 0 0 0;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #ffc0cb; /* #ac4aed */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================================================== *
|
/* =========================================================== *
|
||||||
|
|
@ -19,100 +18,97 @@
|
||||||
|
|
||||||
#typing-wrapper {
|
#typing-wrapper {
|
||||||
margin: auto auto;
|
margin: auto auto;
|
||||||
/* width: 71ch; /* prompt + command + cursor length */
|
|
||||||
/* height: 21ch; */
|
|
||||||
text-align: start;
|
text-align: start;
|
||||||
|
|
||||||
border: 0.5ch solid #ffc0cb; /* #ac4aed */
|
border: 0.5ch solid var(--theme-tty-border); /* #ac4aed */
|
||||||
background-color: var(--theme-bg);
|
background-color: var(--theme-bg);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
||||||
overflow: hidden;
|
/* overflow: hidden; */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
animation: kfs-ending 2s 10s forwards;
|
/* animation: kfs-ending 2s 10s forwards; */
|
||||||
}
|
}
|
||||||
|
|
||||||
#typing-prompt {
|
#typing-prompt {
|
||||||
color: var(--theme-tty-prompt);
|
color: var(--theme-tty-prompt);
|
||||||
width: 10ch; /* prompt + command length */
|
/* width: 10ch; /* prompt + command length */ */
|
||||||
animation: kfs-typing 0.5s steps(4), kfs-cursor-blink 1.2s steps(1, start) 0.6s forwards;
|
/* animation: kfs-typing 0.5s steps(4), kfs-cursor-blink 1.2s steps(1, start) 0.6s forwards; */
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
/* overflow: hidden; */
|
||||||
border-right: 1ch solid;
|
border-right: 1ch solid;
|
||||||
margin-bottom: 0.5ch;
|
margin-bottom: 0.5ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
#typing-result {
|
#typing-result {
|
||||||
color: var(--theme-tty-warning);
|
|
||||||
/* "4.8s" means the result is shown 1.8s after typing ends */
|
/* "4.8s" means the result is shown 1.8s after typing ends */
|
||||||
animation: unhide 1s 1.8s forwards;
|
/* animation: unhide 1s 1.8s forwards; */
|
||||||
visibility: hidden;
|
/* visibility: hidden; */
|
||||||
white-space: nowrap; /* preserve linebreaks */
|
white-space: nowrap; /* preserve linebreaks */
|
||||||
}
|
}
|
||||||
|
|
||||||
#typing-prompt-segfault {
|
#typing-prompt-segfault {
|
||||||
color: var(--theme-tty-prompt);
|
color: var(--theme-tty-prompt);
|
||||||
width: 47ch; /* prompt + command length */
|
/* width: 47ch; /* prompt + command length */
|
||||||
/* animation: kfs-typing-segfault 3s steps(36) 2.6s, cursor-blink 0.6s steps(1, start) 3s infinite alternate; */
|
/* animation: kfs-typing-segfault 3s steps(36) 2.6s, cursor-blink 0.6s steps(1, start) 3s infinite alternate; */
|
||||||
animation: kfs-typing-segfault 3s steps(36) 4s forwards, cursor-blink-segfault 0.6s steps(1, start) 7.1s infinite alternate;
|
/* animation: kfs-typing-segfault 3s steps(36) 4s forwards, cursor-blink-segfault 0.6s steps(1, start) 7.1s infinite alternate; */
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
/* overflow: hidden; */
|
||||||
border-right: 1ch solid;
|
border-right: 1ch solid;
|
||||||
margin-bottom: 0.5ch;
|
margin-bottom: 0.5ch;
|
||||||
visibility: hidden;
|
/* visibility: hidden; */
|
||||||
}
|
}
|
||||||
|
|
||||||
#typing-result-segfault {
|
#typing-result-segfault {
|
||||||
/* "4.8s" means the result is shown 1.8s after typing ends */
|
/* "4.8s" means the result is shown 1.8s after typing ends */
|
||||||
animation: unhide 1s 8.3s forwards;
|
/* animation: unhide 1s 8.3s forwards; */
|
||||||
visibility: hidden;
|
/* visibility: hidden; */
|
||||||
white-space: nowrap; /* preserve linebreaks */
|
white-space: nowrap; /* preserve linebreaks */
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes kfs-typing {
|
/* @keyframes kfs-typing { */
|
||||||
from {
|
/* from { */
|
||||||
width: 6ch; /* ignore prompt width */
|
/* width: 6ch; /* ignore prompt width */ */
|
||||||
}
|
/* } */
|
||||||
}
|
/* } */
|
||||||
|
|
||||||
@keyframes kfs-typing-segfault {
|
/* @keyframes kfs-typing-segfault { */
|
||||||
from {
|
/* from { */
|
||||||
width: 11ch; /* ignore prompt width */
|
/* width: 11ch; /* ignore prompt width */ */
|
||||||
visibility: visible;
|
/* visibility: visible; */
|
||||||
}
|
/* } */
|
||||||
25% {
|
/* 25% { */
|
||||||
width: 11ch;
|
/* width: 11ch; */
|
||||||
}
|
/* } */
|
||||||
to {
|
/* to { */
|
||||||
visibility: visible;
|
/* visibility: visible; */
|
||||||
}
|
/* } */
|
||||||
}
|
/* } */
|
||||||
|
|
||||||
@keyframes kfs-cursor-blink {
|
/* @keyframes kfs-cursor-blink { */
|
||||||
from {
|
/* from { */
|
||||||
border-color: transparent;
|
/* border-color: transparent; */
|
||||||
}
|
/* } */
|
||||||
50% {
|
/* 50% { */
|
||||||
border-color: currentColor;
|
/* border-color: currentColor; */
|
||||||
}
|
/* } */
|
||||||
to {
|
/* to { */
|
||||||
border-color: transparent;
|
/* border-color: transparent; */
|
||||||
}
|
/* } */
|
||||||
}
|
/* } */
|
||||||
|
|
||||||
@keyframes cursor-blink-segfault {
|
/* @keyframes cursor-blink-segfault { */
|
||||||
50% {
|
/* 50% { */
|
||||||
border-color: transparent;
|
/* border-color: transparent; */
|
||||||
}
|
/* } */
|
||||||
}
|
/* } */
|
||||||
|
|
||||||
@keyframes unhide {
|
/* @keyframes unhide { */
|
||||||
to {
|
/* to { */
|
||||||
visibility: visible;
|
/* visibility: visible; */
|
||||||
}
|
/* } */
|
||||||
}
|
/* } */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,42 +12,54 @@
|
||||||
|
|
||||||
<!-- Styling -->
|
<!-- Styling -->
|
||||||
<link type="text/css" rel="stylesheet" href="css/style.css">
|
<link type="text/css" rel="stylesheet" href="css/style.css">
|
||||||
<link type="text/css" rel="stylesheet" href="css/shader-style.css">
|
<link type="text/css" rel="stylesheet" href="css/ppty.css">
|
||||||
<link type="text/css" rel="stylesheet" href="css/typing.css">
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<canvas class="gl-canvas-bg" id="gl-canvas"></canvas>
|
<!-- ShadeMyCanvas (background) -->
|
||||||
|
<canvas id="bg-canvas"></canvas>
|
||||||
|
|
||||||
|
<!-- Psuedo-Pseudo-Terminal -->
|
||||||
<div class="centered heading">
|
<div class="centered heading">
|
||||||
<div id="typing-wrapper">
|
<div class="ppty" id="boot-ppty">
|
||||||
|
|
||||||
<div id="typing-prompt">
|
|
||||||
grub> <span class="theme-tty-command">boot</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="typing-result"> <!-- class="theme-tty-warning"> -->
|
|
||||||
ERROR: Root device mounted successfully, but /sbin/init does not exist.<br/><br/>
|
|
||||||
|
|
||||||
<i class="theme-tty-error">
|
|
||||||
Bailing out, you are on your own.<br/>
|
|
||||||
Good luck
|
|
||||||
</i><br/><br/>
|
|
||||||
|
|
||||||
sh: can't access tty; job control turned off<br/><br/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="typing-prompt-segfault">
|
|
||||||
[rootfs ]# <span class="theme-tty-command">do butterflies cry when they're sad?</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="typing-result-segfault">
|
|
||||||
<i class="theme-tty-error">
|
|
||||||
Segmentation fault (core dumped)
|
|
||||||
</i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="ppty-block">
|
||||||
|
<div class="ppty-prompt">
|
||||||
|
grub>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ppty-command">
|
||||||
|
boot
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ppty-output theme-tty-warning">
|
||||||
|
ERROR: Root device mounted successfully, but /sbin/init does not exist.<br/><br/>
|
||||||
|
|
||||||
|
<i class="theme-tty-error">
|
||||||
|
Bailing out, you are on your own.<br/>
|
||||||
|
Good luck<br/><br/>
|
||||||
|
</i>
|
||||||
|
|
||||||
|
sh: can't access tty; job control turned off<br/><br/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ppty-block">
|
||||||
|
<div class="ppty-prompt">
|
||||||
|
[rootfs ]#
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ppty-command">
|
||||||
|
do butterflies cry when they're sad?
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ppty-output">
|
||||||
|
<i class="theme-tty-error">
|
||||||
|
Segmentation fault (core dumped)
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,59 @@
|
||||||
import { Smc } from "./smc/smc.js"
|
import { Smc, fetchShader } from "./smc/lib.js";
|
||||||
|
import { PPTY } from "./ppty/lib.js";
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|
||||||
async function fetchShader(uri, delegate) {
|
function endBoot(root) {
|
||||||
const res = await fetch(uri);
|
const style = getComputedStyle(root);
|
||||||
if (res.ok)
|
const paddingLeft = parseFloat(style.paddingLeft);
|
||||||
return await res.text();
|
const paddingRight = parseFloat(style.paddingRight);
|
||||||
this.raiseError(
|
const contentWidth = root.clientWidth - paddingLeft - paddingRight;
|
||||||
SmcErr.FETCH_SHADER,
|
const paddingTop = parseFloat(style.paddingTop);
|
||||||
`Failed to load shader source ${url}: ${res.status} ${res.json()}`);
|
const paddingBottom = parseFloat(style.paddingBottom);
|
||||||
return ""
|
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() {
|
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('width', window.innerWidth);
|
||||||
canvas.setAttribute('height', window.innerHeight);
|
canvas.setAttribute('height', window.innerHeight);
|
||||||
|
|
||||||
|
|
||||||
fetchShader("../shaders/segfault.glsl")
|
fetchShader("../shaders/segfault.glsl")
|
||||||
.then(frag =>
|
.then(frag =>
|
||||||
new Smc(canvas)
|
new Smc(canvas)
|
||||||
|
|
|
||||||
133
www/js/main.js.bak
Normal file
133
www/js/main.js.bak
Normal 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
2
www/js/ppty/lib.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
import { PPTY } from "./ppty.js";
|
||||||
|
export { PPTY };
|
||||||
130
www/js/ppty/ppty.js
Normal file
130
www/js/ppty/ppty.js
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
import { Smc } from "./smc.js";
|
import { Smc } from "./smc.js";
|
||||||
import { SmcErr } from "./errors.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 ""
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -164,10 +164,11 @@ class Smc {
|
||||||
var delta = time - this.#prevTimeMs;
|
var delta = time - this.#prevTimeMs;
|
||||||
this.render(time, delta);
|
this.render(time, delta);
|
||||||
|
|
||||||
setTimeout(
|
requestAnimationFrame(this.renderLoop);
|
||||||
() => requestAnimationFrame(this.renderLoop),
|
// setTimeout(
|
||||||
Math.max(0, delta - this.#minDeltaTimeMs)
|
// () => requestAnimationFrame(this.renderLoop),
|
||||||
);
|
// Math.max(0, delta - this.#minDeltaTimeMs)
|
||||||
|
// );
|
||||||
this.#prevTimeMs = time;
|
this.#prevTimeMs = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,8 @@ uniform float uTime;
|
||||||
uniform vec2 uResolution;
|
uniform vec2 uResolution;
|
||||||
|
|
||||||
/* ==== Text Colouring ==== */
|
/* ==== Text Colouring ==== */
|
||||||
#define PHOSPHOR_COL vec4(196./255., 167./255., 231./255., 1.)
|
#define PHOSPHOR_COL vec4(224./255., 222./255., 244./255., 1.)
|
||||||
// #define BG_COL vec4(0.2, 0.0, 0.2, 0.5)
|
#define BG_COL vec4(24./255., 23./255., 30./255., 1.)
|
||||||
#define BG_COL vec4(14./255., 13./255., 20./255., 1.)
|
|
||||||
/* ======= Text Size ======= */
|
/* ======= Text Size ======= */
|
||||||
#define FONT_SIZE vec2(10.,20.)
|
#define FONT_SIZE vec2(10.,20.)
|
||||||
#define ROWCOLS vec2(80., 24.)
|
#define ROWCOLS vec2(80., 24.)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue