Skip to content

Commit aff7dac

Browse files
author
Paul Adams
committed
feat: Implement JavaScript notebook feature
1 parent 3aeb466 commit aff7dac

11 files changed

Lines changed: 652 additions & 85 deletions

File tree

‎app.js‎

Lines changed: 179 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const routes = {
44
"/contact": "pages/contact.html",
55
"/test": "pages/test.html",
66
"/projects": "pages/projects.html",
7+
"/notebook": "pages/notebook.html",
78
};
89

910
const app = document.getElementById("app");
@@ -34,54 +35,24 @@ const router = async () => {
3435
const response = await fetch(route);
3536
if (!response.ok) throw new Error(`Page not found: ${route}`);
3637
const html = await response.text();
37-
38-
app.innerHTML = ""; // Clear previous content
39-
40-
const parser = new DOMParser();
41-
const doc = parser.parseFromString(html, "text/html");
42-
43-
Array.from(doc.body.childNodes).forEach((node) => {
44-
if (node.nodeName === "SCRIPT") {
45-
const script = document.createElement("script");
46-
script.setAttribute("data-dynamic-script", "true"); // Mark as dynamic
47-
48-
if (node.src) {
49-
// Re-create the src to ensure it's correctly resolved
50-
const scriptURL = new URL(
51-
node.getAttribute("src"),
52-
window.location.href
53-
);
54-
script.src = scriptURL.href;
55-
// Start animation after script loads
56-
script.onload = () => {
57-
if (
58-
window.pageAnimations &&
59-
window.pageAnimations[currentPage] &&
60-
typeof window.pageAnimations[currentPage].start === "function"
61-
) {
62-
window.pageAnimations[currentPage].start();
63-
}
64-
};
65-
} else {
66-
script.textContent = node.textContent;
67-
}
68-
// Append to body to ensure execution
69-
document.body.appendChild(script);
70-
71-
// For inline scripts, start animation immediately after append
72-
if (!node.src) {
73-
if (
74-
window.pageAnimations &&
75-
window.pageAnimations[currentPage] &&
76-
typeof window.pageAnimations[currentPage].start === "function"
77-
) {
78-
window.pageAnimations[currentPage].start();
79-
}
80-
}
81-
} else {
82-
app.appendChild(node.cloneNode(true));
38+
app.innerHTML = html;
39+
40+
// Automatically load the corresponding script
41+
const scriptPath = route.replace(".html", ".js");
42+
const script = document.createElement("script");
43+
script.src = scriptPath;
44+
script.setAttribute("data-dynamic-script", "true");
45+
document.body.appendChild(script);
46+
47+
script.onload = () => {
48+
if (
49+
window.pageAnimations &&
50+
window.pageAnimations[currentPage] &&
51+
typeof window.pageAnimations[currentPage].start === "function"
52+
) {
53+
window.pageAnimations[currentPage].start();
8354
}
84-
});
55+
};
8556
} catch (error) {
8657
console.error("Routing error:", error);
8758
app.innerHTML = "<h1>404 - Not Found</h1>";
@@ -132,7 +103,7 @@ document.addEventListener("DOMContentLoaded", () => {
132103
if (matrixTimer > matrixInterval) {
133104
const matrix = window.matrix;
134105
if (matrix && typeof matrix.draw === "function") {
135-
matrix.draw();
106+
matrix.draw(deltaTime);
136107
}
137108
matrixTimer = 0;
138109
}
@@ -177,44 +148,193 @@ const matrixEffect = (canvasId, containerSelector) => {
177148
const canvas = document.getElementById(canvasId);
178149
if (!canvas) return null;
179150
const ctx = canvas.getContext("2d");
151+
//const ctx = canvas.getContext("2d", {
152+
// willReadFrequently: true,
153+
//});
180154
const container = document.querySelector(containerSelector);
181155

182156
canvas.width = container.offsetWidth;
183157
canvas.height = container.offsetHeight;
184158

185159
const matrix = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789@#$%^&*()*&^%";
186160
const font_size = 10;
161+
162+
const circle_buffer_width = canvas.width / font_size;
163+
const circle_buffer_height = canvas.height / font_size;
187164
const columns = canvas.width / font_size;
188165
const drops = [];
166+
const particles = [];
167+
const circle_buffer = new Int8Array(
168+
circle_buffer_width * circle_buffer_height
169+
);
170+
171+
const maxParticles = 6;
189172

190-
let beginMatrixEffect = true;
191173
for (let x = 0; x < columns; x++) {
192174
drops[x] = 1;
193175
}
194176
for (let i = 0; i < drops.length; i++) {
195177
drops[i] = canvas.height / font_size;
196178
}
179+
for (let i = 0; i < maxParticles; i++) {
180+
particles.push({
181+
x: Math.random() * canvas.width,
182+
y: Math.random() * canvas.height,
183+
radius: Math.random() * 50 + 75,
184+
speed: Math.random() * 4,
185+
dir: vec2(Math.random() - 0.5, Math.random() - 0.5),
186+
});
187+
}
188+
189+
function plotPixel(x, y, v) {
190+
const sx = Math.floor(x) | 0;
191+
const sy = Math.floor(y) | 0;
192+
const index = (sy * circle_buffer_width + sx) | 0;
193+
if (
194+
sx >= 0 &&
195+
sx < circle_buffer_width &&
196+
sy >= 0 &&
197+
sy < circle_buffer_height
198+
) {
199+
circle_buffer[index] = v;
200+
}
201+
}
197202

198-
let frame = 0;
199-
let hideIniitalEffect = true;
203+
function plotOctants(x, y, centerX, centerY) {
204+
plotPixel(centerX + x, centerY + y, 1);
205+
plotPixel(centerX - x, centerY + y, 1);
206+
plotPixel(centerX + x, centerY - y, 1);
207+
plotPixel(centerX - x, centerY - y, 1);
208+
plotPixel(centerX + y, centerY + x, 1);
209+
plotPixel(centerX - y, centerY + x, 1);
210+
plotPixel(centerX + y, centerY - x, 1);
211+
plotPixel(centerX - y, centerY - x, 1);
212+
}
200213

201-
function draw() {
214+
function bresenhamCircle(centerX, centerY, radius) {
215+
centerX /= font_size;
216+
centerY /= font_size;
217+
radius /= font_size;
218+
centerX = Math.floor(centerX) | 0;
219+
centerY = Math.floor(centerY) | 0;
220+
radius = Math.floor(radius) | 0;
221+
let x = 0 | 0;
222+
let y = radius | 0;
223+
let d = (3 - 2 * radius) | 0;
224+
while (y > x) {
225+
x++;
226+
if (d > 0) {
227+
y--;
228+
d = (d + 4 * (x - y)) | 0;
229+
} else {
230+
d = (d + 4 * x) | 0;
231+
}
232+
plotOctants(x, y, centerX, centerY);
233+
}
234+
}
235+
const epsilon = 0.01;
236+
237+
function line(x0, y0, x1, y1) {
238+
x0 /= font_size;
239+
y0 /= font_size;
240+
x1 /= font_size;
241+
y1 /= font_size;
242+
x0 = Math.floor(x0) | 0;
243+
y0 = Math.floor(y0) | 0;
244+
x1 = Math.floor(x1) | 0;
245+
y1 = Math.floor(y1) | 0;
246+
const dx = Math.abs(x1 - x0);
247+
const dy = Math.abs(y1 - y0);
248+
const sx = Math.sign(x1 - x0);
249+
const sy = Math.sign(y1 - y0);
250+
let err = dx - dy;
251+
252+
while (true) {
253+
plotPixel(x0, y0, 2);
254+
255+
if (Math.abs(x0 - x1) + Math.abs(y0 - y1) < epsilon) break;
256+
257+
const e2 = 2 * err;
258+
if (e2 > -dy) {
259+
err -= dy;
260+
x0 += sx;
261+
}
262+
if (e2 < dx) {
263+
err += dx;
264+
y0 += sy;
265+
}
266+
}
267+
}
268+
269+
function drawCicleBuffer() {
270+
ctx.fillStyle = "rgba(3, 94, 3, 1)";
271+
for (let y = 0; y < circle_buffer_height; y++) {
272+
for (let x = 0; x < circle_buffer_width; x++) {
273+
const index = (y * circle_buffer_width + x) | 0;
274+
const xx = (x * font_size) | 0;
275+
const yy = (y * font_size) | 0;
276+
if (circle_buffer[index] > 0) {
277+
ctx.fillStyle = "rgba(0, 0, 0, 1)";
278+
ctx.fillRect(xx, yy, font_size, font_size);
279+
ctx.fillStyle = "rgba(5, 130, 5, 1)";
280+
const text = matrix[Math.floor(Math.random() * matrix.length)];
281+
ctx.fillText(text, xx, yy + font_size);
282+
}
283+
}
284+
}
285+
}
286+
287+
function draw(deltaTime) {
202288
ctx.fillStyle = "rgba(0, 0, 0, 0.05)";
203289
ctx.fillRect(0, 0, canvas.width, canvas.height);
204290

205-
if (hideIniitalEffect) frame++;
291+
// Set the fill color to green
292+
ctx.fillStyle = "green";
293+
294+
circle_buffer.fill(0);
295+
296+
for (let i = 0; i < maxParticles; i++) {
297+
const p = particles[i];
298+
p.x += p.dir.x * p.speed;
299+
p.y += p.dir.y * p.speed;
300+
if (p.x < 0 || p.x > canvas.width) p.dir.x *= -1;
301+
if (p.y < 0 || p.y > canvas.height) p.dir.y *= -1;
302+
bresenhamCircle(p.x, p.y, p.radius);
303+
if (Math.random() < 0.01) {
304+
p.dir = vec2(Math.random() - 0.5, Math.random() - 0.5);
305+
}
306+
// if (i > 0) {
307+
// line(
308+
// particles[i - 1].x,
309+
// particles[i - 1].y,
310+
// particles[i].x,
311+
// particles[i].y
312+
// );
313+
// }
314+
}
206315

207-
//if (frame > drops.length) {
208-
// hideIniitalEffect = false;
209316
ctx.fillStyle = "rgba(3, 94, 3, 1)";
210-
//} else {
211-
// ctx.fillStyle = "rgba(210, 15, 15, 1)";
212-
//}
213317
ctx.font = font_size + "px arial";
214318

319+
drawCicleBuffer();
215320
for (let i = 0; i < drops.length; i++) {
216321
const text = matrix[Math.floor(Math.random() * matrix.length)];
217-
ctx.fillText(text, i * font_size, drops[i] * font_size);
322+
323+
const tx = i * font_size;
324+
const ty = drops[i] * font_size;
325+
326+
ctx.fillText(text, tx, ty);
327+
328+
const cx = tx / font_size;
329+
const cy = ty / font_size;
330+
const index = (cy * circle_buffer_width + cx) | 0;
331+
if (circle_buffer[index] === 1) {
332+
ctx.fillStyle = "rgba(18, 215, 202, 1)";
333+
ctx.fillText("code", tx, ty);
334+
ctx.fillText("base", tx + font_size, ty + font_size);
335+
ctx.fillText("hack", tx - font_size, ty + font_size);
336+
}
337+
ctx.fillStyle = "rgba(3, 94, 3, 1)";
218338

219339
if (drops[i] * font_size > canvas.height && Math.random() > 0.975) {
220340
drops[i] = 0;

‎dummy.html‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Popup Slider Near Cursor</title>
7+
<style>
8+
body {
9+
font-family: Arial, sans-serif;
10+
margin: 20px;
11+
}
12+
#popup {
13+
display: none;
14+
position: absolute;
15+
border: 1px solid #ccc;
16+
background-color: white;
17+
padding: 10px;
18+
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
19+
z-index: 1000;
20+
}
21+
.popup-button {
22+
margin-top: 10px;
23+
}
24+
</style>
25+
</head>
26+
<body>
27+
<h1>Click to Show Popup</h1>
28+
29+
<div id="popup">
30+
<label for="slider">Adjust Value:</label>
31+
<input type="range" id="slider" min="0" max="100" />
32+
<div class="popup-button">
33+
<button id="okBtn">OK</button>
34+
<button id="closeBtn">Close</button>
35+
</div>
36+
</div>
37+
38+
<script>
39+
const popup = document.getElementById("popup");
40+
const okBtn = document.getElementById("okBtn");
41+
const closeBtn = document.getElementById("closeBtn");
42+
43+
document.addEventListener("click", (event) => {
44+
// Show the popup only on left mouse button click
45+
if (event.button === 0 && popup.style.display !== "block") {
46+
// 0 indicates left mouse button
47+
popup.style.display = "block";
48+
// Position the popup next to the cursor, with a slight offset
49+
popup.style.left = event.pageX + 10 + "px";
50+
popup.style.top = event.pageY + 10 + "px";
51+
}
52+
});
53+
54+
// Event listener for closing the popup
55+
closeBtn.addEventListener("click", () => {
56+
popup.style.display = "none"; // Close the popup
57+
});
58+
59+
okBtn.addEventListener("click", () => {
60+
const sliderValue = document.getElementById("slider").value;
61+
alert("Slider Value: " + sliderValue); // Show alert with slider value
62+
popup.style.display = "none"; // Close the popup
63+
});
64+
65+
// Prevent the popup from closing when clicking inside of it
66+
popup.addEventListener("click", (event) => {
67+
event.stopPropagation();
68+
});
69+
</script>
70+
</body>
71+
</html>

‎index.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap"
2121
rel="stylesheet"
2222
/>
23+
<script src="/pages/math-lib.js"></script>
2324
</head>
2425
<body>
2526
<canvas id="fullscreen-matrix-canvas"></canvas>
@@ -40,7 +41,6 @@
4041
<a href="#/" data-route>Home</a>
4142
<a href="#/about" data-route>About</a>
4243
<a href="#/contact" data-route>Contact</a>
43-
<a href="#/test" data-route>Test</a>
4444
<a href="#/projects" data-route>Projects</a>
4545
<a href="pages/live-coding.html">Live Coding</a>
4646
</nav>

0 commit comments

Comments
 (0)