// Direction A — UTILITY-ROOM v2 // The current site's vibe, refined. Same warm-dark amber palette, but the // boot theatrics are replaced with a quieter "session resumed" opener that // gets out of the way faster. Wireframes are promoted from background // distractions to deliberate ambient ornament. Adds availability + contact // sections so the site actually pitches commission work. (function () { const W = window.IM_WIRE; const C = window.IM_CONTENT; const { useState, useEffect, useRef } = React; // Short, dry boot lines. The original site's sequence was 21 jokes — // user noted it "gets in the way of content" — so this one keeps four // lines of plausible session-restore chatter and gets out of the way. const BOOT_LINES = [ { txt: '[ok] session restored' }, { txt: '[ok] tmux: 4 panes · 1 dirty buffer (sketchbook.md)' }, { txt: '[ok] last seen 14m ago — chair by the bench' }, { txt: '[ok] welcome back' }, ]; // ── three.js background scene ───────────────────────────────── // Recreates the original site's wireframe background: five rotating // platonic solids + a torus knot, with subtle mouse parallax. The // amber/green palette + low opacities keep it deep in the back. function BgScene() { const canvasRef = useRef(null); useEffect(() => { if (!canvasRef.current || !window.THREE) return; const THREE = window.THREE; const canvas = canvasRef.current; const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0x000000, 0); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 100); camera.position.z = 8; const mAmber = new THREE.LineBasicMaterial({ color: 0xe8a030, opacity: 0.14, transparent: true }); const mGreen = new THREE.LineBasicMaterial({ color: 0x39d353, opacity: 0.07, transparent: true }); const mDim = new THREE.LineBasicMaterial({ color: 0xe8a030, opacity: 0.05, transparent: true }); const wf = (geo, mat, x, y, z) => { const mesh = new THREE.LineSegments(new THREE.WireframeGeometry(geo), mat); mesh.position.set(x, y, z); scene.add(mesh); return mesh; }; const ico = wf(new THREE.IcosahedronGeometry(2.8, 1), mAmber, -1.5, 0.5, -3); const tknot = wf(new THREE.TorusKnotGeometry(1.1, 0.3, 120, 16), mGreen, 4.5, -1.2, -4); const oct = wf(new THREE.OctahedronGeometry(1.5, 2), mDim, -5.5, -2.5, -2); const dodec = wf(new THREE.DodecahedronGeometry(1.0, 0), mAmber, 5.0, 2.8, -1.5); const tet = wf(new THREE.TetrahedronGeometry(0.8, 2), mGreen, -3.5, 3.0, -1); // Each body gets its own slow rotation rate + a slight positional // drift driven by sine waves so nothing reads as orbiting a single // axis. Rates picked low — like watching the storms of Jupiter // from a porthole, not a screensaver. Times are in radians/frame // at 60fps; ~0.0004 ≈ a full revolution every ~4 minutes. const bodies = [ { m: ico, rx: 0.00028, ry: 0.00041, rz: 0, bob: [0.6, 0.0009, 0], home: [-1.5, 0.5, -3] }, { m: tknot, rx: 0.00044, ry: 0.00033, rz: 0.00018, bob: [0.4, 0.0011, 1.7], home: [ 4.5,-1.2,-4] }, { m: oct, rx:-0.00031, ry: 0, rz: 0.00038, bob: [0.5, 0.0008, 3.1], home: [-5.5,-2.5,-2] }, { m: dodec, rx: 0.00018, ry: 0.00046, rz: 0, bob: [0.7, 0.00065,4.6], home: [ 5.0, 2.8,-1.5] }, { m: tet, rx: 0.00036, ry: 0, rz:-0.00041, bob: [0.55,0.00095,2.3], home: [-3.5, 3.0,-1] }, ]; let mx = 0, my = 0; const onMove = (e) => { // Halve the parallax too — the original was punchy, this should // breathe. mx = (e.clientX / window.innerWidth - 0.5) * 0.35; my = -(e.clientY / window.innerHeight - 0.5) * 0.35; }; const onResize = () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }; document.addEventListener('mousemove', onMove); window.addEventListener('resize', onResize); const start = performance.now(); let raf, alive = true; const tick = () => { if (!alive) return; raf = requestAnimationFrame(tick); const t = (performance.now() - start) / 1000; for (const b of bodies) { b.m.rotation.x += b.rx; b.m.rotation.y += b.ry; b.m.rotation.z += b.rz; // sine-wave drift around each body's home position — amplitude // ~0.4–0.7 world units, periods of 90–150s. Reads as floating. const [amp, freq, phase] = b.bob; const [hx, hy, hz] = b.home; b.m.position.x = hx + Math.sin(t * freq * Math.PI * 2 + phase) * amp; b.m.position.y = hy + Math.cos(t * freq * 0.8 * Math.PI * 2 + phase * 1.3) * amp * 0.6; b.m.position.z = hz + Math.sin(t * freq * 0.5 * Math.PI * 2 + phase * 0.7) * amp * 0.3; } camera.position.x += (mx - camera.position.x) * 0.02; camera.position.y += (my - camera.position.y) * 0.02; camera.lookAt(scene.position); renderer.render(scene, camera); }; tick(); return () => { alive = false; cancelAnimationFrame(raf); document.removeEventListener('mousemove', onMove); window.removeEventListener('resize', onResize); renderer.dispose(); }; }, []); return