diff --git a/app.js b/app.js
index 538d6d8..7cd2280 100644
--- a/app.js
+++ b/app.js
@@ -1,5 +1,6 @@
 import * as THREE from 'three'
 import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
+import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
 import * as FPS from 'three/addons/libs/stats.module.js';
 
 let P = (x, y, z = 0) => new THREE.Vector3(x, y, z)
@@ -15,7 +16,7 @@ HTMLElement.prototype.addNewChild = function (tag, properties) {
 }
 
 
-/* Contants */
+/* Constants */
 
 const ROWS = 24
 const SKYLINE = 20
@@ -251,7 +252,7 @@ class MinoMaterial extends THREE.MeshBasicMaterial {
             color: color,
             reflectivity: 0.95,
             envMap: minoRenderTarget.texture,
-            roughness: 0,
+            roughness: 0.1,
             metalness: 0.25
         })
     }
@@ -324,7 +325,7 @@ class Tetromino extends THREE.Group {
 
     move(translation, testFacing) {
         if (this.canMove(translation, testFacing)) {
-            scheduler.clearTimeout(lockDown)
+            scheduler.clearTimeout(game.lockDown)
             this.position.add(translation)
             if (!testFacing) {
                 this.rotatedLast = false
@@ -336,13 +337,13 @@ class Tetromino extends THREE.Group {
             } else {
                 this.locked = true
                 scene.remove(ghost)
-                scheduler.setTimeout(lockDown, stats.lockDelay)
+                scheduler.setTimeout(game.lockDown, stats.lockDelay)
             }
             return true
         } else if (translation == TRANSLATION.DOWN) {
             this.locked = true
-            if (!scheduler.timeoutTasks.has(lockDown))
-                scheduler.setTimeout(lockDown, stats.lockDelay)
+            if (!scheduler.timeoutTasks.has(game.lockDown))
+                scheduler.setTimeout(game.lockDown, stats.lockDelay)
         }
     }
 
@@ -488,95 +489,124 @@ Ghost.prototype.minoesPosition = [
     [P(0, 0, 0), P(0, 0, 0), P(0, 0, 0), P(0, 0, 0)],
 ]
 
-
-class Settings {
-    constructor() {
-        this.form = settingsForm
-        this.load()
-        this.modal = new bootstrap.Modal('#settingsModal')
-        settingsModal.addEventListener('shown.bs.modal', () => {
-            resumeButton.focus()
-        })
-    }
-
-    load() {
-        for (let input of settingsForm.elements) {
-            if (input.name) {
-                if (localStorage[input.name]) input.value = localStorage[input.name]
-            }
-        }
-    }
-
-    save() {
-        for (let element of settingsForm.elements) {
-            if (element.name) {
-                localStorage[element.name] = element.value
-            }
-        }
-    }
-
-    init() {
-        this.form.onsubmit = newGame
-        levelInput.name = "startLevel"
-        levelInput.disabled = false
-        //titleHeader.innerHTML = "teTra"
-        resumeButton.innerHTML = "Jouer"
-    }
-
-    show() {
-        resumeButton.disabled = false
-        settings.form.classList.remove('was-validated')
-        settings.modal.show()
-        settings.form.reportValidity()
-    }
-
-    getInputs() {
-        for (let input of this.form.querySelectorAll("input[type='text']")) {
-            this[input.name] = KEY_NAMES[input.value] || input.value
-        }
-        for (let input of this.form.querySelectorAll("input[type='number'], input[type='range']")) {
-            this[input.name] = input.valueAsNumber
-        }
-        for (let input of this.form.querySelectorAll("input[type='checkbox']")) {
-            this[input.name] = input.checked == true
-        }
-
-        this.keyBind = {}
-        for (let actionName in playerActions) {
-            this.keyBind[settings[actionName]] = playerActions[actionName]
-        }
-    }
-}
-
-window.changeKey = function (input) {
-    let prevValue = input.value
-    input.value = ""
+function changeKey() {
+    let controller = this
+    let input = controller.domElement.getElementsByTagName("input")[0]
+    input.select()
     input.onkeydown = function (event) {
-        event.preventDefault()
-        input.value = KEY_NAMES[event.key] || event.key
+        controller.setValue(KEY_NAMES[event.key] || event.key)
         input.blur()
     }
     input.onblur = function (event) {
-        if (input.value == "") input.value = prevValue
         input.onkeydown = null
         input.onblur = null
+        settings.bindKeys()
+    }
+}
+
+
+class Settings {
+    constructor(gui) {
+        this.startLevel = 1
+
+        this.moveLeft  = "←"
+        this.moveRight = "→"
+        this.rotateCCW = "w"
+        this.rotateCW  = "↑"
+        this.softDrop  = "↓"
+        this.hardDrop  = "Espace"
+        this.hold      = "c"
+        this.pause     = "Échap."
+
+        this.arrDelay = 50
+        this.dasDelay = 300
+        
+        this.musicVolume = 50
+        this.sfxVolume   = 50
+
+        this.gui = gui.addFolder("Options").close()
+
+        this.gui.add(this, "startLevel").name("Niveau initial").min(1).max(15).step(1)
+
+        this.gui.keyFolder = this.gui.addFolder("Commandes").open()
+        let moveLeftController = this.gui.keyFolder.add(this,"moveLeft").name('Gauche')
+        moveLeftController.domElement.onclick = changeKey.bind(moveLeftController)
+        let moveRightController = this.gui.keyFolder.add(this,"moveRight").name('Droite')
+        moveRightController.domElement.onclick = changeKey.bind(moveRightController)
+        let rotateCWController = this.gui.keyFolder.add(this,"rotateCW").name('Rotation horaire')
+        rotateCWController.domElement.onclick = changeKey.bind(rotateCWController)
+        let rotateCCWController = this.gui.keyFolder.add(this,"rotateCCW").name('anti-horaire')
+        rotateCCWController.domElement.onclick = changeKey.bind(rotateCCWController)
+        let softDropController = this.gui.keyFolder.add(this,"softDrop").name('Chute lente')
+        softDropController.domElement.onclick = changeKey.bind(softDropController)
+        let hardDropController = this.gui.keyFolder.add(this,"hardDrop").name('Chute rapide')
+        hardDropController.domElement.onclick = changeKey.bind(hardDropController)
+        let holdController = this.gui.keyFolder.add(this,"hold").name('Garder')
+        holdController.domElement.onclick = changeKey.bind(holdController)
+        let pauseController = this.gui.keyFolder.add(this,"pause").name('Pause')
+        pauseController.domElement.onclick = changeKey.bind(pauseController)
+
+        this.gui.delayFolder = this.gui.addFolder("Répétition automatique").open()
+        this.gui.delayFolder.add(this,"arrDelay").name("ARR (ms)").min(2).max(200).step(1);
+        this.gui.delayFolder.add(this,"dasDelay").name("DAS (ms)").min(100).max(500).step(5);
+
+        this.gui.volumeFolder = this.gui.addFolder("Volume").open()
+        this.gui.volumeFolder.add(this,"musicVolume").name("Musique").min(0).max(100).step(1).onChange((volume) => {
+            music.setVolume(volume/100)
+        })
+        this.gui.volumeFolder.add(this,"sfxVolume").name("SFX").min(0).max(100).step(1).onChange((volume) => {
+            lineClearSound.setVolume(volume/100)
+            tetrisSound.setVolume(volume/100)
+            hardDropSound.setVolume(volume/100)
+        })
+
+        this.load()
+        this.bindKeys()
+    }
+
+    bindKeys() {
+        this.keyBind = {}
+        for (let actionName in playerActions) {
+            this.keyBind[KEY_NAMES[this[actionName]] || this[actionName]] = playerActions[actionName]
+        }
+    }
+
+    load() {
+        if (localStorage["teTraSettings"]) this.gui.load(JSON.parse(localStorage["teTraSettings"]))
+    }
+
+    save() {
+        localStorage["teTraSettings"] = JSON.stringify(this.gui.save())
     }
 }
 
 
 class Stats {
-    constructor() {
-        this.modal = new bootstrap.Modal('#statsModal')
-        this.load()
-    }
+    constructor(parentGui) {
+        this.clock = new THREE.Clock(false)
+        this.clock.timeFormat = new Intl.DateTimeFormat("fr-FR", {
+            hour: "numeric",
+            minute: "2-digit",
+            second: "2-digit",
+            timeZone: "UTC"
+        })
+        this.elapsedTime = 0
 
-    load() {
-        this.highScore = Number(localStorage["highScore"]) || 0
+        this.init()
+
+        this.gui = parentGui.addFolder("Stats")
+        this.gui.add(this, "level").name("Niveau").disable().listen()
+        this.gui.add(this, "goal").name("Objectif").disable().listen()
+        this.gui.add(this, "score").name("Score").disable().listen()
+        this.gui.add(this, "highScore").name("Meilleur score").disable().listen()
+        this.gui.timeController = this.gui.add(this, "time").name("Temps").disable().listen()
     }
 
     init() {
-        this.score = 0
+        this._level = 0
+        this._score = 0
         this.goal = 0
+        this.highScore = Number(localStorage["teTraHighScore"]) || 0
         this.combo = 0
         this.b2b = 0
         this.startTime = new Date()
@@ -593,7 +623,6 @@ class Stats {
         if (score > this.highScore) {
             this.highScore = score
         }
-        scoreDiv.innerText = score.toLocaleString()
     }
 
     get score() {
@@ -603,29 +632,15 @@ class Stats {
     set level(level) {
         this._level = level
         this.goal += level * 5
-        if (level <= 20) {
-            this.fallPeriod = 1000 * Math.pow(0.8 - ((level - 1) * 0.007), level - 1)
-        }
-        if (level > 15)
-            this.lockDelay = 500 * Math.pow(0.9, level - 15)
-        levelInput.value = level
+        if (level <= 20) this.fallPeriod = 1000 * Math.pow(0.8 - ((level - 1) * 0.007), level - 1)
+        if (level > 15) this.lockDelay = 500 * Math.pow(0.9, level - 15)
         messagesSpan.addNewChild("div", { className: "show-level-animation", innerHTML: `<h1>NIVEAU<br/>${this.level}</h1>` })
-        levelDiv.innerText = level
     }
 
     get level() {
         return this._level
     }
 
-    set goal(goal) {
-        this._goal = goal
-        goalDiv.innerText = goal
-    }
-
-    get goal() {
-        return this._goal
-    }
-
     set combo(combo) {
         this._combo = combo
         if (combo > this.maxCombo) this.maxCombo = combo
@@ -644,12 +659,8 @@ class Stats {
         return this._b2b
     }
 
-    set time(time) {
-        this.startTime = new Date() - time
-    }
-
     get time() {
-        return new Date() - this.startTime
+        return this.clock.timeFormat.format(this.clock.elapsedTime * 1000)
     }
 
     lockDown(nbClearedLines, tSpin) {
@@ -735,53 +746,23 @@ class Stats {
         this.goal -= awardedLineClears
         if (this.goal <= 0) this.level++
     }
-
-    show() {
-        let time = stats.time
-        statsModalScoreCell.innerText = this.score.toLocaleString()
-        statsModalHighScoreCell.innerText = this.highScore.toLocaleString()
-        statsModalLevelCell.innerText = this.level
-        statsModalTimeCell.innerText = this.timeFormat.format(time)
-        statsModaltotalClearedLines.innerText = this.totalClearedLines
-        statsModaltotalClearedLinesPM.innerText = (stats.totalClearedLines * 60000 / time).toFixed(2)
-        statsModalNbQuatris.innerText = this.nbQuatris
-        statsModalNbTSpin.innerText = this.nbTSpin
-        statsModalMaxCombo.innerText = this.maxCombo
-        statsModalMaxB2B.innerText = this.maxB2B
-        this.modal.show()
-    }
-
-    save() {
-        localStorage["highScore"] = this.highScore
-    }
-}
-Stats.prototype.timeFormat = new Intl.DateTimeFormat("fr-FR", {
-    hour: "numeric",
-    minute: "2-digit",
-    second: "2-digit",
-    timeZone: "UTC"
-})
-
-function tick() {
-    timeDiv.innerText = stats.timeFormat.format(stats.time)
 }
 
 
 /* Scene */
 
-const manager = new THREE.LoadingManager()
-manager.onStart = function (url, itemsLoaded, itemsTotal) {
-    loadingBar.style.setProperty("width", '0%')
+const loadManager = new THREE.LoadingManager()
+loadManager.onStart = function (url, itemsLoaded, itemsTotal) {
+    loadingPercent.innerText = "0%"
 }
-manager.onProgress = function (url, itemsLoaded, itemsTotal) {
-    loadingBar.style.setProperty("width", 100 * itemsLoaded / itemsTotal + '%')
+loadManager.onProgress = function (url, itemsLoaded, itemsTotal) {
+    loadingPercent.innerText = 100 * itemsLoaded / itemsTotal + '%'
 }
-manager.onLoad = function () {
-    restart()
-    messagesSpan.innerHTML = ""
+loadManager.onLoad = function () {
+    loaddingCircle.remove()
     renderer.setAnimationLoop(animate)
 }
-manager.onError = function (url) {
+loadManager.onError = function (url) {
     messagesSpan.innerHTML = 'Erreur de chargement'
 }
 
@@ -835,7 +816,7 @@ const colorFullOpacity = 0.2
 const commonCylinderGeometry = new THREE.CylinderGeometry(25, 25, 500, 12, 1, true)
 
 // dark space full of stars - background cylinder
-const darkCylinderTexture = new THREE.TextureLoader(manager).load("images/dark.jpg")
+const darkCylinderTexture = new THREE.TextureLoader(loadManager).load("images/dark.jpg")
 darkCylinderTexture.wrapS = THREE.RepeatWrapping
 darkCylinderTexture.wrapT = THREE.MirroredRepeatWrapping
 darkCylinderTexture.repeat.set(1, 1)
@@ -853,7 +834,7 @@ darkCylinder.position.set(5, 10, -10)
 scene.add(darkCylinder)
 
 // colourfull space full of nebulas - main universe cylinder
-const colorFullCylinderTexture = new THREE.TextureLoader(manager).load("images/colorfull.jpg")
+const colorFullCylinderTexture = new THREE.TextureLoader(loadManager).load("images/colorfull.jpg")
 colorFullCylinderTexture.wrapS = THREE.RepeatWrapping
 colorFullCylinderTexture.wrapT = THREE.MirroredRepeatWrapping
 colorFullCylinderTexture.repeat.set(1, 1)
@@ -925,19 +906,6 @@ const hardDroppedMatrix = mixer.clipAction(clip)
 hardDroppedMatrix.loop = THREE.LoopOnce
 hardDroppedMatrix.setDuration(0.2)
 
-
-const lineClearSound = new Audio("audio/line-clear.wav")
-const tetrisSound    = new Audio("audio/tetris.wav")
-const hardDropSound  = new Audio("audio/hard-drop.wav")
-const music          = new Audio("https://iterations.org/files/music/remixes/Tetris_CheDDer_OC_ReMix.mp3")
-      music.loop     = true
-
-window.addEventListener("resize", () => {
-    renderer.setSize(window.innerWidth, window.innerHeight)
-    camera.aspect = window.innerWidth / window.innerHeight
-    camera.updateProjectionMatrix()
-})
-
 let clock = new THREE.Clock()
 
 function animate() {
@@ -961,9 +929,14 @@ function animate() {
     minoCamera.update(renderer, scene)
 
     if (showFPS) fps.update();
-
 }
 
+window.addEventListener("resize", () => {
+    renderer.setSize(window.innerWidth, window.innerHeight)
+    camera.aspect = window.innerWidth / window.innerHeight
+    camera.updateProjectionMatrix()
+})
+
 
 /* Game logic */
 
@@ -971,113 +944,127 @@ messagesSpan.onanimationend = function (event) {
     event.target.remove()
 }
 
-let scheduler = new Scheduler()
-let settings = new Settings()
-let stats = new Stats()
-let playing = false
-//let favicon = document.querySelector("link[rel~='icon']")
+let piece = null
 
-window.restart = function () {
-    stats.modal.hide()
-    stats.init()
-    settings.init()
-    holdQueue.remove(holdQueue.piece)
-    holdQueue.piece = null
-    if (nextQueue.pieces) nextQueue.pieces.forEach(piece => nextQueue.remove(piece))
-    Array.from(matrix.children).forEach(mino => matrix.remove(mino))
-    matrix.init()
-    scene.remove(piece)
-    piece = null
-    scene.remove(ghost)
-    music.currentTime = 0
-    pauseSettings()
-}
+let game = {
+    init: function() {
+        this.playing = false
 
-function pauseSettings() {
-    stats.pauseTime = stats.time
+        stats.init()
+        
+        holdQueue.remove(holdQueue.piece)
+        holdQueue.piece = null
+        if (nextQueue.pieces) nextQueue.pieces.forEach(piece => nextQueue.remove(piece))
+        Array.from(matrix.children).forEach(mino => matrix.remove(mino))
+        matrix.init()
+        scene.remove(piece)
+        piece = null
+        scene.remove(ghost)
+        music.currentTime = 0
+    },
 
-    scheduler.clearInterval(fall)
-    scheduler.clearTimeout(lockDown)
-    scheduler.clearTimeout(repeat)
-    scheduler.clearInterval(autorepeat)
-    scheduler.clearInterval(tick)
+    start: function() {
+        startButton.hide()
 
-    music.pause()
-    document.onkeydown = null
+        this.playing = true
+        stats.clock.start()
 
-    settings.show()
-}
+        onblur = this.pause
 
-function newGame(event) {
-    if (!settings.form.checkValidity()) {
-        event.preventDefault()
-        event.stopPropagation()
-        settings.form.reportValidity()
-        settings.form.classList.add('was-validated')
-    } else {
-        levelInput.name = "level"
-        levelInput.disabled = true
-        //titleHeader.innerHTML = "PAUSE"
-        resumeButton.innerHTML = "Reprendre"
-        event.target.onsubmit = resume
         nextQueue.init()
-        stats.level = levelInput.valueAsNumber
-        localStorage["startLevel"] = levelInput.value
-        playing = true
-        onblur = pauseSettings
-        resume(event)
-    }
-}
 
-function resume(event) {
-    event.preventDefault()
-    event.stopPropagation()
-
-    settings.form.reportValidity()
-    settings.form.classList.add('was-validated')
-
-    if (settings.form.checkValidity()) {
-        settings.modal.hide()
-        settings.getInputs()
-        renderer.domElement.focus()
+        stats.level = settings.startLevel
+        this.resume()
+    },
 
+    resume: function(event) {
         document.onkeydown = onkeydown
         document.onkeyup = onkeyup
 
-        stats.time = stats.pauseTime
+        stats.clock.start()
+        stats.clock.elapsedTime = stats.elapsedTime
+        music.play()
 
-        lineClearSound.volume = settings.sfxVolume
-        tetrisSound.volume    = settings.sfxVolume
-        hardDropSound.volume  = settings.sfxVolume
-        if (settings.musicVolume > 0) {
-            music.volume = settings.musicVolume
-            music.play()
+        if (piece) scheduler.setInterval(game.fall, stats.fallPeriod)
+        else this.generate()
+    },
+
+    generate: function(heldPiece) {
+        if (heldPiece) {
+            piece = heldPiece
+        } else {
+            piece = nextQueue.shift()
         }
+        piece.position.set(4, SKYLINE)
+        scene.add(piece)
+        ghost.copy(piece)
+        scene.add(ghost)
+    
+        if (piece.canMove(TRANSLATION.NONE)) {
+            scheduler.setInterval(game.fall, stats.fallPeriod)
+        } else {
+            game.over() // block out
+        }
+    },
 
-        scheduler.setInterval(tick)
+    fall: function() {
+        piece.move(TRANSLATION.DOWN)
+    },
+    
+    lockDown: function() {
+        scheduler.clearTimeout(game.lockDown)
+        scheduler.clearInterval(game.fall)
+    
+        if (matrix.lock(piece)) {
+            scene.remove(piece)
+            let tSpin = piece.tSpin
+            let nbClearedLines = matrix.clearLines()
+            if (settings.sfxVolume) {
+                if (nbClearedLines == 4 || (tSpin && nbClearedLines)) {
+                    tetrisSound.currentTime = 0
+                    tetrisSound.play()
+                } else if (nbClearedLines || tSpin) {
+                    lineClearSound.currentTime = 0
+                    lineClearSound.play()
+                }
+            }
+            stats.lockDown(nbClearedLines, tSpin)
+    
+            game.generate()
+        } else {
+            game.over() // lock out
+        }
+    },
 
-        if (piece) scheduler.setInterval(fall, stats.fallPeriod)
-        else generate()
-    }
-}
+    pause: function() {
+        stats.elapsedTime = stats.clock.elapsedTime
+        stats.clock.stop()
+    
+        scheduler.clearInterval(game.fall)
+        scheduler.clearTimeout(game.lockDown)
+        scheduler.clearTimeout(repeat)
+        scheduler.clearInterval(autorepeat)
+    
+        music.pause()
+        document.onkeydown = null
+        renderer.domElement.tabIndex = 1
+        renderer.domElement.onfocus = game.resume
 
-var piece = null
-function generate(heldPiece) {
-    if (heldPiece) {
-        piece = heldPiece
-    } else {
-        piece = nextQueue.shift()
-    }
-    piece.position.set(4, SKYLINE)
-    scene.add(piece)
-    ghost.copy(piece)
-    scene.add(ghost)
+        messagesSpan.addNewChild("div", { className: "show-level-animation", innerHTML: `<h1>PAUSE</h1>` })
+    },
 
-    if (piece.canMove(TRANSLATION.NONE)) {
-        scheduler.setInterval(fall, stats.fallPeriod)
-    } else {
-        gameOver() // block out
-    }
+    over: function() {
+        piece.locked = false
+
+        document.onkeydown = null
+        renderer.domElement.onblur = null
+        renderer.domElement.onfocus = null
+        game.playing = false
+        music.pause()
+        stats.clock.stop()
+        localStorage["teTraHighScore"] = stats.highScore
+        messagesSpan.addNewChild("div", { className: "show-level-animation", innerHTML: `<h1>GAME<br/>OVER</h1>` })
+    },
 }
 
 let playerActions = {
@@ -1085,31 +1072,31 @@ let playerActions = {
 
     moveRight: () => piece.move(TRANSLATION.RIGHT),
 
-    rotateClockwise: () => piece.rotate(ROTATION.CW),
+    rotateCW: () => piece.rotate(ROTATION.CW),
 
-    rotateCounterclockwise: () => piece.rotate(ROTATION.CCW),
+    rotateCCW: () => piece.rotate(ROTATION.CCW),
 
     softDrop: function () {
         if (piece.move(TRANSLATION.DOWN)) stats.score++
     },
 
     hardDrop: function () {
-        scheduler.clearTimeout(lockDown)
+        scheduler.clearTimeout(game.lockDown)
         hardDropSound.play()
         if (settings.sfxVolume) {
             hardDropSound.currentTime = 0
             hardDropSound.play()
         }
         while (piece.move(TRANSLATION.DOWN)) stats.score += 2
-        lockDown()
+        game.lockDown()
         hardDroppedMatrix.reset()
         hardDroppedMatrix.play()
     },
 
     hold: function () {
         if (piece.holdEnabled) {
-            scheduler.clearInterval(fall)
-            scheduler.clearTimeout(lockDown)
+            scheduler.clearInterval(game.fall)
+            scheduler.clearTimeout(game.lockDown)
 
             let heldpiece = holdQueue.piece
             holdQueue.piece = piece
@@ -1118,13 +1105,48 @@ let playerActions = {
             holdQueue.piece.position.set(0, 0)
             holdQueue.piece.facing = FACING.NORTH
             holdQueue.add(holdQueue.piece)
-            generate(heldpiece)
+            game.generate(heldpiece)
         }
     },
 
-    pause: pauseSettings,
+    pause: game.pause,
 }
 
+// Sounds
+const listener = new THREE.AudioListener()
+camera.add( listener )
+const audioLoader = new THREE.AudioLoader()
+const music = new THREE.Audio(listener)
+audioLoader.load('audio/Tetris_CheDDer_OC_ReMix.mp3', function( buffer ) {
+	music.setBuffer(buffer)
+	music.setLoop(true)
+    music.setVolume(settings.musicVolume/100)
+	music.play()
+})
+const lineClearSound = new THREE.Audio(listener)
+audioLoader.load('audio/line-clear.wav', function( buffer ) {
+    lineClearSound.setBuffer(buffer)
+    lineClearSound.setVolume(settings.sfxVolume/100)
+})
+const tetrisSound = new THREE.Audio(listener)
+audioLoader.load('audio/tetris.wav', function( buffer ) {
+    tetrisSound.setBuffer(buffer)
+    tetrisSound.setVolume(settings.sfxVolume/100)
+})
+const hardDropSound = new THREE.Audio(listener)
+audioLoader.load('audio/hard-drop.wav', function( buffer ) {
+    hardDropSound.setBuffer(buffer)
+    hardDropSound.setVolume(settings.sfxVolume/100)
+})
+
+let scheduler = new Scheduler()
+var gui = new GUI().title("teTra")
+let startButton = gui.add(game, "start").name("Démarrer")
+let settings  = new Settings(gui)
+let stats = new Stats(gui)
+
+game.init()
+
 // Handle player inputs
 const REPEATABLE_ACTIONS = [
     playerActions.moveLeft,
@@ -1135,18 +1157,19 @@ let pressedKeys = new Set()
 let actionsQueue = []
 
 function onkeydown(event) {
-    if (event.key in settings.keyBind) {
+    let key = event.key
+    if (key in settings.keyBind) {
         event.preventDefault()
-        if (!pressedKeys.has(event.key)) {
-            pressedKeys.add(event.key)
-            let action = settings.keyBind[event.key]
+        if (!pressedKeys.has(key)) {
+            pressedKeys.add(key)
+            let action = settings.keyBind[key]
             action()
             if (REPEATABLE_ACTIONS.includes(action)) {
                 actionsQueue.unshift(action)
                 scheduler.clearTimeout(repeat)
                 scheduler.clearInterval(autorepeat)
                 if (action == playerActions.softDrop) scheduler.setInterval(autorepeat, settings.fallPeriod / 20)
-                else scheduler.setTimeout(repeat, settings.das)
+                else scheduler.setTimeout(repeat, settings.dasDelay)
             }
         }
     }
@@ -1155,7 +1178,7 @@ function onkeydown(event) {
 function repeat() {
     if (actionsQueue.length) {
         actionsQueue[0]()
-        scheduler.setInterval(autorepeat, settings.arr)
+        scheduler.setInterval(autorepeat, settings.arrDelay)
     }
 }
 
@@ -1168,10 +1191,11 @@ function autorepeat() {
 }
 
 function onkeyup(event) {
-    if (event.key in settings.keyBind) {
+    let key = event.key
+    if (key in settings.keyBind) {
         event.preventDefault()
-        pressedKeys.delete(event.key)
-        let action = settings.keyBind[event.key]
+        pressedKeys.delete(key)
+        let action = settings.keyBind[key]
         if (actionsQueue.includes(action)) {
             actionsQueue.splice(actionsQueue.indexOf(action), 1)
             if (!actionsQueue.length) {
@@ -1182,52 +1206,9 @@ function onkeyup(event) {
     }
 }
 
-function fall() {
-    piece.move(TRANSLATION.DOWN)
-}
-
-function lockDown() {
-    scheduler.clearTimeout(lockDown)
-    scheduler.clearInterval(fall)
-
-    if (matrix.lock(piece)) {
-        scene.remove(piece)
-        let tSpin = piece.tSpin
-        let nbClearedLines = matrix.clearLines()
-        if (settings.sfxVolume) {
-            if (nbClearedLines == 4 || (tSpin && nbClearedLines)) {
-                tetrisSound.currentTime = 0
-                tetrisSound.play()
-            } else if (nbClearedLines || tSpin) {
-                lineClearSound.currentTime = 0
-                lineClearSound.play()
-            }
-        }
-        stats.lockDown(nbClearedLines, tSpin)
-
-        generate()
-    } else {
-        gameOver() // lock out
-    }
-}
-
-function gameOver() {
-    piece.locked = false
-
-    document.onkeydown = null
-    onblur = null
-    playing = false
-    music.pause()
-
-    scheduler.clearInterval(tick)
-
-    stats.show()
-}
-
 window.onbeforeunload = function (event) {
-    stats.save()
     settings.save()
-    if (playing) return false
+    if (game.playing) return false
 }
 
 
diff --git a/audio/Tetris_CheDDer_OC_ReMix.mp3 b/audio/Tetris_CheDDer_OC_ReMix.mp3
new file mode 100644
index 0000000..bcc226e
Binary files /dev/null and b/audio/Tetris_CheDDer_OC_ReMix.mp3 differ
diff --git a/gui.html b/gui.html
new file mode 100644
index 0000000..77eb617
--- /dev/null
+++ b/gui.html
@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.css">
+  <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
+  <script type="importmap">
+    {
+      "imports": {
+        "three": "https://unpkg.com/three@0.152.2/build/three.module.js?module",
+        "three/addons/": "https://unpkg.com/three@0.152.2/examples/jsm/"
+      }
+    }
+  </script>
+  <style>
+    body {
+      background: url(https://adrien.malingrey.fr/jeux/.assets/themes/clouds/background.jpg);
+    }
+    .lil-gui {
+      --background-color: rgba(33, 37, 41, 30%);
+      backdrop-filter: blur(15px);
+    }
+    .lil-gui.autoPlace {
+      left: 15px;
+    }
+    .lil-gui .controller.disabled {
+      opacity: .8;
+    }
+    i {
+      display: inline-block;
+      width: 100%;
+      text-align: center;
+    }
+  </style>
+</head>
+<body>
+  <script type="module">
+    import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+    var game = {
+      startLevel: 1,
+      start: () => {
+        gui.gameFolder.hide()
+        gui.statsFolder.show()
+        gui.statsFolder.open()
+        gui.settingsFolder.close()
+      },
+    }
+
+    var stats = {
+      level: 1,
+      goal: 0,
+      score: 0,
+      highScore:0,
+      time: "00:00:00",
+    }
+
+    var settings = {
+      moveLeftKey : "ArrowLeft",
+      moveRightKey: "ArrowRight",
+      rotateCCWKey: "w",
+      rotateCWKey : "ArrowUp",
+      softDropKey : "ArrowDown",
+      hardDropKey : " ",
+      holdKey     : "c",
+      pauseKey    : "Escape",
+
+      arrDelay: 50,
+      dasDelay: 300,
+
+      musicVolume: 50,
+      sfxVolume  : 50,
+    };
+
+    const KEY_NAMES = {
+      ["ArrowLeft"]   : "←",
+      ["ArrowRight"]  : "→",
+      ["ArrowUp"]     : "↑",
+      ["ArrowDown"]   : "↓",
+      [" "]           : "Espace",
+      ["Escape"]      : "Échap.",
+      ["Backspace"]   : "Ret. arrière",
+      ["Enter"]       : "Entrée",
+      ["←"]           : "ArrowLeft",
+      ["→"]           : "ArrowRight",
+      ["↑"]           : "ArrowUp",
+      ["↓"]           : "ArrowDown",
+      ["Espace"]      : " ",
+      ["Échap."]      : "Escape",
+      ["Ret. arrière"]: "Backspace",
+      ["Entrée"]      : "Enter",
+    }
+
+    function changeKey(event) {
+      const input = event.target
+      let prevValue = input.value
+      input.value = ""
+      input.onkeydown = function (event) {
+        event.preventDefault()
+        input.value = KEY_NAMES[event.key] || event.key
+        input.blur()
+      }
+      input.onblur = function (event) {
+        if (input.value == "") input.value = prevValue
+        input.onkeydown = null
+        input.onblur = null
+      }
+    }
+
+    class Gui extends GUI {
+      constructor() {
+        super({title: "teTra"});
+
+        this.gameFolder = this.addFolder("Partie")
+        this.gameFolder.add(game, "startLevel").name("Niveau").min(1).max(15).step(1)
+        this.gameFolder.add(game, "start").name("Commencer")
+        this.gameFolder.open()
+
+        this.statsFolder = this.addFolder("Stats")
+        this.statsFolder.add(stats,"level").name("Niveau").disable()
+        this.statsFolder.add(stats,"goal").name("Objectif").disable()
+        this.statsFolder.add(stats,"score").name("Score").disable()
+        this.statsFolder.add(stats,"highScore").name("Meilleur score").disable()
+        this.statsFolder.add(stats,"time").name("Temps").disable()
+        this.statsFolder.hide()
+
+        this.settingsFolder = this.addFolder("Options");
+        this.settingsFolder.close()
+
+        this.settingsFolder.keyMapping = this.settingsFolder.addFolder("Commandes")
+        this.settingsFolder.keyMapping.add(settings,"moveLeftKey").name('<i class="bi bi-arrow-left"></i>').domElement.onclick = changeKey
+        this.settingsFolder.keyMapping.add(settings,"moveRightKey").name('<i class="bi bi-arrow-right"></i>').domElement.onclick = changeKey
+        this.settingsFolder.keyMapping.add(settings,"rotateCCWKey").name('<i class="bi bi-arrow-counterclockwise"></i>').domElement.onclick = changeKey
+        this.settingsFolder.keyMapping.add(settings,"rotateCWKey").name('<i class="bi bi-arrow-clockwise"></i>').domElement.onclick = changeKey
+        this.settingsFolder.keyMapping.add(settings,"softDropKey").name('<i class="bi bi-arrow-down-short"></i>').domElement.onclick = changeKey
+        this.settingsFolder.keyMapping.add(settings,"hardDropKey").name('<i class="bi bi-download"></i>').domElement.onclick = changeKey
+        this.settingsFolder.keyMapping.add(settings,"holdKey").name('<i class="bi bi-arrow-left-right"></i>').domElement.onclick = changeKey
+        this.settingsFolder.keyMapping.add(settings,"pauseKey").name('<i class="bi bi-pause"></i>').domElement.onclick = changeKey
+        this.settingsFolder.keyMapping.open()
+
+        this.settingsFolder.delayFolder = this.settingsFolder.addFolder("Répétition automatique")
+        this.settingsFolder.delayFolder.add(settings,"arrDelay").name("ARR (ms)").min(2).max(200);
+        this.settingsFolder.delayFolder.add(settings,"dasDelay").name("DAS (ms)").min(100).max(500).step(5);
+        this.settingsFolder.delayFolder.open()
+
+        this.settingsFolder.volumeFolder = this.settingsFolder.addFolder("Volume")
+        this.settingsFolder.volumeFolder.add(settings,"musicVolume").name("Musique").min(0).max(100);
+        this.settingsFolder.volumeFolder.add(settings,"sfxVolume").name("SFX").min(0).max(100)
+        this.settingsFolder.volumeFolder.open()
+
+      }
+    }
+
+    var gui = new Gui();
+
+  </script>
+</body>
+</html>
diff --git a/index.html b/index.html
index 613d3c1..c67d605 100644
--- a/index.html
+++ b/index.html
@@ -4,162 +4,32 @@
 
     <head>
       <meta charset="utf-8" />
-        <title>teTra</title>
-        <link rel="icon" href="favicon.ico">
-        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
-        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.css">
-        <link rel="stylesheet" href="style.css">
-        <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
-        <script type="importmap">
-          {
-            "imports": {
-              "three": "https://unpkg.com/three@0.152.2/build/three.module.js?module",
-              "three/addons/": "https://unpkg.com/three@0.152.2/examples/jsm/"
-            }
+      <title>teTra</title>
+      <link rel="icon" href="favicon.ico">
+      <link rel="stylesheet" href="style.css">
+      <link rel="stylesheet" href="loading.css">
+      <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
+      <script type="importmap">
+        {
+          "imports": {
+            "three": "https://unpkg.com/three@0.152.2/build/three.module.js?module",
+            "three/addons/": "https://unpkg.com/three@0.152.2/examples/jsm/"
           }
-        </script>
+        }
+      </script>
     </head>
 
-    <body data-bs-theme="dark">
-
-      <div class="modal fade" id="settingsModal" data-bs-backdrop="static" data-bs-keyboard="false">
-        <div class="modal-dialog modal-dialog-centered">
-          <div class="modal-content">
-            <div class="modal-header">
-              <h1 id="titleHeader" class="modal-title w-100 text-center">teTra</h1>
-            </div>
-            <div class="modal-body">
-              <form name="settingsForm" class="needs-validation" novalidate>
-                <fieldset id="keyBindFielset" class="row g-2 mb-3 align-items-center text-center">
-                  <legend class="text-start">Commandes</legend>
-                  <label for="moveLeftInput" title="Gauche" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
-                    <i class="bi bi-arrow-left"></i>
-                  </label>
-                  <div class="col-sm-4">
-                    <input name="moveLeft" id="moveLeftInput" type="text" class="form-control text-center" value="←" onclick="changeKey(this)" placeholder="Touche ?" required>
-                  </div>
-                  <div class="col-sm-4">
-                    <input name="moveRight" id="moveRightInput" type="text" class="form-control text-center" value="→" onclick="changeKey(this)" placeholder="Touche ?" required>
-                  </div>
-                  <label for="moveRightInput" title="Droite" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
-                    <i class="bi bi-arrow-right"></i>
-                  </label>
-                  <label for="rotateCounterclockwiseInput" title="Rotation anti-horaire" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
-                    <i class="bi bi-arrow-counterclockwise"></i>
-                  </label>
-                  <div class="col-sm-4">
-                    <input name="rotateCounterclockwise" id="rotateCounterclockwiseInput" type="text" class="form-control text-center" value="w" onclick="changeKey(this)" placeholder="Touche ?" required>
-                  </div>
-                  <div class="col-sm-4">
-                    <input name="rotateClockwise" id="rotateClockwiseInput" type="text" class="form-control text-center" value="↑" onclick="changeKey(this)" placeholder="Touche ?" required>
-                  </div>
-                  <label for="rotateClockwiseInput" title="Rotation horaire" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
-                    <i class="bi bi-arrow-clockwise"></i>
-                  </label>
-                  <label for="softDropInput" title="Chute lente" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
-                    <i class="bi bi-arrow-down-short"></i>
-                  </label>
-                  <div class="col-sm-4">
-                    <input name="softDrop" id="softDropInput" type="text" class="form-control text-center" value="↓" onclick="changeKey(this)" placeholder="Touche ?" required>
-                  </div>
-                  <div class="col-sm-4">
-                    <input name="hardDrop" id="hardDropInput" type="text" class="form-control text-center" value="Espace" onclick="changeKey(this)" placeholder="Touche ?" required>
-                  </div>
-                  <label for="hardDropInput" title="Chute rapide" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
-                    <i class="bi bi-download"></i>
-                  </label>
-                  <label for="holdInput" title="Échanger la pièce" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
-                    <i class="bi bi-arrow-left-right"></i>
-                  </label>
-                  <div class="col-sm-4">
-                    <input name="hold" id="holdInput" type="text" class="form-control text-center" value="c" onclick="changeKey(this)" placeholder="Touche ?" required>
-                  </div>
-                  <div class="col-sm-4">
-                    <input name="pause" id="pauseInput" type="text" class="form-control text-center" value="Échap" onclick="changeKey(this)" placeholder="Touche ?" required>
-                  </div>
-                  <label for="pauseInput" title="Pause" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
-                    <i class="bi bi-pause"></i>
-                  </label>
-                </fieldset>
-                <fieldset id="autorepearFieldset" class="row g-2 mb-3 align-items-center text-center">
-                  <legend class="text-start">Répétition automatique</legend>
-                  <label for="arrInput" class="col-sm-2 col-form-label" title="Automatic Repeat Rate : période de répétition de l'action">ARR</label>
-                  <div class="col-sm-4">
-                    <div class="input-group">
-                      <input name="arr" id="arrInput" type="number" class="form-control text-center" value="50" min="2" max="200" step="1">
-                      <div class="input-group-text">ms</div>
-                    </div>
-                  </div>
-                  <div class="col-sm-4">
-                    <div class="input-group">
-                      <input name="das" id="dasInput" type="number" class="form-control text-center" value="300" min="100" max="500" step="5">
-                      <div class="input-group-text">ms</div>
-                    </div>
-                  </div>
-                  <label for="dasInput" class="col-sm-2 col-form-label" title="Delayed AutoShift : délai initial avant répétition">DAS</label>
-                </fieldset>
-                <fieldset id="audioFieldset" class="row g-2 mb-3 align-items-center text-center">
-                  <legend class="text-start">Volume</legend>
-                  <label for="musicVolumeInput" class="col-sm-2 col-form-label">Musique</label>
-                  <div class="col-sm-4">
-                    <input name="musicVolume" id="musicVolumeInput" type="range" class="form-range" value=".5" min="0" max="1" step="0.01">
-                  </div>
-                  <div class="col-sm-4">
-                    <input name="sfxVolume" id="sfxVolumeInput" type="range" class="form-range" value=".5" min="0" max="1" step="0.01">
-                  </div>
-                  <label for="sfxVolumeInput" class="col-sm-2 col-form-label">SFX</label>
-                </fieldset>
-                <fieldset class="row g-2 mb-3 align-items-center text-center">
-                  <legend class="text-start">Partie</legend>
-                  <label for="levelInput" class="col-sm-2 col-form-label text-center">Niveau</label>
-                  <div class="col-sm-4">
-                    <input name="startLevel" id="levelInput" type="number" class="form-control text-center" value="1" min="1" max="15">
-                  </div>
-                  <div class="col-sm-4">
-                    <button id="resumeButton" type="submit" class="btn btn-primary w-100" autofocus>Jouer</button>
-                  </div>
-                </fieldset>
-              </form>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <span id="messagesSpan">
-        <div class="progress" role="progressbar">
-          <div id="loadingBar" class="progress-bar overflow-visible progress-bar-striped progress-bar-animated" style="width: 0%">Chargement...</div>
-        </div>
-      </span>
-
-      <span id="scoreSpan">Score<br/><div id="scoreDiv">0</div></span>
-      <span id="timeSpan">Temps<br/><div id="timeDiv">00:00:00</div></span>
-      <span id="levelSpan">Niveau<br/><div id="levelDiv">0</div></span>
-      <span id="goalSpan">Objectif<br/><div id="goalDiv">0</div></span>
-
-      <div class="modal fade" id="statsModal" tabindex="-1">
-        <div class="modal-dialog modal-dialog-centered">
-          <div class="modal-dialog">
-            <div class="modal-content">
-              <div class="modal-header">
-                <h2 class="modal-title w-100 text-center">Fin</h2>
-                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
-              </div>
-              <div class="modal-body p-0">
-                <table class="table mb-0">
-                  <tr><th>Score         </th><td id="statsModalScoreCell">        </td><th>Niveau               </th><td id="statsModalLevelCell">          </td></tr>
-                  <tr><th>Meilleur score</th><td id="statsModalHighScoreCell">    </td><th>Temps                </th><td id="statsModalTimeCell">           </td></tr>
-                  <tr><th>Lignes        </th><td id="statsModaltotalClearedLines"></td><th>Lignes par minute    </th><td id="statsModaltotalClearedLinesPM"></td></tr>
-                  <tr><th>Quatris       </th><td id="statsModalNbQuatris">        </td><th>Plus long combo      </th><td id="statsModalMaxCombo">           </td></tr>
-                  <tr><th>Pirouettes    </th><td id="statsModalNbTSpin">          </td><th>Plus long bout à bout</th><td id="statsModalMaxB2B">             </td></tr>
-                </table>
-              </div>
-              <div class="modal-footer">
-                <button id="restartButton" type="button" class="btn btn-primary" onclick="restart()"">Rejouer ?</button>
-              </div>
-            </div>
+    <body>
+      <div id="loaddingCircle">
+        <div class="e-loadholder">
+          <div class="m-loader">
+            <span class="e-text">
+              <div>Chargement</div>
+              <div id="loadingPercent">0%</div></span>
           </div>
         </div>
       </div>
+      <span id="messagesSpan"></span>
       <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
       <script type="module" src="app.js"></script>
     </body>
diff --git a/loading.css b/loading.css
new file mode 100644
index 0000000..81dc7e0
--- /dev/null
+++ b/loading.css
@@ -0,0 +1,274 @@
+@-webkit-keyframes outerRotate1 {
+	0% {
+		transform: translate(-50%, -50%) rotate(0);
+	}
+
+	100% {
+		transform: translate(-50%, -50%) rotate(360deg);
+	}
+}
+
+@-moz-keyframes outerRotate1 {
+	0% {
+		transform: translate(-50%, -50%) rotate(0);
+	}
+
+	100% {
+		transform: translate(-50%, -50%) rotate(360deg);
+	}
+}
+
+@-o-keyframes outerRotate1 {
+	0% {
+		transform: translate(-50%, -50%) rotate(0);
+	}
+
+	100% {
+		transform: translate(-50%, -50%) rotate(360deg);
+	}
+}
+
+@keyframes outerRotate1 {
+	0% {
+		transform: translate(-50%, -50%) rotate(0);
+	}
+
+	100% {
+		transform: translate(-50%, -50%) rotate(360deg);
+	}
+}
+
+@-webkit-keyframes outerRotate2 {
+	0% {
+		transform: translate(-50%, -50%) rotate(0);
+	}
+
+	100% {
+		transform: translate(-50%, -50%) rotate(-360deg);
+	}
+}
+
+@-moz-keyframes outerRotate2 {
+	0% {
+		transform: translate(-50%, -50%) rotate(0);
+	}
+
+	100% {
+		transform: translate(-50%, -50%) rotate(-360deg);
+	}
+}
+
+@-o-keyframes outerRotate2 {
+	0% {
+		transform: translate(-50%, -50%) rotate(0);
+	}
+
+	100% {
+		transform: translate(-50%, -50%) rotate(-360deg);
+	}
+}
+
+@keyframes outerRotate2 {
+	0% {
+		transform: translate(-50%, -50%) rotate(0);
+	}
+
+	100% {
+		transform: translate(-50%, -50%) rotate(-360deg);
+	}
+}
+
+@-webkit-keyframes textColour {
+	0% {
+		color: #fff;
+	}
+
+	100% {
+		color: #3BB2D0;
+	}
+}
+
+@-moz-keyframes textColour {
+	0% {
+		color: #fff;
+	}
+
+	100% {
+		color: #3BB2D0;
+	}
+}
+
+@-o-keyframes textColour {
+	0% {
+		color: #fff;
+	}
+
+	100% {
+		color: #3BB2D0;
+	}
+}
+
+@keyframes textColour {
+	0% {
+		color: #fff;
+	}
+
+	100% {
+		color: #3BB2D0;
+	}
+}
+
+#loaddingCircle {
+	margin: 0;
+	padding: 0;
+	width: 100vw;
+	height: 100vh;
+}
+
+.e-loadholder {
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	-webkit-transform: translate(-51%, -50%);
+	-moz-transform: translate(-51%, -50%);
+	-ms-transform: translate(-51%, -50%);
+	-o-transform: translate(-51%, -50%);
+	transform: translate(-51%, -50%);
+	width: 240px;
+	height: 240px;
+	border: 5px solid #1B5F70;
+	border-radius: 120px;
+	box-sizing: border-box;
+}
+
+.e-loadholder:after {
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	-webkit-transform: translate(-51%, -50%);
+	-moz-transform: translate(-51%, -50%);
+	-ms-transform: translate(-51%, -50%);
+	-o-transform: translate(-51%, -50%);
+	transform: translate(-51%, -50%);
+	content: " ";
+	display: block;
+	background: #222;
+	transform-origin: center;
+	z-index: 0;
+}
+
+.e-loadholder:after {
+	width: 100px;
+	height: 200%;
+	-webkit-animation: outerRotate2 30s infinite linear;
+	-moz-animation: outerRotate2 30s infinite linear;
+	-o-animation: outerRotate2 30s infinite linear;
+	animation: outerRotate2 30s infinite linear;
+}
+
+.e-loadholder .m-loader {
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	-webkit-transform: translate(-51%, -50%);
+	-moz-transform: translate(-51%, -50%);
+	-ms-transform: translate(-51%, -50%);
+	-o-transform: translate(-51%, -50%);
+	transform: translate(-51%, -50%);
+	width: 200px;
+	height: 200px;
+	color: #888;
+	text-align: center;
+	border: 5px solid #2a93ae;
+	border-radius: 100px;
+	box-sizing: border-box;
+	z-index: 20;
+	text-transform: uppercase;
+}
+
+.e-loadholder .m-loader:after {
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	-webkit-transform: translate(-51%, -50%);
+	-moz-transform: translate(-51%, -50%);
+	-ms-transform: translate(-51%, -50%);
+	-o-transform: translate(-51%, -50%);
+	transform: translate(-51%, -50%);
+	content: " ";
+	display: block;
+	background: #222;
+	transform-origin: center;
+	z-index: -1;
+}
+
+.e-loadholder .m-loader:after {
+	width: 100px;
+	height: 106%;
+	-webkit-animation: outerRotate1 15s infinite linear;
+	-moz-animation: outerRotate1 15s infinite linear;
+	-o-animation: outerRotate1 15s infinite linear;
+	animation: outerRotate1 15s infinite linear;
+}
+
+.e-loadholder .m-loader .e-text {
+	font-family: "Open Sans", sans-serif;
+	font-size: 10px;
+	font-size: 1rem;
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	-webkit-transform: translate(-51%, -50%);
+	-moz-transform: translate(-51%, -50%);
+	-ms-transform: translate(-51%, -50%);
+	-o-transform: translate(-51%, -50%);
+	transform: translate(-51%, -50%);
+	-webkit-animation: textColour 1s alternate linear infinite;
+	-moz-animation: textColour 1s alternate linear infinite;
+	-o-animation: textColour 1s alternate linear infinite;
+	animation: textColour 1s alternate linear infinite;
+	display: flex;
+	flex-direction: column;
+  justify-content:  center;
+	width: 140px;
+	height: 140px;
+	text-align: center;
+	border: 5px solid #3bb2d0;
+	border-radius: 70px;
+	box-sizing: border-box;
+	z-index: 20;
+}
+
+.e-loadholder .m-loader .e-text:before, .e-loadholder .m-loader .e-text:after {
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	-webkit-transform: translate(-51%, -50%);
+	-moz-transform: translate(-51%, -50%);
+	-ms-transform: translate(-51%, -50%);
+	-o-transform: translate(-51%, -50%);
+	transform: translate(-51%, -50%);
+	content: " ";
+	display: block;
+	background: #222;
+	transform-origin: center;
+	z-index: -1;
+}
+
+.e-loadholder .m-loader .e-text:before {
+	width: 110%;
+	height: 40px;
+	-webkit-animation: outerRotate2 3.5s infinite linear;
+	-moz-animation: outerRotate2 3.5s infinite linear;
+	-o-animation: outerRotate2 3.5s infinite linear;
+	animation: outerRotate2 3.5s infinite linear;
+}
+
+.e-loadholder .m-loader .e-text:after {
+	width: 40px;
+	height: 110%;
+	-webkit-animation: outerRotate1 8s infinite linear;
+	-moz-animation: outerRotate1 8s infinite linear;
+	-o-animation: outerRotate1 8s infinite linear;
+	animation: outerRotate1 8s infinite linear;
+}
\ No newline at end of file
diff --git a/style.css b/style.css
index 9d0b2df..30605d4 100644
--- a/style.css
+++ b/style.css
@@ -1,58 +1,34 @@
 body {
-    margin: 0
+    margin: 0;
+    background-color: #222;
 }
 
 span {
     position: absolute;
 }
 
-#messagesSpan .progress {
-    margin-top: 70vh;
-    opacity: 1;
+.lil-gui {
+    --background-color: rgba(33, 37, 41, 30%);
+    --width: 200px;
 }
-
-#messagesSpan .progress-bar {
-    opacity: inherit;
-}
-
-#scoreSpan {
-    top: 1rem;
-    left: 1rem;
-    text-align: left;
-}
-
-#timeSpan {
-    top: 1rem;
-    right: 1rem;
-    text-align: right;
-}
-
-#levelSpan {
-    bottom: 1rem;
-    left: 1rem;
-    text-align: left;
-}
-
-#goalSpan {
-    bottom: 1rem;
-    right: 1rem;
-    text-align: right;
-}
-
 @supports (backdrop-filter: blur()) {
-    .card,
-    .modal-content {
-        background-color: rgba(33, 37, 41, 30%);
+    .lil-gui {
         backdrop-filter: blur(15px);
     }
 }
 
-canvas {
-    cursor: grab;
+.lil-gui.autoPlace {
+    top: inherit;
+    bottom: 15px;
+    left: 15px;
 }
 
-#titleHeader {
-    letter-spacing: 1rem;
+.lil-gui .controller.disabled {
+    opacity: .8;
+}
+
+canvas {
+    cursor: grab;
 }
 
 #messagesSpan {
@@ -63,7 +39,12 @@ canvas {
     transform: translate(-50%, 0);
     color: rgba(255, 255, 255, 0.8);
     text-shadow: 1px 1px rgba(0, 0, 0, 0.8);
-    font-size: 4vmin;
+    font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
+        "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji",
+        "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+    font-weight: 400;
+    line-height: 1.5;
+    font-size: 3vmin;
     text-align: center;
 }
 
@@ -72,6 +53,10 @@ canvas {
     overflow: hidden;
 }
 
+h1 {
+    font-size: calc(1.375rem + 1.5vw);
+}
+
 @keyframes show-level-animation {
     from {
         opacity: 0;