• 01 - Critter Models 02
  • 02 - VantaJS Shaders
  • 03 - App Bundle 02
  • 04 - Cathedrals
  • 05 - App Bundle 01
  • 06 - Retroscopic
  • 07 - Games
  • 08 - Voronoi
  • 09 - Terrain
  • 10 - Critter Models 01
  • 11 - Chromatophores
  • 12 - Conformal Maps
  • 13 - Echo Logix
  • 14 - Music
  • 15 - Colour Theory
MIKE'S NOTEBOOK
Notes
Number:
Project:
01Critter Models 02
└ Locust└ Butterfly└ Cockroach└ Spider└ Scorpion└ Dragon└ Brazilian Treehopper└ Tardigrade└ Diatoms└ Bumblebee└ Wasp└ Hammerhead Worm└ Giraffe Weevil└ Trilobite Beetle
02VantaJS Shaders
└ Clouds└ Waves└ Cells└ Rings└ Halo└ Globe└ Net└ Dots└ Birds└ Trunk└ Topology└ About VantaJS
03App Bundle 02
└ Gutindex└ Insect Builder└ OpenLayers
04Cathedrals
└ Lattice└ Lost└ Lucent└ Lumen└ Glass└ Disco└ Hyperion└ Latent
05App Bundle 01
└ Robohash└ PrismJS└ Horoscope└ Binary Jazz└ Morse Code└ URL Extractor└ Entity Escaper└ Phonetic Alphabet└ Toki Pona└ Semaphore└ Cut Up Machine└ Text Mixer└ Currency Freaks└ Host IO└ CrUX API└ Logo Dev└ Web Speech API└ GEO DB
06Retroscopic
└ Galaxy└ Ocean└ Slime└ 24-Cell└ Unicorn└ Polyp└ Oneiric 1└ Oneiric 2└ Node└ Torus Knot└ GLSL Grid└ Liquid Crystal└ Silky└ Butterfly└ Ocean 2└ Icosahedron└ Luma└ Swarm└ Broken Solid└ Hypersphere└ Constraints└ Psycho Killer
01 / 03

