A game made to inspire developers to use GSAP, ES6 and Flexbox
A Pen by Serge Antonov on CodePen.
<div class="splash"></div> | |
<div class="container"> | |
<div class="start-game game-full-flex" id="start-game"> | |
<div class="start-game-top"><a class="play-full-page" href="http://codepen.io/TheSiegfried/full/ObYmyo" target="_blank">Полноэкранный</a></div> | |
<div class="logo-holder"> | |
<p class="logo"> | |
<span>C</span> | |
<span>o</span> | |
<span>l</span> | |
<span>с</span> | |
<span>o</span> | |
<span>l</span> | |
</p> | |
<a class="play-button" href="#" onclick="game.start()">ИГРАТЬ!</a> | |
<h4 class="hint">подсказка: <span>красный</span> цвет столбика всегда первый</h4> | |
</div> | |
<div class="how-to-play"> | |
<div class="section section-1"> | |
<h4>Прыгающий мячик<br>меняет свой цвет</h4> | |
<div class="content"> | |
<div class="ball-demo" id="ball-demo"></div> | |
</div> | |
</div> | |
<div class="section section-2"> | |
<h4>Нажми для изменения цвета колонны<br>(красный, желтый, сириневый)</h4> | |
<div class="content"> | |
<div class="bar bar-1" data-index="0"></div> | |
<div class="bar bar-2"></div> | |
<div class="bar bar-3"></div> | |
</div> | |
</div> | |
<div class="section section-3"> | |
<h4>Всегда следи за тем, чтобы<br>мячик и колонна были одно цвета</h4> | |
<div class="content"> | |
<div class="ball-demo" id="ball-demo"></div> | |
<div class="bar bar-1"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="stop-game game-full-flex"> | |
<div class="score-container"> | |
<h1>СolCol</h1> | |
<div class="final-score"></div> | |
<div class="result"></div> | |
<h4>Сдклай твит и приглоси друзей</h4> | |
<p> | |
<a class="tweet" href="#" onclick="game.generateTweet()"> | |
<i class="fa fa-twitter" aria-hidden="true"></i> Tweet | |
</a> | |
</p> | |
<div> | |
<a class="play-again" href="#" onclick="game.start()">Играть снова</a> | |
<a class="main-menu" href="#" onclick="game.intro()">Меню</a> | |
</div> | |
</div> | |
</div> | |
<div class="small-glows"></div> | |
<div class="glow"><div class="sun"></div></div> | |
<div class="waves"> | |
<div class="top_wave"></div> | |
<div class="wave1"></div> | |
<div class="wave2"></div> | |
<div class="wave3"></div> | |
<div class="wave4"></div> | |
</div> | |
<div class="mounts"> | |
<div class="mount1"></div> | |
<div class="mount2"></div> | |
</div> | |
<div class="clouds"></div> | |
<div class="scene"> | |
<div class="learn-to-play">Нажми на колонну, чтобы изменить её цвет!</div> | |
<div class="score" id="score"></div> | |
<div class="ball-holder"></div> | |
<div class="sticks" id="sticks"></div> | |
</div> | |
<div class="noise"></div> | |
</div> |
class Game { | |
constructor() { | |
this.score = 0; | |
this.isRunning = 0; // игра не играется | |
this.calculateScale(); | |
this.timeline = new TimelineMax({ | |
smoothChildTiming: true | |
}); | |
this.time = 1.6; // скорость | |
this.colors = ["#FF4571", "#FFD145", "#8260F6"]; // используемые цвета | |
this.colorsRGBA = ["rgba(255, 69, 113, 1)", "rgba(255, 69, 113, 1)", "rgba(255, 69, 113, 1)"]; | |
this.color = this.colors[0]; | |
this.prevColor = null; | |
} | |
calculateScale() { | |
this.screen = $(window).width(); // screen width | |
this.screenHeight = $(window).height(); | |
this.scale = (this.screen > this.screenHeight) ? this.screenHeight / 800 : this.screen / 1200; | |
this.stickWidth = 180 * this.scale; | |
this.steps = this.screen / this.stickWidth; // how many steps (stick width + margin) it takes from one end to another | |
} | |
generateSticks() { | |
let numberOfSticks = Math.ceil(this.steps); | |
for (let i = 0; i <= numberOfSticks; i++) | |
new Stick(); | |
} | |
generateBall() { | |
this.balltween = new TimelineMax({ | |
repeat: -1, | |
paused: 1 | |
}); | |
$('.scene .ball-holder').append('<div class="ball red" id="ball"></div>'); | |
this.bounce(); | |
} | |
generateTweet() { | |
let top = $(window).height() / 2 - 150; | |
let left = $(window).width() / 2 - 300; | |
window.open("https://twitter.com/intent/tweet?url=http://codepen.io/gregh/full/yVLOyO&text=I scored " + this.score + " points on Coloron! Can you beat my score?&via=greghvns&hashtags=coloron", "TweetWindow", "width=600px,height=300px,top=" + top + ",left=" + left); | |
} | |
/** | |
* The greeting when the game begins | |
*/ | |
intro() { | |
TweenMax.killAll(); | |
//TweenMax.to('.splash', 0.3, { opacity: 0, display: 'none', delay: 1 }) | |
$('.stop-game').css('display', 'none'); | |
$('.start-game').css('display', 'flex'); | |
let introTl = new TimelineMax(); | |
let ball = new TimelineMax({ | |
repeat: -1, | |
delay: 3 | |
}); | |
introTl | |
.fromTo('.start-game .logo-holder', 0.9, { | |
opacity: 0 | |
}, { | |
opacity: 1 | |
}) | |
.staggerFromTo('.start-game .logo span', 0.5, { | |
opacity: 0 | |
}, { | |
opacity: 1 | |
}, 0.08) | |
.staggerFromTo('.start-game .bar', 1.6, { | |
y: '+100%' | |
}, { | |
y: '0%', | |
ease: Elastic.easeOut.config(1, 0.3) | |
}, 0.08) | |
.staggerFromTo('.start-game .ball-demo', 1, { | |
scale: 0 | |
}, { | |
scale: 1, | |
ease: Elastic.easeOut.config(1, 0.3) | |
}, 0.8, 2) | |
ball.fromTo('.start-game .section-1 .ball-demo', 0.5, { | |
y: "0px" | |
}, { | |
y: "100px", | |
scaleY: 1.1, | |
transformOrigin: "bottom", | |
ease: Power2.easeIn | |
}) | |
.to('.start-game .section-1 .ball-demo', 0.5, { | |
y: "0px", | |
scaleY: 1, | |
transformOrigin: "bottom", | |
ease: Power2.easeOut, | |
onStart: () => { | |
while (this.prevColor == this.color) { | |
this.color = (new Color).getRandomColor(); | |
} | |
this.prevColor = this.color; | |
TweenMax.to('.start-game .section-1 .ball-demo', 0.5, { | |
backgroundColor: this.color | |
}); | |
} | |
}); | |
} | |
/** | |
* Display score | |
*/ | |
showResult() { | |
let score = this.score; | |
$('.stop-game').css('display', 'flex'); | |
$('.stop-game .final-score').text(score + '!'); | |
$('.stop-game .result').text(this.showGrade(score)); | |
$('.nominee').show(); | |
let resultTimeline = new TimelineMax(); | |
resultTimeline | |
.fromTo('.stop-game .score-container', 0.7, { | |
opacity: 0, | |
scale: 0.3 | |
}, { | |
opacity: 1, | |
scale: 1, | |
ease: Elastic.easeOut.config(1.25, 0.5) | |
}) | |
.fromTo('.stop-game .final-score', 2, { | |
scale: 0.5 | |
}, { | |
scale: 1, | |
ease: Elastic.easeOut.config(2, 0.5) | |
}, 0) | |
.fromTo('.stop-game .result', 1, { | |
scale: 0.5 | |
}, { | |
scale: 1, | |
ease: Elastic.easeOut.config(1.5, 0.5) | |
}, 0.3); | |
} | |
/** | |
* Takes players score and generates the cheering copy | |
* @param {int} score | |
* @return {string} grade | |
*/ | |
showGrade(score) { | |
if (score > 30) return "Чак Норрис?"; | |
else if (score > 25) return "Идёшь на рекорд!"; | |
else if (score > 20) return "Невероятно!"; | |
else if (score > 15) return "Отлично!"; | |
else if (score > 13) return "Хорошо!"; | |
else if (score > 10) return "Неплохо!"; | |
else if (score > 5) return "Серьёзно?"; | |
else return "Так себе. Попробуй ещё..."; | |
} | |
start() { | |
this.stop(); // stop the game | |
$('.start-game, .stop-game').css('display', 'none'); // hide all the popups | |
$('.nominee').hide(); | |
new Game(); | |
this.score = 0; // reset | |
this.isRunning = 1; | |
// Clean up the stick and ball holders | |
// and generate new ones | |
$('#sticks, .scene .ball-holder').html(''); | |
$('#score').text(this.score); | |
this.generateSticks(); | |
this.generateBall(); | |
// disables scene animations for Phones | |
if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(window.navigator.userAgent)) { | |
Animation.sceneAnimation(); | |
} | |
this.moveToStart(); | |
this.moveScene(); | |
// reset timescale to normal as the game speeds up | |
this.timeline.timeScale(1); | |
this.balltween.timeScale(1); | |
} | |
stop() { | |
this.isRunning = 0; | |
$('.start-game, .stop-game').css('display', 'none'); | |
$('#sticks, .scene .ball-holder, #score').html(''); | |
TweenMax.killAll(); | |
this.showResult(); | |
} | |
scaleScreen() { | |
TweenMax.killAll(); // prevent multiple calls on resize | |
let height = $(window).height(); | |
let width = $(window).width(); | |
this.calculateScale(); | |
$('.container') | |
.css('transform', 'scale(' + this.scale + ')') | |
.css('height', height / this.scale) | |
.css('width', width / this.scale) | |
.css('transformOrigin', 'left top'); | |
$('#sticks').width(this.screen / this.scale + 3 * this.stickWidth / this.scale); | |
} | |
/** | |
* Calls the above function | |
* If the game is running it stops and shows the score | |
* If the game has stops it takes player to the main menu | |
*/ | |
scaleScreenAndRun() { | |
this.scaleScreen(); | |
if (this.isRunning) { | |
this.stop(); | |
} else { | |
this.intro(); | |
} | |
} | |
/** | |
* This is the initial animation | |
* where the sticks come to the starting position | |
* and the ball appears and falls down | |
*/ | |
moveToStart() { | |
let tip = new TimelineMax({ | |
delay: 2 | |
}); | |
tip | |
.fromTo('.learn-to-play', 1, { | |
scale: 0 | |
}, { | |
scale: 1, | |
opacity: 1, | |
ease: Elastic.easeOut.config(1.25, 0.5) | |
}) | |
.to('.learn-to-play', 1, { | |
scale: 0, | |
opacity: 0, | |
ease: Elastic.easeOut.config(1.25, 0.5) | |
}, 3) | |
TweenMax.fromTo('#ball', this.time, { | |
scale: 0 | |
}, { | |
scale: 1, | |
delay: this.time * ((this.steps - 3) - 1.5), | |
onComplete: () => { | |
this.balltween.play(); | |
} | |
}); | |
this.timeline.add( | |
TweenMax.fromTo('#sticks', this.time * this.steps, { | |
x: this.screen / this.scale | |
}, { | |
x: 0, | |
ease: Power0.easeNone | |
}) | |
); | |
} | |
/** | |
* The animation that moves sticks | |
*/ | |
moveScene() { | |
this.timeline.add( | |
TweenMax.to('#sticks', this.time, { | |
x: '-=180px', | |
ease: Power0.easeNone, | |
repeat: -1, | |
onRepeat: () => { | |
this.rearrange() | |
} | |
}) | |
); | |
} | |
/** | |
* removes the first stick and adds one the the end | |
* this gives the sticks an infinite movement | |
*/ | |
rearrange() { | |
let scale = this.speedUp(); | |
this.timeline.timeScale(scale); | |
this.balltween.timeScale(scale); | |
$('#sticks .stick').first().remove(); | |
new Stick(); | |
} | |
/** | |
* The game speeds up based on score | |
* The GSAP timeScale() function is called on the timeline to speed up the game | |
* This calculates how much shall the game speed up | |
*/ | |
speedUp() { | |
if (this.score > 30) { | |
return 1.8; | |
} | |
if (this.score > 20) { | |
return 1.7; | |
} | |
if (this.score > 15) { | |
return 1.5; | |
} else if (this.score > 12) { | |
return 1.4; | |
} else if (this.score > 10) { | |
return 1.3; | |
} else if (this.score > 8) { | |
return 1.2; | |
} else if (this.score > 5) { | |
return 1.1; | |
} | |
return 1; | |
} | |
/** | |
* Ball bouncing animation | |
* It checks if the ball and stick colors match | |
* And changes the ball color | |
*/ | |
bounce() { | |
this.balltween | |
.to('#ball', this.time / 2, { | |
y: '+=250px', | |
scaleY: 0.7, | |
transformOrigin: "bottom", | |
ease: Power2.easeIn, | |
onComplete: () => { | |
this.checkColor(); | |
} | |
}).to('#ball', this.time / 2, { | |
y: '-=250px', | |
scaleY: 1.1, | |
transformOrigin: "bottom", | |
ease: Power2.easeOut, | |
onStart: () => { | |
while (this.prevColor == this.color) { | |
this.color = (new Color).getRandomColor(); | |
} | |
this.prevColor = this.color; | |
TweenMax.to('#ball', 0.5, { | |
backgroundColor: this.color | |
}); | |
$('#ball').removeClass('red') | |
.removeClass('yellow') | |
.removeClass('purple') | |
.addClass((new Color).colorcodeToName(this.color)); | |
} | |
}) | |
} | |
checkColor() { | |
let ballPos = $('#ball').offset().left + $('#ball').width() / 2; | |
let stickWidth = $('.stick').width(); | |
let score = this.score; | |
$('#sticks .stick').each(function() { | |
if ($(this).offset().left < ballPos && $(this).offset().left > (ballPos - stickWidth)) { | |
if (Color.getColorFromClass($(this)) == Color.getColorFromClass('#ball')) { | |
// if matches increase the score | |
score++; | |
$('#score').text(score); | |
TweenMax.fromTo('#score', 0.5, { | |
scale: 1.5 | |
}, { | |
scale: 1, | |
ease: Elastic.easeOut.config(1.5, 0.5) | |
}); | |
} else { | |
// you loose | |
game.stop(); | |
} | |
} | |
}) | |
this.score = score; | |
} | |
} | |
class Stick { | |
constructor() { | |
this.stick = this.addStick(); | |
} | |
addStick() { | |
this.stick = $('#sticks').append('<div class="stick inactive"></div>'); | |
return this.stick; | |
} | |
} | |
class Color { | |
constructor() { | |
this.colors = ["#FF4571", "#FFD145", "#8260F6"]; | |
this.effects = ["bubble", "triangle", "block"]; | |
this.prevEffect = null; | |
} | |
getRandomColor() { | |
let colorIndex = Math.random() * 3; | |
let color = this.colors[Math.floor(colorIndex)]; | |
return color; | |
} | |
colorcodeToName(color) { | |
let colors = ["#FF4571", "#FFD145", "#8260F6"]; | |
let names = ["red", "yellow", "purple"]; | |
let index = colors.indexOf(color); | |
if (index == -1) return false; | |
return names[index]; | |
} | |
/** | |
* Changes the color of an element | |
* As we as adds verbal name of the color | |
*/ | |
changeColor(el) { | |
let index = el.data("index"); | |
if (index === undefined) { | |
index = 0; | |
} else { | |
index += 1; | |
} | |
if (index == 3) index = 0; | |
el | |
.css('background-color', this.colors[index]) | |
.data('index', index); | |
el.removeClass('red') | |
.removeClass('yellow') | |
.removeClass('purple') | |
.addClass(this.colorcodeToName(this.colors[index])); | |
if (el.hasClass('inactive')) { | |
this.setEffect(el); | |
el.addClass('no-effect'); | |
} | |
el.removeClass('inactive'); | |
} | |
getRandomEffect() { | |
let effectIndex = null; | |
effectIndex = Math.floor(Math.random() * 3); | |
while (effectIndex == this.prevEffect) { | |
effectIndex = Math.floor(Math.random() * 3); | |
} | |
this.prevEffect = effectIndex; | |
return this.effects[effectIndex]; | |
} | |
/** | |
* Adds the effect specific particles to the stick | |
*/ | |
setEffect(el) { | |
let effect = this.getRandomEffect(); | |
el.addClass(effect + '-stick'); | |
for (let i = 1; i <= 14; i++) { | |
if (effect == 'block') { | |
el.append(`<div class="${effect} ${effect}-${i}"><div class="inner"></div><div class="inner inner-2"></div></div>`); | |
} else { | |
el.append(`<div class="${effect} ${effect}-${i}"></div>`); | |
} | |
} | |
} | |
/** | |
* Since the ball and sticks have several classes | |
* This method searches for the color class | |
* @param el [DOM element] | |
* @return {string} class name | |
*/ | |
static getColorFromClass(el) { | |
let classes = $(el).attr('class').split(/\s+/); | |
for (var i = 0, len = classes.length; i < len; i++) { | |
if (classes[i] == 'red' || classes[i] == 'yellow' || classes[i] == 'purple') { | |
return classes[i]; | |
} | |
} | |
} | |
} | |
class Animation { | |
/** | |
* Creates and positions the small glow elements on the screen | |
*/ | |
static generateSmallGlows(number) { | |
let h = $(window).height(); | |
let w = $(window).width(); | |
let scale = (w > h) ? h / 800 : w / 1200; | |
h = h / scale; | |
w = w / scale; | |
for (let i = 0; i < number; i++) { | |
let left = Math.floor(Math.random() * w); | |
let top = Math.floor(Math.random() * (h / 2)); | |
let size = Math.floor(Math.random() * 8) + 4; | |
$('.small-glows').prepend('<div class="small-glow"></div>'); | |
let noise = $('.small-glows .small-glow').first(); | |
noise.css({ | |
left: left, | |
top: top, | |
height: size, | |
width: size | |
}); | |
} | |
} | |
/** | |
* Creates the animations for sticks | |
* The effects is chosen by random | |
* And one of the three functions is | |
* Called accordingly | |
*/ | |
playBubble(el) { | |
let bubble = new TimelineMax(); | |
bubble.staggerFromTo(el.find('.bubble'), 0.3, { | |
scale: 0.1 | |
}, { | |
scale: 1 | |
}, 0.03) | |
bubble.staggerTo(el.find('.bubble'), 0.5, { | |
y: '-=60px', | |
yoyo: true, | |
repeat: -1 | |
}, 0.03); | |
} | |
playTriangle(el) { | |
let triangle = new TimelineMax(); | |
triangle.staggerFromTo(el.find('.triangle'), 0.3, { | |
scale: 0.1 | |
}, { | |
scale: 1 | |
}, 0.03) | |
.staggerTo(el.find('.triangle'), 1.5, { | |
cycle: { | |
rotationY: [0, 360], | |
rotationX: [360, 0], | |
}, | |
repeat: -1, | |
repeatDelay: 0.1 | |
}, 0.1); | |
} | |
playBlock(el) { | |
let block = new TimelineMax(); | |
let block2 = new TimelineMax({ | |
delay: 0.69 | |
}); | |
block.staggerFromTo(el.find('.block'), 0.3, { | |
scale: 0.1 | |
}, { | |
scale: 1 | |
}, 0.03) | |
.staggerTo(el.find('.block .inner:not(.inner-2)'), 1, { | |
cycle: { | |
x: ["+200%", "-200%"] | |
}, | |
repeat: -1, | |
repeatDelay: 0.6, | |
}, 0.1); | |
block2.staggerTo(el.find('.block .inner-2'), 1, { | |
cycle: { | |
x: ["+200%", "-200%"] | |
}, | |
repeat: -1, | |
repeatDelay: 0.6, | |
}, 0.1); | |
} | |
static sceneAnimation() { | |
const speed = 15; // uses it's local speed | |
// animates the small glows in a circular motion | |
$('.small-glow').each(function() { | |
let speedDelta = Math.floor(Math.random() * 8); | |
let radius = Math.floor(Math.random() * 20) + 20; | |
TweenMax.to($(this), speed + speedDelta, { | |
rotation: 360, | |
transformOrigin: "-" + radius + "px -" + radius + "px", | |
repeat: -1, | |
ease: Power0.easeNone | |
}); | |
}) | |
var wavet = TweenMax.to('.top_wave', speed * 1.7 / 42, { | |
backgroundPositionX: '-=54px', | |
repeat: -1, | |
ease: Power0.easeNone | |
}); | |
var wave1 = TweenMax.to('.wave1', speed * 1.9 / 42, { | |
backgroundPositionX: '-=54px', | |
repeat: -1, | |
ease: Power0.easeNone | |
}); | |
var wave2 = TweenMax.to('.wave2', speed * 2 / 42, { | |
backgroundPositionX: '-=54px', | |
repeat: -1, | |
ease: Power0.easeNone | |
}); | |
var wave3 = TweenMax.to('.wave3', speed * 2.2 / 42, { | |
backgroundPositionX: '-=54px', | |
repeat: -1, | |
ease: Power0.easeNone | |
}); | |
var wave4 = TweenMax.to('.wave4', speed * 2.4 / 42, { | |
backgroundPositionX: '-=54px', | |
repeat: -1, | |
ease: Power0.easeNone | |
}); | |
var mount1 = TweenMax.to('.mount1', speed * 8, { | |
backgroundPositionX: '-=1760px', | |
repeat: -1, | |
ease: Power0.easeNone | |
}); | |
var mount2 = TweenMax.to('.mount2', speed * 10, { | |
backgroundPositionX: '-=1782px', | |
repeat: -1, | |
ease: Power0.easeNone | |
}); | |
var clouds = TweenMax.to('.clouds', speed * 3, { | |
backgroundPositionX: '-=1001px', | |
repeat: -1, | |
ease: Power0.easeNone | |
}); | |
} | |
} | |
var game = new Game(); | |
var animation = new Animation(); | |
var color = new Color(); | |
var userAgent = window.navigator.userAgent; | |
Animation.generateSmallGlows(20); | |
$(document).ready(function() { | |
//game.showResult(); | |
game.scaleScreen(); | |
game.intro(); | |
//game.start(); | |
//game.bounce(); | |
if ($(window).height() < 480) { | |
$('.play-full-page').css('display', 'block'); | |
} | |
}) | |
$(document).on('click', '.stick', function() { | |
color.changeColor($(this)); | |
if ($(this).hasClass('no-effect')) { | |
if ($(this).hasClass('bubble-stick')) { | |
animation.playBubble($(this)); | |
} else if ($(this).hasClass('triangle-stick')) { | |
animation.playTriangle($(this)); | |
} else if ($(this).hasClass('block-stick')) { | |
animation.playBlock($(this)); | |
} | |
$(this).removeClass('no-effect'); | |
} | |
}); | |
$(document).on('click', '.section-2 .bar', function() { | |
color.changeColor($(this)); | |
}); | |
$(window).resize(function() { | |
if (!userAgent.match(/iPad/i) && !userAgent.match(/iPhone/i)) { | |
game.scaleScreenAndRun(); | |
} | |
}); | |
$(window).on("orientationchange", function() { | |
game.scaleScreenAndRun(); | |
}); |
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script> |
@mixin bubble_shape ($width: 4px) { | |
border-radius: 50%; | |
position: absolute; | |
background-color: rgba(0, 0, 0, 0.12); | |
border-color: rgba(0, 0, 0, 0.12); | |
border-width: $width; | |
} | |
@mixin bubble ($size, $top, $left) { | |
height: $size; | |
width: $size; | |
top: $top; | |
left: $left; | |
} | |
@mixin bubble_hollow ($size, $top, $left) { | |
@include bubble ($size, $top, $left); | |
background-color: transparent; | |
border-style: solid; | |
} | |
@mixin triangle ($height, $width, $top, $left) { | |
border-left: ($height/2) solid transparent; | |
border-right: ($height/2) solid transparent; | |
border-bottom: $width solid rgba(0, 0, 0, 0.12); | |
background-color: transparent; | |
left: $left; | |
top: $top; | |
} | |
@mixin triangle_hollow ($height, $width, $top, $left) { | |
@include triangle( $height, $width, $top, $left ); | |
&:after { | |
content: " "; | |
display: block; | |
position: absolute; | |
border-left: ($height/4-1) solid transparent; | |
border-right: ($height/4-1) solid transparent; | |
border-bottom: ($height/2-3) solid transparent; | |
top: 6px; | |
left: -($width/4); | |
transition: border-bottom-color 0.4s ease; | |
.red & { | |
border-bottom-color: #fc5c82; | |
} | |
.yellow & { | |
border-bottom-color: #fcd45c; | |
} | |
.purple & { | |
border-bottom-color: #9174f5; | |
} | |
} | |
} | |
@mixin block ($height, $width, $top, $left) { | |
height: $height; | |
width: $width; | |
top: $top; | |
left: $left; | |
} | |
@mixin block_hollow ($height, $width, $top, $left) { | |
@include block ($height, $width, $top, $left); | |
& .inner { | |
background-color: transparent !important; | |
border-style: solid; | |
box-sizing: border-box; | |
} | |
} | |
.bubble { | |
@include bubble_shape; | |
} | |
.bubble-1 { | |
@include bubble(15px, 21px, 59px); | |
} | |
.bubble-2 { | |
@include bubble(27px, 36px, 16px); | |
} | |
.bubble-3 { | |
@include bubble(21px, 63px, 49px); | |
} | |
.bubble-4 { | |
@include bubble(15px, 98px, 37px); | |
} | |
.bubble-5 { | |
@include bubble_hollow(5px, 116px, 20px); | |
} | |
.bubble-6 { | |
@include bubble(6px, 128px, 63px); | |
} | |
.bubble-7 { | |
@include bubble(27px, 150px, 52px); | |
} | |
.bubble-8 { | |
@include bubble(19px, 154px, 18px); | |
} | |
.bubble-9 { | |
@include bubble(10px, 189px, 13px); | |
} | |
.bubble-10 { | |
@include bubble_hollow(5px, 199px, 52px); | |
} | |
.bubble-11 { | |
@include bubble(21px, 220px, 29px); | |
} | |
.bubble-12 { | |
@include bubble(21px, 263px, 48px); | |
} | |
.bubble-13 { | |
@include bubble_hollow(5px, 275px, 16px); | |
} | |
.bubble-14 { | |
@include bubble(15px, 296px, 34px); | |
} | |
.triangle { | |
position: absolute; | |
background-color: rgba(0, 0, 0, 0.12); | |
border-color: rgba(0, 0, 0, 0.12); | |
} | |
.triangle-1 { | |
@include bubble_shape; | |
@include bubble(10px, 22px, 55px); | |
} | |
.triangle-2 { | |
@include triangle_hollow(16px, 14px, 27px, 15px); | |
} | |
.triangle-3 { | |
@include triangle_hollow(24px, 19px, 60px, 43px); | |
} | |
.triangle-4 { | |
@include bubble_shape(3px); | |
@include bubble_hollow(8px, 61px, 17px); | |
} | |
.triangle-5 { | |
@include triangle(10px, 8px, 101px, 25px); | |
transform: rotate(180deg); | |
} | |
.triangle-6 { | |
@include triangle(8px, 6px, 103px, 60px); | |
transform: rotate(-90deg); | |
} | |
.triangle-7 { | |
@include triangle(25px, 19px, 126px, 17px); | |
transform: rotate(180deg); | |
} | |
.triangle-8 { | |
@include triangle(21px, 16px, 149px, 50px); | |
} | |
.triangle-9 { | |
@include triangle(11px, 8px, 177px, 21px); | |
} | |
.triangle-10 { | |
@include bubble_shape; | |
@include bubble(10px, 177px, 60px); | |
} | |
.triangle-11 { | |
@include triangle(18px, 13px, 213px, 33px); | |
transform: rotate(180deg); | |
} | |
.triangle-12 { | |
@include bubble_shape(2px); | |
@include bubble_hollow(10px, 233px, 65px); | |
} | |
.triangle-13 { | |
@include bubble_shape; | |
@include bubble(10px, 250px, 22px); | |
} | |
.triangle-14 { | |
@include triangle_hollow(16px, 14px, 270px, 45px); | |
transform: rotate(180deg); | |
} | |
.stick { | |
.block { | |
position: absolute; | |
overflow: hidden; | |
z-index: 999; | |
border-radius: 7px; | |
.inner { | |
border-radius: 7px; | |
background-color: rgba(0, 0, 0, 0.12); | |
border-color: rgba(0, 0, 0, 0.12); | |
border-width: 3px; | |
height: 100%; | |
width: 100%; | |
position: absolute; | |
} | |
&:nth-child(2n+1) .inner-2 { | |
left: -200%; | |
} | |
&:nth-child(2n+2) .inner-2 { | |
left: 200%; | |
} | |
} | |
.block-1 { | |
@include block_hollow(16px, 31px, 16px, 30px); | |
} | |
.block-2 { | |
@include block(14px, 42px, 50px, 15px); | |
} | |
.block-3 { | |
@include block(18px, 9px, 73px, 64px); | |
} | |
.block-4 { | |
@include block(9px, 14px, 84px, 26px); | |
} | |
.block-5 { | |
@include block(15px, 15px, 109px, 45px); | |
.inner { | |
border-radius: 50%; | |
} | |
} | |
.block-6 { | |
@include block(9px, 27px, 135px, 19px); | |
} | |
.block-7 { | |
@include block(12px, 12px, 144px, 60px); | |
.inner { | |
border-radius: 50%; | |
border-style: solid; | |
box-sizing: border-box; | |
background-color: transparent; | |
} | |
} | |
.block-8 { | |
@include block(27px, 14px, 164px, 24px); | |
} | |
.block-9 { | |
@include block(8px, 8px, 188px, 64px); | |
} | |
.block-10 { | |
@include block_hollow(12px, 22px, 219px, 11px); | |
} | |
.block-11 { | |
@include block(22px, 22px, 226px, 50px); | |
.inner { | |
border-radius: 50%; | |
} | |
} | |
.block-12 { | |
@include block(18px, 9px, 248px, 26px); | |
} | |
.block-13 { | |
@include block(8px, 8px, 278px, 50px); | |
.inner { | |
border-radius: 50%; | |
} | |
} | |
.block-14 { | |
@include block_hollow(12px, 22px, 297px, 18px); | |
} | |
.block-15 { | |
@include block(9px, 27px, 307px, 48px); | |
} | |
} | |
body { | |
background-color: #28DAD4; | |
margin: 0; | |
padding: 0; | |
overflow: hidden; | |
font-family: 'Roboto', sans-serif; | |
} | |
a { | |
cursor: url(https://greghub.github.io/coloron/public/svg/cursor.svg), pointer; | |
&:focus, &:active { | |
cursor: url(https://greghub.github.io/coloron/public/svg/cursor-tap.svg), pointer; | |
} | |
} | |
.container { | |
position: fixed; | |
left: 0; | |
top: 0; | |
height: 100%; | |
width: 100%; | |
} | |
.waves, .mounts { | |
position: absolute; | |
width: 100%; | |
left: 0; | |
bottom: 0; | |
} | |
.waves div, .mounts div { | |
position: absolute; | |
width: 100%; | |
} | |
.clouds { | |
position: absolute; | |
width: 100%; | |
left: 0; | |
top: 77px; | |
height: 151px; | |
background: url(https://greghub.github.io/coloron/public/svg/clouds.svg) repeat-x; | |
background-position-x: 170px; | |
} | |
.top_wave { | |
background: url(https://greghub.github.io/coloron/public/svg/top_wave.png) repeat-x 0 -1px; | |
height: 35px; | |
bottom: 0; | |
z-index: 10001; | |
} | |
.wave1 { | |
background: url(https://greghub.github.io/coloron/public/svg/wave1.svg) repeat-x; | |
height: 150px; | |
bottom: 0; | |
z-index: 23; | |
} | |
.wave2 { | |
background: url(https://greghub.github.io/coloron/public/svg/wave2.svg) repeat-x; | |
height: 180px; | |
bottom: 30px; | |
z-index: 22; | |
} | |
.wave3 { | |
background: url(https://greghub.github.io/coloron/public/svg/wave3.svg) repeat-x; | |
height: 180px; | |
bottom: 90px; | |
z-index: 21; | |
} | |
.wave4 { | |
background: url(https://greghub.github.io/coloron/public/svg/wave4.svg) repeat-x; | |
height: 180px; | |
bottom: 120px; | |
z-index: 20; | |
} | |
.mount1 { | |
background: url(https://greghub.github.io/coloron/public/svg/mount1.svg) repeat-x; | |
height: 150px; | |
bottom: 280px; | |
z-index: 11; | |
} | |
.mount2 { | |
background: url(https://greghub.github.io/coloron/public/svg/mount2.svg) repeat-x; | |
height: 150px; | |
bottom: 290px; | |
z-index: 10; | |
} | |
.noise { | |
position: fixed; | |
width: 100%; | |
height: 100%; | |
left: 0; | |
top: 0; | |
z-index: 1010; | |
background: url(https://greghub.github.io/coloron/public/svg/noise.png); | |
} | |
.glow { | |
position: absolute; | |
left: -350px; | |
top: -350px; | |
width: 800px; | |
height: 800px; | |
background-color: rgba(81, 237, 200, 0.34); | |
border-radius: 50%; | |
box-shadow: 0 0 100px 100px rgba(81, 237, 200, 0.34); | |
z-index: 1010; | |
} | |
.sun { | |
position: relative; | |
left: 50%; | |
top: 50%; | |
width: 1px; | |
height: 1px; | |
background-color: rgba(255, 227, 69, 1); | |
border-radius: 50%; | |
box-shadow: 0 0 32px 32px rgba(255, 227, 69, 1), | |
0 0 150px 150px rgba(103, 244, 210, 0.4); | |
} | |
.small-glow { | |
z-index: 99; | |
width: 12px; | |
height: 12px; | |
border-radius: 50%; | |
position: absolute; | |
background-color: rgba(255, 255, 255, 0.34); | |
box-shadow: 0 0 1px 1px rgba(255, 255, 255, 0.34); | |
} | |
.small-glow.yellow { | |
background-color: rgba(255, 227, 69, 0.34); | |
box-shadow: 0 0 4px 4px rgba(255, 227, 69, 0.34); | |
} | |
.sticks { | |
z-index: 1011; | |
outline: none; | |
-webkit-tap-highlight-color: rgba(0,0,0,0); | |
} | |
.stick { | |
height: 362px; | |
width: 90px; | |
border-radius: 14px; | |
background-image: url(https://greghub.github.io/coloron/public/svg/noise.png); | |
position: relative; | |
overflow: hidden; | |
float: left; | |
margin-right: 90px; | |
transition: background-color 0.4s ease; | |
cursor: url(https://greghub.github.io/coloron/public/svg/cursor.svg), pointer; | |
&:focus, &:active { | |
cursor: url(https://greghub.github.io/coloron/public/svg/cursor-tap.svg), pointer; | |
} | |
} | |
.stick.red { | |
background-color: #FF4571; | |
} | |
.stick.yellow { | |
background-color: #FFD145; | |
} | |
.stick.purple { | |
background-color: #8260F6; | |
} | |
.stick.inactive { | |
background-color: #4C4660; | |
} | |
.ball, .ball-demo { | |
background: url(https://greghub.github.io/coloron/public/svg/ball.svg) right bottom; | |
background-size: 64px 64px; | |
width: 53px; | |
height: 53px; | |
z-index: 1011; | |
background-color: #FF4571; | |
border-radius: 50%; | |
} | |
.ball { | |
margin-bottom: 250px; | |
} | |
.controls { | |
z-index: 999999; | |
position: relative | |
} | |
.game-full-flex { | |
position: fixed; | |
display: none; // gets updated to flex with JS | |
flex-direction: column; | |
justify-content: space-between; | |
align-items: center; | |
width: 100%; | |
height: 100%; | |
top: 0; | |
left: 0; | |
z-index: 9998; | |
} | |
.start-game { | |
.start-game-top { | |
min-height: 20%; | |
.play-full-page { | |
display: none; | |
border: 3px solid #fff; | |
border-radius: 100px; | |
color: #fff; | |
width: 260px; | |
height: 50px; | |
font-size: 28px; | |
text-align: center; | |
font-weight: 900; | |
letter-spacing: -1px; | |
line-height: 52px; | |
text-decoration: none; | |
text-transform: uppercase; | |
margin-top: 24px; | |
&:hover { | |
opacity: 0.7; | |
} | |
} | |
} | |
.logo-holder { | |
width: 513px; | |
height: 162px; | |
background-color: #4C4660; | |
border: 4px solid #FF4571; | |
border-radius: 68px; | |
text-align: center; | |
margin-top: -10%; | |
.logo { | |
color: #fff; | |
text-transform: uppercase; | |
font-weight: 900; | |
font-size: 100px; | |
letter-spacing: -0.1em; | |
margin-top: 10px; | |
margin-bottom: 4px; | |
text-align: center; | |
span { | |
margin-left: -8px; | |
margin-right: -8px; | |
} | |
} | |
.play-button { | |
display: inline-block; | |
background-color: #FF4571; | |
border: 4px solid #fff; | |
border-radius: 100px; | |
color: #fff; | |
width: 200px; | |
height: 56px; | |
font-size: 42px; | |
text-align: center; | |
font-weight: 900; | |
letter-spacing: -3px; | |
line-height: 56px; | |
text-decoration: none; | |
&:hover { | |
background-color: lighten(#FF4571, 5%); | |
} | |
} | |
.hint { | |
color: #fff; | |
font-size: 20px; | |
span { | |
color: #FF4571; | |
} | |
} | |
} | |
.how-to-play { | |
display: flex; | |
justify-content: space-around; | |
width: 100%; | |
.section-1, .section-3 { | |
flex: 1; | |
.content { | |
justify-content: center; | |
} | |
} | |
h4 { | |
color: #fff; | |
font-weight: 400; | |
font-size: 22px; | |
text-align: center; | |
} | |
.content { | |
height: 200px; | |
position: relative; | |
display: flex; | |
justify-content: space-around; | |
} | |
.bar { | |
width: 60px; | |
border-radius: 7px; | |
margin-top: auto; | |
transition: background-color 0.4s ease; | |
&.bar-1 { | |
height: 180px; | |
background: #FF4571; | |
} | |
&.bar-2 { | |
height: 120px; | |
background: #FFD145; | |
} | |
&.bar-3 { | |
height: 150px; | |
background: #4C4660; | |
} | |
} | |
.section-2 { | |
.bar { | |
cursor: url(https://greghub.github.io/coloron/public/svg/cursor.svg), pointer; | |
&:focus, &:active { | |
cursor: url(https://greghub.github.io/coloron/public/svg/cursor-tap.svg), pointer; | |
} | |
} | |
} | |
.section-3 { | |
.ball-demo { | |
background-color: #815FF8; | |
} | |
.bar-1 { | |
height: 120px; | |
background-color: #815FF8; | |
} | |
} | |
} | |
} | |
.stop-game { | |
justify-content: center; | |
.score-container { | |
background-color: #4C4660; | |
width: 433px; | |
height: 386px; | |
border-radius: 38px; | |
text-align: center; | |
h1 { | |
color: #fff; | |
text-transform: uppercase; | |
letter-spacing: -0.1em; | |
margin-top: 20px; | |
margin-bottom: 4px; | |
font-size: 64px; | |
} | |
.final-score { | |
color: #FFE345; | |
font-weight: 900; | |
font-size: 130px; | |
letter-spacing: -6px; | |
line-height: 110px; | |
} | |
.result { | |
color: #FF4571; | |
text-transform: uppercase; | |
font-weight: 700; | |
font-size: 30px; | |
} | |
h4 { | |
color: #fff; | |
margin-top: 12px; | |
} | |
.tweet { | |
background: #fff; | |
padding: 8px 20px; | |
border-radius: 4px; | |
color: #55ACEE; | |
text-decoration: none; | |
font-size: 18px; | |
line-height: 24px; | |
display: inline-block; | |
&:hover { | |
background-color: #55ACEE; | |
color: #fff; | |
} | |
i { | |
font-size: 24px; | |
top: 2px; | |
right: 2px; | |
position: relative; | |
} | |
} | |
.play-again { | |
background-color: #FF4571; | |
border: 2px solid #fff; | |
color: #fff; | |
text-decoration: none; | |
text-transform: uppercase; | |
font-weight: 900; | |
letter-spacing: -1px; | |
font-size: 26px; | |
padding: 6px 24px; | |
border-radius: 22px; | |
margin: 6px 4px; | |
display: inline-block; | |
&:hover { | |
background-color: lighten(#FF4571, 5%); | |
} | |
} | |
.main-menu { | |
background-color: #44BFA3; | |
border: 2px solid #fff; | |
color: #fff; | |
text-decoration: none; | |
text-transform: uppercase; | |
font-weight: 900; | |
letter-spacing: -1px; | |
font-size: 26px; | |
padding: 6px 24px; | |
border-radius: 22px; | |
margin: 6px 4px; | |
display: inline-block; | |
&:hover { | |
background-color: lighten(#44BFA3, 5%); | |
} | |
} | |
} | |
} | |
.scene { | |
display: flex; | |
flex-direction: column; | |
justify-content: space-between; | |
position: fixed; | |
z-index: 9997; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
.ball-holder { | |
flex: 1; | |
display: flex; | |
flex-direction: column; | |
justify-content: flex-end; | |
padding-left: 558px; | |
} | |
.score { | |
position: fixed; | |
right: 54px; | |
top: 20px; | |
color: #33485F; | |
font-size: 90px; | |
font-weight: 900; | |
letter-spacing: -0.1em; | |
} | |
.learn-to-play { | |
z-index: 9999; | |
display: inline-block; | |
text-align: center; | |
position: relative; | |
top: 20%; | |
font-size: 48px; | |
color: rgba(255,255,255,0.85); | |
font-weight: 700; | |
letter-spacing: -2px; | |
opacity: 0; | |
} | |
} | |
.splash { | |
display: none; | |
} | |
@media print { | |
.splash { | |
display: block; | |
position: fixed; | |
z-index: 99999; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
background-color: #28DAD4; | |
background-image: url(https://greghub.github.io/coloron/public/images/coloron-image.png); | |
background-size: auto 100%; | |
background-repeat: no-repeat; | |
background-position: center; | |
} | |
} | |
.nominee { | |
position: fixed; | |
right: 0; | |
top: 0; | |
z-index: 9999; | |
} |
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700,900" rel="stylesheet" /> | |
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" /> |
A game made to inspire developers to use GSAP, ES6 and Flexbox
A Pen by Serge Antonov on CodePen.