Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div class="start-screen">
	<div class="game-data">
		<span class="name"><span>Color</span> <span>Collision</span></span>
	</div>
	<span class="info">
		<span class="highlight _1">FOCUS</span> is the weapon.
		Don't make the wrong move.
		Tap / click to change the color and save the central balls.
		Because if the central balls don't stay alive <span class="highlight _2">YOU DIE</span>.
		Better play <span class="highlight _3">SAFE</span> and make a new highscore.
	</span>
	<button class="btn play">PLAY</button>
</div>

<span class="retry-text hide" data-splitting>Game Over!</span>
<button class="retry-btn hide" data-splitting>RETRY</button>
<canvas data-canvas></canvas>


<div class="support">
	<a href="https://github.com/devloop01/color-collision" target="_blank"><span>fork at</span><i class="fab fa-github"></i></a>
</div>

              
            
!

CSS

              
                * {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}

body {
	width: 100%;
	height: 100vh;
	overflow: hidden;
	display: flex;
	justify-content: center;
	align-items: center;
	font-family: Montserrat, sans-serif;
	background: radial-gradient(#e74c3c 4px, transparent 4px),
		radial-gradient(#e74c3c 4px, transparent 4px),
		linear-gradient(#222 4px, transparent 0),
		linear-gradient(
			45deg,
			transparent 74px,
			transparent 75px,
			#3498db 75px,
			#3498db 76px,
			transparent 77px,
			transparent 109px
		),
		linear-gradient(
			-45deg,
			transparent 75px,
			transparent 76px,
			#3498db 76px,
			#3498db 77px,
			transparent 78px,
			transparent 109px
		),
		#222;
	background-size: 109px 109px, 109px 109px, 100% 6px, 109px 109px,
		109px 109px;
	background-position: 54px 55px, 0px 0px, 0px 0px, 0px 0px, 0px 0px;
}

.highlight {
	font-weight: 900;
	&._1 {
		color: #3498db;
	}
	&._2 {
		color: #e74c3c;
	}
	&._3 {
		color: #21c758;
	}
}

.retry-text {
	position: absolute;
	top: 38%;
	pointer-events: none;
	user-select: none;

	.char {
		font-size: 12vmin;
		font-weight: 800;
		letter-spacing: 5px;

		&::before {
			pointer-events: none;
			content: attr(data-char);
			position: absolute;
			visibility: visible;
			color: #fff;
			transition: all 200ms cubic-bezier(0.1, 0.1, 0.33, 1);
			transition-delay: calc(0.16s + (0.03s * (var(--char-index))));
		}
	}
}

.char {
	overflow: hidden;
	color: transparent;
}

.hide .char::before {
	transform: translateY(50%);
	opacity: 0;
}

.show .char::before {
	transform: translateY(0);
	opacity: 1;
}

.retry-btn {
	position: absolute;
	top: 58%;
	background: transparent;
	font-size: 1.1em;
	border: none;
	outline: none;
	color: #fff;
	width: 120px;
	height: 50px;
	border: 2px solid rgba(255, 255, 255, 0.6);
	cursor: pointer;
	transition: all 180ms cubic-bezier(0.075, 0.82, 0.165, 1);
	user-select: none;
	&.hide {
		pointer-events: none;
		opacity: 0;
	}
	&.show .char {
		color: #fff;
	}
	&:hover {
		background: rgba(255, 255, 255, 0.6);
		backdrop-filter: blur(2px);
	}
}

.retry-btn .char {
	font-size: 0.9em;
	font-weight: 900;
}

.retry-btn:focus {
	outline: none;
}

.start-screen {
	position: absolute;
	background: rgba(0, 0, 0, 0.45);
	backdrop-filter: blur(20px);
	width: 450px;
	height: 250px;
	padding: 10px;
	display: flex;
	flex-direction: column;
	transition: all 300ms cubic-bezier(0.075, 0.82, 0.165, 1);
	&.hide {
		opacity: 0;
		transform: translateY(100%);
		pointer-events: none;
		z-index: -1;
	}

	.game-data {
		width: 100%;
		height: 30%;
		display: flex;
		justify-content: center;
		align-items: center;
		.name {
			font-size: 2rem;
			color: #fff;
			font-weight: 900;
			text-transform: uppercase;
			letter-spacing: 6px;

			span:nth-child(1) {
				color: #3498db;
			}
			span:nth-child(2) {
				color: #e74c3c;
			}
		}
	}
	.info {
		width: 100%;
		height: 50%;
		padding: 5px 10px;
		font-size: 14px;
		line-height: 20px;
		color: rgba(255, 255, 255, 0.7);
	}

	.btn.play {
		position: relative;
		width: 120px;
		height: 50px;
		border: none;
		border: 2px solid rgba(255, 255, 255, 0.6);
		cursor: pointer;
		letter-spacing: 2px;
		font-weight: 900;
		background: none;
		color: #fff;
		align-self: center;
		margin-top: -10px;
		transition: all 400ms cubic-bezier(0.8, 0, 0.33, 1);
		overflow: hidden;
		&::after,
		&::before {
			position: absolute;
			width: 100%;
			transition: all 400ms cubic-bezier(0.8, 0, 0.33, 1);
			z-index: -1;
		}
		&::before {
			content: "";
			height: 0%;
			left: 0;
			bottom: 0;
			border-radius: 50% 50% 0 0;
			background: #fff;
		}
		&::after {
			content: "PLAY";
			height: 180%;
			right: 0;
			top: 0;
			color: #000;
			transform: translateY(-100%);
			font-size: 1.4em;
		}
		&:hover {
			color: transparent;
			border-color: #fff;
			&::after {
				transform: translateY(15%);
			}
			&::before {
				height: 180%;
			}
		}
		&:focus {
			transform: scale(0.9);
			outline: none;
		}
	}
}



.support{
	position: absolute;
	right: 5px;
	bottom: 5px;
	padding: 5px 0;
	a{
		margin: 0 10px;
		color: #fff;
		font-size: 1.8rem;
		transition: all 400ms ease;
		text-decoration: none;
		background: rgba(0, 0, 0, 0.5);
		backdrop-filter: blur(2px);
		padding: 8px;
		display: flex;
		align-items: center;
		user-select: none;
		span{
			text-transform: uppercase;
			font-size: 1rem;
			margin-right: 10px;
		}
		&:hover{
			background: rgba(0, 0, 0, 1);
		}
	}
}

              
            
!

JS

              
                console.clear()

// Utility Functions -->

// This func. gets a random float between the given range
function randomFloatFromRange(min, max){
	return (Math.random() * (max - min + 1) + min);
}

// This func. gets a random item from a given array
function randomFromArray(arr){
	return arr[Math.floor(Math.random() * arr.length)]
}

// This func. gets the distance between two given points
function getDist(x1, y1, x2, y2){
	return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2))
}


// PARTICLE CLASS
class Particle{
	constructor(canvas, ctx, x, y, radius, color, velX, velY){
		this.canvas = canvas
		this.ctx = ctx
		this.x = x
		this.y = y
		this.velocity = {
			x: (Math.random() - 0.5) * velX,
			y: (Math.random() - 0.5) * velY,
		}
		this.radius = radius
		this.color = color
		this.timeToLive = 250
		this.opacity = 1
		this.gravity = 0.25
	}
	draw(){ // This func. draws the particle
		this.ctx.save()
		this.ctx.beginPath()
		this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false)
		this.ctx.fillStyle = this.color
		this.ctx.shadowColor = this.color
		this.shadowBlur = 25
		this.ctx.globalAlpha = this.opacity
		this.ctx.fill()
		this.ctx.closePath()
		this.ctx.restore()        
	}
	update(){ // This func. updates the particle
		this.x += this.velocity.x
		this.y += this.velocity.y
		this.velocity.y += this.gravity

		this.timeToLive -= 1
		this.opacity -= 1 / this.timeToLive
		this.draw()
	}
}

