Raw Source
kbaniszack / InfoCompte

// ==UserScript==
// @name         InfoCompte
// @namespace    https://openuserjs.org/users/The_Stubbs
// @version      9.0.11
// @description  InfoCompte script for OGame
// @author       Vulca, benneb & now The Stubbs
// @license      MIT
// @match        https://*.ogame.gameforge.com/game/*
// @updateURL    https://openuserjs.org/meta/The_Stubbs/InfoCompte.meta.js
// @downloadURL  https://openuserjs.org/install/The_Stubbs/InfoCompte.user.js
// @grant        none
// @run-at       document-idle
// @watch        WxhTbxMSvT0
// @todo         V9, Buildings order, Safari
// ==/UserScript==

const GameData = {
	costs: {
		1: [ 60, 15, 0, 1.5 ],
		2: [ 48, 24, 0, 1.6 ],
		3: [ 225, 75, 0, 1.5 ],
		4: [ 75, 30, 0, 1.5 ],
		12: [ 900, 360, 180, 1.8 ],
		14: [ 400, 120, 200, 2 ],
		15: [ 1000000, 500000, 100000, 2 ],
		21: [ 400, 200, 100, 2 ],
		22: [ 1000, 0, 0, 2 ],
		23: [ 1000, 500, 0, 2 ],
		24: [ 1000, 1000, 0, 2 ],
		31: [ 200, 400, 200, 2 ],
		33: [ 0, 50000, 100000, 2 ],
		34: [ 20000, 40000, 0, 2 ],
		36: [ 200, 0, 50, 5 ],
		41: [ 20000, 40000, 20000, 2 ],
		42: [ 20000, 40000, 20000, 2 ],
		43: [ 2000000, 4000000, 2000000, 2 ],
		44: [ 20000, 20000, 1000, 2 ],
		106: [ 200, 1000, 200, 2 ],
		108: [ 0, 400, 600, 2 ],
		109: [ 800, 200, 0, 2 ],
		110: [ 200, 600, 0, 2 ],
		111: [ 1000, 0, 0, 2 ],
		113: [ 0, 800, 400, 2 ],
		114: [ 0, 4000, 2000, 2 ],
		115: [ 400, 0, 600, 2 ],
		117: [ 2000, 4000, 600, 2 ],
		118: [ 10000, 20000, 6000, 2 ],
		120: [ 200, 100, 0, 2 ],
		121: [ 1000, 300, 100, 2 ],
		122: [ 2000, 4000, 1000, 2 ],
		123: [ 240000, 400000, 160000, 2 ],
		124: [ 4000, 8000, 4000, 1.75 ],
		199: [ 0, 0, 0, 3 ],
		202: [ 2000, 2000, 0 ],
		203: [ 6000, 6000, 0 ],
		204: [ 3000, 1000, 0 ],
		205: [ 6000, 4000, 0 ],
		206: [ 20000, 7000, 2000 ],
		207: [ 45000, 15000, 0 ],
		208: [ 10000, 20000, 10000 ],
		209: [ 10000, 6000, 2000 ],
		210: [ 0, 1000, 0 ],
		211: [ 50000, 25000, 15000 ],
		212: [ 0, 2000, 500 ],
		213: [ 60000, 50000, 15000 ],
		214: [ 5000000, 4000000, 1000000 ],
		215: [ 30000, 40000, 15000 ],
		217: [ 2000, 2000, 1000 ],
		218: [ 85000, 55000, 20000 ],
		219: [ 8000, 15000, 8000 ],
		401: [ 2000, 0, 0 ],
		402: [ 1500, 500, 0 ],
		403: [ 6000, 2000, 0 ],
		404: [ 20000, 15000, 2000 ],
		405: [ 5000, 3000, 0 ],
		406: [ 50000, 50000, 30000 ],
		407: [ 10000, 10000, 0 ],
		408: [ 50000, 50000, 0 ],
		502: [ 8000, 0, 2000 ],
		503: [ 12500, 2500, 10000 ]
	},
	types: {
		buildings: [ 1, 2, 3, 4, 12, 14, 15, 21, 22, 23, 24, 31, 33, 34, 36, 41, 42, 43, 44 ],
		planetBuildings: [ 1, 2, 3, 4, 12, 14, 15, 21, 22, 23, 24, 31, 33, 34, 36, 44 ],
		moonBuildings: [ 14, 21, 22, 23, 24, 41, 42, 43 ],
		mineBuildings: [ 1, 2, 3 ],
		otherBuildings: [ 4, 12, 14, 15, 21, 22, 23, 24, 31, 33, 34, 36, 41, 42, 43, 44 ],
		researches: [ 106, 108, 109, 110, 111, 113, 114, 115, 117, 118, 120, 121, 122, 123, 124, 199 ],
		units: [ 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 401, 402, 403, 404, 405, 406, 407, 408, 502, 503 ],
		ships: [ 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 217, 218, 219 ],
		defenses: [ 401, 402, 403, 404, 405, 406, 407, 408, 502, 503 ]
	}
}

const Utils = {
	reduce( ...objects ){
		const result = {}
		for( const object of objects ){
			for( const key of Object.keys( object ) ){
				const objectValueIsObject = Object( object[ key ] ) === object[ key ]
				const resultValueIsObject = Object( result[ key ] ) === result[ key ]
				if( objectValueIsObject && resultValueIsObject ){
					result[ key ] = this.reduce( result[ key ], object[ key ] )
				}else{
					result[ key ] = object[ key ]
				}
			}
		}
		return result
	}
}

