Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Pulse Painter Pathways</title>
- <style>
- * { margin: 0; padding: 0; box-sizing: border-box; }
- body {
- background: linear-gradient(135deg, #0a0014 0%, #1a0829 50%, #0a0014 100%);
- color: #fff;
- font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
- display: flex; justify-content: center; align-items: center;
- min-height: 100vh; overflow: hidden;
- }
- .container { position: relative; display: flex; flex-direction: column; align-items: center; gap: 20px; }
- .header { text-align: center; animation: float 4s ease-in-out infinite; }
- @keyframes float { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-10px)} }
- h1 {
- font-size: 2.5em; margin-bottom: 10px;
- background: linear-gradient(45deg, #ff00ff, #00ffff, #ff00ff); background-size: 200% 200%;
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
- animation: gradient 3s ease infinite;
- }
- @keyframes gradient { 0%{background-position:0% 50%} 50%{background-position:100% 50%} 100%{background-position:0% 50%} }
- .info { display: flex; gap: 30px; font-size: 1.1em; margin-bottom: 10px; }
- .info-item { padding: 8px 16px; background: rgba(255,255,255,0.1); border-radius: 20px; backdrop-filter: blur(10px); }
- #gameCanvas {
- border: 2px solid rgba(255,255,255,0.2); border-radius: 10px; background: #0a0014;
- box-shadow: 0 0 50px rgba(128,0,255,0.3); cursor: crosshair; position: relative;
- }
- .controls { display: flex; gap: 15px; margin-top: 10px; }
- button {
- padding: 12px 24px; font-size: 1em; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white; border: none; border-radius: 25px; cursor: pointer; transition: all 0.25s ease;
- box-shadow: 0 4px 15px rgba(102,126,234,0.4);
- }
- button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102,126,234,0.6); }
- button:active { transform: translateY(0); }
- .legend {
- position: absolute; top: 20px; right: 20px; background: rgba(0,0,0,0.8);
- padding: 15px; border-radius: 10px; backdrop-filter: blur(10px); font-size: 0.9em;
- }
- .legend-item { display: flex; align-items: center; gap: 10px; margin: 5px 0; }
- .legend-color { width: 20px; height: 20px; border-radius: 50%; }
- .pulse { animation: pulse 2s ease-in-out infinite; }
- @keyframes pulse { 0%,100%{opacity:.3;transform:scale(.95)} 50%{opacity:1;transform:scale(1.05)} }
- .glow { animation: glow 1.5s ease-in-out infinite alternate; }
- @keyframes glow { from{ box-shadow:0 0 10px currentColor } to{ box-shadow:0 0 20px currentColor, 0 0 30px currentColor } }
- .particle {
- position: absolute; width: 4px; height: 4px; background: white; border-radius: 50%; pointer-events: none;
- animation: particleFloat 3s ease-in-out infinite;
- }
- @keyframes particleFloat { 0%{transform:translateY(0) translateX(0);opacity:0} 10%{opacity:1} 90%{opacity:1} 100%{transform:translateY(-100px) translateX(50px);opacity:0} }
- /* ====== START OVERLAY ====== */
- .overlay {
- position: fixed; inset: 0; z-index: 9999; display: grid; place-items: center; padding: 24px;
- background:
- radial-gradient(1200px 600px at 20% 20%, rgba(255,0,255,0.08), transparent 70%),
- radial-gradient(1000px 700px at 80% 70%, rgba(0,255,255,0.08), transparent 70%),
- rgba(6, 2, 16, 0.85);
- backdrop-filter: blur(6px);
- }
- .overlay-card {
- width: min(880px, 92vw); border-radius: 16px; padding: 28px 28px 22px;
- background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02));
- border: 1px solid rgba(255,255,255,0.14); box-shadow: 0 20px 60px rgba(0,0,0,0.45);
- }
- .overlay h2 { font-size: clamp(22px, 3.6vw, 30px); margin-bottom: 12px; }
- .overlay p { color: #e6e6ff; opacity: 0.9; line-height: 1.5; margin-bottom: 14px; }
- .kbd {
- display: inline-flex; align-items: center; justify-content: center; min-width: 1.75em; padding: 2px 8px;
- border-radius: 8px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.18);
- font-weight: 600; font-size: 0.9em; margin: 0 2px; color: #fff;
- }
- .rules { display: grid; gap: 10px; margin: 14px 0 18px; }
- .rule { display: grid; grid-template-columns: 24px 1fr; gap: 10px; align-items: start; }
- .dot { width: 12px; height: 12px; border-radius: 50%; margin-top: 6px; }
- .overlay-actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-end; margin-top: 10px; }
- .ghost-btn {
- background: transparent; border: 1px solid rgba(255,255,255,0.25); box-shadow: none;
- }
- .hidden { display: none !important; }
- /* Dim canvas & controls visually while overlay is up */
- body[data-gate="blocked"] #gameCanvas, body[data-gate="blocked"] .controls, body[data-gate="blocked"] .legend, body[data-gate="blocked"] .info {
- filter: blur(1.5px) saturate(0.8); opacity: 0.75; pointer-events: none;
- }
- </style>
- </head>
- <body data-gate="blocked">
- <!-- Overlay Gate -->
- <div id="startOverlay" class="overlay" aria-modal="true" role="dialog" aria-labelledby="overlayTitle">
- <div class="overlay-card">
- <h2 id="overlayTitle">Welcome to <span style="background:linear-gradient(45deg,#ff00ff,#00ffff);-webkit-background-clip:text;-webkit-text-fill-color:transparent">Pulse Painter Pathways</span></h2>
- <p><strong>The instructions will disappear when you press <span class="kbd">Begin!</span>.</strong> Read these quick notes before you play.</p>
- <div class="rules">
- <div class="rule"><span class="dot" style="background:#00ff88"></span><p><strong>Start</strong>: Click-and-drag beginning from the glowing green node.</p></div>
- <div class="rule"><span class="dot" style="background:#ffaa00"></span><p><strong>Goal</strong>: Reach the pulsing golden portal to clear the level.</p></div>
- <div class="rule"><span class="dot" style="background:#ff00a8"></span><p><strong>Avoid pulses</strong>: Cells with intense magenta pulses are blocked while they’re bright.</p></div>
- <div class="rule"><span class="dot" style="background:#8a2be2"></span><p><strong>Movement</strong>: Draw 4‑way or diagonal (8‑way) adjacent steps across the grid.</p></div>
- <div class="rule"><span class="dot" style="background:#67e8f9"></span><p><strong>Tip</strong>: Time your path between wave peaks — pulses ebb and flow.</p></div>
- </div>
- <p><strong>Controls</strong>: <span class="kbd">Pause/Resume</span> to think; <span class="kbd">Reset</span> to restart the canvas.</p>
- <div class="overlay-actions">
- <button id="btnBegin">Begin!</button>
- </div>
- </div>
- </div>
- <div class="container">
- <div class="header">
- <h1>Pulse Painter Pathways</h1>
- <div class="info">
- <div class="info-item">Score: <span id="score">0</span></div>
- <div class="info-item">Level: <span id="level">1</span></div>
- <div class="info-item">Time: <span id="timer">0</span>s</div>
- </div>
- </div>
- <canvas id="gameCanvas" width="800" height="600" aria-label="Game canvas"></canvas>
- <div class="controls" aria-label="Controls">
- <button id="btnStartJourney" title="Restart the run">Start Journey</button>
- <button id="btnReset">Reset Canvas</button>
- <button id="btnPause">Pause/Resume</button>
- </div>
- <div class="legend">
- <div class="legend-item"><div class="legend-color" style="background:#00ff88"></div><span>Active Path</span></div>
- <div class="legend-item"><div class="legend-color" style="background:#ff00ff"></div><span>Pulse Zone</span></div>
- <div class="legend-item"><div class="legend-color" style="background:#ffaa00"></div><span>Goal Portal</span></div>
- </div>
- </div>
- <script>
- const canvas = document.getElementById('gameCanvas');
- const ctx = canvas.getContext('2d');
- class PulsePainterGame {
- constructor() {
- this.gridSize = 20;
- this.cols = Math.floor(canvas.width / this.gridSize);
- this.rows = Math.floor(canvas.height / this.gridSize);
- this.grid = [];
- this.pulseZones = [];
- this.playerPath = [];
- this.playerPos = null;
- this.goalPos = null;
- this.score = 0;
- this.level = 1;
- this.time = 0;
- this.isRunning = false;
- this.isPaused = false;
- this.pulsePhase = 0;
- this.pulseSpeed = 0.02;
- this.pathColor = '#00ff88';
- this.drawing = false;
- this.init();
- }
- init() {
- // Initialize grid
- for (let i = 0; i < this.rows; i++) {
- this.grid[i] = [];
- for (let j = 0; j < this.cols; j++) {
- this.grid[i][j] = { type: 'empty', pulseIntensity: 0, visited: false };
- }
- }
- // Set start and goal positions
- this.playerPos = { x: 1, y: Math.floor(this.rows / 2) };
- this.goalPos = { x: this.cols - 2, y: Math.floor(this.rows / 2) };
- // Create pulse zones
- this.createPulseZones();
- // Input events (mouse + touch)
- canvas.addEventListener('mousedown', (e) => this.startDrawing(e));
- canvas.addEventListener('mousemove', (e) => this.draw(e));
- canvas.addEventListener('mouseup', () => this.stopDrawing());
- canvas.addEventListener('mouseleave',() => this.stopDrawing());
- canvas.addEventListener('touchstart',(e) => { e.preventDefault(); this.startDrawing(e.touches[0]); }, {passive:false});
- canvas.addEventListener('touchmove', (e) => { e.preventDefault(); this.draw(e.touches[0]); }, {passive:false});
- canvas.addEventListener('touchend', () => this.stopDrawing());
- // Wire up control buttons
- document.getElementById('btnStartJourney').addEventListener('click', () => this.start());
- document.getElementById('btnReset').addEventListener('click', () => this.reset());
- document.getElementById('btnPause').addEventListener('click', () => this.togglePause());
- // Overlay gate buttons
- document.getElementById('btnBegin').addEventListener('click', () => this.gatedStart());
- // Keyboard convenience: Enter starts, Space pauses (after started)
- window.addEventListener('keydown', (e) => {
- if (this.isRunning && (e.code === 'Space')) {
- e.preventDefault();
- this.togglePause();
- }
- });
- }
- gatedStart() {
- // Hide overlay and begin a fresh run
- const overlay = document.getElementById('startOverlay');
- overlay.classList.add('hidden');
- document.body.dataset.gate = 'open';
- this.start();
- }
- createPulseZones() {
- this.pulseZones = [];
- const zoneCount = 3 + Math.floor(this.level / 2);
- for (let i = 0; i < zoneCount; i++) {
- let zone, attempts = 0;
- do {
- const radius = 5 + Math.random() * 10; // grid units
- zone = {
- x: Math.random() * this.cols,
- y: Math.random() * this.rows,
- radius,
- phase: Math.random() * Math.PI * 2,
- speed: 0.01 + Math.random() * 0.03,
- color: `hsl(${Math.random() * 360}, 100%, 50%)`,
- moveSpeed: { x: (Math.random() - 0.5) * 0.1, y: (Math.random() - 0.5) * 0.1 }
- };
- const distStart = Math.hypot(zone.x - this.playerPos.x, zone.y - this.playerPos.y);
- const distGoal = Math.hypot(zone.x - this.goalPos.x, zone.y - this.goalPos.y);
- var isValid = distStart >= zone.radius + 2 && distGoal >= zone.radius + 2; // buffer away from start/goal
- attempts++;
- if (attempts > 50) break; // safety
- } while (!isValid);
- this.pulseZones.push(zone);
- }
- }
- startDrawing(e) {
- if (!this.isRunning || this.isPaused) return;
- const rect = canvas.getBoundingClientRect();
- const x = Math.floor((e.clientX - rect.left) / this.gridSize);
- const y = Math.floor((e.clientY - rect.top) / this.gridSize);
- const pos = { x, y };
- if (this.playerPath.length === 0) {
- // Must begin exactly on start node; allow even if pulse is high at the origin
- if (x === this.playerPos.x && y === this.playerPos.y) {
- this.drawing = true;
- this.playerPath = [pos];
- }
- } else {
- const lastPoint = this.playerPath[this.playerPath.length - 1];
- if (x === lastPoint.x && y === lastPoint.y && this.isValidMove(x, y)) {
- this.drawing = true;
- }
- }
- }
- draw(e) {
- if (!this.drawing || !this.isRunning || this.isPaused) return;
- const rect = canvas.getBoundingClientRect();
- const x = Math.floor((e.clientX - rect.left) / this.gridSize);
- const y = Math.floor((e.clientY - rect.top) / this.gridSize);
- const lastPoint = this.playerPath[this.playerPath.length - 1];
- if (!lastPoint || (x === lastPoint.x && y === lastPoint.y)) return;
- if (this.isValidMove(x, y) && this.isAdjacent(lastPoint, { x, y })) {
- this.playerPath.push({ x, y });
- if (x === this.goalPos.x && y === this.goalPos.y) {
- this.levelComplete();
- }
- }
- }
- stopDrawing() { this.drawing = false; }
- isValidMove(x, y) {
- if (x < 0 || x >= this.cols || y < 0 || y >= this.rows) return false;
- // Check if cell is active (not in blocked pulse zone)
- const pulseIntensity = this.calculatePulseIntensity(x, y);
- return pulseIntensity < 0.7; // can traverse only through calmer cells
- }
- isAdjacent(p1, p2) {
- const dx = Math.abs(p1.x - p2.x);
- const dy = Math.abs(p1.y - p2.y);
- return (dx === 1 && dy === 0) || (dx === 0 && dy === 1) || (dx === 1 && dy === 1);
- }
- calculatePulseIntensity(x, y) {
- let maxIntensity = 0;
- for (const zone of this.pulseZones) {
- const dist = Math.hypot(x - zone.x, y - zone.y);
- if (dist < zone.radius) {
- const intensity = (1 - dist / zone.radius) * (Math.sin(this.pulsePhase + zone.phase) * 0.5 + 0.5);
- maxIntensity = Math.max(maxIntensity, intensity);
- }
- }
- return maxIntensity;
- }
- lose() {
- alert("Lost! A pulse zone hit your path. Resetting level...");
- this.playerPath = [];
- this.createPulseZones();
- }
- update() {
- if (!this.isRunning || this.isPaused) return;
- // Update pulse phase
- this.pulsePhase += this.pulseSpeed;
- // Move pulse zones
- for (const zone of this.pulseZones) {
- zone.x += zone.moveSpeed.x;
- zone.y += zone.moveSpeed.y;
- // bounce edges
- if (zone.x - zone.radius < 0 || zone.x + zone.radius > this.cols) zone.moveSpeed.x *= -1;
- if (zone.y - zone.radius < 0 || zone.y + zone.radius > this.rows) zone.moveSpeed.y *= -1;
- zone.phase += zone.speed;
- }
- // Update grid pulse intensities
- for (let y = 0; y < this.rows; y++) {
- for (let x = 0; x < this.cols; x++) {
- this.grid[y][x].pulseIntensity = this.calculatePulseIntensity(x, y);
- }
- }
- // Check for lose condition if path exists
- if (this.playerPath.length > 0) {
- for (const point of this.playerPath) {
- if (this.calculatePulseIntensity(point.x, point.y) >= 0.7) { this.lose(); break; }
- }
- }
- // Update timer
- this.time += 1/60;
- document.getElementById('timer').textContent = Math.floor(this.time);
- }
- render() {
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- // Draw grid with pulse effects
- for (let y = 0; y < this.rows; y++) {
- for (let x = 0; x < this.cols; x++) {
- const intensity = this.grid[y][x].pulseIntensity;
- // calm background
- ctx.fillStyle = `rgba(128, 0, 255, ${intensity * 0.3})`;
- ctx.fillRect(x * this.gridSize, y * this.gridSize, this.gridSize - 1, this.gridSize - 1);
- // blocked zones
- if (intensity > 0.7) {
- ctx.fillStyle = `rgba(255, 0, 168, ${intensity})`;
- ctx.fillRect(x * this.gridSize, y * this.gridSize, this.gridSize - 1, this.gridSize - 1);
- }
- }
- }
- // Draw pulse zone outlines
- for (const zone of this.pulseZones) {
- const pulseAlpha = (Math.sin(this.pulsePhase + zone.phase) * 0.3 + 0.7);
- ctx.strokeStyle = zone.color; ctx.globalAlpha = pulseAlpha; ctx.lineWidth = 2;
- ctx.beginPath();
- ctx.arc(zone.x * this.gridSize, zone.y * this.gridSize, zone.radius * this.gridSize, 0, Math.PI * 2);
- ctx.stroke();
- ctx.globalAlpha = 1;
- }
- // Draw player path
- if (this.playerPath.length > 0) {
- ctx.strokeStyle = this.pathColor; ctx.lineWidth = 4; ctx.lineCap = 'round'; ctx.lineJoin = 'round';
- ctx.shadowBlur = 20; ctx.shadowColor = this.pathColor;
- ctx.beginPath();
- ctx.moveTo(this.playerPath[0].x * this.gridSize + this.gridSize/2, this.playerPath[0].y * this.gridSize + this.gridSize/2);
- for (let i = 1; i < this.playerPath.length; i++) {
- const p = this.playerPath[i];
- ctx.lineTo(p.x * this.gridSize + this.gridSize/2, p.y * this.gridSize + this.gridSize/2);
- }
- ctx.stroke();
- ctx.shadowBlur = 0;
- // path particles
- for (let i = 0; i < this.playerPath.length; i++) {
- const p = this.playerPath[i];
- const size = 3 + Math.sin(this.pulsePhase * 2 + i * 0.5) * 2;
- ctx.fillStyle = this.pathColor;
- ctx.beginPath();
- ctx.arc(p.x * this.gridSize + this.gridSize/2, p.y * this.gridSize + this.gridSize/2, size, 0, Math.PI * 2);
- ctx.fill();
- }
- }
- // Start node
- ctx.fillStyle = '#00ff88'; ctx.shadowBlur = 15; ctx.shadowColor = '#00ff88';
- ctx.beginPath();
- ctx.arc(this.playerPos.x * this.gridSize + this.gridSize/2, this.playerPos.y * this.gridSize + this.gridSize/2, this.gridSize/3, 0, Math.PI * 2);
- ctx.fill();
- // Goal portal
- const goalPulse = Math.sin(this.pulsePhase * 3) * 0.3 + 0.7;
- ctx.fillStyle = '#ffaa00'; ctx.shadowColor = '#ffaa00'; ctx.shadowBlur = 20 * goalPulse;
- ctx.beginPath();
- ctx.arc(this.goalPos.x * this.gridSize + this.gridSize/2, this.goalPos.y * this.gridSize + this.gridSize/2, this.gridSize/2.5 * goalPulse, 0, Math.PI * 2);
- ctx.fill();
- ctx.shadowBlur = 0;
- }
- levelComplete() {
- this.score += 100 * this.level; this.level++;
- document.getElementById('score').textContent = this.score;
- document.getElementById('level').textContent = this.level;
- this.createCompletionEffect();
- setTimeout(() => {
- this.playerPath = [];
- this.createPulseZones();
- this.pulseSpeed = Math.min(0.05, this.pulseSpeed * 1.1);
- }, 800);
- }
- createCompletionEffect() {
- for (let i = 0; i < 20; i++) {
- setTimeout(() => {
- const particle = document.createElement('div');
- particle.className = 'particle';
- particle.style.left = (Math.random() * canvas.width) + 'px';
- particle.style.top = (Math.random() * canvas.height) + 'px';
- particle.style.background = `hsl(${Math.random() * 360}, 100%, 70%)`;
- canvas.parentElement.appendChild(particle);
- setTimeout(() => particle.remove(), 3000);
- }, i * 50);
- }
- }
- start() {
- this.isRunning = true; this.isPaused = false; this.time = 0; this.score = 0; this.level = 1; this.pulsePhase = 0;
- this.playerPath = [];
- document.getElementById('score').textContent = this.score;
- document.getElementById('level').textContent = this.level;
- document.getElementById('timer').textContent = '0';
- this.createPulseZones();
- // Kick off the loop if not already animating
- if (!this._animating) { this._animating = true; this.animate(); }
- }
- reset() {
- this.isRunning = false; this.isPaused = false; this.playerPath = []; this.time = 0; this.score = 0; this.level = 1; this.pulsePhase = 0;
- document.getElementById('score').textContent = this.score;
- document.getElementById('level').textContent = this.level;
- document.getElementById('timer').textContent = '0';
- this.createPulseZones();
- this.render();
- }
- togglePause() { if (this.isRunning) { this.isPaused = !this.isPaused; } }
- animate() {
- // Single RAF loop controlled by isRunning/isPaused flags
- this.update();
- this.render();
- requestAnimationFrame(() => this.animate());
- }
- }
- const game = new PulsePainterGame();
- game.render(); // static render while gated
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment