diff --git a/index.html b/index.html
index af2fd15..a17c631 100644
--- a/index.html
+++ b/index.html
@@ -577,6 +577,41 @@
}
.s8visual.visible { opacity: 1; }
#s9Text { text-align: center; }
+ .s9canvas {
+ width: 100%;
+ max-width: 55rem;
+ margin-top: 1.5rem;
+ opacity: 0;
+ transition: opacity 4s ease;
+ }
+ .s9canvas.visible { opacity: 1; }
+ #meshCanvas {
+ width: 100%;
+ height: 320px;
+ border: 1px solid #003300;
+ border-radius: 4px;
+ cursor: pointer;
+ display: block;
+ }
+ .mesh-legend {
+ display: flex;
+ gap: 2rem;
+ justify-content: center;
+ margin-top: 0.8rem;
+ font-size: 0.8rem;
+ }
+ .legend-item { display: flex; align-items: center; gap: 0.5rem; }
+ .legend-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
+ .legend-dot.online { background: #00ff00; }
+ .legend-dot.offline { background: #ff4444; }
+ .legend-dot.packet { background: #ffff00; }
+ .mesh-status {
+ text-align: center;
+ color: #007700;
+ font-size: 0.85rem;
+ margin-top: 0.5rem;
+ min-height: 1.5rem;
+ }
.s6section {
margin-top: 1.5rem;
@@ -723,7 +758,15 @@
-
+
+
+
+ ONLINE
+ OFFLINE
+ PACKET
+
+
CLICK A NODE TO SIMULATE FAILURE
+
@@ -1589,14 +1632,179 @@
},30);
}
+ // Mesh network interactive visualization state
+ let meshAnimId = null;
+ let meshClicked = false;
+ let meshState = null;
+
+ function initMesh() {
+ meshClicked = false;
+ meshState = {
+ nodes: [
+ { id: 0, x: 120, y: 50, label: 'A', online: true },
+ { id: 1, x: 400, y: 40, label: 'B', online: true },
+ { id: 2, x: 680, y: 50, label: 'C', online: true },
+ { id: 3, x: 100, y: 150, label: 'D', online: true },
+ { id: 4, x: 400, y: 160, label: 'E', online: true },
+ { id: 5, x: 700, y: 150, label: 'F', online: true },
+ { id: 6, x: 200, y: 260, label: 'G', online: true },
+ { id: 7, x: 600, y: 260, label: 'H', online: true },
+ ],
+ packets: [],
+ };
+ computeConnections();
+ for (let i = 0; i < 4; i++) spawnPacket();
+ }
+
+ function computeConnections() {
+ const online = meshState.nodes.filter(n => n.online);
+ for (const node of meshState.nodes) {
+ if (!node.online) { node.connections = []; continue; }
+ const distances = online
+ .filter(n => n.id !== node.id && n.online)
+ .map(n => ({ node: n, dist: Math.hypot(n.x - node.x, n.y - node.y) }))
+ .sort((a, b) => a.dist - b.dist);
+ node.connections = distances.slice(0, 3).map(d => d.node.id);
+ }
+ }
+
+ function spawnPacket() {
+ const online = meshState.nodes.filter(n => n.online);
+ if (online.length < 2) return;
+ const from = online[Math.floor(Math.random() * online.length)];
+ if (!from.connections || from.connections.length === 0) return;
+ const toId = from.connections[Math.floor(Math.random() * from.connections.length)];
+ const to = meshState.nodes.find(n => n.id === toId);
+ if (!to || !to.online) return;
+ meshState.packets.push({ fromId: from.id, toId: to.id, progress: Math.random() });
+ }
+
+ function drawMesh() {
+ const canvas = document.getElementById('meshCanvas');
+ if (!canvas || canvas.offsetParent === null) { meshAnimId = requestAnimationFrame(drawMesh); return; }
+ const ctx = canvas.getContext('2d');
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ for (const node of meshState.nodes) {
+ if (!node.online) continue;
+ for (const connId of node.connections) {
+ const conn = meshState.nodes.find(n => n.id === connId);
+ if (!conn || !conn.online) continue;
+ ctx.strokeStyle = '#003300';
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ ctx.moveTo(node.x, node.y);
+ ctx.lineTo(conn.x, conn.y);
+ ctx.stroke();
+ }
+ }
+
+ for (const pkt of meshState.packets) {
+ const from = meshState.nodes.find(n => n.id === pkt.fromId);
+ const to = meshState.nodes.find(n => n.id === pkt.toId);
+ if (!from || !to || !from.online || !to.online) continue;
+ const x = from.x + (to.x - from.x) * pkt.progress;
+ const y = from.y + (to.y - from.y) * pkt.progress;
+ ctx.shadowColor = '#ffff00';
+ ctx.shadowBlur = 10;
+ ctx.fillStyle = '#ffff00';
+ ctx.beginPath();
+ ctx.arc(x, y, 5, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.shadowBlur = 0;
+ }
+
+ for (const node of meshState.nodes) {
+ const color = node.online ? '#00ff00' : '#ff4444';
+ if (node.online) {
+ ctx.shadowColor = '#00ff00';
+ ctx.shadowBlur = 12;
+ }
+ ctx.fillStyle = color;
+ ctx.beginPath();
+ ctx.arc(node.x, node.y, 12, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.shadowBlur = 0;
+ ctx.strokeStyle = color;
+ ctx.lineWidth = 2;
+ if (!node.online) ctx.setLineDash([3, 3]);
+ ctx.beginPath();
+ ctx.arc(node.x, node.y, 12, 0, Math.PI * 2);
+ ctx.stroke();
+ ctx.setLineDash([]);
+ ctx.fillStyle = color;
+ ctx.font = 'bold 11px Courier New, monospace';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(node.label, node.x, node.y + 24);
+ }
+
+ for (const pkt of meshState.packets) {
+ pkt.progress += 0.01;
+ if (pkt.progress >= 1) {
+ pkt.progress = 0;
+ pkt.fromId = pkt.toId;
+ const from = meshState.nodes.find(n => n.id === pkt.fromId);
+ if (!from || !from.online) {
+ const online = meshState.nodes.filter(n => n.online);
+ if (online.length > 0) pkt.fromId = online[Math.floor(Math.random() * online.length)].id;
+ }
+ const from2 = meshState.nodes.find(n => n.id === pkt.fromId);
+ if (from2 && from2.online && from2.connections) {
+ const valid = from2.connections.filter(cid => {
+ const cn = meshState.nodes.find(n => n.id === cid);
+ return cn && cn.online;
+ });
+ pkt.toId = valid.length > 0 ? valid[Math.floor(Math.random() * valid.length)] : pkt.fromId;
+ }
+ }
+ }
+
+ meshState.packets = meshState.packets.filter(p => {
+ const from = meshState.nodes.find(n => n.id === p.fromId);
+ const to = meshState.nodes.find(n => n.id === p.toId);
+ return from && to && from.online && to.online && p.fromId !== p.toId;
+ });
+ while (meshState.packets.length < 4) spawnPacket();
+
+ meshAnimId = requestAnimationFrame(drawMesh);
+ }
+
+ function toggleMeshNode(mx, my) {
+ const canvas = document.getElementById('meshCanvas');
+ if (!canvas) return;
+ const rect = canvas.getBoundingClientRect();
+ const cx = (mx - rect.left) * (canvas.width / rect.width);
+ const cy = (my - rect.top) * (canvas.height / rect.height);
+ for (const node of meshState.nodes) {
+ if (Math.hypot(cx - node.x, cy - node.y) < 20) {
+ node.online = !node.online;
+ meshState.packets = meshState.packets.filter(p => p.fromId !== node.id && p.toId !== node.id);
+ computeConnections();
+ const status = document.getElementById('meshStatus');
+ if (status) {
+ status.textContent = node.online
+ ? 'NODE ' + node.label + ' RESTORED — MESH PATH FOUND'
+ : 'NODE ' + node.label + ' OFFLINE — MESH REROUTED';
+ }
+ meshClicked = true;
+ if (document.getElementById('returnFromScene9').style.visibility !== 'visible') {
+ document.getElementById('returnFromScene9').style.cssText = '';
+ showNextBtn('returnFromScene9');
+ }
+ break;
+ }
+ }
+ }
+
function loadScene9(sceneElem) {
s9c=[];
sceneElem.style.display='flex';
const txt=document.getElementById('s9Text');
- const vis=document.getElementById('s9Visual');
+ const canvasDiv=document.getElementById('s9Canvas');
txt.innerHTML='';
- vis.innerHTML='';
- vis.className='s8visual';
+ canvasDiv.classList.remove('visible');
+ if (meshAnimId) { cancelAnimationFrame(meshAnimId); meshAnimId = null; }
let o=0;
const fi=setInterval(()=>{
if (sceneElem.style.display !== 'flex' || document.getElementById('returnFromScene9').style.visibility === 'visible') { clearInterval(fi); return; }
@@ -1605,17 +1813,11 @@
const t1=setTimeout(()=>{
typeCalmly(txt,"\n\nMESH NETWORKS AND P2P PROTOCOLS LET COMMUNITIES BUILD THEIR OWN INTERNET — NO ISP REQUIRED.",()=>{
const t2=setTimeout(()=>{
- vis.classList.add('visible');
- vis.innerHTML='INTERNET WITHOUT AN ISP
'
- +'
🌐MESH NETWORKS —
EVERY DEVICE IS A NODE
'
- +'
🔗PEER TO PEER —
DIRECT CONNECTION, NO MIDDLEMAN
'
- +'
📡NO ISP —
COMMUNITY-OWNED INFRASTRUCTURE
'
- +'
';
- const t3=setTimeout(()=>{
- document.getElementById('returnFromScene9').style.cssText='';
- showNextBtn('returnFromScene9');
- },400);
- s9c.push(t3);
+ canvasDiv.classList.add('visible');
+ initMesh();
+ drawMesh();
+ const status=document.getElementById('meshStatus');
+ if(status) status.textContent='CLICK A NODE TO SIMULATE FAILURE';
},300);
s9c.push(t2);
},8,20,s9c);
@@ -1731,6 +1933,7 @@
});
function showTechHub() {
+ if (meshAnimId) { cancelAnimationFrame(meshAnimId); meshAnimId = null; }
const s6 = document.getElementById('scene6');
s6.style.display = 'flex';
s6.style.opacity = '1';
@@ -1757,6 +1960,10 @@
showTechHub();
});
+ document.getElementById('meshCanvas').addEventListener('click', (e) => {
+ if (meshState) toggleMeshNode(e.clientX, e.clientY);
+ });
+
// Keyboard shortcuts for testing
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
@@ -1922,14 +2129,12 @@
if (s9.style.display === 'flex') {
s9c.forEach(t => clearTimeout(t)); s9c = [];
const txt = document.getElementById('s9Text');
- const vis = document.getElementById('s9Visual');
txt.innerHTML = "THE INTERNET WAS DESIGNED TO BE DECENTRALIZED — BUT ISPS HAVE TURNED IT INTO A UTILITY CONTROLLED BY GATEKEEPERS.\n\nMESH NETWORKS AND P2P PROTOCOLS LET COMMUNITIES BUILD THEIR OWN INTERNET — NO ISP REQUIRED.";
- vis.innerHTML='INTERNET WITHOUT AN ISP
'
- +'
🌐MESH NETWORKS —
EVERY DEVICE IS A NODE
'
- +'
🔗PEER TO PEER —
DIRECT CONNECTION, NO MIDDLEMAN
'
- +'
📡NO ISP —
COMMUNITY-OWNED INFRASTRUCTURE
'
- +'
';
- vis.classList.add('visible');
+ const canvasDiv = document.getElementById('s9Canvas');
+ canvasDiv.classList.add('visible');
+ if (meshAnimId) { cancelAnimationFrame(meshAnimId); meshAnimId = null; }
+ initMesh();
+ drawMesh();
document.getElementById('returnFromScene9').style.cssText='';
showNextBtn('returnFromScene9');
}