const Lang = ( function(){

	const languages = {
		en: {
			classes: {
				alliance: {
					none: 'no alliance class selected',
					trader: 'Trader',
					warrior: 'Warrior',
					explorer: 'Researcher'
				},
				player: {
					none: 'No player class selected',
					miner: 'Collector',
					warrior: 'General',
					explorer: 'Discoverer'
				}
			},
			technologies: {
				1: 'Metal mine',
				2: 'Crystal mine',
				3: 'Deuterium synthesizer',
				4: 'Solar plant',
				12: 'Fusion plant',
				14: 'Robotics factory',
				15: 'Nanite factory',
				21: 'Shipyard',
				22: 'Metal storage',
				23: 'Crystal storage',
				24: 'Deuterium storage',
				31: 'Research laboratory',
				33: 'Terraformer',
				34: 'Alliance depot',
				36: 'Repair dock',
				41: 'Moonbase',
				42: 'Sensor phalanx',
				43: 'Jump gate',
				44: 'Missile silo',
				106: 'Espionage',
				108: 'Computer',
				109: 'Weapons',
				110: 'Shielding',
				111: 'Armour',
				113: 'Energy',
				114: 'Hyperspace',
				115: 'Combustion drive',
				117: 'Impulse drive',
				118: 'Hyperspace drive',
				120: 'Laser',
				121: 'Ion',
				122: 'Plasma',
				123: 'Intergalacticresearch network',
				124: 'Astrophysics',
				199: 'Graviton',
				202: 'Small cargo',
				203: 'Large cargo',
				204: 'Light fighter',
				205: 'Heavy fighter',
				206: 'Cruiser',
				207: 'Battleship ',
				208: 'Colony ship',
				209: 'Recycler',
				210: 'Espionage probe',
				211: 'Bomber',
				212: 'Solar satellite',
				213: 'Destroyer',
				214: 'Deathstar',
				215: 'Battlecruiser',
				217: 'Crawler',
				218: 'Reaper',
				219: 'Pathfinder',
				401: 'Rocket launchers',
				402: 'Light lasers',
				403: 'Heavy laser',
				404: 'Gauss cannon',
				405: 'Ion cannon',
				406: 'Plasma turret',
				407: 'Small shield dome',
				408: 'Large shield dome',
				502: 'Anti-ballistic missiles',
				503: 'Interplanetary missile',
				11101: 'Residential Sector',
				11102: 'Biosphere Farm',
				11103: 'Research Centre',
				11104: 'Academy of Sciences',
				11105: 'Neuro-Calibration Centre',
				11106: 'High Energy Smelting',
				11107: 'Food Silo',
				11108: 'Fusion-Powered Production',
				11109: 'Skyscraper',
				11110: 'Biotech Lab',
				11111: 'Metropolis',
				11112: 'Planetary Shield',
				11201: 'Intergalactic Envoys',
				11202: 'High-Performance Extractors',
				11203: 'Fusion Drives',
				11204: 'Stealth Field Generator',
				11205: 'Orbital Den',
				11206: 'Research AI',
				11207: 'High-Performance Terraformer',
				11208: 'Enhanced Production Technologies',
				11209: 'Light Fighter Mk II',
				11210: 'Cruiser Mk II',
				11211: 'Improved Lab Technology',
				11212: 'Plasma Terraformer',
				11213: 'Low-Temperature Drives',
				11214: 'Bomber Mk II',
				11215: 'Destroyer Mk II',
				11216: 'Battlecruiser Mk II',
				11217: 'Robot Assistants',
				11218: 'Supercomputer'
			},
			titles: {
				missing: 'Missing data',
				overview: 'Points investment',
				empire: ( player, universe, lang ) => `${ player }'s player account on ${ universe }.${ lang }`,
				production: ( player, universe, lang ) => `Player's production ${ player.name } on ${ universe.name }.${ universe.lang }`,
				generated: ( date, time ) => `Generated on ${ date } at ${ time } by `
			},
			alliance: 'Alliance',
			crystal: 'Crystal',
			daily: 'Daily',
			defense: 'Defense',
			defenses: 'Defenses',
			destroyed: 'place(s) if destroyed',
			deuterium: 'deuterium',
			facilities: 'Facilities',
			fleet: 'Fleet',
			fleets: 'Fleets',
			highscore: 'Highscore',
			hint: 'There is missing data. Visit the empire view and/or the following pages',
			hourly: 'Hourly',
			metal: 'Metal',
			mines: 'Mines',
			minesPoints: 'Mines points invested',
			minesProduction: 'Mines production',
			moonBuildings: 'Moon buildings',
			moonDefenses: 'Moon defenses',
			moonFields: 'Moon fields',
			noAmProduction: 'Production without AM',
			notification: 'Export placed in clipboard',
			others: 'Others',
			otherBuildings: 'Other buildings',
			planetBuildings: 'Planet buildings',
			planetDefenses: 'Planet defenses',
			planetFields: 'Planet fields',
			production: 'Production',
            productions: 'Productions',
			research: 'Research',
			researches: 'Researches',
			resources: 'Resources',
			upgraded: 'place(s) when finished',
			upgrades: 'In construction',
			used: 'Used',
			weekly: 'Weekly'
		},
		fr: {
			classes: {
				alliance: {
					none: "aucune classe d'alliance sélectionnée",
					trader: 'Marchand',
					warrior: 'Guerrier',
					explorer: 'Chercheur'
				},
				player: {
					none: "aucune classe de base sélectionnée",
					miner: 'Collecteur',
					warrior: 'Général',
					explorer: 'Explorateur'
				}
			},
			technologies: {
				1: 'Mine de métal',
				2: 'Mine de cristal',
				3: 'Synthétiseur de deutérium',
				4: 'Centrale électrique solaire',
				12: 'Centrale électrique de fusion',
				14: 'Usine de robots',
				15: 'Usine de nanites',
				21: 'Chantier spatial',
				22: 'Hangar de métal',
				23: 'Hangar de cristal',
				24: 'Réservoir de deutérium',
				31: 'Laboratoire de recherche',
				33: 'Terraformeur',
				34: 'Dépôt de ravitaillement',
				36: 'Dock spatial',
				41: 'Base lunaire',
				42: 'Phalange de capteur',
				43: 'Porte de saut spatial',
				44: 'Silo de missiles',
				106: 'Espionnage',
				108: 'Ordinateur',
				109: 'Armes',
				110: 'Bouclier',
				111: 'Protection',
				113: 'Énergie',
				114: 'Hyperespace',
				115: 'Réacteur à combustion',
				117: 'Réacteur à impulsion',
				118: 'Propulsion hyperespace',
				120: 'Laser',
				121: 'Ions',
				122: 'Plasma',
				123: 'Réseau de recherche',
				124: 'Astrophysique',
				199: 'Graviton',
				202: 'Petit transporteur',
				203: 'Grand transporteur',
				204: 'Chasseur léger',
				205: 'Chasseur lourd',
				206: 'Croiseur',
				207: 'Vaisseau de bataille ',
				208: 'Vaisseau de colonisation',
				209: 'Recycleur',
				210: "Sonde d'espionnage",
				211: 'Bombardier',
				212: 'Satellite solaire',
				213: 'Destructeur',
				214: 'Étoile de la mort',
				215: 'Traqueur',
				217: 'Foreuse',
				218: 'Faucheur',
				219: 'Éclaireur',
				401: 'Lanceur de missiles',
				402: 'Artillerie laser légère',
				403: 'Artillerie laser lourde',
				404: 'Canon de Gauss',
				405: 'Artillerie à ions',
				406: 'Lanceur de plasma',
				407: 'Petit bouclier',
				408: 'Grand bouclier',
				502: "Missile d'interception",
				503: 'Missile interplanétaire'
			},
			titles: {
				missing: 'Données manquantes',
				overview: 'Investissement des points',
				empire: ( player, universe, lang ) => `Compte du joueur ${ player } sur ${ universe }.${ lang }`,
				production: ( player, universe, lang ) => `Production du joueur ${ player.name } sur ${ universe.name }.${ universe.lang }`,
				generated: ( date, time ) => `Généré le ${ date } à ${ time } par `
			},
			alliance: 'Alliance',
			crystal: 'Cristal',
			daily: 'Journalière',
			defense: 'Défense',
			defenses: 'Défenses',
			destroyed: 'place(s) si détruit',
			deuterium: 'Deutérium',
			facilities: 'Installations',
			fleet: 'Flotte',
			fleets: 'Flottes',
			highscore: 'Classement',
			hint: 'Il manque des données. Visitez la vue empire et/ou les pages suivantes',
			hourly: 'Horaire',
			metal: 'Métal',
			mines: 'Mines',
			minesPoints: 'Points investis en mines',
			minesProduction: 'Production des mines',
			moonBuildings: 'Bâtiments lunaires',
			moonDefenses: 'Défenses lunaires',
			moonFields: 'Cases lunaires',
			noAmProduction: 'Production sans AM',
			notification: 'Export placé dans le presse-papier',
			others: 'Autres',
			otherBuildings: 'Autres bâtiments',
			planetBuildings: 'Bâtiments planétaires',
			planetDefenses: 'Défenses planétaires',
			planetFields: 'Cases planétaires',
			production: 'Production',
            productions: 'Productions',
			research: 'Recherche',
			researches: 'Recherches',
			resources: 'Ressources',
			upgraded: 'place(s) quand terminé',
			upgrades: 'En construction',
			used: 'Utilisé',
			weekly: 'Hebdomadaire'
		}
	}

	return languages[ document.documentElement.lang ] || languages.en
} )()

const StorageData = ( function(){

	const key = 'InfoCompte-9'
	let item
	let id

	return {
		get(){
			item = JSON.parse( localStorage.getItem( key ) ) || {}
			id = document.head.querySelector( 'meta[name=ogame-player-name]' ).content
			return item[ id ] ||= {}
		},
		set( value ){
			item[ id ] = value
			localStorage.setItem( key, JSON.stringify( item ) )
		}
	}
} )()

