/*
	DrSnuggles ℗©®™
	for Revision MMXXVI

	ToDo/TaDa:																	Length
	+ Prepare: Browser audio policy (only when needed)							0:00
	+ Opener: TheHorseInMotion													0:04.2

	- Greetings 																5:00
	- Credits 																	1:16 (1:12 Song + 0:04 Logo)
																		Total	x:xx (5:00 max game time?)

	Credits:
	- The Horse In Motion was shot by photographer Eadweard Muybridge
	- Music/Voice/Gfx/Video by Gnu
	- Code by DrSnuggles

	Dedicated to us!
*/

//
// Part -1 loading workers and data while waiting for user interaction or showing opener
//

// maybe i show the specs of the used demo machine ??
{
	console.log("cpuThreads: " + navigator.hardwareConcurrency);
	// Video
	const c = document.createElement("canvas");
	const gl = c.getContext("webgl2");
	const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
	const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
	const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
	//console.log(debugInfo, vendor, renderer);
	// i miss the Hz, and gfx card infos from webgl, i have that code somewhere
	console.log(renderer);
	// color space
	const hasSRGBColor = window.matchMedia('(color-gamut: srgb)').matches;
	const hasHighDynamicRange = window.matchMedia('(dynamic-range: high)').matches;
	const hasP3Color = window.matchMedia('(color-gamut: p3)').matches;
	const hasRec2020Color = window.matchMedia('(color-gamut: rec2020)').matches;
	console.log(
		"Screen: " +
			screen.width +
			"x" +
			screen.height +
			"x" +
			screen.colorDepth +
			"bits dpr=" +
			devicePixelRatio,
	)
	console.log('sRGB='+hasSRGBColor+' HDR='+ hasHighDynamicRange+' P3='+hasP3Color+' Rec2020='+hasRec2020Color)
	
	// Audio
	const a = new AudioContext()
	console.log(
		a.sampleRate +
			"Hz@32bits " + // webAudio
			a.destination.channelCount +
			"Ch baseLatency: " +
			a.baseLatency +
			" outputLatency: " +
			a.outputLatency,
	)
}

let audioReady = false, videoReady = false, tunnelReady = false, mode = 'title',
pausedAt, startedAt, paused = false, timer = false

const canvasWorker = new Worker(new URL('./game.canvas.worker.js',import.meta.url),{type:'module'})
canvasWorker.onmessage = (e) => {
	console.log('received from game canvas worker:', e)
	if (e.data.status == 'resources loaded') videoReady = true
	if (e.data.status == 'dice') {
		play('dice')
	}
	if (e.data.status == 'tunnel') {
		mode = 'tunnel'
		pausedAt = ctx.currentTime - startedAt
		paused = true
		aNodes.NiceDayniceLife.stop()	// pause audio
		aNodes.NiceDayniceLife = null	// clean up, try to free mem
		canv.classList.add('hidden')	// hide game
		// play video
		const tunnelVid = document.getElementById('tunnelVid')
		tunnelVid.position = 0
		tunnelVid.classList.remove('hidden')
		tunnelVid.playbackRate = 1
		tunnelVid.play()
	}
	if (e.data.status == 'win') {
		aNodes.NiceDayniceLife.stop()	// stop audio
		mode = 'win'
		play( ['atari','amiga'][e.data.player] )
		setTimeout(()=>{
			credits()
		},3000)
	}
}

let startTime = 0

//
// preload audio
//
//
const aFiles = ['NiceDayniceLife','credits','dice','atari','amiga'],
isPlaying = [],
aNodes = {},
aBuf = {},
ctx = new AudioContext()
//ctx.audioWorklet.addModule(new URL('./fft-processor.js', import.meta.url), {type: 'module'})
//.then(()=>{
//	aNodes.fft = new AudioWorkletNode(ctx, 'fft-processor')
//	aNodes.fft.connect(ctx.destination)
	// add gain node before
	aNodes.gain = ctx.createGain()
	aNodes.gain.gain.value = 1 //DEBUG
	//aNodes.gain.connect(aNodes.fft)
	aNodes.gain.connect(ctx.destination)
	//fftNode.port.onmessage = (e) => {
	//	canvasWorker.postMessage({ audioTime: ctx.currentTime - startTime, freqData: e.data.frequencyData, timeDomain: e.data.timeDomainData })
	//}
	preloadAudio()
//})