// BALL CLASS
class Ball{
	constructor(canvas, ctx, x, y, radius, color, particlesArr, velX, velY, dontCheck){
		this.canvas = canvas
		this.ctx = ctx
		this.x = x
		this.y = y
		this.radius = radius
		this.color = color
		this.velocity = {
			x: velX || 0,
			y: velY || 0
		}
		this.acc = 0.01
		this.origin = { x: x, y: y }
		this.dontCheck = dontCheck
		this.opacity = 1
		this.particlesArr = particlesArr
		this.collided = false
	}
	draw(){ // This func. draws the ball
		this.ctx.save()
		this.ctx.beginPath()
		this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false)
		this.ctx.fillStyle = this.color
		this.ctx.shadowColor = this.color
		this.shadowBlur = 25
		this.ctx.shadowOffsetX = 0;
		this.ctx.shadowOffsetY = 0;
		this.ctx.globalAlpha = this.opacity
		this.ctx.fill()
		this.ctx.closePath()
		this.ctx.restore()
	}
	// This func. updates the ball. The first argument of this func. takes an array where all the insrtance of balls are stored
	// The second argument is opotional [default is False]. If set to true then the position of the ball changes with its respective Velocity
	update(ballsArr, updateVel = false){
		if(this.origin.y <= 0){
			this.y += this.velocity.y
		}
		else if(this.origin.y >= this.canvas.height){
			this.y -= this.velocity.y
		}
		if(updateVel == true){
			this.y += this.velocity.y
			this.x += this.velocity.x
		}

		this.collisionDetect(ballsArr)
		this.draw()
	}
	// This func. is used to detect the collisions bettween any two balls
	// The func. takes an argument which is the array where al the balls are stored
	collisionDetect(ballsArr){
		for(let i = 0; i < ballsArr.length; i++){
			if(this === ballsArr[i] || this.dontCheck) continue
			let distBetweenPoints = getDist(this.x, this.y, ballsArr[i].x, ballsArr[i].y) - this.radius * 2
			if(distBetweenPoints < 0){
				if(this.color == ballsArr[i].color){
					for(let j = 0; j < Math.floor(randomFloatFromRange(20, 25)); j++){
						this.break(this.particlesArr, 0.4, 0.8)
						this.collided = true
					}
					this.opacity = 0
				}
				else if(this.color != ballsArr[i].color){
					for(let j = 0; j < Math.floor(randomFloatFromRange(40, 55)); j++){
						ballsArr.forEach((ball) => {
							ball.opacity = 0
							this.break(this.particlesArr, 2, 5, ball.x, ball.y, ball.color)
						})
						this.particlesArr.forEach((particle) => {
							particle.gravity = 0
						})
					}
				}
			}
		}
	}
	// This func. is used to detect if the ball hits any of the corners of the canvas
	// If hits any of the canvas sides then the ball would change its velocity direction
	edgeDetect(){
		if (this.y + this.radius + this.velocity.y > this.canvas.height) {
			this.velocity.y *= -1
		}
		else if(this.y - this.radius <= 0){
			this.velocity.y *= -1
		}

		if (this.x + this.radius + this.velocity.x > this.canvas.width) {
			this.velocity.x *= -1
		}
		else if (this.x - this.radius <= 0) {
			this.velocity.x *= -1
		}
	}
	// This function is used to show that when any ball hits each other then they create many small particles [Which looks kinda like sparks]
	// This func. takes 6 arguments [too many]
	// The first accepts an array where the sparks OR the small particles would be stored
	// The second and the third argument is nothing but accepts a min and max radius
	// The forth and fifth args. tahes where the sparks would be spawned
	// The sixth is nothing but 'c' which means color. I want to make the sparks the same color as the ball
	break(arr, minRadius, maxRadius, x, y, c){
		var randRadius = randomFloatFromRange(minRadius, maxRadius)
		var randVel = {
			x: randomFloatFromRange(-20, 20),
			y: randomFloatFromRange(-20, 20),
		}
		if(this.origin.y <= 0){
			let spawnX , spawnY
			let color
			if(x && y){
				spawnX = x
				spawnY = y
				color = c
			}else{
				spawnX = this.x
				spawnY = this.y + this.radius
				color = this.color
			}
			arr.push(
				new Particle(
					this.canvas, this.ctx,
					spawnX, spawnY,
					randRadius, color , randVel.x, randVel.y
				)
			)
		}else{
			let spawnX , spawnY
			let color
			if(x && y){
				spawnX = x
				spawnY = y
				color = c
			}else{
				spawnX = this.x
				spawnY = this.y - this.radius
				color = this.color
			}
			arr.push(
				new Particle(
					this.canvas, this.ctx,
					spawnX, spawnY,
					randRadius, color , randVel.x, randVel.y
				)
			)
		}
	}
	// When this func. is called with two colors passed as args. then it swaps the color between the two color provided
	change(colorDefault, colorTochange){
		if(this.color != colorDefault){
			this.color = colorDefault
		}else{
			this.color = colorTochange
		}
	}
}

