diff --git a/src/App.svelte b/src/App.svelte
index 8e1d81a..9792ed2 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -11,31 +11,16 @@ import { loadLeaderboard, saveScore, playerName, leaderboard , hasSubmitted } fr
import { updateEnvironment, skyColors } from './environment.js';
import { createObstacle, handleCollisions } from './obstacles.js';
import { createSketch } from './p5overlay.js';
-import {
- handleInput,
- processInteraction,
- updatePhysics,
- updateGameFlow // Add this
-} from './GameController.js';
-
-import {
- createWorldChunk,
- createClouds,
- moveWorld,
- animateClouds // Added this
-} from './WorldScene.js';
+import { handleInput, processInteraction, updatePhysics, updateGameFlow } from './GameController.js';
+import { createWorldChunk, createClouds, moveWorld,animateClouds } from './WorldScene.js';
import { ObstacleFactory } from './ObstacleFactory.js';
import { GameManager } from './GameManager.js';
-let showLanding = true;
-let lastTime = performance.now();
-
async function handleStart() {
showLanding = false;
await tick();
init();
- // 3. Setup the Bridge Object
const gameState = {
get isPlaying() { return isPlaying; },
get score() { return score; },
@@ -50,13 +35,10 @@ async function handleStart() {
set gamePhase(v) { gamePhase = v; },
get lastStarScore() { return lastStarScore; },
set lastStarScore(v) { lastStarScore = v; },
-
- // FIX: Use getters for the arrays so they don't get stale!
get targets() { return targets; },
get scorePopups() { return scorePopups; },
onHit: (t) => {
- // MVC: Delegate the 'Decision' to the Controller
processInteraction(t, gameState, scoreMultiplier, BOOST_DURATION);
},
onActivateBoost: (mult, dur) => {
@@ -64,8 +46,6 @@ async function handleStart() {
multiplierTimer = dur;
}
};
-
- // 4. Initialize p5 now that p5Container exists
p5Instance = new p5(createSketch(gameState, textures), p5Container);
lastTime = performance.now();
@@ -81,72 +61,87 @@ async function handleStart() {
loop();
}
+//App flow & timing
+let showLanding = true; // Toggles between LandingPage and the Game Wrapper
+let lastTime = performance.now(); // High-resolution timestamp for delta time calculation
+// Game Constants & State Variables for physical rules of the world
const CONFIG = {
lane: 2.5,
jump: 0.35,
- grav: 0.015,
- playerScale: 1.7,
+ grav: 0.015, // Gravity constant applied per frame
+ playerScale: 1.7, // Visual scale multiplier for the 3D character
START_SPEED: 45, // Initial slow speed
- MAX_SPEED: 95, // The "chaos" threshold
+ MAX_SPEED: 95, // The maximum speed threshold
ACCELERATION: 1, // Speed added per second
- CYCLE_INTERVAL: 7000
};
+const BOOST_DURATION = 20; // Length of the 'Star' multiplier in seconds
-let currentSpeed = CONFIG.START_SPEED;
+// RENDERING REFERENCES
+//View references used to bridge Svelte with Three.js and P5.js
+let container, canvas, p5Container; // HTML Element bindings
+let scene, camera, renderer; // Three.js Core components
+let p5Instance; // p5.js Overlay instance
+let animationFrame; // ID for the requestAnimationFrame loop
+let uTime = { value: 0 }; // Global time tracker for shader animations and world logic
+
+// Game State Variables
let score = 0, isPlaying = false, gameOver = false, startScreen = true;
-let attentiveness = 100;
-let lives = 5;
+let lives = 5; let isDying = false, hitFlash = false;
+let currentSpeed = CONFIG.START_SPEED; // The active world velocity
+
+//Player physics, tracks the 3D position and physics state of the character.
let lane = 0, currX = 0, isJumping = false, jumpV = 0, playerY = 0;
-let container, canvas, scene, camera, renderer, p5Container;
-let worldObjects = [], animationFrame, p5Instance;
-let isDying = false, hitFlash = false;
+let playerAnchor, currentModel = null, currentMixer = null, swapToken = 0;
-let spawnDistanceTracker = 0;
+// WORLD OBJECTS & GENERATION
+let worldObjects = [];
+let spawnDistanceTracker = 0; // Tracks distance traveled since last spawn
const SPAWN_INTERVAL = 40; // Physical distance between obstacles
-let cloudGroup;
+let cloudGroup; // Container for background parallax clouds
+let CHUNKS = []; // Ground segments for the infinite loop
+const CHUNK_COUNT = 3; // Number of segments in the pool
+const CHUNK_SIZE = 140;
+// ENVIRONMENT & LIGHTING
let sun, moon, ambientLight, sunLight, headLight;
// 2D Game Logic
let gamePhase = "START";
let instructionTimer = 3;
let targetType = "STRAWBERRY";
-let targets = [];
+let targets = []; // Active 2D floating target objects
let scorePopups = []; // To track the floating +100 labels
+let scoreMultiplier = 1; //to double the score when star is active
+let multiplierTimer = 0; // Countdown for active Star power-up
+let lastStarScore = 0; // Checkpoint to trigger Star spawn every 10k points
-let playerAnchor, currentModel = null, currentMixer = null, swapToken = 0;
-let CHUNKS = [];
-const CHUNK_COUNT = 3;
-const CHUNK_SIZE = 140;
-
-let uTime = { value: 0 };
+// ASSET CACHE & LOADING
+// Memory management for models and textures to prevent redundant loads.
const loader = new GLTFLoader();
const glbCache = new Map();
-
let textures = {};
-let scoreMultiplier = 1;
-let multiplierTimer = 0; // Remaining seconds of boost
-let lastStarScore = 0; // Add this line to prevent the crash
-const BOOST_DURATION = 20; // 20 seconds
async function getCachedGLTF(file) {
if (!glbCache.has(file)) glbCache.set(file, await loader.loadAsync(file));
return glbCache.get(file);
}
+// Asynchronously swaps the 3D player model and manages its animations
async function swapCharacter(file, isDeathAnimation = false) {
+ // Incrementing a token ensures that if multiple swaps are called rapidly,
+ // only the most recent request (the latest token) actually updates the scene.
const myToken = ++swapToken;
const source = await getCachedGLTF(`3dmodels/${file}`);
- if (myToken !== swapToken) return;
+ if (myToken !== swapToken) return; // Exit if a newer swap request has already started (stale request prevention)
const model = cloneSkeleton(source.scene);
model.scale.setScalar(CONFIG.playerScale);
model.rotation.y = Math.PI;
const mixer = new THREE.AnimationMixer(model);
if (source.animations?.length) {
const action = mixer.clipAction(source.animations[0]);
- if (isDeathAnimation) { action.setLoop(THREE.LoopOnce, 1); action.clampWhenFinished = true; }
+ if (isDeathAnimation) { action.setLoop(THREE.LoopOnce, 1); action.clampWhenFinished = true; } // Stop on the last frame instead of resetting
action.play();
}
if (currentModel) playerAnchor.remove(currentModel);
@@ -154,6 +149,7 @@ async function swapCharacter(file, isDeathAnimation = false) {
playerAnchor.add(currentModel);
}
+// Initializes the Three.js engine, environment, and procedural world elements
function init() {
if (!canvas || !container) return; // Safety check
scene = new THREE.Scene();
@@ -189,29 +185,15 @@ function init() {
playerAnchor = new THREE.Group();
scene.add(playerAnchor);
- const ro = new ResizeObserver(() => {
- if (!container || !renderer) return;
- const { width, height } = container.getBoundingClientRect();
- renderer.setSize(width, height);
- camera.aspect = width / height;
- camera.updateProjectionMatrix();
- });
- ro.observe(container);
-
- // 1. Update Fog to use the day color
scene.fog = new THREE.Fog(skyColors.day, 150, 300);
-
- // 2. Setup Lights
ambientLight = new THREE.AmbientLight(0xffffff, 1.5);
sunLight = new THREE.DirectionalLight(0xffffff, 1.0);
sunLight.position.set(0, 50, -50);
-
- // 3. Add the "Headlight" to the camera (stays off during day)
headLight = new THREE.PointLight(0x00d2ff, 0, 40);
camera.add(headLight);
scene.add(camera, ambientLight, sunLight);
- // 4. Create Low-Poly Sun and Moon
+ // Create Low-Poly Sun and Moon
const lowPolyGeo = new THREE.IcosahedronGeometry(10, 1);
sun = new THREE.Mesh(lowPolyGeo, new THREE.MeshBasicMaterial({ color: 0xffffcc }));
moon = new THREE.Mesh(lowPolyGeo, new THREE.MeshBasicMaterial({ color: 0x94b0ff }));
@@ -220,30 +202,40 @@ function init() {
sun.position.set(60, 100, -250);
moon.position.set(60, -100, -250);
scene.add(sun, moon);
+
+ // Responsive Design (Viewport Observer)
+ // Automatically handles canvas resizing without reloading the engine
+ const ro = new ResizeObserver(() => {
+ if (!container || !renderer) return;
+ const { width, height } = container.getBoundingClientRect();
+ renderer.setSize(width, height);
+ camera.aspect = width / height;
+ camera.updateProjectionMatrix();
+ });
+ ro.observe(container);
}
-async function spawn() {
+async function spawn() { //spawning obstacles with bigger one having 0.2 chance, smaller ones 0.8 chance
const isRare = Math.random() < 0.2;
const modelFile = isRare ? "bird_in_a_claw_machine.glb" : "Simple computer.glb";
const source = await getCachedGLTF(modelFile);
- // Call the module function
const obstacleData = createObstacle(isRare, source, CONFIG.lane);
scene.add(obstacleData.mesh);
worldObjects = [...worldObjects, obstacleData];
}
+// Main Animation Loop: Executed every frame to update game state and rendering
function update() {
- const now = performance.now();
+ const now = performance.now(); // Time Synchronization
const delta = (now - lastTime) / 1000;
lastTime = now;
- uTime.value += delta;
+ uTime.value += delta; // Update global shader uniforms and active 3D animations
if (currentMixer) currentMixer.update(delta);
if (!isPlaying) return;
- // --- ADD THIS LINE HERE ---
// This updates the instruction timer and switches the phase
updateGameFlow({
get gamePhase() { return gamePhase; },
@@ -252,21 +244,18 @@ function update() {
set instructionTimer(v) { instructionTimer = v; }
}, delta);
- // If we are still in instructions, stop the rest of the game logic
- // (like movement and spawning) so the player doesn't die while reading.
- if (gamePhase === "INSTRUCTIONS") return;
+ if (gamePhase === "INSTRUCTIONS") return; // Halt world movement and physics during the instruction countdown
- const moveStep = currentSpeed * delta;
+ const moveStep = currentSpeed * delta; // Determine distance traveled this frame based on current velocity
- // 1. Environment Controller
+ // Environment: Updates Day/Night cycle, lighting, and celestial movement
updateEnvironment(uTime.value, scene, { ambientLight, sunLight, headLight }, { sun, moon });
- // 2. World Controller
+ // Loops ground segments and animates background parallax clouds
moveWorld(CHUNKS, moveStep, CHUNK_SIZE, CHUNK_COUNT);
animateClouds(cloudGroup, moveStep);
- // 3. Player Physics Controller
- updatePhysics(
+ updatePhysics( // Processes gravity, jumping, and lane-shifting kinematics
{
get lane() { return lane; },
get currX() { return currX; }, set currX(v) { currX = v; },
@@ -282,22 +271,24 @@ function update() {
playerAnchor.position.x = currX;
playerAnchor.position.y = playerY;
- // 4. Obstacle Controller
- worldObjects.forEach(obj => { obj.mesh.position.z += moveStep; });
+ // Obstacle Controller
+ worldObjects.forEach(obj => { obj.mesh.position.z += moveStep; }); // Move obstacles toward the player and check for bounding-box intersections
worldObjects = handleCollisions(worldObjects, lane, playerY, triggerGameOver);
- worldObjects = worldObjects.filter(obj => {
+ worldObjects = worldObjects.filter(obj => { // Garbage Collection: Remove obstacles that have passed behind the camera to free memory
const active = obj.mesh.position.z < 25;
if (!active) scene.remove(obj.mesh);
return active;
});
- // 5. Scoring & Spawning Logic
+ // Manage active multiplier power-ups (Star/Boost)
if (multiplierTimer > 0) {
multiplierTimer -= delta;
if (multiplierTimer <= 0) { multiplierTimer = 0; scoreMultiplier = 1; }
}
+ // Calculate score based on speed and active multiplier
score += Math.floor((currentSpeed / 40) * scoreMultiplier);
+ // Gradually increase velocity to scale game difficulty
if (currentSpeed < CONFIG.MAX_SPEED) currentSpeed += CONFIG.ACCELERATION * delta;
// 1. Ask the Static Manager if we should spawn
@@ -308,11 +299,6 @@ function update() {
worldObjects = [...worldObjects, obstacle];
});
}
- // spawnDistanceTracker += moveStep;
- // if (spawnDistanceTracker >= SPAWN_INTERVAL) {
- // spawn();
- // spawnDistanceTracker = 0;
- // }
}
@@ -327,8 +313,8 @@ function triggerGameOver() {
async function startGame() {
if (!scene) return;
currentSpeed = CONFIG.START_SPEED;
- // Choose a random target type for this mission
- const types = ["STRAWBERRY", "WATERMELON", "BLUEBERRY"];
+
+ const types = ["STRAWBERRY", "WATERMELON", "BLUEBERRY"]; // Choose a random target type for this mission
targetType = types[Math.floor(Math.random() * types.length)];
worldObjects.forEach(obj => scene.remove(obj.mesh));
targets.length = 0;
@@ -343,7 +329,7 @@ async function startGame() {
}
const handleKeyDown = (e) => {
- // MVC: Delegate keyboard input to the Controller
+ // Delegate keyboard input to the Controller
handleInput(e,
{
isPlaying,
@@ -364,7 +350,7 @@ const handleKeyDown = (e) => {
onMount(() => {
loadLeaderboard();
window.addEventListener("keydown", handleKeyDown);
- return () => {
+ return () => { // This return block executes when the component is destroyed (e.g., navigating away)
cancelAnimationFrame(animationFrame);
window.removeEventListener("keydown", handleKeyDown);
if (p5Instance) p5Instance.remove();
@@ -479,7 +465,7 @@ onMount(() => {
text-align: center;
font-size: 0.8rem;
letter-spacing: 2px;
- color: hsl(308, 100%, 87%); /* Change color to cyan to match your theme */
+ color: hsl(308, 100%, 87%);
text-transform: uppercase;
}
diff --git a/src/GameController.js b/src/GameController.js
index aeaaaa0..0590da3 100644
--- a/src/GameController.js
+++ b/src/GameController.js
@@ -1,4 +1,3 @@
-// GameController.js
// @ts-nocheck
export function handleInput(event, state, config, swapFn) {
if (!state.isPlaying || state.isDying) return;
@@ -53,7 +52,6 @@ export function updatePhysics(state, config, delta, swapFn) {
}
}
-// GameController.js
export function updateGameFlow(state, delta) {
if (state.gamePhase === "INSTRUCTIONS") {
state.instructionTimer -= delta;
diff --git a/src/LandingPage.svelte b/src/LandingPage.svelte
index c9609ba..5b39a75 100644
--- a/src/LandingPage.svelte
+++ b/src/LandingPage.svelte
@@ -8,29 +8,29 @@
let container;
let charCanvas;
- let animFrame;
+ let animFrame; // Animation and rendering state
let charMixer;
onMount(() => {
const clock = new THREE.Clock();
-
+ // Alpha: true allows the CSS background-image to show through the canvas
const charRenderer = new THREE.WebGLRenderer({
canvas: charCanvas,
antialias: true,
alpha: true
});
- const charScene = new THREE.Scene();
+ const charScene = new THREE.Scene(); //Scene and Camera Configuration
const charCam = new THREE.PerspectiveCamera(40, charCanvas.clientWidth / charCanvas.clientHeight, 0.1, 200);
-
charCam.position.set(0, 3, 13);
charCam.lookAt(0, 2.5, 0);
+ // Lighting Setup
charScene.add(new THREE.AmbientLight(0xffffff, 7));
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(5, 5, 5);
charScene.add(dirLight);
- const loader = new GLTFLoader();
+ const loader = new GLTFLoader(); //Asset Loading (3D Model & Animation)
loader.load("3dmodels/hiphop.glb", (gltf) => {
const model = gltf.scene;
model.scale.setScalar(3.5);
@@ -38,13 +38,13 @@
model.position.x = -1;
charScene.add(model);
- if (gltf.animations.length) {
+ if (gltf.animations.length) { // Initialize AnimationMixer if clips are present in the GLB
charMixer = new THREE.AnimationMixer(model);
charMixer.clipAction(gltf.animations[0]).play();
}
});
- const handleResize = () => {
+ const handleResize = () => { //Responsive Handling
charRenderer.setSize(charCanvas.clientWidth, charCanvas.clientHeight);
charCam.aspect = charCanvas.clientWidth / charCanvas.clientHeight;
charCam.updateProjectionMatrix();
@@ -53,7 +53,7 @@
window.addEventListener('resize', handleResize);
handleResize();
- function loop() {
+ function loop() { //Component Animation Loop
animFrame = requestAnimationFrame(loop);
const delta = clock.getDelta();
charRenderer.render(charScene, charCam);
@@ -61,7 +61,7 @@
}
loop();
- return () => {
+ return () => { //Cleanup & Resource Disposal
cancelAnimationFrame(animFrame);
window.removeEventListener('resize', handleResize);
};
@@ -75,6 +75,12 @@
+
+ Collect targets to maintain focus while navigating through obstacles. Hearts are only for missed or wrong targets, + it won't save you from crashing into obstacles. The higher the score, you might get reward boosters for setting records! + Rapid task-switching trains mental flexibility, attention, and working memory. +
+ @@ -87,6 +93,58 @@ - -// function update() { -// const now = performance.now(); -// const delta = (now - lastTime) / 1000; -// lastTime = now; - -// uTime.value += delta; -// if (currentMixer) currentMixer.update(delta); -// if (!isPlaying) return; - -// // --- MULTIPLIER COUNTDOWN --- -// if (multiplierTimer > 0) { -// multiplierTimer -= delta; -// if (multiplierTimer <= 0) { -// multiplierTimer = 0; -// scoreMultiplier = 1; -// } -// } - -// // --- NEW MODULAR ENVIRONMENT CALL --- -// updateEnvironment(uTime.value, scene, -// { ambientLight, sunLight, headLight }, -// { sun, moon } -// ); - -// if (gamePhase === "INSTRUCTIONS") { -// instructionTimer -= delta; -// if (instructionTimer <= 0) gamePhase = "PLAYING"; -// return; -// } - -// if (currentSpeed < CONFIG.MAX_SPEED) { -// currentSpeed += CONFIG.ACCELERATION * delta; -// } - -// const moveStep = currentSpeed * delta; -// // --- BOOSTED DISTANCE SCORE --- -// // We apply the multiplier to the floor calculation -// score += Math.floor((currentSpeed / 40) * scoreMultiplier); - - - -// if (cloudGroup) { -// // Moving at 40% speed (moveStep * 0.4) creates a nice parallax depth -// cloudGroup.children.forEach(cloud => { -// cloud.position.z += moveStep * 0.4; - -// // Reset cloud position if it goes too far behind the camera -// if (cloud.position.z > 50) { -// cloud.position.z = -250; -// cloud.position.x = (Math.random() - 0.5) * 280; // Randomize X again for variety -// } -// }); -// } - -// if (lives <= 0) triggerGameOver(); - -// CHUNKS.forEach(chunk => { -// chunk.position.z += moveStep; -// if (chunk.position.z > CHUNK_SIZE) chunk.position.z -= CHUNK_SIZE * CHUNK_COUNT; -// }); - -// currX += (lane * CONFIG.lane - currX) * 0.18; -// playerAnchor.position.x = currX; - -// if (isJumping) { -// jumpV -= CONFIG.grav; -// playerY += jumpV; -// if (playerY <= 0) { -// playerY = 0; -// isJumping = false; -// if (!isDying) swapCharacter("Running.glb"); -// } -// } -// playerAnchor.position.y = playerY; - -// // Update object positions -// worldObjects.forEach(obj => { obj.mesh.position.z += moveStep; }); - -// // Handle Collisions using the module -// worldObjects = handleCollisions(worldObjects, lane, playerY, triggerGameOver); - -// // Filter out-of-bounds objects -// worldObjects = worldObjects.filter(obj => { -// const active = obj.mesh.position.z < 25; -// if (!active) scene.remove(obj.mesh); -// return active; -// }); - -// // Normal Obstacle Spawning -// spawnDistanceTracker += moveStep; -// if (spawnDistanceTracker >= SPAWN_INTERVAL) { -// spawn(); -// spawnDistanceTracker = 0; - -// } -// } \ No newline at end of file diff --git a/src/main.js b/src/main.js index 458c7a8..ac3585a 100644 --- a/src/main.js +++ b/src/main.js @@ -1,9 +1,9 @@ -import { mount } from 'svelte' -import './app.css' -import App from './App.svelte' +// @ts-nocheck +import { mount } from 'svelte'; +import App from './App.svelte'; const app = mount(App, { target: document.getElementById('app'), -}) +}); -export default app +export default app; \ No newline at end of file diff --git a/src/p5overlay.js b/src/p5overlay.js index 6a2cfe2..6323899 100644 --- a/src/p5overlay.js +++ b/src/p5overlay.js @@ -3,15 +3,33 @@ export const createSketch = (state, texturesRef) => { return (p) => { // --- PRIVATE UTILITIES --- const drawHeart = (x, y, size, active) => { + const s = size / 5; + + // Helper to draw the 8-bit grid shape + const drawShape = (posX, posY, pixelSize) => { + p.rect(posX + pixelSize, posY, pixelSize, pixelSize); + p.rect(posX + 3 * pixelSize, posY, pixelSize, pixelSize); + p.rect(posX, posY + pixelSize, 5 * pixelSize, pixelSize); + p.rect(posX, posY + 2 * pixelSize, 5 * pixelSize, pixelSize); + p.rect(posX + pixelSize, posY + 3 * pixelSize, 3 * pixelSize, pixelSize); + p.rect(posX + 2 * pixelSize, posY + 4 * pixelSize, pixelSize, pixelSize); + }; p.push(); p.noStroke(); - p.fill(active ? [255, 50, 50] : [100, 100, 100, 150]); - const s = size / 5; - p.rect(x + s, y, s, s); p.rect(x + 3 * s, y, s, s); - p.rect(x, y + s, 5 * s, s); - p.rect(x, y + 2 * s, 5 * s, s); - p.rect(x + s, y + 3 * s, 3 * s, s); - p.rect(x + 2 * s, y + 4 * s, s, s); + // 1. Draw the Outline (Black, slightly offset/larger) + p.fill(0); + // We draw it 2 pixels wider in all directions for that thick Minecraft border + drawShape(x - 2, y - 2, (size + 4) / 5); + + // 2. Draw the Inner Heart + p.fill(active ? [255, 50, 50] : [60, 60, 60, 180]); + drawShape(x, y, s); + + // 3. Add a "Highlight" pixel (Minecraft hearts have a little white glint) + if (active) { + p.fill(255, 150); + p.rect(x + s, y + s, s, s); + } p.pop(); }; @@ -21,7 +39,6 @@ export const createSketch = (state, texturesRef) => { p.loadImage(`images/${path}`, img => resolve(img), () => resolve(null)); }); - // Load into the reference object passed from App.svelte texturesRef.STRAWBERRY = await loadImg('strawberry.png'); texturesRef.WATERMELON = await loadImg('watermelon.png'); texturesRef.BLUEBERRY = await loadImg('blubb.png'); @@ -70,7 +87,7 @@ export const createSketch = (state, texturesRef) => { } // 4. Render Hearts - for (let i = 0; i < 5; i++) drawHeart(20 + (i * 35), 20, 25, i < state.lives); + Array.from({ length: 5 }).map((_, i) => drawHeart(25 + (i * 60), 25, 45, i < state.lives)); // 5. Random Target Spawning if (p.random(1) < 0.004) { @@ -83,12 +100,12 @@ export const createSketch = (state, texturesRef) => { // 6. Current Target Box p.push(); - p.fill(0, 150); p.rect(10, 60, 120, 140, 15); + p.fill(0, 150); p.rect(10, 80, 140, 140, 15); p.fill(255); p.textSize(14); p.textAlign(p.CENTER); - p.text("CURRENT TARGET", 70, 85); + p.text("CURRENT TARGET",80, 105); const boxImg = texturesRef[state.targetType]; - if (boxImg) p.image(boxImg, 40, 95, 60, 60); - p.fill(0, 255, 200); p.text(state.targetType, 70, 185); + if (boxImg) p.image(boxImg, 40, 120, 70, 70); + p.fill(0, 255, 200); p.text(state.targetType, 80, 210); p.pop(); // 7. Render/Move Targets diff --git a/vite.config.js b/vite.config.js index d32eba1..2f557a4 100644 --- a/vite.config.js +++ b/vite.config.js @@ -4,4 +4,5 @@ import { svelte } from '@sveltejs/vite-plugin-svelte' // https://vite.dev/config/ export default defineConfig({ plugins: [svelte()], + base: './', })