async function preloadAudio() {
	console.time('Audio_LoadDecode')
	for (let i = aFiles.length-1; i >= 0; i--) {
		const f = aFiles.pop()
		const res = await fetch('./sfx/'+f+'.m4a')
		const ab = await res.arrayBuffer()
		aBuf[f] = await ctx.decodeAudioData(ab)
	}
	console.timeEnd('Audio_LoadDecode')
	audioReady = true
}
function play(f) {
	console.log('play',f,mode,paused,startedAt,pausedAt)
	aNodes[f] = ctx.createBufferSource()
	aNodes[f].buffer = aBuf[f]
	aNodes[f].connect(aNodes.gain)
	console.log(paused, ctx.currentTime, pausedAt, startedAt)	// debug
	if (f == 'dice') {
		aNodes[f].loop = false
		aNodes[f].start(0)
	} else if (mode == 'game' || mode == 'title') {
		aNodes[f].loop = true
		if (paused) {
			startedAt = ctx.currentTime - pausedAt
			aNodes[f].start(0, pausedAt)
		} else {
			startedAt = ctx.currentTime
			aNodes[f].start(0)
		}
		paused = false
	} else {
		aNodes[f].start(0)
	}
}

//
// Part 0 Prepare
//
function part0() {
	if (ctx.state !== 'running') {
		// stupid no audio till user interaction policy thingy
		document.body.innerHTML = '<style>#m{width:100vw;height:100vh;font-size:18vw;line-height:100vh;text-align:center;}</style><div id="m">👉💻👂🎶</div>';
		const evs = ['keyup', 'click', 'touchend'],
		resume = () => {
			evs.forEach((e) => { removeEventListener(e, resume)	})
			ctx.resume()
			part1()
		}
		evs.forEach((e) => { addEventListener(e, resume) })
	} else {
		part1()
	}
}

function part1() {	// Part 1 Intro - Horse In Motion
	console.time('HiM')
	document.body.innerHTML = '<style>body{background:#000}#intro{position:relative;width:100vw;height:100vh;animation:move 2s ease-in-out 0s 1 normal forwards;will-change:transform}@keyframes move{0%{transform:translateX(-50vw)}100%{transform:translateX(30vw)}}#Gnax{width:40vw;position:absolute;top:50%;color:#FFF;font:4vw Syncopate;text-align:center;border:5px solid #FFF;border-radius:10px;animation:zoom 1.5s linear 2s 1 normal forwards,end .5s linear 4s normal forwards;will-change:transform;white-space:nowrap;}@keyframes zoom{0%{transform:scale(1.00,1.00)}100%{transform:scale(1.03,1.03)}}@keyframes end{0%{transform:scale(1.03,1.03);filter:blur(0);opacity:1}100%{transform:scale(100.00,100.00);filter:blur(100px);opacity:0}}#HiM{background-color:white;width:10vw;position:absolute;top:calc(50% - 1vw);animation:move2 .75s ease-in-out 2s 1 normal forwards,rainbowAnim 500s linear infinite;will-change:transform;transform:translateX(43vw)}@keyframes move2{0%{transform:translateX(43vw)}100%{transform:translateX(80vw)}}.rainbow{background-image:linear-gradient(to right in oklch, red,orange,yellow,green,blue,indigo,violet,red);animation:rainbowAnim 35s linear infinite}@keyframes rainbowAnim{to{background-position:4500vh}}</style><main id="intro"><span id="Gnax">Revision 2026</span><img id="HiM" src="./gfx/HQ_mask.gif" class="rainbow"/></main>'
	function checkCSSEnded() {
		if (Gnax.getBoundingClientRect().height < 3300) {
			setTimeout(() => {
				checkCSSEnded()
			}, 100)
			return
		}
		function checkPlay() {
			if (audioReady && videoReady && tunnelReady) {
				//title()
				game()
				return
			}
			console.log('audio loaded='+audioReady+' video loaded='+videoReady+' tunnel ready='+tunnelReady)
			setTimeout(() => {
				checkPlay()
			}, 100)
		}
		console.timeEnd('HiM')
		checkPlay()
	}
	checkCSSEnded()
}

part0() // lets play !!

function title() {
	mode = 'title'
	document.body.innerHTML = [
		'<style>div{text-align:center;margin:3vw}span{padding:10px;font:4vw Syncopate;cursor:pointer;border:5px solid #fff;border-radius:10px}</style>',
		'<div><span>Play</span></div><div><span>Greetings</span></div><div><span>Credits</span></div><div><span>Exit</span></div>',
	].join('')
	document.querySelectorAll('div')[0].onclick = (ev) => { game() }
	document.querySelectorAll('div')[1].onclick = (ev) => { greetings() }
	document.querySelectorAll('div')[2].onclick = (ev) => { credits() }
	document.querySelectorAll('div')[3].onclick = (ev) => { location.href='about:blank';close() }	// close() wont work

}