// calling the spliiting function
Splitting()

// Selecting the canvas
const canvas = document.querySelector('[data-canvas]')
// getting its context
const ctx = canvas.getContext('2d')

// Setting its width and height
let canvasMaxHeight = 800,
	 canvasWidth = 400,
	 canvasHeight = innerHeight - 50 > canvasMaxHeight ? canvasMaxHeight : innerHeight - 50;
canvas.width = canvasWidth
canvas.height = canvasHeight

let retryBtn = document.querySelector('.retry-btn')
let retryText = document.querySelector('.retry-text')
let playBtn = document.querySelector('.btn.play')
let startScreen = document.querySelector('.start-screen')


// Initializing everything

let balls = [], // balls array
	 particles = [] // sparks array
var redBall, blueBall // the TWO cantral balls
var separation = 35 // separation between central balls
var globalRadius = 18 // radius for all the Balls
let generateBall = false // generate a new ball or not
let timeInterval
let velocityOfBall // velocity of the ball
let failed = false // game failed or not
let timer = 0 // timer (increments every 1ms)
let score = 0 // score counter
let fillColor // Text fill color
// colors array
var colors = ['#e74c3c', '#3498db']
// random points where the ball would generate and start moving
let randPoints;

// Function that initializes the canvas
function init(){
	balls = []
	particles = []
	uselessBalls = []
	generateBall = true
	timeInterval = 2000
	timer = 0
	velocityOfBall = 2.5
	score = 0
	fillColor = '#fff'

	blueBall = new Ball(
		canvas, ctx,
		canvasWidth/2, canvasHeight/2 - separation,
		globalRadius, colors[1], particles, 0, 0, true
	)
	redBall = new Ball(
		canvas, ctx,
		canvasWidth/2, canvasHeight/2 + separation,
		globalRadius, colors[0], particles, 0, 0, true
	)
	balls.push(redBall, blueBall)

	randPoints = [
		{
			x: canvas.width / 2,
			y: -50
		},
		{
			x: canvas.width / 2,
			y: canvas.height + 50
		}
	]
}