LUMA - RETROSCOPIC CRT SHADER SOURCE CODE

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>Luma</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body { margin: 0; overflow: hidden; background-color: black; }
			#container {
				position: fixed;
				inset: 0;
				width: 100vw;
				height: 100vh;
				filter: grayscale(1) sepia(1) hue-rotate(80deg) saturate(5) brightness(1.15) contrast(1.2);
				z-index: 2;
			}
			/* Retroscopic Overlay */
			#scope {
				position: fixed;
				inset: 0;
				z-index: 4;
				pointer-events: none;
				display: block;
			}
			#scope>div {
				position: absolute;
				inset: 0;
			}
			#scope .glow {
				background: radial-gradient(circle at 50% 46%, rgba(30, 255, 130, .08), rgba(0, 0, 0, 0) 62%);
				animation: nvflick 3.6s steps(24) infinite;
			}
			#scope .scan {
				background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, 0) 0 2px, rgba(0, 25, 8, .45) 2px 3px);
				animation: nvscan 7s linear infinite;
			}
			#scope .vig {
				box-shadow: inset 0 0 200px 50px rgba(0, 0, 0, .8);
			}
			#scope .mask {
				background: radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0) 54%, rgba(0, 0, 0, .5) 72%, rgba(0, 0, 0, .94) 100%);
			}
			@keyframes nvscan {
				from { background-position: 0 0; }
				to { background-position: 0 240px; }
			}
			@keyframes nvflick {
				0%, 100% { opacity: .55; }
				40% { opacity: .9; }
				62% { opacity: .4; }
				80% { opacity: .75; }
			}
		</style>
		<script type="importmap">
			{
				"imports": {
					"three": "https://esm.sh/three@0.128.0",
					"three/addons/": "https://esm.sh/three@0.128.0/examples/jsm/"
				}
			}
		</script>
	</head>
	<body>
		<div id="scope">
			<div class="glow"></div>
			<div class="scan"></div>
			<div class="vig"></div>
			<div class="mask"></div>
		</div>
		<div id="container"></div>

		<script type="module">
			import * as THREE from 'three';

			let container;
			let camera, scene, renderer;
			let wireframe, points;
			let pointsPositions;
			let edgesPositions;
			let lineColorAttribute, pointColorAttribute;

			init();

			function init() {

				container = document.getElementById( 'container' );

				camera = new THREE.PerspectiveCamera( 27, window.innerWidth / window.innerHeight, 1, 3500 );
				camera.position.z = 917;

				scene = new THREE.Scene();
				scene.background = new THREE.Color( 0x050505 );
				scene.fog = new THREE.Fog( 0x050505, 2000, 3500 );

				const triangles = 5000;
				const geometry = new THREE.BufferGeometry();

				const positions = [];
				const normals = [];
				const colors = [];

				const color = new THREE.Color();

				const n = 800, n2 = n / 2;
				const d = 12, d2 = d / 2;

				const pA = new THREE.Vector3();
				const pB = new THREE.Vector3();
				const pC = new THREE.Vector3();

				const cb = new THREE.Vector3();
				const ab = new THREE.Vector3();

				for ( let i = 0; i < triangles; i ++ ) {

					const x = Math.random() * n - n2;
					const y = Math.random() * n - n2;
					const z = Math.random() * n - n2;

					const ax = x + Math.random() * d - d2;
					const ay = y + Math.random() * d - d2;
					const az = z + Math.random() * d - d2;

					const bx = x + Math.random() * d - d2;
					const by = y + Math.random() * d - d2;
					const bz = z + Math.random() * d - d2;

					const cx = x + Math.random() * d - d2;
					const cy = y + Math.random() * d - d2;
					const cz = z + Math.random() * d - d2;

					positions.push( ax, ay, az );
					positions.push( bx, by, bz );
					positions.push( cx, cy, cz );

					pA.set( ax, ay, az );
					pB.set( bx, by, bz );
					pC.set( cx, cy, cz );

					cb.subVectors( pC, pB );
					ab.subVectors( pA, pB );
					cb.cross( ab );
					cb.normalize();

					const nx = cb.x;
					const ny = cb.y;
					const nz = cb.z;

					normals.push( nx * 32767, ny * 32767, nz * 32767 );
					normals.push( nx * 32767, ny * 32767, nz * 32767 );
					normals.push( nx * 32767, ny * 32767, nz * 32767 );

					const vx = ( x / n ) + 0.5;
					const vy = ( y / n ) + 0.5;
					const vz = ( z / n ) + 0.5;

					color.setRGB( vx, vy, vz );

					colors.push( color.r * 255, color.g * 255, color.b * 255 );
					colors.push( color.r * 255, color.g * 255, color.b * 255 );
					colors.push( color.r * 255, color.g * 255, color.b * 255 );

				}

				const positionAttribute = new THREE.Float32BufferAttribute( positions, 3 );
				const normalAttribute = new THREE.Int16BufferAttribute( normals, 3 );
				const colorAttribute = new THREE.Uint8BufferAttribute( colors, 3 );

				normalAttribute.normalized = true;
				colorAttribute.normalized = true;

				geometry.setAttribute( 'position', positionAttribute );
				geometry.setAttribute( 'normal', normalAttribute );
				geometry.setAttribute( 'color', colorAttribute );

				geometry.computeBoundingSphere();

				pointsPositions = positions;

				const edgesGeo = new THREE.EdgesGeometry( geometry );
				edgesPositions = edgesGeo.attributes.position.array.slice();

				const lineGeo = new THREE.BufferGeometry();
				lineGeo.setAttribute( 'position', new THREE.Float32BufferAttribute( edgesPositions, 3 ) );

				const lineCount = edgesPositions.length / 3;
				const lineColors = [];
				for ( let i = 0; i < lineCount; i ++ ) {
					const ix = i * 3;
					const x = edgesPositions[ ix ];
					const y = edgesPositions[ ix + 1 ];
					const z = edgesPositions[ ix + 2 ];

					let hue = ( ( x / 800 ) * 0.5 + ( y / 800 ) * 0.3 + ( z / 800 ) * 0.2 + 1.0 ) % 1.0;

					const rgb = hslToRgb( hue, 0.8, 0.3 );
					lineColors.push( rgb[0], rgb[1], rgb[2] );
				}
				lineColorAttribute = new THREE.Float32BufferAttribute( lineColors, 3 );
				lineGeo.setAttribute( 'color', lineColorAttribute );

				const lineMat = new THREE.LineBasicMaterial( {
					vertexColors: true,
					linewidth: 1,
					depthTest: false
				} );
				wireframe = new THREE.LineSegments( lineGeo, lineMat );
				scene.add( wireframe );

				const pointsGeo = new THREE.BufferGeometry();
				pointsGeo.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );

				const pointCount = positions.length / 3;
				const pointColors = [];
				for ( let i = 0; i < pointCount; i ++ ) {
					pointColors.push( 0.8, 0.5, 0.1 );
				}
				pointColorAttribute = new THREE.Float32BufferAttribute( pointColors, 3 );
				pointsGeo.setAttribute( 'color', pointColorAttribute );

				const pointsMat = new THREE.PointsMaterial( {
					size: 6.0,
					vertexColors: true,
					sizeAttenuation: true,
					transparent: true,
					opacity: 0.95
				} );
				points = new THREE.Points( pointsGeo, pointsMat );
				scene.add( points );

				renderer = new THREE.WebGLRenderer( { antialias: true } );
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.setAnimationLoop( animate );
				container.appendChild( renderer.domElement );

				window.addEventListener( 'resize', onWindowResize );
			}

			function hslToRgb( h, s, l ) {
				let r, g, b;
				if ( s === 0 ) {
					r = g = b = l;
				} else {
					const hue2rgb = ( p, q, t ) => {
						if ( t < 0 ) t += 1;
						if ( t > 1 ) t -= 1;
						if ( t < 1/6 ) return p + ( q - p ) * 6 * t;
						if ( t < 1/2 ) return q;
						if ( t < 2/3 ) return p + ( q - p ) * ( 2/3 - t ) * 6;
						return p;
					};
					const q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s;
					const p = 2 * l - q;
					r = hue2rgb( p, q, h + 1/3 );
					g = hue2rgb( p, q, h );
					b = hue2rgb( p, q, h - 1/3 );
				}
				return [ r, g, b ];
			}

			function onWindowResize() {
				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();
				renderer.setSize( window.innerWidth, window.innerHeight );
			}

			function animate() {
				const time = Date.now() * 0.001;

				wireframe.rotation.x = time * 0.25;
				wireframe.rotation.y = time * 0.5;
				points.rotation.x = time * 0.25;
				points.rotation.y = time * 0.5;

				const lineColors = wireframe.geometry.attributes.color.array;
				const lPos = edgesPositions;
				const lCount = lPos.length / 3;

				const lineSpeed = 3.5;
				const lineFreq = 0.008;

				for ( let i = 0; i < lCount; i ++ ) {
					const ix = i * 3;
					const x = lPos[ ix ];
					const y = lPos[ ix + 1 ];
					const z = lPos[ ix + 2 ];

					const wave1 = Math.sin( time * lineSpeed + x * lineFreq + y * lineFreq * 0.7 + z * lineFreq * 1.1 );
					const wave2 = Math.cos( time * 2.2 + y * lineFreq * 1.3 + z * lineFreq * 0.9 );
					const wave3 = Math.sin( time * 1.4 + x * lineFreq * 1.2 + z * lineFreq * 0.8 );

					let t = ( wave1 * 0.6 + wave2 * 0.3 + wave3 * 0.2 + 1 ) / 2;
					t = Math.pow( t, 1.2 ) * 1.5;

					const r0 = lineColors[ ix ];
					const g0 = lineColors[ ix + 1 ];
					const b0 = lineColors[ ix + 2 ];

					const mix = Math.min( t, 1.0 );
					const r = r0 * (1 - mix) + 1.0 * mix;
					const g = g0 * (1 - mix) + 1.0 * mix;
					const b = b0 * (1 - mix) + 1.0 * mix;

					lineColors[ ix ] = r;
					lineColors[ ix + 1 ] = g;
					lineColors[ ix + 2 ] = b;
				}
				wireframe.geometry.attributes.color.needsUpdate = true;

				const pointColors = points.geometry.attributes.color.array;
				const pPos = pointsPositions;
				const pCount = pPos.length / 3;

				const pointSpeed = 2.8;
				const pointFreq = 0.006;

				for ( let i = 0; i < pCount; i ++ ) {
					const ix = i * 3;
					const x = pPos[ ix ];
					const y = pPos[ ix + 1 ];
					const z = pPos[ ix + 2 ];

					const wave1 = Math.sin( time * pointSpeed + x * pointFreq + y * pointFreq * 0.8 + z * pointFreq * 1.2 );
					const wave2 = Math.cos( time * 1.8 + y * pointFreq * 1.5 + z * pointFreq * 0.7 );
					const wave3 = Math.sin( time * 1.0 + x * pointFreq * 0.9 + z * pointFreq * 1.1 );

					let t = ( wave1 * 0.5 + wave2 * 0.3 + wave3 * 0.2 + 1 ) / 2;
					t = Math.pow( t, 1.8 ) * 1.5;

					const r0 = 0.8, g0 = 0.5, b0 = 0.1;
					const mix = Math.min( t, 1.0 );
					const r = r0 * (1 - mix) + 1.0 * mix;
					const g = g0 * (1 - mix) + 1.0 * mix;
					const b = b0 * (1 - mix) + 0.8 * mix;

					pointColors[ ix ] = r;
					pointColors[ ix + 1 ] = g;
					pointColors[ ix + 2 ] = b;
				}
				points.geometry.attributes.color.needsUpdate = true;

				renderer.render( scene, camera );
			}
		</script>
	</body>
</html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.