function game() {
	document.body.innerHTML = ['<canvas id="canv" width="3840" height="2160" style="cursor:pointer;"></canvas>',
		'<video id="tunnelVid" class="hidden" src="',tunnelURL,'"></video>'
	].join('')
	document.getElementById('tunnelVid').onended = (ev) => {
		console.log('tunnel ended')
		//aNodes.NiceDayniceLife.start()	// continue audio
		mode = 'game'
		play('NiceDayniceLife')
		tunnelVid.classList.add('hidden')	// hide video
		canv.classList.remove('hidden')	// show game
	}
	const offscreen = canv.transferControlToOffscreen()
	canvasWorker.postMessage({canvas:offscreen, devicePixelRatio:devicePixelRatio},[offscreen])
	//aNodes.NiceDayniceLife.start()
	mode = 'title'
	play('NiceDayniceLife')
	canvasWorker.postMessage({title:true})
	setTimeout(()=>{	// delay a bit else play click is counted as dice
		onkeydown = interact
		onclick = interact
		oncontextmenu = interact
	},1000)
	function interact(ev){
		console.log(mode,ev)

		if (ev.key == 'Escape') {
			if (mode == 'title') {
				location.href='about:blank'
				close()
			}
			// stop all audio?
			for (let i = 0, k = Object.keys(aNodes); i < k.length; i++) {
				if (aNodes[k[i]].stop) aNodes[k[i]].stop()
			}
			canvasWorker.postMessage({reset:true})
			mode = 'title'
			game()
		}

		if (mode != 'game' && mode != 'title') return

		if (ev.clientX && mode == 'title') {
			const clicked = {	// coords in 4k space
				x: ev.clientX/canv.clientWidth*3840,
				y: ev.clientY/canv.clientHeight*2160
			}
			console.log(clicked.x, clicked.y)
			// todo: need if we are in title? or do we get that info from worker?
			// todo: redo when title is finished
			if (clicked.x > 1653 && clicked.x < 2188 && clicked.y > 472 && clicked.y < 646) {
				mode = 'game'
				canvasWorker.postMessage({play:true})
				return
			}
			if (clicked.x > 1384 && clicked.x < 2457 && clicked.y > 742 && clicked.y < 912) {
				aNodes.NiceDayniceLife.stop()
				greetings()
				return
			}
			if (clicked.x > 1521 && clicked.x < 2320 && clicked.y > 1008 && clicked.y < 1184) {
				aNodes.NiceDayniceLife.stop()
				credits()
				return
			}
			// todo: wrong clicked.y
			if (clicked.x > 1709 && clicked.x < 2132 && clicked.y > 1280 && clicked.y < 1450) {
				location.href='about:blank'
				close()
			}
		}

		canvasWorker.postMessage({action:'dice'})

		// keyboard
		if (ev.key == 'ArrowUp') canvasWorker.postMessage({move:'u',shift:ev.shiftKey})
		if (ev.key == 'ArrowDown') canvasWorker.postMessage({move:'d',shift:ev.shiftKey})
		if (ev.key == 'ArrowLeft') canvasWorker.postMessage({move:'l',shift:ev.shiftKey})
		if (ev.key == 'ArrowRight') canvasWorker.postMessage({move:'r',shift:ev.shiftKey})
		if (ev.key == 'Enter') canvasWorker.postMessage({store:1})

		//return false
	}
}