// This is an array for a bunch of useless balls on the start of the game
var uselessBalls = []
// This function will push many useless balls balls to the useless array and then push all the useless balls to the default ballas array
function initUseless(){
	for(let i = 0; i < 20; i++){
		let randVelXY = {
			x: randomFloatFromRange(-5, 5),
			y: randomFloatFromRange(-5, 5)
		}
		let r = randomFloatFromRange(5, 10)
		uselessBalls.push(
			new Ball(
				canvas, ctx, canvasWidth / 2, canvasHeight / 2,
				r, colors[Math.floor(Math.random() * colors.length)],
				particles, randVelXY.x, randVelXY.y, true
			)
		)
	}
	balls.push([...uselessBalls])
}
// calling it on execution of code
initUseless()


// initialiazing the background in a variable
var background = BG_Gradient('#2c3e50', '#34495e')

// this func. calls itself again and again every 60ms and is the reason you can play this game
function loop(){
	// func. that will call itself
	requestAnimationFrame(loop)

	// seting the background
	ctx.fillStyle = background
	ctx.fillRect(0, 0, canvas.width, canvas.height)

	// setting the scorecard
	ctx.fillStyle = fillColor
	ctx.font = '21px sans-serif'
	ctx.fillText(`SCORE : ${score.toString()}`, 20, 35)

	// updating every uselessballs
	uselessBalls.forEach(ball => {
		ball.update(balls, true)
		ball.edgeDetect()
	})
	if(uselessBalls.length != 0){
		return
	}

	// updating every balls in the balls array
	if(balls.length != 0){
		balls.forEach((ball, index) => {
			ball.update(balls)
			if(ball.collided == true){
				score += 10
				fillColor = ball.color
			}
			if(ball.opacity <= 0){
				ball.dontCheck = true
				balls.splice(index, 1)
			}
		})
	}
	if(balls.length == 0 || balls.length == 1){
		failed = true
		generateBall = false
	}
	if(balls.length == 2){
		generateBall = true
	}
	if(timeInterval % timer == 0 && generateBall == true){
		generateBall = false
		pushNewBalls()
	}

	// updating every particles or sparks in the particles array
	if(particles.length != 0){
		particles.forEach((particle, index) => {
			particle.update()
			if(particle.opacity <= 0.05){
				particles.splice(index, 1)
			}
		})
	}

	// reseting the timer to 0 every 600ms
	if(timer == 600){
		timer = 0
	}

	// func. that is used to show and hide the UI options
	showHideOptions()
	// increment timer by 1 every 1ms
	timer++
}
// calling the func. once will make the recurrsion possible which in return will start the animations
loop()


