diff --git a/index.html b/index.html
new file mode 100644
index 0000000..1d308f4
--- /dev/null
+++ b/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+ Heartstopper
+
+
+
+
+
+
Score: 0
+
+
Heart Stopped
+
Score: 0
+
+
+
+
+
+
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..74faff2
--- /dev/null
+++ b/script.js
@@ -0,0 +1,325 @@
+const canvas = document.getElementById('gameCanvas');
+const ctx = canvas.getContext('2d');
+const scoreEl = document.getElementById('score');
+const finalScoreEl = document.getElementById('finalScore');
+const gameOverEl = document.getElementById('gameOver');
+const restartBtn = document.getElementById('restartBtn');
+
+// Game State
+let score = 0;
+let gameActive = true;
+let animationId;
+let frames = 0;
+
+// Entities
+let bullets = [];
+let particles = [];
+
+// Mouse
+const mouse = {
+ x: innerWidth / 2,
+ y: innerHeight / 2
+};
+
+// Resize handling
+function resize() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+}
+window.addEventListener('resize', resize);
+resize();
+
+// Input handling
+window.addEventListener('mousemove', (e) => {
+ mouse.x = e.clientX;
+ mouse.y = e.clientY;
+});
+
+window.addEventListener('touchmove', (e) => {
+ mouse.x = e.touches[0].clientX;
+ mouse.y = e.touches[0].clientY;
+});
+
+// Utility
+function randomRange(min, max) {
+ return Math.random() * (max - min) + min;
+}
+
+// Classes
+class Heart {
+ constructor() {
+ this.x = canvas.width / 2;
+ this.y = canvas.height / 2;
+ this.baseSize = 30;
+ this.size = this.baseSize;
+ this.pulseSpeed = 0.05;
+ this.angle = 0;
+ }
+
+ draw() {
+ // Update center position on resize
+ this.x = canvas.width / 2;
+ this.y = canvas.height / 2;
+
+ // Pulse animation
+ this.angle += this.pulseSpeed;
+ const scale = 1 + Math.sin(this.angle) * 0.1;
+
+ ctx.save();
+ ctx.translate(this.x, this.y);
+ ctx.scale(scale, scale);
+
+ // Draw Heart
+ ctx.beginPath();
+ ctx.fillStyle = '#ff4d4d';
+ ctx.shadowColor = '#ff4d4d';
+ ctx.shadowBlur = 20;
+
+ // Heart shape using bezier curves
+ // Starting from top center
+ ctx.moveTo(0, -10);
+ ctx.bezierCurveTo(15, -25, 35, -10, 0, 25);
+ ctx.bezierCurveTo(-35, -10, -15, -25, 0, -10);
+
+ ctx.fill();
+ ctx.restore();
+ }
+}
+
+class Shield {
+ constructor() {
+ this.radius = 80; // Distance from heart
+ this.angle = 0;
+ this.width = 60; // Arc length in radians (approx)
+ this.thickness = 10;
+ this.color = '#4d94ff';
+ }
+
+ update() {
+ const dx = mouse.x - canvas.width / 2;
+ const dy = mouse.y - canvas.height / 2;
+ this.angle = Math.atan2(dy, dx);
+ }
+
+ draw() {
+ const centerX = canvas.width / 2;
+ const centerY = canvas.height / 2;
+
+ ctx.save();
+ ctx.translate(centerX, centerY);
+ ctx.rotate(this.angle);
+
+ ctx.beginPath();
+ // Draw an arc for the shield
+ // We want the shield to be centered on the angle
+ // So we draw from -arcLength/2 to +arcLength/2
+ const arcLength = 0.8; // Radians
+ ctx.arc(0, 0, this.radius, -arcLength / 2, arcLength / 2);
+
+ ctx.lineWidth = this.thickness;
+ ctx.strokeStyle = this.color;
+ ctx.lineCap = 'round';
+ ctx.shadowColor = this.color;
+ ctx.shadowBlur = 15;
+ ctx.stroke();
+
+ ctx.restore();
+ }
+}
+
+class Bullet {
+ constructor() {
+ const edge = Math.floor(Math.random() * 4); // 0: top, 1: right, 2: bottom, 3: left
+
+ if (edge === 0) { // Top
+ this.x = Math.random() * canvas.width;
+ this.y = -20;
+ } else if (edge === 1) { // Right
+ this.x = canvas.width + 20;
+ this.y = Math.random() * canvas.height;
+ } else if (edge === 2) { // Bottom
+ this.x = Math.random() * canvas.width;
+ this.y = canvas.height + 20;
+ } else { // Left
+ this.x = -20;
+ this.y = Math.random() * canvas.height;
+ }
+
+ this.radius = 5;
+ this.color = '#fff';
+
+ // Calculate velocity towards center
+ const centerX = canvas.width / 2;
+ const centerY = canvas.height / 2;
+ const angle = Math.atan2(centerY - this.y, centerX - this.x);
+
+ // Speed increases with score
+ const speedMultiplier = 1 + (score * 0.01);
+ this.velocity = {
+ x: Math.cos(angle) * (2 + Math.random()) * speedMultiplier,
+ y: Math.sin(angle) * (2 + Math.random()) * speedMultiplier
+ };
+ }
+
+ update() {
+ this.x += this.velocity.x;
+ this.y += this.velocity.y;
+ }
+
+ draw() {
+ ctx.beginPath();
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
+ ctx.fillStyle = this.color;
+ ctx.shadowColor = this.color;
+ ctx.shadowBlur = 5;
+ ctx.fill();
+ }
+}
+
+class Particle {
+ constructor(x, y, color) {
+ this.x = x;
+ this.y = y;
+ this.radius = Math.random() * 3;
+ this.color = color;
+ this.velocity = {
+ x: (Math.random() - 0.5) * 5,
+ y: (Math.random() - 0.5) * 5
+ };
+ this.alpha = 1;
+ }
+
+ update() {
+ this.x += this.velocity.x;
+ this.y += this.velocity.y;
+ this.alpha -= 0.02;
+ }
+
+ draw() {
+ ctx.save();
+ ctx.globalAlpha = this.alpha;
+ ctx.beginPath();
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
+ ctx.fillStyle = this.color;
+ ctx.fill();
+ ctx.restore();
+ }
+}
+
+// Game Objects
+const heart = new Heart();
+const shield = new Shield();
+
+function spawnBullet() {
+ // Spawn rate increases with score
+ const spawnRate = Math.max(20, 100 - score);
+ if (frames % spawnRate === 0) {
+ bullets.push(new Bullet());
+ }
+}
+
+function checkCollisions() {
+ const centerX = canvas.width / 2;
+ const centerY = canvas.height / 2;
+
+ bullets.forEach((bullet, index) => {
+ // Distance to center (Heart)
+ const distToCenter = Math.hypot(bullet.x - centerX, bullet.y - centerY);
+
+ // 1. Check Shield Collision
+ // Shield is at a specific radius. If bullet is at that radius, check angle difference.
+ if (distToCenter < shield.radius + shield.thickness && distToCenter > shield.radius - shield.thickness) {
+ const angleToBullet = Math.atan2(bullet.y - centerY, bullet.x - centerX);
+
+ // Normalize angles to -PI to PI
+ let angleDiff = angleToBullet - shield.angle;
+ while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
+ while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
+
+ // Shield arc length is approx 0.8 radians. Half is 0.4.
+ // Add a bit of buffer for the bullet size
+ if (Math.abs(angleDiff) < 0.5) {
+ // Hit Shield
+ createExplosion(bullet.x, bullet.y, '#4d94ff');
+ bullets.splice(index, 1);
+ score += 10;
+ scoreEl.textContent = `Score: ${score}`;
+ return;
+ }
+ }
+
+ // 2. Check Heart Collision
+ if (distToCenter < 30) { // Heart approx radius
+ endGame();
+ }
+ });
+}
+
+function createExplosion(x, y, color) {
+ for (let i = 0; i < 8; i++) {
+ particles.push(new Particle(x, y, color));
+ }
+}
+
+function endGame() {
+ gameActive = false;
+ cancelAnimationFrame(animationId);
+ gameOverEl.classList.remove('hidden');
+ finalScoreEl.textContent = score;
+}
+
+function restartGame() {
+ score = 0;
+ scoreEl.textContent = `Score: 0`;
+ bullets = [];
+ particles = [];
+ gameActive = true;
+ gameOverEl.classList.add('hidden');
+ animate();
+}
+
+restartBtn.addEventListener('click', restartGame);
+
+function animate() {
+ if (!gameActive) return;
+
+ animationId = requestAnimationFrame(animate);
+ frames++;
+
+ // Clear with trail effect
+ ctx.fillStyle = 'rgba(17, 17, 17, 0.2)';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ heart.draw();
+ shield.update();
+ shield.draw();
+
+ spawnBullet();
+
+ // Update and draw bullets
+ bullets.forEach((bullet, index) => {
+ bullet.update();
+ bullet.draw();
+
+ // Remove off-screen bullets (shouldn't happen often as they move to center)
+ if (bullet.x < -50 || bullet.x > canvas.width + 50 ||
+ bullet.y < -50 || bullet.y > canvas.height + 50) {
+ bullets.splice(index, 1);
+ }
+ });
+
+ // Update and draw particles
+ particles.forEach((particle, index) => {
+ if (particle.alpha <= 0) {
+ particles.splice(index, 1);
+ } else {
+ particle.update();
+ particle.draw();
+ }
+ });
+
+ checkCollisions();
+}
+
+// Start
+animate();
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..9f219b1
--- /dev/null
+++ b/style.css
@@ -0,0 +1,90 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ background-color: #111;
+ color: #fff;
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ overflow: hidden;
+ height: 100vh;
+ width: 100vw;
+}
+
+canvas {
+ display: block;
+}
+
+#ui {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+#score {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ font-size: 24px;
+ font-weight: bold;
+ opacity: 0.8;
+}
+
+#gameOver {
+ background: rgba(0, 0, 0, 0.85);
+ padding: 40px;
+ border-radius: 12px;
+ text-align: center;
+ pointer-events: auto;
+ backdrop-filter: blur(5px);
+ border: 1px solid #333;
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
+}
+
+#gameOver.hidden {
+ display: none;
+}
+
+#gameOver h1 {
+ color: #ff4d4d;
+ margin-bottom: 10px;
+ font-size: 3em;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+}
+
+#gameOver p {
+ font-size: 1.5em;
+ margin-bottom: 30px;
+}
+
+#restartBtn {
+ background: #ff4d4d;
+ color: white;
+ border: none;
+ padding: 12px 30px;
+ font-size: 1.2em;
+ border-radius: 30px;
+ cursor: pointer;
+ transition: transform 0.2s, background 0.2s;
+ text-transform: uppercase;
+ font-weight: bold;
+}
+
+#restartBtn:hover {
+ background: #ff3333;
+ transform: scale(1.05);
+}
+
+#restartBtn:active {
+ transform: scale(0.95);
+}