diff --git a/.eslintrc b/.eslintrc
index 9923934..7d1b29b 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -2,7 +2,7 @@
     "extends": "airbnb",
     "globals": {
         "Phaser": true,
-        "document":
+        "document": true
     },
     "env": {
         "browser": true
diff --git a/gruntfile.js b/gruntfile.js
index 19383ed..d28e898 100644
--- a/gruntfile.js
+++ b/gruntfile.js
@@ -1,240 +1,240 @@
-var properties = require('./src/js/game/properties.js');
+const properties = require('./src/js/game/properties.js');
 
 module.exports = function (grunt) {
+    grunt.loadNpmTasks('grunt-browserify');
+    grunt.loadNpmTasks('grunt-cache-bust');
+    grunt.loadNpmTasks('grunt-contrib-clean');
+    grunt.loadNpmTasks('grunt-contrib-compress');
+    grunt.loadNpmTasks('grunt-contrib-connect');
+    grunt.loadNpmTasks('grunt-contrib-copy');
+    grunt.loadNpmTasks('grunt-contrib-jade');
+    grunt.loadNpmTasks('grunt-contrib-jshint');
+    grunt.loadNpmTasks('grunt-contrib-stylus');
+    grunt.loadNpmTasks('grunt-contrib-uglify');
+    grunt.loadNpmTasks('grunt-contrib-watch');
+    grunt.loadNpmTasks('grunt-open');
+    grunt.loadNpmTasks('grunt-pngmin');
+
+    const productionBuild = !!(grunt.cli.tasks.length && grunt.cli.tasks[0] === 'build');
+
+    grunt.initConfig({
+
+        pkg: grunt.file.readJSON('package.json'),
+
+        properties,
+
+        project: {
+            src: 'src/js',
+            js: '<%= project.src %>/game/{,*/}*.js',
+            dest: 'build/js',
+            bundle: 'build/js/app.min.js',
+            port: properties.port,
+            banner:
+            '/*\n' +
+            ' * <%= properties.title %>\n' +
+            ' * <%= pkg.description %>\n' +
+            ' *\n' +
+            ' * @author <%= pkg.author %>\n' +
+            ' * @version <%= pkg.version %>\n' +
+            ' * @copyright <%= pkg.author %>\n' +
+            ' * @license <%= pkg.license %> licensed\n' +
+            ' *\n' +
+            ' * Made using Phaser JS Boilerplate' +
+            ' <https://github.com/lukewilde/phaser-js-boilerplate>\n' +
+            ' */\n',
+        },
+
+        connect: {
+            dev: {
+                options: {
+                    port: '<%= project.port %>',
+                    base: './build',
+                },
+            },
+        },
+
+        jshint: {
+            files: [
+                'gruntfile.js',
+                '<%= project.js %>',
+            ],
+            options: {
+                jshintrc: '.jshintrc',
+            },
+        },
+
+        watch: {
+            options: {
+                livereload: productionBuild ? false : properties.liveReloadPort,
+            },
+            js: {
+                files: '<%= project.dest %>/**/*.js',
+                tasks: ['jade'],
+            },
+            jade: {
+                files: 'src/templates/*.jade',
+                tasks: ['jade'],
+            },
+            stylus: {
+                files: 'src/style/*.styl',
+                tasks: ['stylus'],
+            },
+            images: {
+                files: 'src/images/**/*',
+                tasks: ['copy:images'],
+            },
+            audio: {
+                files: 'src/audio/**/*',
+                tasks: ['copy:audio'],
+            },
+        },
+
+        browserify: {
+            app: {
+                src: ['<%= project.src %>/game/app.js'],
+                dest: '<%= project.bundle %>',
+                options: {
+                    transform: ['browserify-shim', ['babelify', { presets: ['es2015'] }]],
+                    watch: true,
+                    browserifyOptions: {
+                        debug: !productionBuild,
+                    },
+                },
+            },
+        },
+
+        open: {
+            server: {
+                path: 'http://localhost:<%= project.port %>',
+            },
+        },
+
+        cacheBust: {
+            options: {
+                assets: ['audio/**', 'images/**', 'js/**', 'style/**'],
+                baseDir: './build/',
+                deleteOriginals: true,
+                length: 5,
+            },
+            files: {
+                src: ['./build/js/app.min.*', './build/index.html'],
+            },
+        },
+
+        jade: {
+            compile: {
+                options: {
+                    data: {
+                        properties,
+                        productionBuild,
+                    },
+                },
+                files: {
+                    'build/index.html': ['src/templates/index.jade'],
+                },
+            },
+        },
+
+        stylus: {
+            compile: {
+                files: { 'build/style/index.css': ['src/style/index.styl'] },
+                options: {
+                    sourcemaps: !productionBuild,
+                },
+            },
+        },
+
+        clean: ['./build/'],
+
+        pngmin: {
+            options: {
+                ext: '.png',
+                force: true,
+            },
+            compile: {
+                files: [{ src: 'src/images/*.png', dest: 'src/images/' }]
+            },
+        },
+
+        copy: {
+            images: {
+                files: [{ expand: true, cwd: 'src/images/', src: ['**'], dest: 'build/images/' }],
+            },
+            audio: {
+                files: [{ expand: true, cwd: 'src/audio/', src: ['**'], dest: 'build/audio/' }],
+            },
+            phaserArcade: {
+                files: [{
+                    src: ['node_modules/phaser/build/custom/phaser-arcade-physics.js'],
+                    dest: 'build/js/phaser.js',
+                }],
+            },
+            phaserArcadeMin: {
+                files: [{
+                    src: ['node_modules/phaser/build/custom/phaser-arcade-physics.min.js'],
+                    dest: 'build/js/phaser.js',
+                }],
+            },
+            phaserP2: {
+                files: [{
+                    src: ['node_modules/phaser/build/phaser.js'],
+                    dest: 'build/js/phaser.js',
+                }],
+            },
+            phaserP2Min: {
+                files: [{
+                    src: ['node_modules/phaser/build/phaser.min.js'],
+                    dest: 'build/js/phaser.js',
+                }],
+            },
+        },
+
+        uglify: {
+            options: {
+                banner: '<%= project.banner %>',
+            },
+            dist: {
+                files: { '<%= project.bundle %>': '<%= project.bundle %>' },
+            },
+        },
 
-  grunt.loadNpmTasks('grunt-browserify');
-  grunt.loadNpmTasks('grunt-cache-bust');
-  grunt.loadNpmTasks('grunt-contrib-clean');
-  grunt.loadNpmTasks('grunt-contrib-compress');
-  grunt.loadNpmTasks('grunt-contrib-connect');
-  grunt.loadNpmTasks('grunt-contrib-copy');
-  grunt.loadNpmTasks('grunt-contrib-jade');
-  grunt.loadNpmTasks('grunt-contrib-jshint');
-  grunt.loadNpmTasks('grunt-contrib-stylus');
-  grunt.loadNpmTasks('grunt-contrib-uglify');
-  grunt.loadNpmTasks('grunt-contrib-watch');
-  grunt.loadNpmTasks('grunt-open');
-  grunt.loadNpmTasks('grunt-pngmin');
-
-  var productionBuild = !!(grunt.cli.tasks.length && grunt.cli.tasks[0] === 'build');
-
-  grunt.initConfig({
-
-    pkg: grunt.file.readJSON('package.json'),
-
-    properties: properties,
-
-    project: {
-      src: 'src/js',
-      js: '<%= project.src %>/game/{,*/}*.js',
-      dest: 'build/js',
-      bundle: 'build/js/app.min.js',
-      port: properties.port,
-      banner:
-        '/*\n' +
-        ' * <%= properties.title %>\n' +
-        ' * <%= pkg.description %>\n' +
-        ' *\n' +
-        ' * @author <%= pkg.author %>\n' +
-        ' * @version <%= pkg.version %>\n' +
-        ' * @copyright <%= pkg.author %>\n' +
-        ' * @license <%= pkg.license %> licensed\n' +
-        ' *\n' +
-        ' * Made using Phaser JS Boilerplate' +
-        ' <https://github.com/lukewilde/phaser-js-boilerplate>\n' +
-        ' */\n'
-    },
-
-    connect: {
-      dev: {
-        options: {
-          port: '<%= project.port %>',
-          base: './build'
-        }
-      }
-    },
-
-    jshint: {
-      files: [
-        'gruntfile.js',
-        '<%= project.js %>'
-      ],
-      options: {
-        jshintrc: '.jshintrc'
-      }
-    },
-
-    watch: {
-      options: {
-        livereload: productionBuild ? false : properties.liveReloadPort
-      },
-      js: {
-        files: '<%= project.dest %>/**/*.js',
-        tasks: ['jade']
-      },
-      jade: {
-        files: 'src/templates/*.jade',
-        tasks: ['jade']
-      },
-      stylus: {
-        files: 'src/style/*.styl',
-        tasks: ['stylus']
-      },
-      images: {
-        files: 'src/images/**/*',
-        tasks: ['copy:images']
-      },
-      audio:{
-        files: 'src/audio/**/*',
-        tasks: ['copy:audio']
-      }
-    },
-
-    browserify: {
-      app: {
-        src: ['<%= project.src %>/game/app.js'],
-        dest: '<%= project.bundle %>',
-        options: {
-          transform: ['browserify-shim', ['babelify', {presets: ['es2015']}]],
-          watch: true,
-          browserifyOptions: {
-            debug: !productionBuild
-          }
-        }
-      }
-    },
-
-    open: {
-      server: {
-        path: 'http://localhost:<%= project.port %>'
-      }
-    },
-
-    cacheBust: {
-      options: {
-        assets: ['audio/**', 'images/**', 'js/**', 'style/**'],
-        baseDir: './build/',
-        deleteOriginals: true,
-        length: 5
-      },
-      files: {
-        src: ['./build/js/app.min.*', './build/index.html']
-      }
-    },
-
-    jade: {
-      compile: {
-        options: {
-          data: {
-            properties: properties,
-            productionBuild: productionBuild
-          }
+        compress: {
+            options: { archive: '<%= pkg.name %>.zip' },
+            zip: { files: [{ expand: true, cwd: 'build/', src: ['**/*'], dest: '<%= pkg.name %>/' }] },
+            cocoon: { files: [{ expand: true, cwd: 'build/', src: ['**/*'] }] },
         },
-        files: {
-          'build/index.html': ['src/templates/index.jade']
-        }
-      }
-    },
-
-    stylus: {
-      compile: {
-        files: { 'build/style/index.css': ['src/style/index.styl'] },
-          options: {
-            sourcemaps: !productionBuild
-        }
-      }
-    },
-
-    clean: ['./build/'],
-
-    pngmin: {
-      options: {
-        ext: '.png',
-        force: true
-      },
-      compile: {
-        files: [ { src: 'src/images/*.png', dest: 'src/images/' } ] }
-      },
-
-    copy: {
-      images: {
-        files: [ { expand: true, cwd: 'src/images/', src: ['**'], dest: 'build/images/' } ]
-      },
-      audio: {
-        files: [ { expand: true, cwd: 'src/audio/', src: ['**'], dest: 'build/audio/' } ]
-      },
-      phaserArcade: {
-        files: [ {
-          src: ['node_modules/phaser/build/custom/phaser-arcade-physics.js'],
-          dest: 'build/js/phaser.js'
-        } ]
-      },
-      phaserArcadeMin: {
-        files: [ {
-          src: ['node_modules/phaser/build/custom/phaser-arcade-physics.min.js'],
-          dest: 'build/js/phaser.js'
-        } ]
-      },
-      phaserP2: {
-        files: [ {
-          src: ['node_modules/phaser/build/phaser.js'],
-          dest: 'build/js/phaser.js'
-        } ]
-      },
-      phaserP2Min: {
-        files: [ {
-          src: ['node_modules/phaser/build/phaser.min.js'],
-          dest: 'build/js/phaser.js'
-        } ]
-      }
-    },
-
-    uglify: {
-      options: {
-        banner: '<%= project.banner %>'
-      },
-      dist: {
-        files: { '<%= project.bundle %>': '<%= project.bundle %>' }
-      }
-    },
-
-    compress: {
-      options: { archive: '<%= pkg.name %>.zip' },
-      zip: { files: [ { expand: true, cwd: 'build/', src: ['**/*'], dest: '<%= pkg.name %>/' } ] },
-      cocoon: { files: [ { expand: true, cwd: 'build/', src: ['**/*'] } ] }
-    }
-  });
-
-  grunt.registerTask('default', [
-    'clean',
-    'browserify',
-    'jade',
-    'stylus',
-    'copy:images',
-    'copy:audio',
-    'copy:phaserArcade',
-    'connect',
-    'open',
-    'watch'
-  ]);
-
-  grunt.registerTask('build', [
-    /*'jshint',
-    */'clean',
-    'browserify',
-    'jade',
-    'stylus',
-    // 'uglify',
-    'copy:images',
-    'copy:audio',
-    'copy:phaserArcadeMin',
-    'cacheBust',
-    // 'connect',
-    // 'open',
-    // 'watch'
-  ]);
-
-  grunt.registerTask('optimise', ['pngmin', 'copy:images']);
-  grunt.registerTask('cocoon', ['compress:cocoon']);
-  grunt.registerTask('zip', ['compress:zip']);
+    });
+
+    grunt.registerTask('default', [
+        'clean',
+        'browserify',
+        'jade',
+        'stylus',
+        'copy:images',
+        'copy:audio',
+        'copy:phaserArcade',
+        'connect',
+        'open',
+        'watch',
+    ]);
+
+    grunt.registerTask('build', [
+        // 'jshint',
+        'clean',
+        'browserify',
+        'jade',
+        'stylus',
+        // 'uglify',
+        'copy:images',
+        'copy:audio',
+        'copy:phaserArcadeMin',
+        // 'cacheBust',
+        // 'connect',
+        // 'open',
+        // 'watch'
+    ]);
+
+    grunt.registerTask('optimise', ['pngmin', 'copy:images']);
+    grunt.registerTask('cocoon', ['compress:cocoon']);
+    grunt.registerTask('zip', ['compress:zip']);
 };
diff --git a/src/js/game/objects/Bouncer.js b/src/js/game/objects/Bouncer.js
new file mode 100644
index 0000000..5f4d67b
--- /dev/null
+++ b/src/js/game/objects/Bouncer.js
@@ -0,0 +1,93 @@
+/**
+ * A Bouncer handles the player's controls on an object's bounce
+ */
+function Bouncer(opts) {
+    Object.assign(this, {
+        actor: null,
+        timings: {
+            // TODO adjust the default timings
+            perfect: 0,
+            good: 2,
+            poor: 5,
+        },
+    }, opts);
+
+    this.reset();
+}
+
+Bouncer.create = (...args) => new Bouncer(...args);
+
+Bouncer.prototype = {
+    reset() {
+        Object.assign(this, {
+            frame: 0,
+            didBounce: false,
+            bouncedAt: 0,
+            didTrigger: false,
+            triggeredAt: 0,
+        });
+    },
+
+    // True if too much time elapsed after the bounce
+    timedOut() {
+        return this.didBounce && this.frame - this.bouncedAt > this.timings.poor;
+    },
+
+    update(trigger, bounced) {
+        this.frame += 1;
+
+        // Register trigger and bounce events
+        if (bounced && !this.didBounce) {
+            this.didBounce = true;
+            this.bouncedAt = this.frame;
+        }
+
+        // Ignore input right after the actor bounce was resolved
+        const ignoreInput = !this.didBounce && this.actor.body.velocity.y <= 0;
+        // if (ignoreInput && trigger) {
+        //     console.log('Ignore input');
+        // }
+
+        if (!ignoreInput && !this.didTrigger && (
+            trigger || this.timedOut() // automatically trigger after timeout
+        )) {
+            this.didTrigger = true;
+            this.triggeredAt = this.frame;
+        }
+
+        // Once both happened we can resolve the actor's new speed
+        if (this.didTrigger && this.didBounce) {
+            this.adjustBounce();
+            this.killIfNeeded();
+            this.reset();
+        }
+    },
+
+    adjustBounce() {
+        const { perfect, good, poor } = this.timings;
+        const distance = Math.abs(this.bouncedAt - this.triggeredAt);
+
+        let multiplier;
+        if (distance <= perfect) {
+            multiplier = 1.2;
+        } else if (distance <= good) {
+            multiplier = 1;
+        } else if (distance <= poor) {
+            multiplier = 0.8;
+        } else {
+            multiplier = 0.5;
+        }
+
+        this.actor.body.velocity.y *= multiplier;
+        this.actor.body.velocity.x *= 1.05;
+    },
+
+    killIfNeeded() {
+        const { actor } = this;
+        if (Math.abs(actor.body.velocity.y) < 50) {
+            actor.kill();
+        }
+    },
+};
+
+module.exports = Bouncer;
diff --git a/src/js/game/objects/Shadow.js b/src/js/game/objects/Shadow.js
new file mode 100644
index 0000000..e0cecf8
--- /dev/null
+++ b/src/js/game/objects/Shadow.js
@@ -0,0 +1,43 @@
+/**
+ * A shadow for the disc
+ */
+function Shadow(game, opts) {
+    Object.assign(this, {
+        color: 0x000000,
+        ground: 0,
+        max_height: 0,
+        size: 0,
+    }, opts);
+
+    this.graphics = game.add.graphics(0, this.ground);
+    this.graphics.visible = false;
+}
+
+Shadow.create = (game, args) => new Shadow(game, args);
+
+Shadow.prototype = {
+    attachTo(actor) {
+        this.actor = actor;
+        this.graphics.beginFill(this.color);
+        this.graphics.drawCircle(this.size / 2, -(this.size + actor.height), this.size);
+        this.graphics.endFill();
+        this.graphics.visible = this.actor.alive;
+        return this;
+    },
+
+    update() {
+        if (!this.actor.alive) {
+            this.graphics.kill();
+            return;
+        }
+
+        const gap = Math.abs(this.ground - this.actor.body.position.y);
+        const factor = 1 - (gap / this.max_height);
+        this.graphics.position.x = this.actor.body.position.x;
+        this.graphics.scale.y = factor / 2;
+        this.graphics.scale.x = factor;
+        this.graphics.alpha = factor ** 2;
+    },
+};
+
+module.exports = Shadow;
diff --git a/src/js/game/states/play.js b/src/js/game/states/play.js
index 6693123..ec66009 100644
--- a/src/js/game/states/play.js
+++ b/src/js/game/states/play.js
@@ -1,5 +1,7 @@
 const _ = require('lodash');
 const properties = require('../properties');
+const Bouncer = require('../objects/Bouncer');
+const Shadow = require('../objects/Shadow');
 
 const play = {};
 
@@ -17,19 +19,32 @@ function createWater(game, height) {
 }
 
 function createDisc(game) {
-    const disc = game.add.sprite(32, game.world.height * 0.7, 'disc');
+    const disc = game.add.sprite(32, game.world.height * 0.5, 'disc');
     const anim = disc.animations.add('rotate');
     anim.play(40, true);
 
     disc.anchor.setTo(0.5, 0.5);
 
     game.physics.arcade.enable([disc]);
-    disc.body.bounce.y = 0.95;
     disc.body.collideWorldBounds = true;
+    disc.body.velocity.x = 250;
+    disc.body.gravity.y = 200;
+    disc.body.bounce.y = 1; // Higher will be less punitive for the player
+    disc.body.maxVelocity.y = 250;
+    disc.body.maxVelocity.x = 750;
 
     return disc;
 }
 
+function createShadow(game) {
+    return Shadow.create(game, {
+        color: 0x11367a,
+        ground: game.height - 64,
+        max_height: game.height,
+        size: 16,
+    });
+}
+
 function createSky(game, height) {
     const sky = game.add.tileSprite(0, height - 172, game.world.width, 88, 'sky');
     sky.scale.setTo(2, 2);
@@ -44,7 +59,7 @@ function makeSplash(game, disc) {
     }
 
     // Create the splash sprite below the disc
-    const splash = game.add.sprite(disc.centerX, disc.bottom, 'splash', 5);
+    const splash = game.add.sprite(disc.body.position.x + 24, disc.bottom, 'splash', 5);
     splash.anchor.setTo(0.5, 1);
 
     // Adapt animation to disc velocity
@@ -68,42 +83,50 @@ function makeSplash(game, disc) {
     });
 }
 
-function handleInput(game) {
-    const key = game.input.keyboard;
-    if (key.isDown(Phaser.Keyboard.SPACE)) {
-        // TODO
-    }
-}
-
 play.create = function create() {
     this.game.world.setBounds(0, 0, properties.size.x * 30, properties.size.y);
     this.game.stage.backgroundColor = '#eeeeee';
     this.game.physics.startSystem(Phaser.Physics.ARCADE);
-    this.game.physics.arcade.gravity.y = 200;
 
     this.water = createWater(this.game, this.game.height);
     this.sky = createSky(this.game, this.water.top);
-    this.disc = createDisc(this.game);
 
-    this.disc.body.velocity.x = 250;
+    const shadow = createShadow(this.game); // NOTE Disc has to be created after shadow (or help find a way to play with the z-indexes ^.^)
+    this.disc = createDisc(this.game);
+    this.disc.shadow = shadow.attachTo(this.disc);
 
     this.game.camera.follow(this.disc, Phaser.Camera.FOLLOW_LOCKON, 0.1, 0.1);
+
+    this.bouncer = Bouncer.create({ actor: this.disc });
 };
 
 play.update = function update() {
-    const { disc, water, sky, game } = this;
-    game.physics.arcade.collide(disc, water, () => {
-        makeSplash(game, disc);
-    });
-
-    handleInput(game, sky, water);
+    const { disc, water, game } = this;
+
+    disc.shadow.update();
+
+    if (disc.alive) {
+        // Handle bounce
+        const didCollide = game.physics.arcade.collide(disc, water);
+        if (didCollide) {
+            makeSplash(game, disc);
+        }
+        this.bouncer.update(game.input.keyboard.isDown(Phaser.Keyboard.SPACEBAR), didCollide);
+
+        // The disc was too slow and killed
+        if (!disc.alive) {
+            // Reset the game later
+            setTimeout(() => {
+                this.game.state.clearCurrentState();
+                this.game.state.restart();
+            }, 750);
+        }
+    }
 };
 
+
 play.render = function render() {
-    // const { disc, water, game } = this;
-    // game.debug.body(disc);
-    // game.debug.bodyInfo(disc, 32, 72);
-    // game.debug.body(water);
+    // Empty
 };
 
 module.exports = play;