// Function that is used tto push new balls to the Balls array whenever called
function pushNewBalls(){
	var randomPoint = randomFromArray(randPoints),
		 randomColor = randomFromArray(colors)
	balls.push(
		new Ball(
			canvas, ctx,
			randomPoint.x, randomPoint.y,
			globalRadius, randomColor, particles, 0, velocityOfBall, false
		)
	)
}

// Func. to show and hide the UI
function showHideOptions(){
	if(failed == true && generateBall == false){
		retryText.classList.remove('hide')
		retryText.classList.add('show')
		retryBtn.classList.remove('hide')
		retryBtn.classList.add('show')
	}else if(failed == false && generateBall == true){
		retryText.classList.add('hide')
		retryText.classList.remove('show')
		retryBtn.classList.add('hide')
		retryBtn.classList.remove('show')
	}
}

// Func. that returns simple color gradient by providing two colors
function BG_Gradient(color1, color2){
	let bg = ctx.createLinearGradient(0, 0, canvasWidth, canvasHeight)
	bg.addColorStop(0, color1)
	bg.addColorStop(1, color2)
	return bg
}

// This will be called every 1000ms and the code would execute
setInterval(() => {
	if(velocityOfBall <= 7){
		velocityOfBall += 0.08
	}else{
		velocityOfBall += 0
	}
}, 1500)


// EVENT LISTENERS

// Clicking on the canvas would change the color
canvas.addEventListener('mousedown', () => {
	redBall.change(colors[0], colors[1])
	blueBall.change(colors[1], colors[0])
})

// retry again by clicking the retry button
retryBtn.addEventListener('mousedown', () => {
	failed = false
	init()
})

// Start playing now.
playBtn.addEventListener('mousedown', () => {
	startScreen.classList.add('hide')
	init()
})

window.addEventListener('resize', () => {
	if(canvasHeight >= canvasMaxHeight){
		canvasHeight = canvasMaxHeight;
	}else{
		canvasHeight = innerHeight - 50		
	}
	canvas.height = canvasHeight
})
              
            
!
999px

Console