1/** 2 * Sector-grid wireframe icons â cube, wave, orbital, shield, neural, route. 3 * 4 * Direct vanilla-JS port of TWGWireframeCube/Wave/Orbital/Shield in 5 * export-westly/twg/viz.jsx. Same reasoning as hero-canvas.js: each is self-contained 6 * Canvas 2D (a draw loop plus a hover flag that speeds the animation up), no React 7 * dependency in the drawing logic itself. 8 * 9 * Matches each `<canvas class="sector-wireframe" data-wf="cube|wave|orbital|shield|neural|route">` to 10 * its draw function via the `data-wf` attribute, set by sector-grid.php in the same 11 * cube/wave/orbital/shield card order as the design's 12 * `[TWGWireframeCube, TWGWireframeWave, TWGWireframeOrbital, TWGWireframeShield][i]`. 13 * 14 * Round 24 (client revision): the grid went 4 -> 5 cards and the icon order changed to 15 * `[Wave, Neural, Cube, Route, Shield]` â see export-westly-v2/twg/pages-main.jsx. 16 * `neural` and `route` are ported from export-westly-v2/twg/viz.jsx, same self-contained 17 * Canvas 2D shape as the original four. 18 */ 19( function () { 20 'use strict'; 21 22 var RGB = '0,125,194'; // --twg-blue 23 24 function rotY( v, a ) { 25 var c = Math.cos( a ), s = Math.sin( a ); 26 return [ v[ 0 ] * c + v[ 2 ] * s, v[ 1 ], -v[ 0 ] * s + v[ 2 ] * c ]; 27 } 28 function rotX( v, a ) { 29 var c = Math.cos( a ), s = Math.sin( a ); 30 return [ v[ 0 ], v[ 1 ] * c - v[ 2 ] * s, v[ 1 ] * s + v[ 2 ] * c ]; 31 } 32 33 function drawCube( ctx, size, getTime ) { 34 var vertices = [ [ -1, -1, -1 ], [ 1, -1, -1 ], [ 1, 1, -1 ], [ -1, 1, -1 ], [ -1, -1, 1 ], [ 1, -1, 1 ], [ 1, 1, 1 ], [ -1, 1, 1 ] ]; 35 var edges = [ [ 0, 1 ], [ 1, 2 ], [ 2, 3 ], [ 3, 0 ], [ 4, 5 ], [ 5, 6 ], [ 6, 7 ], [ 7, 4 ], [ 0, 4 ], [ 1, 5 ], [ 2, 6 ], [ 3, 7 ] ]; 36 var time = getTime(); 37 ctx.clearRect( 0, 0, size, size ); 38 var sc = size * 0.22; 39 var projected = vertices.map( function ( v ) { 40 var r = rotY( v.slice(), time ); 41 r = rotX( r, time * 0.7 ); 42 var fov = 4, z = r[ 2 ] + fov, ps = fov / z; 43 return { x: r[ 0 ] * sc * ps + size / 2, y: r[ 1 ] * sc * ps + size / 2, s: ps }; 44 } ); 45 ctx.strokeStyle = 'rgba(' + RGB + ',0.45)'; 46 ctx.lineWidth = 1;
47 edges.forEach( function ( e ) { 48 ctx.beginPath(); 49 ctx.moveTo( projected[ e[ 0 ] ].x, projected[ e[ 0 ] ].y ); 50 ctx.lineTo( projected[ e[ 1 ] ].x, projected[ e[ 1 ] ].y ); 51 ctx.stroke(); 52 } ); 53 projected.forEach( function ( p ) { 54 ctx.fillStyle = 'rgba(' + RGB + ',0.6)'; 55 ctx.beginPath(); 56 ctx.arc( p.x, p.y, 1.5 * p.s, 0, Math.PI * 2 ); 57 ctx.fill(); 58 } ); 59 } 60 61 function drawWave( ctx, size, getTime ) { 62 var time = getTime(); 63 var cols = 20, rows = 12, cy = size / 2; 64 function point( i, j ) { 65 var x = ( i / cols ) * size * 0.85 + size * 0.075; 66 var z = ( j / rows - 0.5 ) * 2; 67 var xNorm = ( i / cols - 0.5 ) * 2; 68 var wave = Math.sin( xNorm * 3 + time ) * Math.cos( z * 2 + time * 0.7 ) * 0.35; 69 var y = cy + z * size * 0.25 - wave * size * 0.3; 70 return [ x, y ]; 71 } 72 ctx.clearRect( 0, 0, size, size ); 73 for ( var j = 0; j <= rows; j++ ) { 74 ctx.beginPath(); 75 ctx.strokeStyle = 'rgba(' + RGB + ',' + ( 0.22 + ( j / rows ) * 0.2 ) + ')'; 76 ctx.lineWidth = 0.8; 77 for ( var i = 0; i <= cols; i++ ) { 78 var p = point( i, j ); 79 if ( i === 0 ) { ctx.moveTo( p[ 0 ], p[ 1 ] ); } else { ctx.lineTo( p[ 0 ], p[ 1 ] ); } 80 } 81 ctx.stroke(); 82 } 83 for ( var i2 = 0; i2 <= cols; i2 += 2 ) { 84 ctx.beginPath(); 85 ctx.strokeStyle = 'rgba(' + RGB + ',0.18)'; 86 ctx.lineWidth = 0.6; 87 for ( var j2 = 0; j2 <= rows; j2++ ) { 88 var p2 = point( i2, j2 ); 89 if ( j2 === 0 ) { ctx.moveTo( p2[ 0 ], p2[ 1 ] ); } else { ctx.lineTo( p2[ 0 ], p2[ 1 ] ); } 90 } 91 ctx.stroke(); 92 } 93 for ( var j3 = 0; j3 <= rows; j3 += 2 ) { 94 for ( var i3 = 0; i3 <= cols; i3 += 2 ) { 95 var p3 = point( i3, j3 ); 96 ctx.fillStyle = 'rgba(' + RGB + ',0.45)'; 97 ctx.beginPath(); 98 ctx.arc( p3[ 0 ], p3[ 1 ], 1, 0, Math.PI * 2 ); 99 ctx.fill(); 100 } 101 } 102 } 103 104 function drawOrbital( ctx, size, getTime ) { 105 var time = getTime(); 106 var cx = size / 2, cy = size / 2, r = size * 0.3; 107 var rings = [ 108 { tiltX: 0.3, tiltZ: 0 }, 109 { tiltX: 0.8, tiltZ: Math.PI / 3 }, 110 { tiltX: 1.2, tiltZ: ( 2 * Math.PI ) / 3 }, 111 ]; 112 ctx.clearRect( 0, 0, size, size ); 113 ctx.strokeStyle = 'rgba(' + RGB + ',0.4)'; 114 ctx.lineWidth = 1; 115 rings.forEach( function ( ring ) { 116 ctx.beginPath(); 117 var steps = 64; 118 for ( var i = 0; i <= steps; i++ ) { 119 var angle = ( i / steps ) * Math.PI * 2 + time; 120 var x = Math.cos( angle ) * r; 121 var y = Math.sin( angle ) * r; 122 var cosT = Math.cos( ring.tiltX ), sinT = Math.sin( ring.tiltX ); 123 var y2 = y * cosT, z2 = y * sinT; 124 var cosZ = Math.cos( ring.tiltZ + time * 0.3 ), sinZ = Math.sin( ring.tiltZ + time * 0.3 ); 125 var xf = x * cosZ - y2 * sinZ; 126 var yf = x * sinZ + y2 * cosZ; 127 var fov = 4, zz = z2 + fov, ps = fov / zz; 128 var px = xf * ps + cx, py = yf * ps + cy; 129 if ( i === 0 ) { ctx.moveTo( px, py ); } else { ctx.lineTo( px, py ); } 130 } 131 ctx.stroke(); 132 } ); 133 134 ctx.fillStyle = 'rgba(' + RGB + ',0.6)'; 135 ctx.beginPath(); 136 ctx.arc( cx, cy, 2.5, 0, Math.PI * 2 ); 137 ctx.fill(); 138 139 rings.forEach( function ( ring, ri ) { 140 var angle = time * ( 1 + ri * 0.3 ) + ri * 2; 141 var x = Math.cos( angle ) * r; 142 var y = Math.sin( angle ) * r; 143 var cosT = Math.cos( ring.tiltX ), sinT = Math.sin( ring.tiltX ); 144 var y2 = y * cosT, z2 = y * sinT; 145 var cosZ = Math.cos( ring.tiltZ + time * 0.3 ), sinZ = Math.sin( ring.tiltZ + time * 0.3 ); 146 var xf = x * cosZ - y2 * sinZ; 147 var yf = x * sinZ + y2 * cosZ; 148 var fov = 4, zz = z2 + fov, ps = fov / zz; 149 ctx.fillStyle = 'rgba(' + RGB + ',0.7)'; 150 ctx.beginPath(); 151 ctx.arc( xf * ps + cx, yf * ps + cy, Math.abs( 1.8 * ps ), 0, Math.PI * 2 ); 152 ctx.fill(); 153 } ); 154 } 155 156 function drawShield( ctx, size, getTime ) { 157 var time = getTime(); 158 var cx = size / 2, cy = size / 2; 159 160 function shield( s ) { 161 var w = size * 0.30 * s, top = cy - size * 0.34 * s, bot = cy + size * 0.36 * s; 162 ctx.beginPath(); 163 ctx.moveTo( cx - w, top ); 164 ctx.lineTo( cx + w, top ); 165 ctx.lineTo( cx + w, cy + size * 0.06 * s ); 166 ctx.quadraticCurveTo( cx + w, bot - size * 0.04 * s, cx, bot ); 167 ctx.quadraticCurveTo( cx - w, bot - size * 0.04 * s, cx - w, cy + size * 0.06 * s ); 168 ctx.closePath(); 169 ctx.stroke(); 170 } 171 172 ctx.clearRect( 0, 0, size, size ); 173 174 for ( var i = 0; i < 3; i++ ) { 175 var phase = ( time + i * 0.55 ) % 1.65; 176 var s = 0.42 + phase * 0.36; 177 var fade = Math.min( 1, phase / 0.3 ) * Math.max( 0, 1 - ( phase - 1.0 ) / 0.65 ); 178 ctx.strokeStyle = 'rgba(' + RGB + ',' + ( 0.5 * fade ) + ')'; 179 ctx.lineWidth = i === 0 ? 1.1 : 0.7; 180 shield( s ); 181 } 182 183 var r = size * 0.11; 184 ctx.strokeStyle = 'rgba(' + RGB + ',0.5)'; 185 ctx.lineWidth = 0.6; 186 var nodes = []; 187 for ( var n = 0; n < 6; n++ ) { 188 var a = ( Math.PI / 3 ) * n + time * 0.6; 189 nodes.push( [ cx + r * Math.cos( a ), cy + r * Math.sin( a ) ] ); 190 }
191 nodes.forEach( function ( node, i2 ) { 192 var next = nodes[ ( i2 + 2 ) % 6 ]; 193 ctx.beginPath(); ctx.moveTo( node[ 0 ], node[ 1 ] ); ctx.lineTo( next[ 0 ], next[ 1 ] ); ctx.stroke(); 194 ctx.beginPath(); ctx.moveTo( node[ 0 ], node[ 1 ] ); ctx.lineTo( cx, cy ); ctx.stroke(); 195 } ); 196 ctx.fillStyle = 'rgba(' + RGB + ',0.65)'; 197 nodes.forEach( function ( node ) { 198 ctx.beginPath(); ctx.arc( node[ 0 ], node[ 1 ], 1.3, 0, Math.PI * 2 ); ctx.fill(); 199 } ); 200 ctx.beginPath(); ctx.arc( cx, cy, 2.1, 0, Math.PI * 2 ); ctx.fill(); 201 } 202 203 /** 204 * Neural node lattice (AI) â port of TWGWireframeNeural, export-westly-v2/twg/viz.jsx. 205 * 206 * Four columns of 2/3/3/2 nodes, every node joined to every node in the next column. 207 * Edge opacity pulses along the depth axis; node radius breathes on its own phase. 208 */ 209 function drawNeural( ctx, size, getTime ) { 210 var time = getTime(); 211 var cols = [ 2, 3, 3, 2 ]; 212 var nodes = []; 213 cols.forEach( function ( n, ci ) { 214 for ( var i = 0; i < n; i++ ) { 215 nodes.push( { 216 c: ci, 217 x: size * ( 0.17 + ci * 0.22 ), 218 y: size * ( 0.5 + ( i - ( n - 1 ) / 2 ) * 0.21 ), 219 p: ci * 0.7 + i * 0.5, 220 } ); 221 } 222 } ); 223 224 ctx.clearRect( 0, 0, size, size ); 225 ctx.lineWidth = 1; 226 227 function column( index ) { 228 var out = []; 229 for ( var k = 0; k < nodes.length; k++ ) { 230 if ( nodes[ k ].c === index ) { out.push( nodes[ k ] ); } 231 } 232 return out; 233 } 234 235 for ( var ci = 0; ci < cols.length - 1; ci++ ) { 236 var a = column( ci ); 237 var b = column( ci + 1 ); 238 for ( var ai = 0; ai < a.length; ai++ ) { 239 for ( var bi = 0; bi < b.length; bi++ ) { 240 var pulse = 0.14 + 0.24 * Math.max( 0, Math.sin( time - ci * 0.9 - ( ai + bi ) * 0.35 ) ); 241 ctx.strokeStyle = 'rgba(' + RGB + ',' + pulse.toFixed( 3 ) + ')'; 242 ctx.beginPath(); 243 ctx.moveTo( a[ ai ].x, a[ ai ].y ); 244 ctx.lineTo( b[ bi ].x, b[ bi ].y ); 245 ctx.stroke(); 246 } 247 } 248 } 249 250 nodes.forEach( function ( n ) { 251 var g = 0.5 + 0.45 * Math.sin( time * 1.3 + n.p ); 252 ctx.fillStyle = 'rgba(' + RGB + ',' + ( 0.4 + g * 0.5 ).toFixed( 3 ) + ')'; 253 ctx.beginPath(); 254 ctx.arc( n.x, n.y, 2.1 + g * 0.9, 0, Math.PI * 2 ); 255 ctx.fill(); 256 } ); 257 } 258 259 /** 260 * Route + wheel (mobility) â port of TWGWireframeRoute, export-westly-v2/twg/viz.
260jsx. 261 * 262 * A rotating six-spoke wheel over a travelling dotted path. 263 */ 264 function drawRoute( ctx, size, getTime ) { 265 var time = getTime(); 266 var cx = size / 2, cy = size / 2, R = size * 0.3; 267 268 ctx.clearRect( 0, 0, size, size ); 269 ctx.lineWidth = 1; 270 271 ctx.strokeStyle = 'rgba(' + RGB + ',0.42)'; 272 ctx.beginPath(); ctx.arc( cx, cy, R, 0, Math.PI * 2 ); ctx.stroke(); 273 ctx.beginPath(); ctx.arc( cx, cy, R * 0.34, 0, Math.PI * 2 ); ctx.stroke(); 274 275 for ( var i = 0; i < 6; i++ ) { 276 var a = time + i * Math.PI / 3; 277 ctx.strokeStyle = 'rgba(' + RGB + ',' + ( 0.2 + 0.28 * Math.abs( Math.cos( a ) ) ).toFixed( 3 ) + ')'; 278 ctx.beginPath(); 279 ctx.moveTo( cx + Math.cos( a ) * R * 0.34, cy + Math.sin( a ) * R * 0.34 ); 280 ctx.lineTo( cx + Math.cos( a ) * R, cy + Math.sin( a ) * R ); 281 ctx.stroke(); 282 } 283 284 var py = size * 0.88; 285 ctx.strokeStyle = 'rgba(' + RGB + ',0.18)'; 286 ctx.beginPath(); 287 ctx.moveTo( size * 0.08, py ); 288 ctx.lineTo( size * 0.92, py ); 289 ctx.stroke(); 290 291 for ( var d = 0; d < 3; d++ ) { 292 var t = ( time * 0.22 + d / 3 ) % 1; 293 ctx.fillStyle = 'rgba(' + RGB + ',' + ( 0.65 - Math.abs( t - 0.5 ) * 0.7 ).toFixed( 3 ) + ')'; 294 ctx.beginPath(); 295 ctx.arc( size * 0.08 + t * size * 0.84, py, 1.9, 0, Math.PI * 2 ); 296 ctx.fill(); 297 } 298 } 299 300 var DRAWERS = { 301 cube: { fn: drawCube, slow: 0.004, fast: 0.012 }, 302 wave: { fn: drawWave, slow: 0.015, fast: 0.04 }, 303 orbital: { fn: drawOrbital, slow: 0.004, fast: 0.012 }, 304 shield: { fn: drawShield, slow: 0.012, fast: 0.03 }, 305 neural: { fn: drawNeural, slow: 0.018, fast: 0.05 }, 306 route: { fn: drawRoute, slow: 0.016, fast: 0.045 }, 307 }; 308 309 function initCanvas( canvas ) { 310 var kind = canvas.getAttribute( 'data-wf' ); 311 var spec = DRAWERS[ kind ]; 312 if ( ! spec ) { 313 return; 314 } 315 var ctx = canvas.getContext( '2d' ); 316 if ( ! ctx ) { 317 return; 318 } 319 320 // Animates for EVERY visitor, whatever their OS "Reduce Motion" setting â owner decision, 321 // 2026-09-15, matching the design, which never checks it. (Earlier versions returned 322 // before drawing, then drew a still frame, when prefers-reduced-motion matched.) 323 324 var size = canvas.offsetWidth || 64; 325 var dpr = window.devicePixelRatio || 1; 326 canvas.width = size * dpr; 327 canvas.height = size * dpr; 328 ctx.scale( dpr, dpr ); 329 330 var hovered = false; 331 var time = 0; 332 var animId; 333 334 canvas.addEventListener( 'mouseenter', function () { hovered = true; } ); 335 canvas.addEventListener( 'mouseleave', function () { hovered = false; } ); 336 337 function tick() { 338 time += hovered ? spec.fast : spec.slow; 339 spec.fn( ctx, size, function () { return time; } ); 340 animId = window.requestAnimationFrame( tick ); 341 } 342 tick(); 343 344 return function stop() { window.cancelAnimationFrame( animId ); }; 345 } 346 347 function init() { 348 var canvases = document.querySelectorAll( '.sector-wireframe' ); 349 for ( var i = 0; i < canvases.length; i++ ) { 350 initCanvas( canvases[ i ] ); 351 } 352 } 353 354 if ( document.readyState === 'loading' ) { 355 document.addEventListener( 'DOMContentLoaded', init ); 356 } else { 357 init(); 358 } 359} )();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.