function greetings() {
	mode = 'greetings'
	//console.time('Greetings')
	const v = [
'Limace',
'L-Sync',
'Gaziel',
'Bjorn',
'MooZ',
'p0ke',
'Alkama',
'goodchip',
'ricolaoz',
'Lastic',
'saaturne',
'Stéphane',
'Pouale',
'psenough',
'Peregrine',
'Glafouk',
'g33kou',
'Franck',
'Logiker',
'flopine',
'Ziphoid',

'leonofsgr / sgr/chr/rsc',
'susencrusen / 5711',
'D3T0X / DT ^ CJ',
'St0fF / Neoplasia^theObsessedManiacs',
'Viti / HF',
'designated driver of mfx',
'incommunicado / science',
'Uctumi / LIA / Hokuto Force / PMV',
'JAC! / WUDSN',
'Sentcool',

'CYB3R / N3U @ 🛰1',
'4Ndr / N3U @ 🛰1',
'g33kou / NoIdea',
'rubix / Ctrl-Alt-Test',
'V|bE / Nuance^Vantage',
'noglin / brz',
'ted / TRBL',
'ElRyoGrande',
'Trakatranks',
'raggamuffin / Desire',

'bitl / 7dump',
'Noctis / RBBS^LFG',
'PAL / Offence, Fairlight and FIG',
'Noodles / Map',
'Mon / Oxygene',
'cgi',
'response / darklite',
'scamp',
'Carp',
'dipswitch / ds!^imp^up^5711',
'Fazeshift',
'Arne / Apollo Team',
'Almighty God / Atlantis/Excess/Level 64',
'LJ / Alcatraz ^ TRBL',
'toxi',
'dojoe / HUP',
'doh / X-Men, X-Women and X-Goblins',
'Trigger / HOODLUM',
'rubix',
'Macross / Wizard Works Multimedia',
'ra / nooon',
'Tolle / RBBS',
'fgenesis',
'hmstx',
'moziz',
'Nomix / Void Main',
'Kuttenbrunzer / Neoplasia',
'whaaag',
'Agrigor / hacksaar',
'Bausparfuchs / hacksaar',
'vexe / simulaatio.org',
'Nrg / PHn. & simulaatio.org',
'lfo / TEAM VyRaL ^ Hokuto Force ^ RESiStANCE',
'Doclands / Overlanders',
'Hawk78',
'Subdream',
'Moeff / Celebrity Deathmatch',
'Tachy Bunker / BUS ERROR Collective / Teletype Corporation',
'Overkill / Celebrity Deathmatch',
'Amblin',
'eff-ess',
'NebulaH',
'grau',
'cslag / Titan',
'darklon',
'neon / darklite',
'4ntler',
'RaDi / Plush',
'HellMood / Desire',
'Whistler / Realsm of .....',
'Speedman / Spectrals',
'glxblt / Brainstorm',
'sTEELER / 5711',
'Kalms / TBL',
'cryptic / approximate',
'jiawei',
'd0pefish',
'Frenchkiss / BrainWave',
'melezov',
'Rouze',
'Zac / Jewels',
'Oni / Poo-Brain',
'Luisa / Akronyme Analogiker ^ Poo-Brain ^ Rabenauge',
'CyReVolt / IAOTAI',
'draugven / Ctrl-Alt-Elite',
'!ZAJC! / Galactic Dreams Software',
'Hamster / TRSi',
'boneman / Poo-Brain',
'Zookah / poobrain',
'meloensel / poobrain',
'katfox / Furry Trash Group',
'Steel / Brainstorm',
'Jena / MetalWorx',
'Xiny6581 / Pretzel Logic/Genesis Project/The Solution',
'andr00d / Edge of Panic',
'podge',
'A.Ch / BrainWave',
'Map / Plush',
'Ohmego / RBBS^F.SyS',
'RoccoW / Poo-Brain',
'sannarchie / dreamteam210',
'ND / Pretzel Logic',
'pnm / SpeckDrumm / TRSi',
'Deyta / Cyanoacrylate Dreams/Furry Trash Group/Slipstream',
'mgr',
'UnitZeroOne / Badsquare',
'Atanua / Monoceros',
'AdeptApril / Monoceros',
'Sumaleth / Disaster Area',
'Nightshft / TTE',
'nytrik / cocoon',
'cybørt / Twilight Brothers',
'Arrakis / Brain Wave',
'chock / nuance',
'cP! / oMN',
'Hurles / mergeconflict',
'Gunstick / Unlimited Matricks',
'd0dge / moods plateau',
'epr',
'movax / TRSi',
'BreakDecks',
'Mrs Beanbag / Slipstream^RiFT',
'arnolTT aka Thyroxin / Avena',
'Mysdee / Byterapers, Inc.',
'Darkleaf',
'coolcat / TRSi',
'Medo / TiTAN',
'SainT / RetroHQ',
'dino / 5711',
'Řrřola',
'Cedric',
'A-Life',
'sauli / jumalauta',
'wqqwwq',
'virgill',
'sBeam / Desire',
'BUS ERROR / BUS ERROR Collective / Teletype Corporation',
'amiganoid',
'regines / Lampenfieber',
'Brix / Plush, Excess',
'Aghnar / Agima',
'jtruant',
'xq',
'Brainstorm / Mayday',
'mysh',
'AnyBody / TRSi',
'EOF / TRSi',
'cosowi / plush',
'Glow / Inque',
'Turritom',
'cyc / haujobb',
'Danko / Fairlight',
'DDL_Blue',
'RonjaPonja / KittenLabs',
'canmom',
'Manawyrm / KittenLabs',
'chemik / addict^aion',
'Uncle-X / mfx',
'Wayne / ArtWaY',
'TheBat / guideline',
'Derrik / ArtWaY',
'D/V / HF^NL^VRL^.dot^RR^P12',
'Punqtured / Fnuque',
'Koshk',
'seatsea / FTG^session0^freeside',
'henne / ac+drm',
'pesco',
'v3to / Oxyron / PriorArt / TRSi',
'ByteMaiden / epoqe',
'ref/crs',
'jh0ker / Akronyme Analogiker',
'krusty / benediction',
'willscott / freeside',
'amigo / binary',
'seven_dc / GRAY MARCHERS',
'OUTRON / GRAY MARCHERS',
'Terwiz / Alumni^Byterapers^Extend^Wide Load',
'Ruczka',
'Szi-Szi',
'axis (TFF) / The Furious Four',
'Miklesz / Damage',
'Silvester747 / Edge of Panic',
'NullDev / D-Faktory',
'bSr43 / D-FaKtory',
'NuSan / AMIGALS',
'Conquintor / mergeconflict',
'darngeek / mergeconflict^tte',
'Yuzu!',
'V1rgul',
'Rones',
'Simechamon',
'Arda',
'Blueskuhl / Multi Generation',
'AnaMark / Multi Generation',
'nex',
'hooverphonique / The Twitch Elite',
'luxploit',
'Elder0010 / Genesis Project',
'marcopasta',
'zynth',
'Riku55',
'Brie / MoonShine^hYmen0ptera.',
'Sen',
'Sharky- / Flaming Marshmellows',
'_da_swan_ / Flaming Marshmellows',
'Sjonny / Flaming Marshmellows',
'thunder_burn',
'Key-Real / Very Important Pictures',
'Anon Monk / Very Important Pictures',
'docravendark',
'Phoenix / Hornet^DC5',
'Azzaro / Madwizards + Floppy',
'Frano / Damage ^ .dot ^ TMC',
'djedditt',
'Evil / a1k.org',
'Voxy / Benediction',
'Blahrafn / Machine.rzr',
'bernie',
'Krikkit / Censor Design ^ Noice',
'Scythoior / SquoQuo',
'yugecin / RBBS',
'Doctor Soft / Lame',
'sensenstahl',
'Samurai / Inter^Effect',
'Captain JiNX',
'_Ook',
'Verkel / Palloeläin Productions',
'faeriel / Palloeläin Productions',
'Tony',
'Der Motzer / Ihdk.',
'Sychamis',
'DoenerAli / G*P',
'chorikii / DualityBeyond',
'tFt / Darklite.Dekadence',
'Tanteregenbogen',
'salie',
'P_Malin / Bitshifters',
'Butcher',
'AArt1256 / OTL^MS^AMBC',
'sp9k',
'SgtSlayer / X-men',
'Razdee / ONSLAUGHT^TRSI',
'Leia / Cellfish & Boozoholics',
'frauke',
'Mase',
'ReinekeWF',
'Iron-Wolf',
'Picsany',
'dritter',
'Bitgoat / Freakshow',
'vex',
'Easy32',
'CyReVolt / IAOTAI',
'juN3bula / N3U @ 🛰1',
'CYB3R / N3U @ 🛰1',
'4Ndr / N3U @ 🛰1',
'g33kou / NoIdea',
'rubix / Ctrl-Alt-Test',
'V|bE / Nuance^Vantage',
'noglin / brz',
'ted / TRBL',
'ElRyoGrande',
'Trakatranks',
'raggamuffin / Desire',
'Snipsel',
'bytefighter',
'titus / rabenauge',
'Beb / Vanity',
'FL1N',
'mwr / ViSdoM',
'kusma / excess',
'Gosia / Anadune',
'Mr.Acryl / Anadune',
'H05h1 / Akronyme Analogiker',
'friol',
'ginus / SOD',
'Ramses / Rabenauge^Scarab',
'hmnd',
'Fred-Eric / hacksaar',
'marv / Calodox',
'lft',
'korshun / dasErbgut^neoplasia',
'Marvin',
'void / hicknhack',
'lucky',
'muhmac / speckdrumm',
'VAdaPEGA',
'neu / eucalyptus^vaahtera',
'xeenu',
'aspen',
'nullgemm',
'-pOWL! / Alcatraz',
'Bitbreaker / Performers',
'Scrap28',
'Lynn / Furry Trash Group ^ Feline Frequencies',
'premium / iNSANE^Padua^Paradise',
'Kestrel / Syndrome',
'novel / moods plateau^gnumpf',
'Fab',
'dan',
'Velo / Approximate',
'fml.remmy / Caca multimedia group',
'hfr / haujobb',
'Lotek Style / .tSCc.^YM Rockerz^Demozoo',
'Jeff-20 / Denial',
'boreq / freeside',
'Cobalt / vacuum',
'mop2 / Akronyme Analogiker',
'Blueberry / Loonies^Fnuque^D.A.',
'mAtt / haujobb',
'Acryl / haujobb :: konsumer',
'jazZ / haujobb :: konsumer',
'Long name ... you know who this is (: / Accession/RBBS/TTE/Scenesat',
'Bongo',
'tschieses / tonleben',
'Siz / Absence',
'nicode',
'Psycho / Loonies',
'echo~parallax',
'bezo97 / fractal chats',
'gopher / alcatraz',
'Iloé',
'LLB / Ctrl-Alt-Test',
'Zavie / Ctrl-Alt-Test',
'Minki',
'Säschu',
'MiCroN3000 / Piko3D',
'Paramike / Piko3D',
'Retrorojber / NrG',
'DJ_Level_3 / BEC/TTC',
'Lambdacore / BaaS',
'Targhan / Arkos',
'Funkstill / epoqe',
'CyberGeeK',
'kwahu / Lepsi De',
'DREES / AMID',
'bod / ATW^CN112^Team210',
'madame / Schnappsgirls',
'Hammerfist / Desire',
'Green / AttentionWhore',
'pezia',
'_docd / farbrausch^haujobb',
'btr / Team210',
'Harvey / AMID',
'J.C.',
'dossi / tonleben',
'jonas',
'blala / a moiré misszió bemutatja',
'borg / tonleben',
'Rhino / Torment',
'FRaNKy / RBBS / Padua / Accession / Stuff',
'Steltek',
'dOc.K / moods plateau',
'Coreus / The Retro Spirit',
'DJ Daemon / ericade.radio',
'Qubix / Architects',
'MartiniMoe / epoqe',
'wiseman / Paradise',
'Purple / Furry Trash Group',
'Wracky / Piko3D',
'nerdine / the solution',
'Subi / Desire ^ SSR',
'EAT-DRINK-FUCK / poo-brain',
'Heaven / Desire',
'Hicks / Vanity',
'Cracker(tje) / BaaS',
'qetu',
'Kümmel',
'limace',
'fieserWolF / Abyss-Connection',
'Harekiet',
'spacey',
'Andy / Nuance',
'CrackY',
'Gaspode',
'zeroZshadow / DESiRE',
'Weasel / Padua/G*P/C.A.P. TV',
'kunsi',
'toms / Pulpo Corrosivo',
'Frequent / Ephidrena',
'BlackStar / Edge of Panic^Tribal Rage',
'myy / JML',
'waffle / Jumalauta',
'totetmatt',
'coderobe / session0^freeside',
'tubo / retroguru',
'nightmare / nuance',
'ps / TPOLM^TPOLM',
'pixeldesu / session0^CORTEX IMPLANT',
'sdt',
'Starchaser / Nectarine',
'Weyland / FairLight',
'Berdes / Calodox',
'Ebu / Team210',
'ftx / kolor',
'Unlock / Padua^Vantage',
'xtrium / LNX',
'Lycan / LNX',
'sirfancy',
'RaccoonViolet / SLP^FTG^SVatG^MMT^SGZ',
'hannes! / akronyme analogiker',
'Soundy / Deadliners / NGC',
'Bonefish / Reality',
'bagno',
'Alysius / Akustikrausch',
'Bartman / Abyss',
'SiR / SoD^Agents of S.H.I.T^Human',
'SunSpire / SceneSat \ Titan \ SunSpire Records',
'wrighter / sgz',
'Alex Menchi / Agima',
'jdb78',
'poti / bm & 5711',
'.misha. / nah-kolor^tatanka',
'riker77 / TTE^a1k.org',
'M_Myers / a1k.org',
'tsh / BEHELiT',
'MaD][ / Starship',
'Rotzloeffel / A1k.org',
'Stormcaller / Slipstream',
'Chucky / The Gang',
'xxx / haujobb^5711',
'tokra / Akronyme Analogiker',
'lycium',
'slerpy / SGZ',
'Marex / Gpo',
'Scythoior / SquoQuo',
'farfar / FLEX^DUB^LNS^FD^Madpakke',
'root42',
'PotcFdk',
'Charlie / Singular Crew',
'Glu / Wildfurs',
'Cmdr.Homer / Poo-Brain^Wildfurs',
'h0ffman / lgc^tbl^tte^up^ps^iris',
'peripatus',
'Dex / DualityBeyond',
'Pellicus',
'Callisto / Gpo',
'rc55',
'GRX / PandaCube',
'kieran / Bitshifters ^ Rabenauge',
'teo / rebels',
'Nosferatu / Offence',
'Dano / Padua',
'Suule / Oftenhide^Damage',
'PoroCYon / K2^SGZ',
'Curt Cool / Depth^Nah-Kolor+Janeway+AMP+...',
'djbase',
'arrbyte / epoqe',
'Raven / NUANCE',
'Gasman / H-Prg^SLP^LFG^Wavesitter',
'Steffest / Desire',
'progen / Desire',
'juvi / epoqe^Stargaze^Team210',
'MAGIC / Nah-Kolor / Vandalism News Staff',
'Aiko / Hork',
'Nuke / Hork',
'revival / fnuque',
'W.O.T.W. / TRSI',
'Saga Musix / Nuance^SVatG^HUP',
'spiny / Torment',
'Dwarf / DUB',
'reality404 / PooBrain^RBBS',
'Shantee',
'eriQue / snk clQ ^ tBL ^ nUMP71e5',
'Cheesy / DUB^TBC',
'Break / Mfx',
'Puryx / DUB^TBC^Schnappsgirls^TRSAC Staff',
'Don Juan',
'prowler / Nectarine',
'Rufus / Abyss',
'MusicMan / speedfisters',
'DJefke / RBBS^BaaS',
'halcy / SVatG',
'TMC / ROLE. ^ CPI',
'metheara / red_scarves',
'Q / TheEnbyperor',
'emoon / TBL ^ TTE',
'CJR / Abyss-Connection',
'TLC / Abyss-Connection',
'Tanja / Abyss-Connection',
'Bert / Abyss-Connection',
'MRC / Abyss-Connection',
'Octoate',
'TMA / Abyss-Connection^TRSi^TSA',
'Losso / AttentionWhore',
'benJam / SalvaMea^TRBL^TUM',
'aldroid / slipstream^TUHB',
'Lila / Team210',
'NR4 / Farbrausch^Team210^Schranzvergleich',
'darya / Farbrausch^Team210^SlayBells^5711',
'Enfys / SLP^SGZ^OTL^BBC',
'bitch',
'v3nom / TRBL^BTF',
'moqui / Schnappsgirls',
'shana / GEON',
'styx / headcrash^5711^DKMF',
'D.Fox / Digital Overflow'
	]
	document.body.innerHTML = ['<style>body{overflow:hidden;background:#FFF}',
		'#revisionLogo{display:block;position:absolute;top:0;width:100vw;height:100vh;margin:0 auto 0 auto;text-align:center;will-change:transform;animation:turn 10s linear 0s infinite normal forwards}',
		//'.visitors>div{float:left;padding-right:6vw;white-space:nowrap;color:#00F;text-shadow:.4vw .4vw #0FF}',
		//'.visitors>div:nth-child(3n+2){color:#0D0;text-shadow:.4vw .4vw #8F8}',
		//'.visitors>div:nth-child(3n+1){color:#F00;text-shadow:.4vw .4vw #FF0}',
		'.visitors>div{margin-left:15px;text-align:left;white-space:nowrap;color:#00F;text-shadow:.4vw .4vw #0FF}',
		'.visitors>div:nth-child(even){margin-right:15px;text-align:right;color:#F00;text-shadow:-.4vw .4vw #FF0}',
		'#visitors{position:absolute;top:10vh;height:80vh;width:100vw;font:2.5vw AtariHi;font-weight:bold;line-height:4vw;will-change:transform;animation:scroll 72s linear 0s 1 normal forwards;scroll-behavior:smooth}',
		'@keyframes turn{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}',
		'@keyframes scroll{0%{transform:translateY(100%)}100%{transform:translateY(-',(document.body.clientWidth/3840)*80000,'px)}}</style>',	// needs adjustment to height WHICH IS resolution dependent !!! todo: remove this dep
		'<img id="revisionLogo" src="./gfx/revisionLogo.svg" style="transform-origin: 49.4% 50.1%"/>',
		'<div id="visitors"><div class="visitors"><div>',v.sort().join('</div><div>'),'</div></div></div>'
	].join('')
	play('credits')
	if (timer) {
		clearTimeout(timer)
		timer = false
	}
	timer = setTimeout(()=>{
		//console.timeEnd('Greetings')
		if (mode != 'greetings') return
		mode = 'title'
		game()
	}, 74000)
}