const PageData = ( function(){

	function parseNumber( string ){
		return parseInt( string.replace( /\./g, '' ) )
	}

	function elementExists( selector ){
		return new Promise( function( resolve ){
			const observer = new MutationObserver( function(){
				const element = document.querySelector( selector )
				if( element ){
					observer.disconnect()
					resolve( element )
				}
			} )
			observer.observe( document.body, { childList: true, subtree: true } )
		} )
	}

	function getMetaData(){
		const elements = document.head
		return {
			universe: {
				lang: elements.querySelector( 'meta[name=ogame-language]' ).content,
				name: elements.querySelector( 'meta[name=ogame-universe-name]' ).content,
				speeds: {
					economy: parseInt( elements.querySelector( 'meta[name=ogame-universe-speed]' ).content )
				}
			},
			player: {
				name: elements.querySelector( 'meta[name=ogame-player-name]' ).content
			}
		}
	}

	async function getEmpirePageData(){

		function getPositionsData(){

			function getBuildingsTechnologies( element ){
				const currents = {}
				const upgraded = {}
				const upgrades = {}
				for( const { childNodes, classList } of element.querySelectorAll( '.supply > div, .station > div' ) ){
					const id = parseInt( classList[ 0 ] )
					let [ stock, , target = 0 ] = childNodes
					stock = parseNumber( stock.textContent )
					target &&= parseNumber( target.textContent )
					currents[ id ] = upgraded[ id ] = stock
					upgrades[ id ] = 0
					if( stock < target ){
						upgraded[ id ] = target
						upgrades[ id ] = 1
					}
				}
				return { currents, upgrades, upgraded }
			}

			function getUnitsTechnologies( element ){
				const currents = {}
				const upgraded = {}
				const upgrades = {}
				for( const { childNodes, classList } of element.querySelectorAll( '.ships > div, .defence > div' ) ){
					const id = parseInt( classList[ 0 ] )
					let [ value, , active = 0 ] = childNodes
					value = parseNumber( value.textContent )
					active &&= parseNumber( active.textContent )
					currents[ id ] = upgraded[ id ] = value
					upgrades[ id ] = 0
					if( active ){
						upgraded[ id ] = value + active
						upgrades[ id ] = active
					}
				}
				return { currents, upgrades, upgraded }
			}

			const type = document.querySelector( '#planetsTab.active' ) ? 'P' : 'M'
			const result = {}
			for( const element of document.querySelectorAll( '.planet:not( .summary )' ) ){
				const coordinates = element.querySelector( '.planetData :first-child li:first-child' ).textContent.slice( 1, -1 )
				result[ `${ coordinates }:${ type }` ] = {
					technologies: Utils.reduce( getBuildingsTechnologies( element ), getUnitsTechnologies( element ) )
				}
			}
			return { player: { positions: result } }
		}

		function getResearchesTechnologies(){
			const currents = {}
			const upgraded = {}
			const upgrades = {}
			for( const { childNodes, classList } of document.querySelectorAll( '.planet:first-child .research > div' ) ){
				const id = parseInt( classList[ 0 ] )
				let [ value, , active ] = childNodes
				value = parseInt( value.textContent )
				active &&= parseInt( active.textContent )
				currents[ id ] = upgraded[ id ] = value
				upgrades[ id ] = 0
				if( active ){
					upgraded[ id ] = active
					upgrades[ id ] = 1
				}
			}
			return { player: { researches: { currents, upgrades, upgraded } } }
		}

		return Utils.reduce( getMetaData(), getPositionsData(), getResearchesTechnologies() )
	}

	async function getOtherPageData(){

		function getCommonPageData(){

			function getClassData(){
				const list = document.querySelector( '#characterclass div' ).classList
				let result = null
				for( const key of [ 'miner', 'warrior', 'explorer' ] ){
					if( list.contains( key ) ){
						result = key
						break
					}
				}
				return result
			}

			function getOfficersData(){
				const result = {}
				for( const key of [ 'commander', 'admiral', 'engineer', 'geologist', 'technocrat' ] ){
					const element = document.querySelector( `#officers a.on.${ key }` )
					result[ key ] = element ? true : false
				}
				return result
			}

			function getPositionsData(){

				function getID( string ){
					const [ result ] = string.match( /(?<=cp=)\d+/ )
					return parseInt( result )
				}

				function getName( string ){
					const [ result ] = string.split( ' [' )
					return result
				}

				function getCoordinates( string ){
					const [ , result ] = string.split( /\[(.+)\]/ )
					return result
				}

				function getFields( string ){
					const [ , result ] = string.split( /\((.+)\)/ )
					const [ used, count ] = result.split( '/' )
					return { count: parseInt( count ), used: parseInt( used ) }
				}

				function getTemperatures( string ){
					const result = {}
					if( string.includes( '°' ) ){
						const [ , min, , max ] = string.replace( /\s/g, '' ).split( /(-?\d+)(?=°)/ )
						result.min = parseInt( min )
						result.max = parseInt( max )
					}
					return result
				}

				const elements = document.querySelectorAll( '#planetList .smallplanet :is( a.planetlink, a.moonlink )' )
				const template = document.createElement( 'template' )
				const result = {}
				for( const { classList, title } of elements ){
					template.innerHTML = title
					const text = template.content.textContent
					const coordinates = getCoordinates( text )
					const type = classList.contains( 'planetlink' ) ? 'P' : 'M'
					result[ `${ coordinates }:${ type }` ] = {
						id: getID( title ),
						name: getName( text ),
						fields: getFields( text ),
						temperatures: getTemperatures( text )
					}
				}
				return result
			}

			const result = {
				player: {
					class: getClassData(),
					officers: getOfficersData(),
					positions: getPositionsData()
				}
			}
			return Utils.reduce( getMetaData(), result )
		}

		async function getSpecificPageData(){

			function getPositionKey(){
				const element = document.head
				const coordinates = element.querySelector( 'meta[name=ogame-planet-coordinates]' ).content
				const type = element.querySelector( 'meta[name=ogame-planet-type]' ).content
				return `${ coordinates }:${ type.charAt( 0 ).toUpperCase() }`
			}

			async function getAllianceData(){
				let result = 'none'
				if( document.querySelector( '#applicationTab' ) ){
					await elementExists( '#allyData' )
					const list = document.querySelector( '.alliance_class' ).classList
					for( const key of [ 'trader', 'warrior', 'explorer' ] ){
						if( list.contains( key ) ){
							result = key
							break
						}
					}
				}
				return { class: result }
			}

			function getHighscoreData(){
				const ranks = {}
				let rank = {}
				for( const element of document.querySelectorAll( '#ranks tr' ) ){
					const position = parseInt( element.querySelector( '.position' ).textContent )
					const score = parseNumber( element.querySelector( '.score' ).textContent )
					if( position && score ){
						if( element.classList.contains( 'myrank' ) ) rank = { position, score }
						else ranks[ position ] = score
					}
				}
				return { rank, ranks }
			}

			function getBuildingsTechnologies(){
				const currents = {}
				const upgraded = {}
				const upgrades = {}
				for( const element of document.querySelectorAll( '#technologies > ul > li' ) ){
					const value = element.querySelector( '.level' )?.getAttribute( 'data-value' )
					if( value ){
						const id = parseInt( element.getAttribute( 'data-technology' ) )
						currents[ id ] = upgraded[ id ] = parseInt( value )
						upgrades[ id ] = 0
					}
				}
				const active = document.querySelector( '.technology[data-status=active]' )
				if( active ){
					const stock = parseInt( active.querySelector( '.stockAmount' ).textContent )
					const target = parseInt( active.querySelector( '.targetlevel' ).textContent )
					if( stock < target ){
						const id = parseInt( active.getAttribute( 'data-technology' ) )
						upgraded[ id ] = target
						upgrades[ id ] = 1
					}
				}
				return { currents, upgraded, upgrades }
			}

			function getUnitsTechnologies(){
				const currents = {}
				const upgraded = {}
				const upgrades = {}
				for( const element of document.querySelectorAll( '#technologies ul > li' ) ){
					const id = parseInt( element.getAttribute( 'data-technology' ) )
					const value = parseInt( element.querySelector( '.amount' ).getAttribute( 'data-value' ) )
					currents[ id ] = upgraded[ id ] = value
					upgrades[ id ] = 0
				}
				for( const element of document.querySelectorAll( 'td.first, .queue td' ) ){
					const id = parseInt( element.querySelector( 'img' ).alt.substring( 7 ) )
					const value = parseNumber( element.textContent )
					upgraded[ id ] += value
					upgrades[ id ] = value
				}
				return { currents, upgrades, upgraded }
			}

			function getResearchesTechnologies(){
				const currents = {}
				const upgraded = {}
				const upgrades = {}
				for( const technology of document.querySelectorAll( '#technologies ul > li' ) ){
					const id = parseInt( technology.getAttribute( 'data-technology' ) )
					const value = parseInt( technology.querySelector( '.level' ).getAttribute( 'data-value' ) )
					currents[ id ] = upgraded[ id ] = value
					upgrades[ id ] = 0
				}
				const active = document.querySelector( '.technology[data-status=active]' )
				if( active ){
					const id = parseInt( active.getAttribute( 'data-technology' ) )
					upgraded[ id ] = parseInt( active.querySelector( '.targetlevel' ).textContent )
					upgrades[ id ] = 1
				}
				return { currents, upgrades, upgraded }
			}

			function getProductions(){
                    function getValueFromElementText(selector){
                        return parseNumber(table.querySelector(selector).innerText)
                    }
                     function getValueFromElementTitle(selector){
                        return parseNumber(table.querySelector(selector).title)
                    }
                    const table = document.querySelector( '.listOfResourceSettingsPerPlanet' )
                    const geologist = document.querySelector( `#officers a.on.geologist` )
                    const crawlersRate = geologist ? .9 : 1
                    return {
                        basic: {
                            metal: getValueFromElementText( ':nth-child( 3 ) > :nth-child( 2 )' ),
                            crystal: getValueFromElementText( ':nth-child( 3 ) > :nth-child( 3 )' )
                        },
                        mines: {
                            metal: getValueFromElementTitle( ':nth-child( 4 ) > :nth-child( 3 ) > span' ),
                            crystal: getValueFromElementTitle( ':nth-child( 5 ) > :nth-child( 4 ) > span' ),
                            deuterium: getValueFromElementTitle( ':nth-child( 6 ) > :nth-child( 5 ) > span' )
                        },
                        fusion: {
                            deuterium: getValueFromElementText( ':nth-child( 8 ) > :nth-child( 5 )' ) * -1
                        },
                        crawlers: {
                            metal: Math.round( getValueFromElementText( ':nth-child( 10 ) > :nth-child( 3 )' ) * crawlersRate ),
                            crystal: Math.round( getValueFromElementText( ':nth-child( 10 ) > :nth-child( 4 )' ) * crawlersRate ),
                            deuterium: Math.round( getValueFromElementText( ':nth-child( 10 ) > :nth-child( 5 )' ) * crawlersRate )
                        },
                        plasma: {
                            metal: getValueFromElementText( ':nth-child( 11 ) > :nth-child( 3 )' ),
                            crystal: getValueFromElementText( ':nth-child( 11 ) > :nth-child( 4 )' ),
                            deuterium: getValueFromElementText( ':nth-child( 11 ) > :nth-child( 5 )' )
                        },
                        classes: {
                            alliance: {
                                metal: getValueFromElementText( ':nth-child( 17 ) > :nth-child( 3 )' ),
                                crystal: getValueFromElementText( ':nth-child( 17 ) > :nth-child( 4 )' ),
                                deuterium: getValueFromElementText( ':nth-child( 17 ) > :nth-child( 5 )' )
                            },
                            player: {
                                metal: getValueFromElementText( ':nth-child( 16 ) > :nth-child( 3 )' ),
                                crystal: getValueFromElementText( ':nth-child( 16 ) > :nth-child( 4 )' ),
                                deuterium: getValueFromElementText( ':nth-child( 16 ) > :nth-child( 5 )' )
                            }
                        }
                    }
                }



			const result = {}
			switch( currentPage ){
				case 'supplies':
				case 'facilities':
					result.player = {
						positions: {
							[ getPositionKey() ]: {
								technologies: getBuildingsTechnologies()
							}
						}
					}
					break
				case 'resourceSettings':
					result.player = {
						positions: {
							[ getPositionKey() ]: {
								productions: getProductions()
							}
						}
					}
					break
				case 'shipyard':
				case 'defenses':
					result.player = {
						positions: {
							[ getPositionKey() ]: {
								technologies: getUnitsTechnologies()
							}
						}
					}
					break
				case 'research':
					result.player = {
						researches: getResearchesTechnologies()
					}
					break
				case 'alliance':
					result.player = {
						alliance: await getAllianceData()
					}
					break
				case 'highscore':
					result.universe = {
						highscore: getHighscoreData()
					}
					break
			}
			return result
		}

		return Utils.reduce( getCommonPageData(), await getSpecificPageData() )
	}

	return {
		async get(){
			if( currentPage === 'empire' ) return await getEmpirePageData()
			else return await getOtherPageData()
		}
	}
} )()

