From 2f04d4f90575b1d7d5b4c712e30e358e9756b5aa Mon Sep 17 00:00:00 2001 From: Mak Date: Sun, 10 May 2026 17:39:40 +0900 Subject: [PATCH] cleaned up a bit --- src/App.svelte | 166 +++++---- src/GameController.js | 2 - src/LandingPage.svelte | 85 ++++- src/ObstacleFactory.js | 4 +- src/WorldScene.js | 6 - src/app.css | 296 ---------------- src/cool.svelte | 748 ----------------------------------------- src/main.js | 10 +- src/p5overlay.js | 43 ++- vite.config.js | 1 + 10 files changed, 180 insertions(+), 1181 deletions(-) delete mode 100644 src/app.css delete mode 100644 src/cool.svelte 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: './', })