function credits() {
	mode = 'credits'
	// CSS version generated with js
	//console.time('Credits')
	const text = [
		'Tunnel Love',
		'Revision 2026',
		'',
		'Idea by Gnax (Gnu & Max)',
		'Music by Gnu',
		'Video by Gnu',
		'Code by DrSnuggles',
		'Images from Wikipedia',
		'The Horse in Motion 1878 by E. Muybridge',
		'Tunnel video and yells from Revision 2024',
		'',
		'Love Amiga & Atari',
		'Love Assembler & BASIC & C',
		'Love JS & PHP & Pizza & Beer',
		'Love digital & analog',
		'Love your friends & family',
		'Love the tunnel',
		'',
		'No CO² was wasted on agents',
		'Please drink responsible ;)',
		'and don\'t forget to eat',
		'Thanks for playing',
		'',
		'See you next year',
	]
	const s = [
		'<style>div.line{width:100vw;height:100vh;font-size:8vw;font-weight:bold;line-height:100vh;text-align:center;position:absolute;top:0;opacity:0;will-change:transform,opacity;transform:translateZ(0)}',
		'@keyframes fade{0%{opacity:0}5%{opacity:1;transform:scale(1,1)}95%{opacity:1;transform:scale(1.03,1.03)}100%{opacity:0}}',
		'@keyframes fade2{0%{opacity:0}5%{opacity:1;transform:scale(1,1) rotate(0deg)}95%{opacity:1;transform:scale(1,1) rotate(360deg)}100%{opacity:0;transform:scale(100,100)}}',
		'@keyframes fadebg{0%{background-color:#fff}95%{background-color:#fff}100%{background-color:#000}}',
	]
	const h = []
	for (let i = 0; i < text.length; i++) {
		h.push('<div class="line" id="t', i, '">', text[i], '</div>')
		// duration | easing-function | delay | iteration-count | direction | fill-mode | play-state | name
		s.push(
			'#t', // eleName
			i, // eleName
			'{font-size: 3vw;',
			'animation:fade 3s linear ',
			3 * i, // delay
			's 1 normal forwards}',
		)
	}
	h.push('<img id="t', text.length, '" src="./gfx/revisionLogo.svg"/>')
	s.push(
		'#t', // eleName
		text.length, // eleName
		'{display:block;height:100vh;margin:0 auto 0 auto;text-align:center;opacity:0;background:#FFF;animation:fade2 4s linear ',
		3 * text.length, // delay
		's 1 normal forwards;transform-origin: 49.4% 50.1%}',
	)
	s.push(
		'body{will-change:background-color;background-color:#000;animation:fadebg 4s linear ',
		3 * text.length, // delay
		's 1 normal forwards}',
	)

	s.push('</style>')
	document.body.innerHTML = s.join('') + h.join('')
	play('credits')
	if (timer) {
		clearTimeout(timer)
		timer = false
	}
	timer = setTimeout(()=>{
		//console.timeEnd('Credits')
		if (mode != 'credits') return
		mode = 'title'
		game()
	},1.3*60000)
}

// preload tunnel video
let tunnelURL	// used in HTML gen
console.time('preload video')
fetch('./gfx/tunnela_h264.mp4')	// convert 29,97fps ProRes to 60 fps AV1: ffmpeg -i tunnel.mov -vf "setpts=0.4995*PTS" -c:v libx264 -preset veryslow -crf 15 -r 60 tunnel.mp4
.then(r=>r.arrayBuffer())
.then(ab=>new Blob([ab], {type:'video/mp4'}))
.then(b=>{
	tunnelURL=URL.createObjectURL(b)
	console.timeEnd('preload video')
	tunnelReady = true
})
.catch(e=>console.error(e))
