From 439415e748f4b9b700e58a31cefb1f60880b91d8 Mon Sep 17 00:00:00 2001 From: avi Date: Thu, 14 May 2026 11:42:20 -0500 Subject: [PATCH] =?UTF-8?q?Save=20point:=20Data=20Exfiltration=20Simulator?= =?UTF-8?q?=20(scene8b)=20=E2=80=94=20interactive=20canvas=20showing=20whe?= =?UTF-8?q?re=20phone=20data=20goes=20on=20GrapheneOS/Android/iOS,=20acces?= =?UTF-8?q?sible=20via=20SEE=20THE=20RISK=20button=20from=20scene8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 656 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 634 insertions(+), 22 deletions(-) diff --git a/index.html b/index.html index ac1d008..873720a 100644 --- a/index.html +++ b/index.html @@ -612,6 +612,22 @@ margin-top: 0.5rem; min-height: 1.5rem; } + .s8canvas { + width: 100%; + max-width: 55rem; + margin-top: 0.8rem; + opacity: 0; + transition: opacity 4s ease; + } + .s8canvas.visible { opacity: 1; } + #phoneCanvas { + width: 100%; + height: 320px; + border: 1px solid #003300; + border-radius: 4px; + cursor: pointer; + display: block; + } .s6section { margin-top: 1.5rem; @@ -651,6 +667,63 @@ } .comp-table td.graphene-col .no { color: #44ff44; } .comp-table td.graphene-col .yes { color: #ff4444; } + + /* Scene 8b — Exfiltration Simulator */ + #s8BtnRow { + position: fixed; + bottom: 5%; + left: 50%; + transform: translateX(-50%); + z-index: 5; + display: flex; + gap: 1.5rem; + justify-content: center; + visibility: hidden; + opacity: 0; + transition: opacity 0.5s ease; + } + #s8BtnRow .btnNext { + position: static; + transform: none; + margin: 0; + visibility: visible; + opacity: 1; + pointer-events: auto; + } + .s8bcanvas { + width: 100%; + max-width: 55rem; + margin-top: 0.8rem; + opacity: 0; + transition: opacity 4s ease; + } + .s8bcanvas.visible { opacity: 1; } + #exfilCanvas { + width: 100%; + height: 360px; + border: 1px solid #003300; + border-radius: 4px; + cursor: pointer; + display: block; + } + .exfil-counter { + text-align: center; + color: #00ff00; + font-size: 1.1rem; + padding: 0.5rem; + min-height: 2rem; + font-weight: bold; + } + .exfil-legend { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; + margin-top: 0.3rem; + font-size: 0.75rem; + } + .exfil-legend-item { display: flex; align-items: center; gap: 0.3rem; } + .exfil-legend-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; } @@ -750,10 +823,27 @@ -
+
-
- +
+ +
CLICK A TAB TO COMPARE OS SECURITY
+
+
+ + +
+
+ +
+
+
+ +
DATA VALUE EXPOSED: $0/yr
+
CLICK A TAB TO SEE WHERE YOUR DATA GOES
+
+
+
@@ -1601,30 +1691,231 @@ },30); } + // Phone security interactive visualization state + let phoneAnimId = null; + let phoneMode = 'grapheneos'; + let phonePackets = []; + let phoneClicked = false; + + const phoneModeData = { + grapheneos: { + label: 'GRAPHENEOS', color: '#00ff00', + locks: { camera: true, mic: true, location: true, contacts: true, clipboard: true, network: true, storage: true, sensors: true }, + packetType: 'stay', + status: 'EVERY PERMISSION REQUIRES EXPLICIT CONSENT — APPS CANNOT ACCESS SENSORS WITHOUT YOUR APPROVAL' + }, + android: { + label: 'ANDROID', color: '#ff4444', + locks: { camera: false, mic: false, location: false, contacts: false, clipboard: false, network: false, storage: false, sensors: false }, + packetType: 'leak', + status: 'GOOGLE PLAY SERVICES HAS SYSTEM-LEVEL ACCESS TO NEARLY EVERY SENSOR — DATA COLLECTED BY DEFAULT' + }, + ios: { + label: 'iOS', color: '#ffff00', + locks: { camera: false, mic: false, location: true, contacts: false, clipboard: false, network: true, storage: true, sensors: false }, + packetType: 'mixed', + status: 'APPLE BLOCKS SOME TRACKING BUT STILL COLLECTS DATA THROUGH ITS OWN SERVICES' + } + }; + + const phoneTabs = [ + { id: 'grapheneos', x: 170, w: 130, label: 'GRAPHENEOS' }, + { id: 'android', x: 335, w: 130, label: 'ANDROID' }, + { id: 'ios', x: 500, w: 130, label: 'iOS' }, + ]; + + const phoneFeatList = [ + { id: 'camera', label: 'CAMERA', x: 215, y: 100, lx: 365 }, + { id: 'mic', label: 'MICROPHONE', x: 215, y: 135, lx: 365 }, + { id: 'clipboard', label: 'CLIPBOARD', x: 215, y: 170, lx: 365 }, + { id: 'storage', label: 'STORAGE', x: 215, y: 205, lx: 365 }, + { id: 'location', label: 'LOCATION', x: 440, y: 100, lx: 570 }, + { id: 'contacts', label: 'CONTACTS', x: 440, y: 135, lx: 570 }, + { id: 'network', label: 'NETWORK', x: 440, y: 170, lx: 570 }, + { id: 'sensors', label: 'SENSORS', x: 440, y: 205, lx: 570 }, + ]; + + function initPhoneScene() { + phoneClicked = false; + phoneMode = 'grapheneos'; + initPhonePackets(); + } + + function initPhonePackets() { + const mode = phoneModeData[phoneMode]; + phonePackets = []; + for (let i = 0; i < 4; i++) { + const orbit = mode.packetType === 'stay' || (mode.packetType === 'mixed' && i < 2); + phonePackets.push({ + angle: (i / 4) * Math.PI * 2, + speed: 0.012 + Math.random() * 0.008, + radius: 30 + i * 15, + orbit: orbit, + leakX: 400 + (Math.random() - 0.5) * 200, + leakY: 30 + Math.random() * 40, + progress: Math.random(), + }); + } + } + + function drawPhoneScene() { + const canvas = document.getElementById('phoneCanvas'); + if (!canvas || canvas.offsetParent === null) { phoneAnimId = requestAnimationFrame(drawPhoneScene); return; } + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + const mode = phoneModeData[phoneMode]; + + for (const tab of phoneTabs) { + const active = tab.id === phoneMode; + ctx.fillStyle = active ? mode.color : '#001100'; + ctx.strokeStyle = active ? mode.color : '#003300'; + ctx.lineWidth = active ? 2 : 1; + const r = 4; + ctx.beginPath(); + ctx.moveTo(tab.x + r, 8); + ctx.lineTo(tab.x + tab.w - r, 8); + ctx.quadraticCurveTo(tab.x + tab.w, 8, tab.x + tab.w, 8 + r); + ctx.lineTo(tab.x + tab.w, 30 - r); + ctx.quadraticCurveTo(tab.x + tab.w, 30, tab.x + tab.w - r, 30); + ctx.lineTo(tab.x + r, 30); + ctx.quadraticCurveTo(tab.x, 30, tab.x, 30 - r); + ctx.lineTo(tab.x, 8 + r); + ctx.quadraticCurveTo(tab.x, 8, tab.x + r, 8); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = active ? '#000' : (active ? '#000' : mode.color); + ctx.font = 'bold 11px Courier New, monospace'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(tab.label, tab.x + tab.w / 2, 19); + } + + ctx.strokeStyle = '#003300'; + ctx.lineWidth = 2; + ctx.fillStyle = '#000400'; + ctx.beginPath(); + ctx.roundRect(170, 65, 460, 205, 16); + ctx.fill(); + ctx.stroke(); + + ctx.fillStyle = '#003300'; + ctx.fillRect(370, 65, 60, 4); + ctx.fillRect(370, 252, 60, 2); + + for (const feat of phoneFeatList) { + ctx.fillStyle = '#00ff00'; + ctx.font = '11px Courier New, monospace'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + ctx.fillText(feat.label, feat.x, feat.y); + const locked = mode.locks[feat.id]; + ctx.fillStyle = locked ? '#00ff00' : '#ff4444'; + ctx.font = '14px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText(locked ? '\u{1F512}' : '\u{1F513}', feat.lx, feat.y); + } + + for (const pkt of phonePackets) { + let x, y; + if (pkt.orbit) { + const cx = 400, cy = 165; + x = cx + Math.cos(pkt.angle + pkt.progress * Math.PI * 2) * pkt.radius; + y = cy + Math.sin(pkt.angle + pkt.progress * Math.PI * 2) * pkt.radius * 0.6; + x = Math.max(185, Math.min(615, x)); + y = Math.max(80, Math.min(255, y)); + } else { + const t = pkt.progress; + if (t < 0.5) { + const p = t * 2; + x = 400 + (pkt.leakX - 400) * p; + y = 170 + (pkt.leakY - 170) * p; + } else { + const p = (t - 0.5) * 2; + x = pkt.leakX + (pkt.leakX - 400) * p; + y = pkt.leakY - 80 * p; + } + } + ctx.shadowColor = mode.color; + ctx.shadowBlur = 8; + ctx.fillStyle = mode.color; + ctx.beginPath(); + ctx.arc(x, y, 4, 0, Math.PI * 2); + ctx.fill(); + ctx.shadowBlur = 0; + pkt.progress += pkt.speed; + if (pkt.progress >= 1) { + pkt.progress = 0; + if (!pkt.orbit) { + pkt.leakX = 400 + (Math.random() - 0.5) * 200; + pkt.leakY = 30 + Math.random() * 40; + } + } + } + + phoneAnimId = requestAnimationFrame(drawPhoneScene); + } + + function handlePhoneCanvasClick(mx, my) { + const canvas = document.getElementById('phoneCanvas'); + 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 tab of phoneTabs) { + if (cx >= tab.x && cx <= tab.x + tab.w && cy >= 8 && cy <= 30) { + if (tab.id !== phoneMode) { + phoneMode = tab.id; + initPhonePackets(); + const status = document.getElementById('phoneStatus'); + if (status) status.textContent = phoneModeData[phoneMode].status; + phoneClicked = true; + const s8Row = document.getElementById('s8BtnRow'); + if (s8Row.style.visibility !== 'visible') { + s8Row.style.cssText = ''; + s8Row.style.visibility = 'visible'; + let ro = 0; + const rfi = setInterval(() => { + ro += 0.05; if (ro >= 1) { ro = 1; clearInterval(rfi); s8Row.style.pointerEvents = 'auto'; } + s8Row.style.opacity = ro; + }, 30); + } + } + break; + } + } + } + function loadScene8(sceneElem) { s8c=[]; sceneElem.style.display='flex'; const txt=document.getElementById('s8Text'); - const vis=document.getElementById('s8Visual'); + const canvasDiv=document.getElementById('s8Canvas'); txt.innerHTML=''; - vis.className='s8visual'; + canvasDiv.classList.remove('visible'); + if (phoneAnimId) { cancelAnimationFrame(phoneAnimId); phoneAnimId = null; } + if (exfilAnimId) { cancelAnimationFrame(exfilAnimId); exfilAnimId = null; } + const s8Row = document.getElementById('s8BtnRow'); + s8Row.style.visibility = 'hidden'; + s8Row.style.opacity = '0'; + s8Row.style.pointerEvents = 'none'; let o=0; const fi=setInterval(()=>{ - if (sceneElem.style.display !== 'flex' || document.getElementById('returnFromScene8').style.visibility === 'visible') { clearInterval(fi); return; } + if (sceneElem.style.display !== 'flex' || document.getElementById('s8BtnRow').style.visibility === 'visible') { clearInterval(fi); return; } o+=0.05;if(o>=1){o=1;clearInterval(fi); typeCalmly(txt,"YOUR PHONE IS LITERALLY A TRACKING DEVICE IN YOUR POCKET — UNLESS YOU CHOOSE OTHERWISE.",()=>{ const t1=setTimeout(()=>{ - txt.innerHTML+="\n\n"; - vis.classList.add('visible'); - vis.innerHTML=buildS6MobileTable(); - typeCalmly(txt,"GRAPHENEOS PROVES THAT PRIVACY AND USABILITY CAN COEXIST — BUT ONLY WHEN YOU TAKE CONTROL.",()=>{ - const t3=setTimeout(()=>{ - document.getElementById('returnFromScene8').style.cssText=''; - showNextBtn('returnFromScene8'); - },400); - s8c.push(t3); + typeCalmly(txt,"\n\nGRAPHENEOS LOCKS DOWN EVERY SENSOR AND PERMISSION — CLICK A TAB TO SEE THE DIFFERENCE.",()=>{ + const t2=setTimeout(()=>{ + canvasDiv.classList.add('visible'); + initPhoneScene(); + drawPhoneScene(); + const status=document.getElementById('phoneStatus'); + if(status) status.textContent=phoneModeData[phoneMode].status; + },300); + s8c.push(t2); },8,20,s8c); - },300); + },600); s8c.push(t1); },8,20,s8c); } @@ -1839,6 +2130,283 @@ let s7c = []; let s8c = []; let s9c = []; + let s8bc = []; + + // Scene 8b — Data Exfiltration Simulator + let exfilAnimId = null; + let exfilMode = 'grapheneos'; + let exfilPackets = []; + let exfilCounter = 0; + let exfilCounterTarget = 0; + + const exfilModeData = { + grapheneos: { label: 'GRAPHENEOS', counterMax: 0, color: '#00ff00', status: 'YOUR DATA STAYS ON YOUR DEVICE. NO TRACKING. NO PROFILING.' }, + android: { label: 'ANDROID', counterMax: 1200, color: '#ff4444', status: 'GOOGLE AND THIRD-PARTY APPS COLLECT YOUR DATA BY DEFAULT. YOU ARE THE PRODUCT.' }, + ios: { label: 'iOS', counterMax: 600, color: '#ffff00', status: 'APPLE COLLECTS DATA THROUGH ITS SERVICES. SOME THIRD-PARTY TRACKING IS BLOCKED.' }, + }; + + const exfilTabs = [ + { id: 'grapheneos', x: 170, w: 130, label: 'GRAPHENEOS' }, + { id: 'android', x: 335, w: 130, label: 'ANDROID' }, + { id: 'ios', x: 500, w: 130, label: 'iOS' }, + ]; + + const exfilServers = { + grapheneos: [], + android: [ + { id: 'google', label: 'GOOGLE', x: 540, y: 55, w: 100, h: 34, color: '#4285f4' }, + { id: 'broker', label: 'DATA BROKER', x: 645, y: 115, w: 115, h: 34, color: '#ff6644' }, + { id: 'ads', label: 'AD NETWORKS', x: 555, y: 195, w: 115, h: 34, color: '#ff44ff' }, + { id: 'hacker', label: 'HACKER', x: 635, y: 285, w: 100, h: 34, color: '#ff0000' }, + ], + ios: [ + { id: 'apple', label: 'APPLE', x: 575, y: 65, w: 100, h: 34, color: '#aaaaaa' }, + { id: 'ads', label: 'AD NETWORKS', x: 555, y: 185, w: 115, h: 34, color: '#ff44ff' }, + ], + }; + + const exfilDataTypes = [ + { id: 'location', label: 'LOC', color: '#ff6644' }, + { id: 'contacts', label: 'CONTACTS', color: '#44aaff' }, + { id: 'camera', label: 'CAMERA', color: '#ff44ff' }, + { id: 'mic', label: 'MIC', color: '#ffff44' }, + { id: 'browsing', label: 'BROWSING', color: '#44ffaa' }, + { id: 'sms', label: 'SMS', color: '#ff8844' }, + ]; + + function initExfilScene() { + exfilMode = 'grapheneos'; + exfilPackets = []; + exfilCounter = 0; + exfilCounterTarget = 0; + const legend = document.getElementById('exfilLegend'); + if (legend) { + legend.innerHTML = ''; + for (const dt of exfilDataTypes) { + const item = document.createElement('span'); + item.className = 'exfil-legend-item'; + item.innerHTML = '' + dt.label; + legend.appendChild(item); + } + } + } + + function spawnExfilPacket() { + const servers = exfilServers[exfilMode]; + if (servers.length === 0) return; + const server = servers[Math.floor(Math.random() * servers.length)]; + const dataType = exfilDataTypes[Math.floor(Math.random() * exfilDataTypes.length)]; + const px = 80, py = 65, pw = 140, ph = 245; + const fromX = px + 10 + Math.random() * (pw - 20); + const fromY = py + 40 + Math.random() * (ph - 60); + const toX = server.x + Math.random() * server.w; + const toY = server.y + Math.random() * server.h; + exfilPackets.push({ + fromX, fromY, toX, toY, + dataType, + progress: 0, + speed: 0.008 + Math.random() * 0.006, + }); + } + + function drawExfilScene() { + const canvas = document.getElementById('exfilCanvas'); + if (!canvas || canvas.offsetParent === null) { exfilAnimId = requestAnimationFrame(drawExfilScene); return; } + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + const mode = exfilModeData[exfilMode]; + const servers = exfilServers[exfilMode]; + + // Tabs + for (const tab of exfilTabs) { + const active = tab.id === exfilMode; + ctx.fillStyle = active ? mode.color : '#001100'; + ctx.strokeStyle = active ? mode.color : '#003300'; + ctx.lineWidth = active ? 2 : 1; + const r = 4; + ctx.beginPath(); + ctx.moveTo(tab.x + r, 8); + ctx.lineTo(tab.x + tab.w - r, 8); + ctx.quadraticCurveTo(tab.x + tab.w, 8, tab.x + tab.w, 8 + r); + ctx.lineTo(tab.x + tab.w, 30 - r); + ctx.quadraticCurveTo(tab.x + tab.w, 30, tab.x + tab.w - r, 30); + ctx.lineTo(tab.x + r, 30); + ctx.quadraticCurveTo(tab.x, 30, tab.x, 30 - r); + ctx.lineTo(tab.x, 8 + r); + ctx.quadraticCurveTo(tab.x, 8, tab.x + r, 8); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = active ? '#000' : mode.color; + ctx.font = 'bold 11px Courier New, monospace'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(tab.label, tab.x + tab.w / 2, 19); + } + + // Phone silhouette + const px = 70, py = 60, pw = 140, ph = 250; + ctx.shadowColor = mode.color; + ctx.shadowBlur = 15; + ctx.strokeStyle = mode.color; + ctx.lineWidth = 2; + ctx.fillStyle = '#000800'; + ctx.beginPath(); + ctx.roundRect(px, py, pw, ph, 18); + ctx.fill(); + ctx.stroke(); + ctx.shadowBlur = 0; + + // Phone screen + ctx.fillStyle = '#001100'; + ctx.beginPath(); + ctx.roundRect(px + 8, py + 35, pw - 16, ph - 55, 6); + ctx.fill(); + + // Camera dot + ctx.fillStyle = '#003300'; + ctx.beginPath(); + ctx.arc(px + pw / 2, py + 16, 4, 0, Math.PI * 2); + ctx.fill(); + + // Phone label + ctx.fillStyle = mode.color; + ctx.font = 'bold 12px Courier New, monospace'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(exfilMode === 'grapheneos' ? 'SECURE' : 'LEAKING', px + pw / 2, py + ph / 2); + + // Servers + connection lines + for (const srv of servers) { + ctx.strokeStyle = srv.color + '22'; + ctx.lineWidth = 1; + ctx.setLineDash([4, 6]); + ctx.beginPath(); + ctx.moveTo(px + pw / 2, py + ph / 2); + ctx.lineTo(srv.x + srv.w / 2, srv.y + srv.h / 2); + ctx.stroke(); + ctx.setLineDash([]); + + ctx.fillStyle = srv.color + '33'; + ctx.strokeStyle = srv.color; + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.roundRect(srv.x, srv.y, srv.w, srv.h, 4); + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = srv.color; + ctx.font = 'bold 10px Courier New, monospace'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(srv.label, srv.x + srv.w / 2, srv.y + srv.h / 2); + } + + // Spawn packets + if (servers.length > 0 && Math.random() < 0.06) spawnExfilPacket(); + + // Draw and update packets + for (let i = exfilPackets.length - 1; i >= 0; i--) { + const pkt = exfilPackets[i]; + const x = pkt.fromX + (pkt.toX - pkt.fromX) * pkt.progress; + const y = pkt.fromY + (pkt.toY - pkt.fromY) * pkt.progress; + + ctx.strokeStyle = pkt.dataType.color + '44'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(pkt.fromX, pkt.fromY); + ctx.lineTo(x, y); + ctx.stroke(); + + ctx.shadowColor = pkt.dataType.color; + ctx.shadowBlur = 10; + ctx.fillStyle = pkt.dataType.color; + ctx.beginPath(); + ctx.arc(x, y, 5, 0, Math.PI * 2); + ctx.fill(); + ctx.shadowBlur = 0; + + ctx.fillStyle = pkt.dataType.color; + ctx.font = '8px Courier New, monospace'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'bottom'; + ctx.fillText(pkt.dataType.label, x, y - 6); + + pkt.progress += pkt.speed; + if (pkt.progress >= 1) { + exfilPackets.splice(i, 1); + if (exfilCounterTarget < mode.counterMax) { + exfilCounterTarget = Math.min(mode.counterMax, exfilCounterTarget + 18); + } + } + } + + while (exfilPackets.length > 30) exfilPackets.shift(); + + // Smooth counter + if (exfilCounter < exfilCounterTarget) { + exfilCounter = Math.min(exfilCounterTarget, exfilCounter + 5); + } else if (exfilCounter > exfilCounterTarget) { + exfilCounter = Math.max(exfilCounterTarget, exfilCounter - 5); + } + const ce = document.getElementById('exfilCounter'); + if (ce) { + ce.textContent = 'DATA VALUE EXPOSED: $' + exfilCounter.toFixed(0) + '/yr'; + ce.style.color = exfilCounter > 0 ? '#ff4444' : '#00ff00'; + } + + const st = document.getElementById('exfilStatus'); + if (st) st.textContent = mode.status; + + exfilAnimId = requestAnimationFrame(drawExfilScene); + } + + function handleExfilCanvasClick(mx, my) { + const canvas = document.getElementById('exfilCanvas'); + 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 tab of exfilTabs) { + if (cx >= tab.x && cx <= tab.x + tab.w && cy >= 8 && cy <= 30) { + if (tab.id !== exfilMode) { + exfilMode = tab.id; + exfilPackets = []; + exfilCounterTarget = 0; + } + break; + } + } + } + + function loadScene8b(sceneElem) { + s8bc = []; + sceneElem.style.display = 'flex'; + const txt = document.getElementById('s8bText'); + const canvasDiv = document.getElementById('s8bCanvas'); + txt.innerHTML = ''; + canvasDiv.classList.remove('visible'); + if (exfilAnimId) { cancelAnimationFrame(exfilAnimId); exfilAnimId = null; } + let o = 0; + const fi = setInterval(() => { + if (sceneElem.style.display !== 'flex' || document.getElementById('returnFromScene8b').style.visibility === 'visible') { clearInterval(fi); return; } + o += 0.05; if (o >= 1) { o = 1; clearInterval(fi); + typeCalmly(txt, "EACH TIME YOUR PHONE SENDS DATA TO THIRD-PARTY SERVERS, IT CREATES A DIGITAL PROFILE OF YOU — SOLD TO THE HIGHEST BIDDER.", () => { + const t1 = setTimeout(() => { + typeCalmly(txt, "\n\nCLICK THE TABS TO SEE WHERE YOUR DATA ACTUALLY GOES.", () => { + const t2 = setTimeout(() => { + canvasDiv.classList.add('visible'); + initExfilScene(); + drawExfilScene(); + }, 300); + s8bc.push(t2); + }, 8, 20, s8bc); + }, 600); + s8bc.push(t1); + }, 8, 20, s8bc); + } + sceneElem.style.opacity = o; + }, 30); + } // Initialize on load window.addEventListener('load', () => { @@ -1938,6 +2506,8 @@ function showTechHub() { if (meshAnimId) { cancelAnimationFrame(meshAnimId); meshAnimId = null; } + if (phoneAnimId) { cancelAnimationFrame(phoneAnimId); phoneAnimId = null; } + if (exfilAnimId) { cancelAnimationFrame(exfilAnimId); exfilAnimId = null; } const s6 = document.getElementById('scene6'); s6.style.display = 'flex'; s6.style.opacity = '1'; @@ -1968,6 +2538,27 @@ if (meshState) toggleMeshNode(e.clientX, e.clientY); }); + document.getElementById('phoneCanvas').addEventListener('click', (e) => { + handlePhoneCanvasClick(e.clientX, e.clientY); + }); + + document.getElementById('exfilCanvas').addEventListener('click', (e) => { + handleExfilCanvasClick(e.clientX, e.clientY); + }); + + document.getElementById('nextFromScene8').addEventListener('click', () => { + if (exfilAnimId) { cancelAnimationFrame(exfilAnimId); exfilAnimId = null; } + if (phoneAnimId) { cancelAnimationFrame(phoneAnimId); phoneAnimId = null; } + document.getElementById('scene8').style.display = 'none'; + loadScene8b(document.getElementById('scene8b')); + }); + + document.getElementById('returnFromScene8b').addEventListener('click', () => { + if (exfilAnimId) { cancelAnimationFrame(exfilAnimId); exfilAnimId = null; } + document.getElementById('scene8b').style.display = 'none'; + showTechHub(); + }); + // Keyboard shortcuts for testing document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { @@ -2120,12 +2711,18 @@ if (s8.style.display === 'flex') { s8c.forEach(t => clearTimeout(t)); s8c = []; const txt = document.getElementById('s8Text'); - const vis = document.getElementById('s8Visual'); - txt.innerHTML = "YOUR PHONE IS LITERALLY A TRACKING DEVICE IN YOUR POCKET — UNLESS YOU CHOOSE OTHERWISE.\n\nGRAPHENEOS PROVES THAT PRIVACY AND USABILITY CAN COEXIST — BUT ONLY WHEN YOU TAKE CONTROL."; - vis.innerHTML=buildS6MobileTable(); - vis.classList.add('visible'); - document.getElementById('returnFromScene8').style.cssText=''; - showNextBtn('returnFromScene8'); + txt.innerHTML = "YOUR PHONE IS LITERALLY A TRACKING DEVICE IN YOUR POCKET — UNLESS YOU CHOOSE OTHERWISE.\n\nGRAPHENEOS LOCKS DOWN EVERY SENSOR AND PERMISSION — CLICK A TAB TO SEE THE DIFFERENCE."; + const canvasDiv = document.getElementById('s8Canvas'); + canvasDiv.classList.add('visible'); + if (phoneAnimId) { cancelAnimationFrame(phoneAnimId); phoneAnimId = null; } + if (exfilAnimId) { cancelAnimationFrame(exfilAnimId); exfilAnimId = null; } + initPhoneScene(); + drawPhoneScene(); + const s8Row = document.getElementById('s8BtnRow'); + s8Row.style.cssText = ''; + s8Row.style.visibility = 'visible'; + s8Row.style.opacity = '1'; + s8Row.style.pointerEvents = 'auto'; } // Scene 9 skip @@ -2143,6 +2740,21 @@ showNextBtn('returnFromScene9'); } + // Scene 8b skip + const s8b = document.getElementById('scene8b'); + if (s8b.style.display === 'flex') { + s8bc.forEach(t => clearTimeout(t)); s8bc = []; + const txt = document.getElementById('s8bText'); + txt.innerHTML = "EACH TIME YOUR PHONE SENDS DATA TO THIRD-PARTY SERVERS, IT CREATES A DIGITAL PROFILE OF YOU — SOLD TO THE HIGHEST BIDDER.\n\nCLICK THE TABS TO SEE WHERE YOUR DATA ACTUALLY GOES."; + const canvasDiv = document.getElementById('s8bCanvas'); + canvasDiv.classList.add('visible'); + if (exfilAnimId) { cancelAnimationFrame(exfilAnimId); exfilAnimId = null; } + initExfilScene(); + drawExfilScene(); + document.getElementById('returnFromScene8b').style.cssText=''; + showNextBtn('returnFromScene8b'); + } + } if (e.key === 'Delete') {