const PageComponent = ( function(){

	function getAsNumber( value ){
		return isNaN( value ) ? parseInt( value.match( /\d+/ )[ 0 ] ) : value
	}

	function getAsLocaleString( value ){
		return value.toLocaleString( 'de-DE' )
	}

	function getAsPaddedString( value, targetLength, padString ){
		return value.toString().padStart( targetLength, padString )
	}

	function getAsPercentage( value ){
		const result = Math.round( value * 10000 ) * .01
		return result.toFixed( 2 ).padStart( 5, '0' ).replace( '.', ',' ) + ' %'
	}

	function getBuildingResources( id, level ){
		const [ metal, crystal, deuterium, factor ] = GameData.costs[ id ]
		return 	Math.floor( metal * factor ** ( level - 1 ) )
				+ Math.floor( crystal * factor ** ( level - 1 ) )
				+ Math.floor( deuterium * factor ** ( level - 1 ) )
	}

	function getCumulativeBuildingResources( id, level ){
		let result = 0
		for( let i = 1; i <= level; i++ ) result += getBuildingResources( id, i )
		return result
	}

	function getResearchResources( id, level ){
		const [ metal, crystal, deuterium, factor ] = GameData.costs[ id ]
		return 	Math.round( metal * factor ** ( level - 1 ) * .01 ) * 100
				+ Math.round( crystal * factor ** ( level - 1 ) * .01 ) * 100
				+ Math.round( deuterium * factor ** ( level - 1 ) * .01 ) * 100
	}

	function getCumulativeResearchResources( id, level ){
		let result = 0
		for( let i = 1; i <= level; i++ ) result += getResearchResources( id, i )
		return result
	}

	function getUnitsResources( id, amount ){
		const [ metal, crystal, deuterium ] = GameData.costs[ id ]
		return metal * amount + crystal * amount + deuterium * amount
	}

	function getMetalMineProduction( level, position, speed ){
		const rate = GameData.bonuses.positions.metal[ position - 1 ]
		return Math.floor( 30 * level * 1.1 ** level * rate * speed )
	}

	function getCrystalMineProduction( level, position, speed ){
		const rate = GameData.bonuses.positions.metal[ position - 1 ]
		return Math.floor( 20 * level * 1.1 ** level * rate * speed )
	}

	function getDeuteriumMineProduction( level, temperature, speed ){
		return Math.floor( 10 * level * 1.1 ** level * ( 1.44 - .004 * temperature ) * speed )
	}

	function getMetalPlasmaProduction( production, level ){
		return Math.round( production * level * .01 )
	}

	function getCrystalPlasmaProduction( production, level ){
		return Math.round( production * level * .0066 )
	}

	function getDeuteriumPlasmaProduction( production, level ){
		return Math.round( production * level * .0033 )
	}


	const SharedStyles = {
		// OGame's positionning fix
		common: `
                #productionboxBottom {
                    position: initial;
                }
				#ic-component {
					all: inherit;
					background: #0d1014;
					border: 2px solid #000;
					box-sizing: border-box;
					display: flex;
					flex-direction: column;
					gap: 8px;
					line-height: 1;
					margin: 0 7px 24px;
					padding: 0 8px 8px;
					position: relative;
					width: 654px;
				}
				#ic-component::before, #infocompte::after {
					content: "";
					left: -9px;
					position: absolute;
					width: 668px;
				}
				#ic-component::before {
					background: url( "//gf3.geo.gfsrv.net/cdn53/f333e15eb738b8ec692340f507e1ae.png" ) bottom left no-repeat, url( "//gf2.geo.gfsrv.net/cdnd5/66551209db14e23b3001901b996cc6.png" ) bottom right no-repeat;
					height: 28px;
					top: -3px;
				}
				#ic-component::after {
					background: url( "//gf3.geo.gfsrv.net/cdnea/0330abcdca0d125d35a0ebace4b584.png" ) bottom left no-repeat, url( "//gf1.geo.gfsrv.net/cdn9b/8003a40825bc96919c5fec01b018b8.png" ) bottom right no-repeat;
					bottom: -4px;
					height: 50px;
					z-index: -1;
				}
				#ic-title {
					background: url( "//gf1.geo.gfsrv.net/cdnfb/a4e7913209228ebaf2297429aeb87b.png" );
					color: #6f9fc8;
					font: bold 12px/27px Verdana,Arial,Helvetica,sans-serif;
					position: relative;
					text-align: center;
				}
				#ic-title::before, #ic-title::after {
					content: "";
					height: 27px;
					position: absolute;
					top: 0;
					width: 26px;
				}
				#ic-title::before {
					background: url( "//gf2.geo.gfsrv.net/cdn4a/127bd495b9325216af08a588ecc540.png" );
					left: -5px;
				}
				#ic-title::after {
					background: url( "//gf2.geo.gfsrv.net/cdn1d/80db96934a5b82ce002f839cd85a44.png" );
					right: -5px;
				}`
	}

	const MissingDataComponent = ( function(){

		function getComponentData( { player, universe } ){
			const { positions, researches, alliance } = player
			const { highscore } = universe
			const result = {
				supplies: [],
				productions: [],
				facilities: [],
				shipyard: [],
				defenses: [],
				research: true,
				alliance: true,
				highscore: true
			}
			for( const [ key, position ] of Object.entries( positions ) ){
				const { id, name, technologies, productions } = position
				if( !technologies || !( 22 in technologies.currents ) ) result.supplies.push( [ id, name ] )
				if( !technologies || !( 14 in technologies.currents ) ) result.facilities.push( [ id, name ] )
				if( !technologies || !( 202 in technologies.currents ) ) result.shipyard.push( [ id, name ] )
				if( !technologies || !( 401 in technologies.currents ) ) result.defenses.push( [ id, name ] )
				if( key.endsWith( 'P' ) && !productions ) result.productions.push( [ id, name ] )
			}
			if( !result.supplies.length ) delete result.supplies
			if( !result.productions.length ) delete result.productions
			if( !result.facilities.length ) delete result.facilities
			if( !result.shipyard.length ) delete result.shipyard
			if( !result.defenses.length ) delete result.defenses
			if( highscore ) delete result.highscore
			if( researches ) delete result.research
			if( alliance ) delete result.alliance
			return Object.keys( result ).length ? result : null
		}

		function getStyle(){
			return `${ SharedStyles.common }
					   #ic-title {
						margin-bottom: 8px;
					   }
					   #ic-component h4 {
						color: #677e98;
						font-size: 16px;
						font-weight: 700;
						margin-top: 8px;
					   }
					   #ic-component p {
						line-height: 1.5;
					   }`
		}

		function getTemplate( data ){
			let result = `<p>${ Lang.hint } :</p>`
			if( data.supplies ){
				const links = data.supplies.map( ( [ id, key ] ) => `<a href=?page=ingame&component=supplies&cp=${ id }>${ key }</a>` )
				result += `<h4>${ Lang.resources }</h4><p>${ links.join( ', ' ) }.</p>`
			}
			if( data.productions ){
				const links = data.productions.map( ( [ id, key ] ) => `<a href=?page=resourceSettings&cp=${ id }>${ key }</a>` )
				result += `<h4>${ Lang.productions }</h4><p>${ links.join( ', ' ) }.</p>`
			}
			if( data.facilities ){
				const links = data.facilities.map( ( [ id, key ] ) => `<a href=?page=ingame&component=facilities&cp=${ id }>${ key }</a>` )
				result += `<h4>${ Lang.facilities }</h4><p>${ links.join( ', ' ) }.</p>`
			}
			if( data.shipyard ){
				const links = data.shipyard.map( ( [ id, key ] ) => `<a href=?page=ingame&component=shipyard&cp=${ id }>${ key }</a>` )
				result += `<h4>${ Lang.fleets }</h4><p>${ links.join( ', ' ) }.</p>`
			}
			if( data.defenses ){
				const links = data.defenses.map( ( [ id, key ] ) => `<a href=?page=ingame&component=defenses&cp=${ id }>${ key }</a>` )
				result += `<h4>${ Lang.defenses }</h4><p>${ links.join( ', ' ) }.</p>`
			}
			if( data.highscore || data.research || data.alliance ){
				const links = []
				if( data.highscore ) links.push( `<a href=${ document.querySelector( '#bar li:nth-child(2) a' ).href }>${ Lang.highscore }</a>`)
				if( data.research ) links.push( `<a href=?page=ingame&component=research>${ Lang.research }</a>` )
				if( data.alliance ) links.push( `<a href=?page=ingame&component=alliance>${ Lang.alliance }</a>` )
				result += `<h4>${ Lang.others }</h4><p>${ links.join( ', ' ) }.</p>`
			}
			return `<div id="ic-component">
						<h3 id="ic-title">${ Lang.titles.missing }</h3>
						${ result }
					</div>`
		}

		return {
			init( data ){
				const missing = getComponentData( data )
				if( missing && currentPage === 'overview' ){
					const style = document.createElement( 'style' )
					const template = document.createElement( 'template' )
					style.innerHTML = getStyle()
					template.innerHTML = getTemplate( missing )
					document.head.appendChild( style )
					document.querySelector( '#middle' ).appendChild( template.content )
				}
				return missing ? true : false
			}
		}
	} )()

	const OverviewComponent = ( function(){

		function getComponentData( { player, universe } ){

			function getPoints( player, universe ){

				function getCounts( player, universe ){

					function getMineBuildingsPoints( { positions } ){
						let metal = 0
						let crystal = 0
						let deuterium = 0
						for( const { technologies: { currents } } of Object.values( positions ) ){
							metal += getCumulativeBuildingResources( 1, currents[ 1 ] )
							crystal += getCumulativeBuildingResources( 2, currents[ 2 ] )
							deuterium += getCumulativeBuildingResources( 3, currents[ 3 ] )
						}
						const m = Math.floor( metal * .001 )
						const c = Math.floor( crystal * .001 )
						const d = Math.floor( deuterium * .001 )
						return { metal: m, crystal: c, deuterium: d, total: Math.floor((metal + crystal + deuterium)*.001) }
					}

					function getOtherBuildingsPoints( { positions } ){
						let result = 0
						for( const { technologies: { currents } } of Object.values( positions ) ){
							for( const id of GameData.types.otherBuildings ){
								result += getCumulativeBuildingResources( id, currents[ id ] )
							}
						}
						return Math.floor( result * .001 )
					}

					function getMoonBuildingsPoints( { positions } ){
						let result = 0
						for( const [ key, { technologies: { currents } } ] of Object.entries( positions ) ){
							if( key.endsWith( 'M' ) ){
								for( const id of GameData.types.moonBuildings ){
									result += getCumulativeBuildingResources( id, currents[ id ] )
								}
							}
						}
						return Math.floor( result * .001 )
					}

					function getDefensePoints( { positions } ){
						let result = 0
						for( const { technologies: { currents } } of Object.values( positions ) ){
							for( const id of GameData.types.defenses ){
								result += getUnitsResources( id, currents[ id ] )
							}
						}
						return Math.floor( result * .001 )
					}

					function getResearchesPoints( { researches } ){
						let result = 0
						for( const id of GameData.types.researches ){
							result += getCumulativeResearchResources( id, researches.currents[ id ] )
						}
						return Math.floor( result * .001 )
					}

					function getUpgradePoints( { positions, researches } ){

						function getUpgradeBuildingsResources( positions ){
							let result = 0
							for( const { technologies: { upgraded, upgrades } } of Object.values( positions ) ){
								for( const id of GameData.types.buildings ){
									if( upgrades[ id ] ){
										result += getBuildingResources( id, upgraded[ id ] )
									}
								}
							}
							return result
						}

						function getUpgradeUnitsResources( positions ){
							let result = 0
							for( const { technologies: { upgraded, upgrades } } of Object.values( positions ) ){
								for( const id of GameData.types.units ){
									if( upgrades[ id ] ){
										result += getUnitsResources( id, upgrades[ id ] )
									}
								}
							}
							return result
						}

						function getUpgradeResearchesResources( researches ){
							let result = 0
							for( const id of GameData.types.researches ){
								if( researches.upgrades[ id ] ){
									result = getResearchResources( id, researches.upgraded[ id ] )
								}
							}
							return result
						}

						let result = getUpgradeBuildingsResources( positions )
						result += getUpgradeUnitsResources( positions )
						result += getUpgradeResearchesResources( researches )
						return Math.floor( result * .001 )
					}

					const	total = universe.highscore.rank.score,
							mineBuildings = getMineBuildingsPoints( player ),
							otherBuildings = getOtherBuildingsPoints( player ),
							researches = getResearchesPoints( player ),
							defense = getDefensePoints( player ),
							fleet = total - mineBuildings.total - otherBuildings - researches - defense,
							upgrades = getUpgradePoints( player ),
							indestructibles = total - defense - fleet - getMoonBuildingsPoints( player )
					return { total, mineBuildings, otherBuildings, researches, fleet, defense, upgrades, indestructibles }
				}

				function getPercentages( counts ){
					return {
						mineBuildings: counts.mineBuildings.total / counts.total,
						otherBuildings: counts.otherBuildings / counts.total,
						researches: counts.researches / counts.total,
						fleet: counts.fleet / counts.total,
						defense: counts.defense / counts.total,
						upgrades: counts.upgrades / counts.total,
						indestructibles: counts.indestructibles / counts.total
					}
				}

				const counts = getCounts( player, universe )
				const percentages = getPercentages( counts )
				return { counts, percentages }
			}

			function getRanks( universe, points ){
				const { score, position } = universe.highscore.rank
				let destructed = score - ( score - points.counts.indestructibles )
				let upgraded = score + points.counts.upgrades
				for( const [ key, value ] of Object.entries( universe.highscore.ranks ) ){
					if( value < destructed ){
						destructed = key - 1 - position
						break
					}
				}
				for( const [ key, value ] of Object.entries( universe.highscore.ranks ) ){
					if( value < upgraded ){
						upgraded = Math.max( 0, position - key )
						break
					}
				}
				return { destructed, upgraded }
			}

			function getChart( { percentages } ){

				function getCicle( color, percentage, angle ){
					const offset = 2 * Math.PI * 42
					const length = offset - offset * percentage
					const rotation = angle * 360 - 90
					return `<circle fill=transparent
									stroke=${ color }
									stroke-width=16
									stroke-dasharray=${ offset }
									stroke-dashoffset=${ length }
									cx=50
									cy=50
									r=42
									transform="rotate( ${ rotation } 50 50 )"/>`
				}

				const colors = [ '#eb782d', '#9c3d00', '#0077b6', '#e30613', '#16bd05' ]
				const { mineBuildings, otherBuildings, researches, fleet, defense } = percentages
				let	offset = 0
				let result = ''
				for( const [ index, percentage ] of Object.entries( [ mineBuildings, otherBuildings, researches, fleet, defense ] ) ){
					result += getCicle( colors[ index ], percentage, offset )
					offset += percentage
				}
				return result
			}

			function getProductions( player, universe ){

				function getMinesProduction( { positions } ){
					let metal = 0
					let crystal = 0
					let deuterium = 0
					for( const { productions } of Object.values( positions ) ){
						if( productions ){
							metal += productions.mines.metal
							crystal += productions.mines.crystal
							deuterium += productions.mines.deuterium
						}
					}
					return {
						hourly: { metal, crystal, deuterium },
						daily: { metal: metal * 24, crystal: crystal * 24, deuterium: deuterium * 24 },
						weekly: { metal: metal * 168, crystal: crystal * 168, deuterium: deuterium * 168 }
					}
				}

				function getTotalProduction( { positions } ){
					let metal = 0
					let crystal = 0
					let deuterium = 0
					for( const { productions } of Object.values( positions ) ){
						if( productions ){
							const { basic, mines, fusion, crawlers, plasma, classes: { alliance, player } } = productions
							metal += basic.metal + mines.metal + crawlers.metal + plasma.metal + alliance.metal + player.metal
							crystal += basic.crystal + mines.crystal + crawlers.crystal + plasma.crystal + alliance.crystal + player.crystal
							deuterium += mines.deuterium + fusion.deuterium + crawlers.deuterium + plasma.deuterium + alliance.deuterium + player.deuterium
						}
					}
					return {
						hourly: { metal, crystal, deuterium },
						daily: { metal: metal * 24, crystal: crystal * 24, deuterium: deuterium * 24 },
						weekly: { metal: metal * 168, crystal: crystal * 168, deuterium: deuterium * 168 }
					}
				}

				return {
					mines: getMinesProduction( player ),
					total: getTotalProduction( player )
				}
			}

			const points = getPoints( player, universe )
			const productions = getProductions( player, universe )
			const ranks = getRanks( universe, points )
			const chart = getChart( points )
			return { points, productions, ranks, chart }
		}

		function getStyle(){
			return `${ SharedStyles.common }
					   .ic-flex-container {
						   display: flex;
					   }
					   #ic-main-container {
						   border: 1px solid black;
					   }
					   #ic-footer-container {
						   color: grey;
						   justify-content: space-between;
						   padding: 0 1px;
					   }
					   #ic-export-container {
						   gap: 8px;
						   justify-content: start;
					   }
					   #ic-notification {
						   align-items: center;
						   display: none;
						   height: 35.333px;
						   margin-left: 8px;
					   }
					   #ic-notification::before{
						   content: '✓';
						   margin-right: 6px;
					   }
					   #ic-link-container {
						   align-items: center;
						   gap: 8px;
					   }
					   #ic-component table {
						   color: grey;
						   flex: auto;
					   }
					   #ic-component tr {
						   display: flex;
						   justify-content: space-between;
						   padding: 8px;
					   }
					   #ic-component tr:nth-child( odd ) {
						   background-color: #12171C;
					   }
					   #ic-component td > div {
						   text-align: right;
					   }
					   #ic-component td > div:not( :last-child ) {
						   margin-bottom: 8px;
					   }
					   .ic-marked-cell::before {
						   color: transparent;
						   content: '⬤';
						   margin-right: 6px;
					   }
					   #ic-mines-cell::before {
						   color: #eb782d;
					   }
					   #ic-buildings-cell::before {
						   color: #9c3d00;
					   }
					   #ic-researches-cell::before {
						   color: #0077b6;
					   }
					   #ic-fleet-cell::before {
						   color: #e30613;
					   }
					   #ic-defense-cell::before {
						   color: #16BD05;
					   }
					   #ic-chart {
						   height: 188px;
						   margin: 24px;
					   }
					   #ic-component button {
						   background: linear-gradient(to bottom, #2D3743 0%, #181E25 100%);
						   border: 1px solid black;
						   border-color: #28323E #222B34 #232A34 #222B34;
						   border-radius: 3px;
						   box-shadow: 0 1px 3px 0 black, inset 0 1px 1px 0 hsl(213, 22%, 32%);
						   color: grey;
						   padding: 8px 12px;
					   }
					   #ic-component button::before {
						   content: '⭳';
						   font-weight: bolder;
						   margin-right: 6px;
					   }
					   #ic-component button:hover, #infocompte input:hover {
						   cursor: pointer;
					   }
					   #ic-component label {
						   align-items: center;
						   display: flex;
					   }
					   #ic-component input {
						   margin-right: 6px;
					   }`
		}

		function getTemplate( data ){
			const { points, ranks, chart } = data.component
			const { counts, percentages } = points
			return `<div id="ic-component">
						<h3 id="ic-title">${ Lang.titles.overview }</h3>
						<div id= "ic-main-container" class="ic-flex-container">
							<table>
								<tr>
									<td id="ic-mines-cell" class="ic-marked-cell">${ Lang.mines }</td>
									<td><span>${ getAsLocaleString( counts.mineBuildings.total ) }</span> · <span>${ getAsPercentage( percentages.mineBuildings ) }</span></td>
								</tr>
								<tr>
									<td id="ic-buildings-cell" class="ic-marked-cell">${ Lang.otherBuildings }</td>
									<td><span>${ getAsLocaleString( counts.otherBuildings ) }</span> · <span>${ getAsPercentage( percentages.otherBuildings ) }</span></td>
								</tr>
								<tr>
									<td id="ic-researches-cell" class="ic-marked-cell">${ Lang.researches }</td>
									<td><span>${ getAsLocaleString( counts.researches ) }</span> · <span>${ getAsPercentage( percentages.researches ) }</span></td>
								</tr>
								<tr>
									<td id="ic-fleet-cell" class="ic-marked-cell">${ Lang.fleet }</td>
									<td><span>${ getAsLocaleString( counts.fleet ) }</span> · <span>${ getAsPercentage( percentages.fleet ) }</span></td>
								</tr>
								<tr>
									<td id="ic-defense-cell" class="ic-marked-cell">${ Lang.defense }</td>
									<td><span>${ getAsLocaleString( counts.defense ) }</span> · <span>${ getAsPercentage( percentages.defense ) }</span></td>
								</tr>
								<tr>
									<td class="ic-marked-cell">Indestructibles</td>
									<td>
										<div><span>${ getAsLocaleString( counts.indestructibles ) }</span> · <span>${ getAsPercentage( percentages.indestructibles ) }</span></div>
										<div>- ${ ranks.destructed } ${ Lang.destroyed }</div>
									</td>
								</tr>
								<tr>
									<td class="ic-marked-cell">${ Lang.upgrades }</td>
									<td>
										<div><span>${ getAsLocaleString( counts.upgrades ) }</span> · <span>${ getAsPercentage( percentages.upgrades ) }</span></div>
										<div>+ ${ ranks.upgraded } ${ Lang.upgraded }</div>
									</td>
								</tr>
							</table>
							<svg id="ic-chart" viewBox="0 0 100 100">${ chart }</svg>
						</div>
						<div id="ic-footer-container" class="ic-flex-container">
							<div id="ic-export-container" class="ic-flex-container">
								<button id="ic-empire-export-button">Empire</button>
								<button id="ic-production-export-button">Production</button>
								<label><input type="checkbox" checked>BBCode</label>
							</div>
							<div id="ic-notification">${ Lang.notification }</div>
							<div id="ic-link-container" class="ic-flex-container">
								<a href="https://board.fr.ogame.gameforge.com/index.php?thread/746302-infocompte/" target="_blank">InfoCompte ${ GM_info.script.version }</a>
								<span>·</span>
								<a href="https://ko-fi.com/A0A4DEZRA" target="_blank">
									<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="24" height="24">
										<path fill="#fff" d="M43.8 17.3h3.1a11 11 0 0 1 11 11v.7a11 11 0 0 1-11 11h-3.1v3.3a5 5 0 0 1-5.1 5.1h-24a5 5 0 0 1-5.1-5V19.7a2.5 2.5 0 0 1 2.5-2.5Zm0 5.9v11h2.8a5.5 5.5 0 0 0 0-11Z"/>
										<path fill="#f15f5e" d="M26 27.7c.8-2.3 2.6-3.5 5.6-3.5 4.4 0 6 5.5 3.7 9.1q-2.3 3.6-9.3 9-7-5.4-9.3-9c-2.3-3.6-.7-9 3.7-9q4.4 0 5.6 3.4Z"/>
									</svg>
								</a>
							</div>
						</div>
					</div>`
		}

		const Listeners = ( function(){

			async function animate(){
				const container = document.querySelector( '#ic-export-container' )
				const notification = document.querySelector( '#ic-notification' )
				await container.animate( { opacity: 0 }, 500 ).finished
				container.style.display = 'none'
				notification.style.display = 'flex'
				await notification.animate( { opacity: [ 0, 1 ] }, 500 ).finished
				await notification.animate( { opacity: [ 1, 0 ] }, { delay: 1500, duration: 500 } ).finished
				notification.style.display = 'none'
				container.style.display = 'flex'
				container.animate( { opacity: [ 0, 1 ] }, 500 ).finished
			}

			function formate( text ){
				if( !document.querySelector( '#ic-export-container input' ).checked ){
					text = text.replace( /\[\/?[^\]]*\]/g, '' )
				}
				return text
			}

			return {
				getEmpire(){
					const result = formate( Exports.getEmpire( data ) )
					navigator.clipboard.writeText( result )
					animate()
				},
				getProduction(){
					const result = formate( Exports.getProduction( data ) )
					navigator.clipboard.writeText( result )
					animate()
				}
			}
		} )()

		return {
			init( data ){
				const style = document.createElement( 'style' )
				const template = document.createElement( 'template' )
				data.component = getComponentData( data )
				style.innerHTML = getStyle()
				template.innerHTML = getTemplate( data )
				document.head.appendChild( style )
				document.querySelector( '#middle' ).appendChild( template.content )
				document.querySelector( '#ic-empire-export-button' ).addEventListener( 'click', Listeners.getEmpire )
				document.querySelector( '#ic-production-export-button' ).addEventListener( 'click', Listeners.getProduction )
			}
		}
	} )()

	const Exports = ( function(){

		function getDate(){
			const date = new Date()
			return [ date.toLocaleDateString( 'fr-FR' ), date.toLocaleTimeString( 'de-DE', { timeStyle: 'short'} ) ]
		}

		function getTechnologyResume( array ){
			const list = array.map( element => `[color=#9dc2e1]${ getAsLocaleString( element ) }[/color]` ).join( ', ' )
			const average = array.reduce( ( previous, current ) => previous + getAsNumber( current ), 0 ) / array.length
			return `${ list } · ø ${ getAsLocaleString( Math.round( average * 10 ) / 10 ) }`
		}

		return {
			getEmpire( { player, universe, component } ){
				const { counts, percentages } = component.points
				const { mines, total } = component.productions
				const [ date, time ] = getDate()
				return `[size=18]${ Lang.titles.empire( player.name, universe.name, universe.lang ) }[/size]
						[size=8]${ Lang.titles.generated( date, time ) } [url=https://board.fr.ogame.gameforge.com/index.php?thread/746302-infocompte/]InfoCompte ${ GM_info.script.version }[/url][/size]

						${ Lang.classes.player[ player.class ] } & ${ Lang.classes.alliance[ player.alliance.class ] }

						[color=#6f9fc8]POINTS[/color]
						${ Lang.mines } : [color=#9dc2e1]${ getAsLocaleString( counts.mineBuildings.total ) }[/color] · ${ getAsPercentage( percentages.mineBuildings ) }
						${ Lang.otherBuildings } : [color=#9dc2e1]${ getAsLocaleString( counts.otherBuildings ) }[/color] · ${ getAsPercentage( percentages.otherBuildings ) }
						${ Lang.researches } : [color=#9dc2e1]${ getAsLocaleString( counts.researches ) }[/color] · ${ getAsPercentage( percentages.researches ) }
						${ Lang.fleet } : [color=#9dc2e1]${ getAsLocaleString( counts.fleet ) }[/color] · ${ getAsPercentage( percentages.fleet ) }
						${ Lang.defense } : [color=#9dc2e1]${ getAsLocaleString( counts.defense ) }[/color] · ${ getAsPercentage( percentages.defense ) }
						Total : [color=#9dc2e1]${ getAsLocaleString( counts.total ) }[/color] · ${ getAsPercentage( percentages.indestructibles ) } indestructible

						[color=#6f9fc8]${ Lang.production.toUpperCase() }[/color]
						${ Lang.metal } : [color=#9dc2e1]${ getAsLocaleString( total.daily.metal ) }[/color]
						${ Lang.crystal } : [color=#9dc2e1]${ getAsLocaleString( total.daily.crystal ) }[/color]
						${ Lang.deuterium } : [color=#9dc2e1]${ getAsLocaleString( total.daily.deuterium ) }[/color]

						${ ( function(){
							const planets = { counts: [], used: [] }
							const moons = { counts: [], used: [] }
							for( const [ key, { fields } ] of Object.entries( player.positions ) ){
								const type = key.endsWith( 'P' ) ? planets : moons
								type.counts.push( fields.count )
								type.used.push( fields.used )
							}
							let result = `[color=#6f9fc8]${ Lang.planetFields.toUpperCase() }[/color]
										  Total : ${ getTechnologyResume( planets.counts ) }
										  ${ Lang.used } : ${ getTechnologyResume( planets.used ) }\n`
							if( moons.counts.length ){
								result += `\n[color=#6f9fc8]${ Lang.moonFields.toUpperCase() }[/color]
										   Total : ${ getTechnologyResume( moons.counts ) }
										   ${ Lang.used } : ${ getTechnologyResume( moons.used ) }\n`
							}
							return result
						} )() }
						${ ( function(){
							const results = {}
							for( const [ key, { technologies } ] of Object.entries( player.positions ) ){
								if( key.endsWith( 'P' ) ){
									const { currents, upgraded, upgrades } = technologies
									for( const key of GameData.types.planetBuildings ){
										const value = upgrades[ key ] ? `[u]${ upgraded[ key ] }[/u]` : currents[ key ]
										results[ key ] ||= []
										results[ key ].push( value )
									}
								}
							}
							let result = `[color=#6f9fc8]${ Lang.planetBuildings.toUpperCase() }[/color]\n`
							for( const [ key, value ] of Object.entries( results ) ){
								result += `${ Lang.technologies[ key ] } : ${ getTechnologyResume( value ) }\n`
							}
							return result
						} )() } ${ ( function(){
							const results = {}
							for( const [ key, { technologies } ] of Object.entries( player.positions ) ){
								if( key.endsWith( 'M' ) ){
									const { currents, upgraded, upgrades } = technologies
									for( const key of GameData.types.moonBuildings ){
										const value = upgrades[ key ] ? `[u]${ upgraded[ key ] }[/u]` : currents[ key ]
										results[ key ] ||= []
										results[ key ].push( value )
									}
								}
							}
							let result = ''
							if( Object.keys( results ).length ){
								result = `\n[color=#6f9fc8]${ Lang.moonBuildings.toUpperCase() }[/color]\n`
								for( const [ key, value ] of Object.entries( results ) ){
									result += `${ Lang.technologies[ key ] } : ${ getTechnologyResume( value ) }\n`
								}
							}
							return result
						} )() }
						${ ( function(){
							const { currents, upgraded, upgrades } = player.researches
							let result = `[color=#6f9fc8]${ Lang.researches.toUpperCase() }[/color]\n`
							for( const key of Object.keys( currents ) ){
								const value = upgrades[ key ] ? `[u]${ upgraded[ key ] }[/u]`: currents[ key ]
								result += `${ Lang.technologies[ key ] } : [color=#9dc2e1]${ value }[/color]\n`
							}
							return result
						} )() }
						${ ( function(){
							const results = {}
							for( const [ key, { technologies } ] of Object.entries( player.positions ) ){
								const { currents, upgraded, upgrades } = technologies
								for( const key of GameData.types.ships ){
									const value = upgrades[ key ] ? `[u]${ upgraded[ key ] }[/u]` : currents[ key ]
									results[ key ] ||= 0
									results[ key ] += value
								}
							}
							let result = `[color=#6f9fc8]${ Lang.fleet.toUpperCase() }[/color]\n`
							for( const [ key, value ] of Object.entries( results ) ){
								result += `${ Lang.technologies[ key ] } : [color=#9dc2e1]${ getAsLocaleString( value ) }[/color]\n`
							}
							return result
						} )() }
						${ ( function(){
							const results = {}
							for( const [ key, { technologies } ] of Object.entries( player.positions ) ){
								if( key.endsWith( 'P' ) ){
									const { currents, upgraded, upgrades } = technologies
									for( const key of GameData.types.defenses ){
										const value = upgrades[ key ] ?`[u]${ upgraded[ key ] }[/u]` : currents[ key ]
										results[ key ] ||= []
										results[ key ].push( value )
									}
								}
							}
							let result = `[color=#6f9fc8]${ Lang.planetDefenses.toUpperCase() }[/color]\n`
							for( const [ key, value ] of Object.entries( results ) ){
								result += `${ Lang.technologies[ key ] } : ${ getTechnologyResume( value ) }\n`
							}
							return result
						} )() }
						${ ( function(){
							const results = {}
							for( const [ key, { technologies } ] of Object.entries( player.positions ) ){
								if( key.endsWith( 'M' ) ){
									const { currents, upgraded, upgrades } = technologies
									for( const key of GameData.types.defenses ){
										const value = upgrades[ key ] ? `[u]${ upgraded[ key ] }[/u]` : currents[ key ]
										results[ key ] ||= []
										results[ key ].push( value )
									}
								}
							}
							let result = ''
							if( Object.entries( results ).length ){
								result = `[color=#6f9fc8]${ Lang.moonDefenses.toUpperCase() }[/color]\n`
								for( const [ key, value ] of Object.entries( results ) ){
									result += `${ Lang.technologies[ key ] } : ${ getTechnologyResume( value ) }\n`
								}
							}
							return result
						} )() }`
			},
			getProduction( { player, universe, component } ){
				const { counts } = component.points
				const { mines, total } = component.productions
				const { positions, researches } = player
				const [ date, time ] = getDate()
				return `[size=14]${ Lang.titles.production( player, universe ) }[/size]
						[size=8]${ Lang.titles.generated( date, time ) } [url=https://board.fr.ogame.gameforge.com/index.php?thread/746302-infocompte/]InfoCompte ${ GM_info.script.version }[/url][/size]

						${ Lang.classes.player[ player.class ] } & ${ Lang.classes.alliance[ player.alliance.class ] }

						[color=#6f9fc8]POSITIONS[/color]
						${ ( function(){
							let index = 0,
								result = ''
							for( const [ key, { technologies, temperatures } ] of Object.entries( positions ) ){
								if( key.endsWith( 'P' ) ){
									const { currents, upgraded, upgrades } = technologies
									result += `${ getAsPaddedString( ++index, 2, '0' ) }. `
									result += `[color=#9a9ac1]${ upgrades[ 1 ] ? `[u]${ upgraded[ 1 ] }[/u]` : currents[ 1 ] }[/color], `
									result += `[color=#8dceec]${ upgrades[ 2 ] ? `[u]${ upgraded[ 2 ] }[/u]` : currents[ 2 ] }[/color], `
									result += `[color=#6cc6a3]${ upgrades[ 3 ] ? `[u]${ upgraded[ 3 ] }[/u]` : currents[ 3 ] }[/color], `
									result += `[color=#bb875b]${ upgrades[ 217 ]? `[u]${ getAsLocaleString( upgraded[ 217 ] ) }[/u]` : getAsLocaleString( currents[ 217 ] ) }[/color], `
									result += `[color=#f5bbb4]${ temperatures.max } °C[/color]\n`
								}
							}
							return result
						} )() }
						[color=#6f9fc8]${ Lang.researches.toUpperCase() }[/color]
						${ Lang.technologies[ 122 ] } : ${ researches.upgrades[ 122 ] ? `[u]${ researches.upgraded[ 122 ] }[/u]` : researches.currents[ 122 ] }

						[color=#6f9fc8]${ Lang.noAmProduction.toUpperCase() }[/color]
						${ Lang.hourly } : [color=#9a9ac1]${ getAsLocaleString( total.hourly.metal ) }[/color], [color=#8dceec]${ getAsLocaleString( total.hourly.crystal ) }[/color], [color=#6cc6a3]${ getAsLocaleString( total.hourly.deuterium ) }[/color]
						${ Lang.daily } : [color=#9a9ac1]${ getAsLocaleString( total.daily.metal ) }[/color], [color=#8dceec]${ getAsLocaleString( total.daily.crystal ) }[/color], [color=#6cc6a3]${ getAsLocaleString( total.daily.deuterium ) }[/color]
						${ Lang.weekly } : [color=#9a9ac1]${ getAsLocaleString( total.weekly.metal ) }[/color], [color=#8dceec]${ getAsLocaleString( total.weekly.crystal ) }[/color], [color=#6cc6a3]${ getAsLocaleString( total.weekly.deuterium ) }[/color]

						[color=#6f9fc8]${ Lang.minesProduction.toUpperCase() }[/color]
						${ Lang.hourly } : [color=#9a9ac1]${ getAsLocaleString( mines.hourly.metal ) }[/color], [color=#8dceec]${ getAsLocaleString( mines.hourly.crystal ) }[/color], [color=#6cc6a3]${ getAsLocaleString( mines.hourly.deuterium ) }[/color]
						${ Lang.daily } : [color=#9a9ac1]${ getAsLocaleString( mines.daily.metal ) }[/color], [color=#8dceec]${ getAsLocaleString( mines.daily.crystal ) }[/color], [color=#6cc6a3]${ getAsLocaleString( mines.daily.deuterium ) }[/color]
						${ Lang.weekly } : [color=#9a9ac1]${ getAsLocaleString( mines.weekly.metal ) }[/color], [color=#8dceec]${ getAsLocaleString( mines.weekly.crystal ) }[/color], [color=#6cc6a3]${ getAsLocaleString( mines.weekly.deuterium ) }[/color]

						[color=#6f9fc8]${ Lang.minesPoints.toUpperCase() }[/color]
						${ Lang.metal } : [color=#9a9ac1]${ getAsLocaleString( counts.mineBuildings.metal ) }[/color]
						${ Lang.crystal } : [color=#8dceec]${ getAsLocaleString( counts.mineBuildings.crystal ) }[/color]
						${ Lang.deuterium } : [color=#6cc6a3]${ getAsLocaleString( counts.mineBuildings.deuterium ) }[/color]
						Total : ${ getAsLocaleString( counts.mineBuildings.total ) }`
			}
		}
	} )()

	return {
		init( data ){
			if( !MissingDataComponent.init( data ) ){
				switch( currentPage ){
					case 'overview':
						OverviewComponent.init( data )
						break
				}
			}
		}
	}
} )()

function getReducedData( storage, page ){
	const result = Utils.reduce( storage, page )
	if( currentPage !== 'empire' && storage.player?.positions ){
		for( const key of Object.keys( storage.player.positions ) ){
			if( !page.player.positions[ key ] ){
				delete result.player.positions[ key ]
			}
		}
	}
	return result
}

const data = getReducedData( StorageData.get(), await PageData.get() )
PageComponent.init( data )
StorageData.set( data )