feat: replace JSX with TSX for type safety

pull/35/merge^2
Bart Zalewski 2024-02-29 00:26:24 +01:00
parent 8b08815d5c
commit d0b2a73185
68 changed files with 2344 additions and 1835 deletions

View File

@ -1,27 +1,27 @@
{
"name": "Mario Kart Dev Container",
"image": "mcr.microsoft.com/devcontainers/javascript-node",
"customizations": {
"vscode": {
"extensions": [
"eamodio.gitlens",
"esbenp.prettier-vscode",
"VisualStudioExptTeam.vscodeintellicode",
"dbaeumer.vscode-eslint",
"dsznajder.es7-react-js-snippets",
"antfu.vite",
"meganrogge.template-string-converter",
"ambar.bundle-size",
"aaron-bond.better-comments"
]
}
},
"forwardPorts": [4000],
"portsAttributes": {
"4000": {
"label": "Application",
"onAutoForward": "notify"
}
},
"postCreateCommand": "npm install"
"name": "Mario Kart Dev Container",
"image": "mcr.microsoft.com/devcontainers/javascript-node",
"customizations": {
"vscode": {
"extensions": [
"eamodio.gitlens",
"esbenp.prettier-vscode",
"VisualStudioExptTeam.vscodeintellicode",
"dbaeumer.vscode-eslint",
"dsznajder.es7-react-js-snippets",
"antfu.vite",
"meganrogge.template-string-converter",
"ambar.bundle-size",
"aaron-bond.better-comments"
]
}
},
"forwardPorts": [4000],
"portsAttributes": {
"4000": {
"label": "Application",
"onAutoForward": "notify"
}
},
"postCreateCommand": "npm install"
}

View File

@ -1,4 +1,5 @@
# Mario Kart 3.js - JavaScript/WebGL Mario Kart
[Link](https://mario-kart-3-js.vercel.app/)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/Lunakepio/Mario-Kart-3.js)
@ -53,7 +54,7 @@ Start the dev server
- [ ] Add curve/length modifiers to drift particles 3/4
- [ ] Add Skid marks
- [ ] Add Skid marks
- [x] Add smokes

View File

@ -7,7 +7,7 @@
<title>Mario Kart 3.js</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

1998
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,25 +9,30 @@
"preview": "vite preview"
},
"dependencies": {
"@react-three/drei": "^9.96.4",
"@react-three/fiber": "^8.15.15",
"@react-three/postprocessing": "^2.15.11",
"@react-three/rapier": "^1.2.1",
"@react-three/drei": "^9.99.4",
"@react-three/fiber": "^8.15.16",
"@react-three/postprocessing": "^2.16.0",
"@react-three/rapier": "^1.3.0",
"gsap": "^3.12.5",
"leva": "^0.9.35",
"playroomkit": "^0.0.66",
"playroomkit": "^0.0.68",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-gamepad": "^1.0.3",
"react-joystick-component": "^6.2.1",
"three": "^0.160.1",
"three-mesh-bvh": "^0.7.0",
"zustand": "^4.5.0"
"three": "^0.161.0",
"three-mesh-bvh": "^0.7.3",
"vite-tsconfig-paths": "^4.3.1",
"zustand": "^4.5.1"
},
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react-swc": "^3.0.0",
"vite": "^4.2.0"
"@types/react": "^18.2.60",
"@types/react-dom": "^18.2.19",
"@types/three": "^0.161.2",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"@vitejs/plugin-react-swc": "^3.6.0",
"typescript": "^5.3.3",
"vite": "^5.1.4"
}
}

View File

@ -11,9 +11,25 @@ export function ario(props) {
const { nodes, materials } = useGLTF('/mariokart-transformed.glb')
return (
<group {...props} dispose={null}>
<mesh castShadow receiveShadow geometry={nodes.mt_kart_Mario_S.geometry} material={materials.mt_kart_Mario_S} />
<mesh castShadow receiveShadow geometry={nodes.mt_Kart_Mario_Tire_S001.geometry} material={materials.mt_Kart_Mario_Tire_S} position={[0, 0.22, -0.347]} />
<mesh castShadow receiveShadow geometry={nodes.mt_mario.geometry} material={materials.mt_mario} />
<mesh
castShadow
receiveShadow
geometry={nodes.mt_kart_Mario_S.geometry}
material={materials.mt_kart_Mario_S}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.mt_Kart_Mario_Tire_S001.geometry}
material={materials.mt_Kart_Mario_Tire_S}
position={[0, 0.22, -0.347]}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.mt_mario.geometry}
material={materials.mt_mario}
/>
</group>
)
}

View File

@ -24,16 +24,36 @@ type GLTFResult = GLTF & {
animations: GLTFAction[]
}
type ContextType = Record<string, React.ForwardRefExoticComponent<JSX.IntrinsicElements['mesh']>>
type ContextType = Record<
string,
React.ForwardRefExoticComponent<JSX.IntrinsicElements['mesh']>
>
export function Model(props: JSX.IntrinsicElements['group']) {
const { nodes, materials } = useGLTF('/mariokarttest.glb') as GLTFResult
return (
<group {...props} dispose={null}>
<mesh geometry={nodes.mt_kart_Mario_S.geometry} material={materials.mt_kart_Mario_S} />
<mesh geometry={nodes.mt_Kart_Mario_Tire_S001.geometry} material={materials.mt_Kart_Mario_Tire_S} position={[0, 0.22, -0.347]} />
<mesh geometry={nodes.mt_Kart_Mario_Tire_S002.geometry} material={materials.mt_Kart_Mario_Tire_S} position={[0.384, 0.193, 0.441]} />
<mesh geometry={nodes.mt_Kart_Mario_Tire_S003.geometry} material={materials.mt_Kart_Mario_Tire_S} position={[-0.393, 0.193, 0.441]} rotation={[-Math.PI, 0, 0]} scale={-1} />
<mesh
geometry={nodes.mt_kart_Mario_S.geometry}
material={materials.mt_kart_Mario_S}
/>
<mesh
geometry={nodes.mt_Kart_Mario_Tire_S001.geometry}
material={materials.mt_Kart_Mario_Tire_S}
position={[0, 0.22, -0.347]}
/>
<mesh
geometry={nodes.mt_Kart_Mario_Tire_S002.geometry}
material={materials.mt_Kart_Mario_Tire_S}
position={[0.384, 0.193, 0.441]}
/>
<mesh
geometry={nodes.mt_Kart_Mario_Tire_S003.geometry}
material={materials.mt_Kart_Mario_Tire_S}
position={[-0.393, 0.193, 0.441]}
rotation={[-Math.PI, 0, 0]}
scale={-1}
/>
<mesh geometry={nodes.mt_mario.geometry} material={materials.mt_mario} />
</group>
)

View File

@ -2,11 +2,10 @@ import { Canvas } from '@react-three/fiber'
import { Experience } from './components/Experience'
import { Suspense, useEffect, useMemo } from 'react'
import { Physics } from '@react-three/rapier'
import { Environment, KeyboardControls, Loader, OrbitControls, Preload, Stats } from '@react-three/drei'
import { KeyboardControls, Loader, Preload } from '@react-three/drei'
import { insertCoin, onPlayerJoin } from 'playroomkit'
import { useStore } from "./components/store";
import * as THREE from "three";
import { ParisBis } from './components/models/tracks/Paris-bis'
import { useStore } from './components/store'
import * as THREE from 'three'
export const Controls = {
up: 'up',
@ -17,8 +16,9 @@ export const Controls = {
shoot: 'shoot',
slow: 'slow',
reset: 'reset',
escape: 'escape'
}
escape: 'escape',
jump: 'jump',
} as const
function App() {
const map = useMemo(
@ -31,55 +31,58 @@ function App() {
{ name: Controls.slow, keys: ['Shift'] },
{ name: Controls.shoot, keys: ['KeyE', 'Click'] },
{ name: Controls.reset, keys: ['KeyR'] },
{ name: Controls.escape, keys: ['Escape']}
{ name: Controls.escape, keys: ['Escape'] },
],
[]
)
const { actions } = useStore();
const { actions } = useStore()
const start = async () => {
await insertCoin();
await insertCoin()
onPlayerJoin((state) => {
actions.addPlayer(state);
actions.addPlayer(state)
actions.setId(state.id);
actions.setId(state.id)
state.onQuit(() => {
actions.removePlayer(state);
});
});
actions.removePlayer(state)
})
})
}
useEffect(() => {
start();
start()
}, [])
return (
<>
<Loader />
<Canvas
// shadows
dpr={1}
gl={{ antialias: false, stencil: false, depth:false, powerPreference: 'high-performance' }}
mode="concurrent"
onCreated={({ gl, camera }) => {
<Loader />
<Canvas
// shadows
dpr={1}
gl={{
antialias: false,
stencil: false,
depth: false,
powerPreference: 'high-performance',
}}
mode="concurrent"
onCreated={({ gl }) => {
gl.toneMapping = THREE.AgXToneMapping
gl.setClearColor(0x000000, 0)
}}>
<Suspense fallback={null}>
<Preload all />
<Physics
gravity={[0, -90, 0]}
timeStep={'vary'}
>
<KeyboardControls map={map}>
<Experience />
</KeyboardControls>
</Physics>
</Suspense>
</Canvas>
}}
>
<Suspense fallback={null}>
<Preload all />
<Physics gravity={[0, -90, 0]} timeStep={'vary'}>
<KeyboardControls map={map}>
<Experience />
</KeyboardControls>
</Physics>
</Suspense>
</Canvas>
</>
)
}

View File

@ -1,144 +0,0 @@
import React, { useEffect, useRef, useState } from "react";
import { useStore } from "./components/store";
import { Joystick } from "react-joystick-component";
export const HUD = () => {
const wheel = useRef();
const [image, setImage] = useState("");
const { item, gameStarted, actions, controls } = useStore();
useEffect(() => {
const handleMouseMove = (e) => {
if (wheel.current) {
const { clientX, clientY } = e;
const screenWidth = window.innerWidth;
const rotation = ((clientX - screenWidth / 2) / screenWidth) * 180;
wheel.current.style.left = `${clientX - 100}px`;
wheel.current.style.top = `${clientY - 100}px`;
wheel.current.style.transform = `rotate(${rotation}deg)`;
}
};
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, []);
const handleMove = (e) => {
actions.setJoystickX(e.x);
};
const handleStop = () => {
actions.setJoystickX(0);
};
useEffect(() => {
switch (item) {
case "banana":
setImage("./images/banana.webp");
break;
case "mushroom":
setImage("./images/mushroom.png");
break;
case "shell":
setImage("./images/shell.webp");
break;
default:
setImage("");
}
}, [item]);
return (
<div className="overlay">
{gameStarted && (
<>
<div className="item">
<div className="borderOut">
<div className="borderIn">
<div className="background">
{image && <img src={image} alt="item" width={90} />}
</div>
</div>
</div>
</div>
{controls === "touch" && (
<>
<div className="controls joystick">
<Joystick
size={100}
sticky={false}
baseColor="rgba(255, 255, 255, 0.5)"
stickColor="rgba(255, 255, 255, 0.5)"
move={handleMove}
stop={handleStop}
></Joystick>
</div>
<div
className="controls drift"
onMouseDown={(e) => {
actions.setDriftButton(true);
}}
onMouseUp={(e) => {
actions.setDriftButton(false);
}}
onTouchStart={(e) => {
e.preventDefault();
actions.setDriftButton(true);
}}
onTouchEnd={(e) => {
e.preventDefault();
actions.setDriftButton(false);
}}
>
drift
</div>
<div
className="controls itemButton"
onMouseDown={(e) => {
actions.setItemButton(true);
}}
onMouseUp={(e) => {
actions.setItemButton(false);
}}
onTouchStart={(e) => {
e.preventDefault();
actions.setItemButton(true);
}}
onTouchEnd={(e) => {
e.preventDefault();
actions.setItemButton(false);
}}
>
item
</div>
<div
className="controls menuButton"
onMouseDown={(e) => {
actions.setMenuButton(true);
}}
onMouseUp={(e) => {
actions.setMenuButton(false);
}}
onTouchStart={(e) => {
e.preventDefault();
actions.setMenuButton(true);
}}
onTouchEnd={(e) => {
e.preventDefault();
actions.setMenuButton(false);
}}
>
menu
</div>
</>
)}
</>
)}
</div>
);
};

147
src/HUD.tsx Normal file
View File

@ -0,0 +1,147 @@
import { useEffect, useRef, useState } from 'react'
import { useStore } from './components/store'
import { Joystick } from 'react-joystick-component'
import { IJoystickUpdateEvent } from 'react-joystick-component/build/lib/Joystick'
export const HUD = () => {
const wheel = useRef()
const [image, setImage] = useState('')
const { item, gameStarted, actions, controls } = useStore()
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (wheel.current) {
const { clientX, clientY } = e
const screenWidth = window.innerWidth
const rotation = ((clientX - screenWidth / 2) / screenWidth) * 180
if (wheel.current) {
;(wheel.current as HTMLDivElement).style.left = `${clientX - 100}px`
;(wheel.current as HTMLDivElement).style.top = `${clientY - 100}px`
;(
wheel.current as HTMLDivElement
).style.transform = `rotate(${rotation}deg)`
}
}
}
window.addEventListener('mousemove', handleMouseMove)
return () => {
window.removeEventListener('mousemove', handleMouseMove)
}
}, [])
const handleMove = (e: IJoystickUpdateEvent) => {
actions.setJoystickX(e.x ?? 0)
}
const handleStop = () => {
actions.setJoystickX(0)
}
useEffect(() => {
switch (item) {
case 'banana':
setImage('./images/banana.webp')
break
case 'mushroom':
setImage('./images/mushroom.png')
break
case 'shell':
setImage('./images/shell.webp')
break
default:
setImage('')
}
}, [item])
return (
<div className="overlay">
{gameStarted && (
<>
<div className="item">
<div className="borderOut">
<div className="borderIn">
<div className="background">
{image && <img src={image} alt="item" width={90} />}
</div>
</div>
</div>
</div>
{controls === 'touch' && (
<>
<div className="controls joystick">
<Joystick
size={100}
sticky={false}
baseColor="rgba(255, 255, 255, 0.5)"
stickColor="rgba(255, 255, 255, 0.5)"
move={handleMove}
stop={handleStop}
></Joystick>
</div>
<div
className="controls drift"
onMouseDown={() => {
actions.setDriftButton(true)
}}
onMouseUp={() => {
actions.setDriftButton(false)
}}
onTouchStart={(e) => {
e.preventDefault()
actions.setDriftButton(true)
}}
onTouchEnd={(e) => {
e.preventDefault()
actions.setDriftButton(false)
}}
>
drift
</div>
<div
className="controls itemButton"
onMouseDown={() => {
actions.setItemButton(true)
}}
onMouseUp={() => {
actions.setItemButton(false)
}}
onTouchStart={(e) => {
e.preventDefault()
actions.setItemButton(true)
}}
onTouchEnd={(e) => {
e.preventDefault()
actions.setItemButton(false)
}}
>
item
</div>
<div
className="controls menuButton"
onMouseDown={() => {
actions.setMenuButton(true)
}}
onMouseUp={() => {
actions.setMenuButton(false)
}}
onTouchStart={(e) => {
e.preventDefault()
actions.setMenuButton(true)
}}
onTouchEnd={(e) => {
e.preventDefault()
actions.setMenuButton(false)
}}
>
menu
</div>
</>
)}
</>
)}
</div>
)
}

View File

@ -1,128 +0,0 @@
import React, { useEffect, useRef, useState } from "react";
import { useStore } from "./components/store";
import gsap from "gsap";
export const Landing = () => {
const { gameStarted, actions } = useStore();
const logo = useRef();
const startButton = useRef();
const homeRef = useRef();
const [setupStatus, setSetupStatus] = useState(0);
const [controlStyle, setControlStyle] = useState("");
useEffect(() => {
const tl = gsap.timeline();
if (setupStatus === 0) {
if (logo.current && startButton.current) {
tl.from(logo.current, {
scale: 122,
opacity: 0,
duration: 0,
ease: "power4.out",
})
.to(logo.current, {
scale: 1,
opacity: 1,
duration: 1.5,
ease: "power4.out",
})
.to(startButton.current, {
opacity: 1,
duration: 3,
delay: 1,
ease: "power4.out",
});
}
}
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
setSetupStatus(1);
}
};
document.body.addEventListener('keydown', handleKeyDown);
return () => {
document.body.removeEventListener('keydown', handleKeyDown);
};
}, [setupStatus]);
if (gameStarted) {
return null;
}
return (
<>
{setupStatus === 0 && (
<div className="home" ref={homeRef}>
<div className="logo">
<img ref={logo} src="./logo.png" alt="logo" />
</div>
<div className="start" ref={startButton}>
<button className="start-button"
onClick={() => setSetupStatus(1)}
onKeyDown={(event) => {
if (event.key === 'Enter') {
setSetupStatus(1);
}}} autoFocus>
PRESS ENTER TO START
</button>
</div>
</div>
)}
{setupStatus === 1 && (
<div className="home">
<div className="glassy">
<h1>CHOOSE YOUR CONTROL STYLE</h1>
<div className="articles">
<div className={controlStyle === "keyboard" ? "article selected" : "article"} onClick={() =>
setControlStyle("keyboard")}>
<img src="./images/keyboard.png" alt="keyboard" />
<div className="article_label">
<p>Keyboard</p>
</div>
</div>
<div className={controlStyle === "gamepad" ? "article selected" : "article"} onClick={() =>
setControlStyle("gamepad")}>
<img src="./images/gamepad.png" alt="gamepad" />
<div className="article_label">
<p>Gamepad</p>
</div>
</div>
<div className={controlStyle === "mouseKeyboard" ? "article selected" : "article"} onClick={() =>
setControlStyle("mouseKeyboard")}>
<img src="./images/mousekeyboard.png" alt="mouse & keyboard" />
<div className="article_label">
<p>Mouse & Keyboard</p>
</div>
</div>
<div className={controlStyle === "touch" ? "article selected" : "article"} onClick={() =>
setControlStyle("touch")}>
<img src="./images/mobile.png" alt="mobile" />
<div className="article_label">
<p>Mobile</p>
</div>
</div>
</div>
<div className={controlStyle != "" ? "submit" : "submit disabled"}>
<button
className={controlStyle != "" ? "submit-button" : "submit-button disabled"}
onClick={() => {
actions.setControls(controlStyle);
actions.setGameStarted(true);
}}
>
CONFIRM
</button>
</div>
</div>
</div>
)}
</>
);
};

152
src/Landing.tsx Normal file
View File

@ -0,0 +1,152 @@
import { useEffect, useRef, useState } from 'react'
import { useStore } from './components/store'
import gsap from 'gsap'
export const Landing = () => {
const { gameStarted, actions } = useStore()
const logo = useRef()
const startButton = useRef()
const homeRef = useRef()
const [setupStatus, setSetupStatus] = useState(0)
const [controlStyle, setControlStyle] = useState('')
useEffect(() => {
const tl = gsap.timeline()
if (setupStatus === 0) {
if (logo.current && startButton.current) {
tl.from(logo.current, {
scale: 122,
opacity: 0,
duration: 0,
ease: 'power4.out',
})
.to(logo.current, {
scale: 1,
opacity: 1,
duration: 1.5,
ease: 'power4.out',
})
.to(startButton.current, {
opacity: 1,
duration: 3,
delay: 1,
ease: 'power4.out',
})
}
}
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
setSetupStatus(1)
}
}
document.body.addEventListener('keydown', handleKeyDown)
return () => {
document.body.removeEventListener('keydown', handleKeyDown)
}
}, [setupStatus])
if (gameStarted) {
return null
}
return (
<>
{setupStatus === 0 && (
<div className="home" ref={homeRef}>
<div className="logo">
<img ref={logo} src="./logo.png" alt="logo" />
</div>
<div className="start" ref={startButton}>
<button
className="start-button"
onClick={() => setSetupStatus(1)}
onKeyDown={(event) => {
if (event.key === 'Enter') {
setSetupStatus(1)
}
}}
autoFocus
>
PRESS ENTER TO START
</button>
</div>
</div>
)}
{setupStatus === 1 && (
<div className="home">
<div className="glassy">
<h1>CHOOSE YOUR CONTROL STYLE</h1>
<div className="articles">
<div
className={
controlStyle === 'keyboard' ? 'article selected' : 'article'
}
onClick={() => setControlStyle('keyboard')}
>
<img src="./images/keyboard.png" alt="keyboard" />
<div className="article_label">
<p>Keyboard</p>
</div>
</div>
<div
className={
controlStyle === 'gamepad' ? 'article selected' : 'article'
}
onClick={() => setControlStyle('gamepad')}
>
<img src="./images/gamepad.png" alt="gamepad" />
<div className="article_label">
<p>Gamepad</p>
</div>
</div>
<div
className={
controlStyle === 'mouseKeyboard'
? 'article selected'
: 'article'
}
onClick={() => setControlStyle('mouseKeyboard')}
>
<img src="./images/mousekeyboard.png" alt="mouse & keyboard" />
<div className="article_label">
<p>Mouse & Keyboard</p>
</div>
</div>
<div
className={
controlStyle === 'touch' ? 'article selected' : 'article'
}
onClick={() => setControlStyle('touch')}
>
<img src="./images/mobile.png" alt="mobile" />
<div className="article_label">
<p>Mobile</p>
</div>
</div>
</div>
<div className={controlStyle != '' ? 'submit' : 'submit disabled'}>
<button
className={
controlStyle != ''
? 'submit-button'
: 'submit-button disabled'
}
onClick={() => {
actions.setControls(controlStyle)
actions.setGameStarted(true)
}}
>
CONFIRM
</button>
</div>
</div>
</div>
)}
</>
)
}

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -1,62 +1,47 @@
import {
Environment,
OrbitControls,
PerspectiveCamera,
Lightformer,
Bvh,
} from "@react-three/drei";
import { Ground } from "./Ground";
import { PlayerController } from "./PlayerController";
import { PlayerControllerGamepad } from "./PlayerControllerGamepad";
import { PlayerControllerKeyboard } from "./PlayerControllerKeyboard";
import { PlayerControllerTouch } from "./PlayerControllerTouch";
import { Paris } from "./models/tracks/Tour_paris_promenade";
} from '@react-three/drei'
import { Ground } from './Ground'
import { PlayerController } from './PlayerController'
import { PlayerControllerGamepad } from './PlayerControllerGamepad'
import { PlayerControllerKeyboard } from './PlayerControllerKeyboard'
import { PlayerControllerTouch } from './PlayerControllerTouch'
import {
EffectComposer,
N8AO,
Bloom,
TiltShift2,
HueSaturation,
SMAA,
ChromaticAberration,
Vignette,
LUT,
} from "@react-three/postprocessing";
import { Banana } from "./models/items/Banana_peel_mario_kart";
import { ItemBox } from "./models/misc/Gift";
import { useStore } from "./store";
import { Shell } from "./models/items/Mario_shell_red";
import { Coin } from "./models/misc/Super_mario_bros_coin";
} from '@react-three/postprocessing'
import { Banana } from './models/items/Banana_peel_mario_kart'
import { ItemBox } from './models/misc/Gift'
import { useStore } from './store'
import { Shell } from './models/items/Mario_shell_red'
import { Coin } from './models/misc/Super_mario_bros_coin'
import {
RPC,
getState,
insertCoin,
isHost,
myPlayer,
onPlayerJoin,
useMultiplayerState,
} from "playroomkit";
import { PlayerDummies } from "./PlayerDummies";
import { useEffect, useState, useRef } from "react";
import { useFrame, useLoader } from "@react-three/fiber";
import { LUTPass, LUTCubeLoader } from "three-stdlib";
import { useCurvedPathPoints } from "./useCurvedPath";
import { ParisBis } from "./models/tracks/Paris-bis";
} from 'playroomkit'
import { PlayerDummies } from './PlayerDummies'
import { useEffect, useState, useRef } from 'react'
import { useFrame, useLoader } from '@react-three/fiber'
import { LUTCubeLoader } from 'three-stdlib'
import { useCurvedPathPoints } from './useCurvedPath'
import { ParisBis } from './models/tracks/Paris-bis'
export const Experience = () => {
const onCollide = (event) => {};
const onCollide = (event) => {}
const { gameStarted, bananas, shells, players, id, actions, controls } =
useStore();
const [networkBananas, setNetworkBananas] = useMultiplayerState(
"bananas",
[]
);
useStore()
const [networkBananas, setNetworkBananas] = useMultiplayerState('bananas', [])
const { points, loading, error } = useCurvedPathPoints("./CurvedPath.json");
const { points, loading, error } = useCurvedPathPoints('./CurvedPath.json')
const [networkShells, setNetworkShells] = useMultiplayerState("shells", []);
const [pointest, setPointest] = useState([]);
const [currentPoint, setCurrentPoint] = useState(0);
const [networkShells, setNetworkShells] = useMultiplayerState('shells', [])
const [pointest, setPointest] = useState([])
const [currentPoint, setCurrentPoint] = useState(0)
useEffect(() => {
if (points) {
//This is adjusted to Paris scale
@ -64,14 +49,14 @@ export const Experience = () => {
x: point.x * 50,
y: point.y * 50,
z: point.z * 50,
}));
setPointest(scaledPoints.reverse());
}))
setPointest(scaledPoints.reverse())
}
}, [points]);
}, [points])
const testing = getState("bananas");
const cam = useRef();
const lookAtTarget = useRef();
const testing = getState('bananas')
const cam = useRef()
const lookAtTarget = useRef()
// useEffect(() => {
// setNetworkBananas(bananas);
// }, [bananas]);
@ -79,41 +64,41 @@ export const Experience = () => {
// useEffect(() => {
// setNetworkShells(shells);
// }, [shells]);
const speedFactor = 5;
const { texture } = useLoader(LUTCubeLoader, "./cubicle-99.CUBE");
const speedFactor = 5
const { texture } = useLoader(LUTCubeLoader, './cubicle-99.CUBE')
useFrame((state, delta) => {
if (!gameStarted) {
const camera = cam.current;
const camera = cam.current
if (currentPoint < pointest.length - 1) {
camera.position.lerp(pointest[currentPoint], delta * speedFactor);
camera.position.lerp(pointest[currentPoint], delta * speedFactor)
lookAtTarget.current.position.lerp(
pointest[currentPoint + 1],
delta * speedFactor
);
camera.lookAt(lookAtTarget.current.position);
)
camera.lookAt(lookAtTarget.current.position)
if (camera.position.distanceTo(pointest[currentPoint]) < 5) {
setCurrentPoint(currentPoint + 1);
setCurrentPoint(currentPoint + 1)
}
} else {
setCurrentPoint(0);
setCurrentPoint(0)
}
}
});
})
return (
<>
{gameStarted &&
players.map((player) => {
const ControllerComponent =
controls === "keyboard"
controls === 'keyboard'
? PlayerControllerKeyboard
: controls === "gamepad"
: controls === 'gamepad'
? PlayerControllerGamepad
: controls === "touch"
: controls === 'touch'
? PlayerControllerTouch
: PlayerController;
: PlayerController
return (
<ControllerComponent
@ -125,7 +110,7 @@ export const Experience = () => {
networkBananas={networkBananas}
networkShells={networkShells}
/>
);
)
})}
{gameStarted &&
players.map((player) => (
@ -207,5 +192,5 @@ export const Experience = () => {
{/* <Vignette eskil={false} offset={0.1} darkness={0.4} /> */}
</EffectComposer>
</>
);
};
)
}

View File

@ -1,15 +0,0 @@
import { CircleCoinParticle } from "./CircleCoinParticle"
import { StarCoinParticle } from "./StarCoinParticle"
export const CoinParticles = ({ coins }) => {
return (
<>
<CircleCoinParticle position={[0,0.8, 0.2]} coins={coins}/>
<StarCoinParticle position={[0,0.8, 0.2]} coins={coins} timeModifier={50}/>
<StarCoinParticle position={[0,0.8, 0.2]} coins={coins} timeModifier={60}/>
<StarCoinParticle position={[0,0.8, 0.2]} coins={coins} timeModifier={40}/>
<StarCoinParticle position={[0,0.8, 0.2]} coins={coins} timeModifier={90}/>
</>
)
}

View File

@ -0,0 +1,30 @@
import { CircleCoinParticle } from './CircleCoinParticle'
import { StarCoinParticle } from './StarCoinParticle'
export const CoinParticles = ({ coins }) => {
return (
<>
<CircleCoinParticle position={[0, 0.8, 0.2]} coins={coins} />
<StarCoinParticle
position={[0, 0.8, 0.2]}
coins={coins}
timeModifier={50}
/>
<StarCoinParticle
position={[0, 0.8, 0.2]}
coins={coins}
timeModifier={60}
/>
<StarCoinParticle
position={[0, 0.8, 0.2]}
coins={coins}
timeModifier={40}
/>
<StarCoinParticle
position={[0, 0.8, 0.2]}
coins={coins}
timeModifier={90}
/>
</>
)
}

View File

@ -1,54 +0,0 @@
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import * as THREE from 'three';
export const Particles2 = ({ turboColor, scale, ...props }) => {
const ref = useRef();
const velocity = useRef({
x: Math.random() * 0.05,
y: Math.random() * 0.05,
z: Math.random() * 0.02,
});
const gravity = -0.003;
useFrame((state, delta) => {
let position = ref.current.position;
let velocityVector = new THREE.Vector3(velocity.current.x, velocity.current.y, velocity.current.z);
velocity.current.y += gravity * delta * 144;
position.x += velocity.current.x * delta * 144;
position.y += velocity.current.y * delta * 144;
position.z += velocity.current.z * delta * 144;
if (!velocityVector.equals(new THREE.Vector3(0, 0, 0))) {
const alignmentQuaternion = new THREE.Quaternion();
alignmentQuaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), velocityVector.normalize());
ref.current.quaternion.slerp(alignmentQuaternion, 0.1);
}
if (position.y < 0.05) {
position.set(0.6, 0.05, 0.5);
velocity.current = {
x: Math.random() * 0.05,
y: Math.random() * 0.05,
z: Math.random() * 0.02,
};
}
ref.current.position.set(position.x, position.y, position.z);
});
return (
<mesh ref={ref} position={[0.6, 0.05, 0.5]} scale={[scale, scale * 5, scale]}>
<sphereGeometry args={[0.01, 16, 16]} />
<meshStandardMaterial
emissive={turboColor}
toneMapped={false}
emissiveIntensity={5}
/>
</mesh>
);
};

View File

@ -0,0 +1,63 @@
import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
import * as THREE from 'three'
export const Particles2 = ({ turboColor, scale, ...props }) => {
const ref = useRef()
const velocity = useRef({
x: Math.random() * 0.05,
y: Math.random() * 0.05,
z: Math.random() * 0.02,
})
const gravity = -0.003
useFrame((state, delta) => {
let position = ref.current.position
let velocityVector = new THREE.Vector3(
velocity.current.x,
velocity.current.y,
velocity.current.z
)
velocity.current.y += gravity * delta * 144
position.x += velocity.current.x * delta * 144
position.y += velocity.current.y * delta * 144
position.z += velocity.current.z * delta * 144
if (!velocityVector.equals(new THREE.Vector3(0, 0, 0))) {
const alignmentQuaternion = new THREE.Quaternion()
alignmentQuaternion.setFromUnitVectors(
new THREE.Vector3(0, 1, 0),
velocityVector.normalize()
)
ref.current.quaternion.slerp(alignmentQuaternion, 0.1)
}
if (position.y < 0.05) {
position.set(0.6, 0.05, 0.5)
velocity.current = {
x: Math.random() * 0.05,
y: Math.random() * 0.05,
z: Math.random() * 0.02,
}
}
ref.current.position.set(position.x, position.y, position.z)
})
return (
<mesh
ref={ref}
position={[0.6, 0.05, 0.5]}
scale={[scale, scale * 5, scale]}
>
<sphereGeometry args={[0.01, 16, 16]} />
<meshStandardMaterial
emissive={turboColor}
toneMapped={false}
emissiveIntensity={5}
/>
</mesh>
)
}

View File

@ -1,55 +0,0 @@
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import * as THREE from 'three';
export const Particles4 = ({ turboColor, scale, ...props }) => {
const ref = useRef();
const velocity = useRef({
x: Math.random() * 0.05,
y: Math.random() * 0.05,
z: Math.random() * 0.02,
});
const gravity = -0.001;
useFrame((state, delta) => {
let position = ref.current.position;
let velocityVector = new THREE.Vector3(velocity.current.x, velocity.current.y, velocity.current.z);
// Adjust gravity and velocity based on delta
velocity.current.y += gravity * delta * 144; // Multiply by 144 to scale for 144 FPS
// Scale velocity changes by delta
position.x += velocity.current.x * delta * 144;
position.y += velocity.current.y * delta * 144;
position.z += velocity.current.z * delta * 144;
if (!velocityVector.equals(new THREE.Vector3(0, 0, 0))) {
const alignmentQuaternion = new THREE.Quaternion();
alignmentQuaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), velocityVector.normalize());
ref.current.quaternion.slerp(alignmentQuaternion, 0.1);
}
if (position.y < 0.05) {
position.set(0.6, 0.05, 0.5);
velocity.current = {
x: Math.random() * 0.05,
y: Math.random() * 0.05,
z: Math.random() * 0.02,
};
}
ref.current.position.set(position.x, position.y, position.z);
});
return (
<mesh ref={ref} position={[0.6, 0.05, 0.5]} scale={[scale, scale * 20, scale]}>
<sphereGeometry args={[0.01, 16, 16]} />
<meshStandardMaterial
emissive={turboColor}
toneMapped={false}
emissiveIntensity={5}
/>
</mesh>
);
};

View File

@ -0,0 +1,65 @@
import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
import * as THREE from 'three'
export const Particles4 = ({ turboColor, scale, ...props }) => {
const ref = useRef()
const velocity = useRef({
x: Math.random() * 0.05,
y: Math.random() * 0.05,
z: Math.random() * 0.02,
})
const gravity = -0.001
useFrame((state, delta) => {
let position = ref.current.position
let velocityVector = new THREE.Vector3(
velocity.current.x,
velocity.current.y,
velocity.current.z
)
// Adjust gravity and velocity based on delta
velocity.current.y += gravity * delta * 144 // Multiply by 144 to scale for 144 FPS
// Scale velocity changes by delta
position.x += velocity.current.x * delta * 144
position.y += velocity.current.y * delta * 144
position.z += velocity.current.z * delta * 144
if (!velocityVector.equals(new THREE.Vector3(0, 0, 0))) {
const alignmentQuaternion = new THREE.Quaternion()
alignmentQuaternion.setFromUnitVectors(
new THREE.Vector3(0, 1, 0),
velocityVector.normalize()
)
ref.current.quaternion.slerp(alignmentQuaternion, 0.1)
}
if (position.y < 0.05) {
position.set(0.6, 0.05, 0.5)
velocity.current = {
x: Math.random() * 0.05,
y: Math.random() * 0.05,
z: Math.random() * 0.02,
}
}
ref.current.position.set(position.x, position.y, position.z)
})
return (
<mesh
ref={ref}
position={[0.6, 0.05, 0.5]}
scale={[scale, scale * 20, scale]}
>
<sphereGeometry args={[0.01, 16, 16]} />
<meshStandardMaterial
emissive={turboColor}
toneMapped={false}
emissiveIntensity={5}
/>
</mesh>
)
}

View File

@ -2,12 +2,18 @@
* FakeFlame material component by Anderson Mancini - Jan 2024.
*/
import React, { useMemo, useRef } from 'react'
import { useMemo, useRef } from 'react'
import { shaderMaterial } from '@react-three/drei'
import { extend, useFrame } from '@react-three/fiber'
import { Color, DoubleSide, AdditiveBlending } from 'three'
export default function FakeFlame({ falloff = 3, glowInternalRadius = 1.0, glowColor = 'orange', glowSharpness = 1.0 , isBoosting,}) {
export default function FakeFlame({
falloff = 3,
glowInternalRadius = 1.0,
glowColor = 'orange',
glowSharpness = 1.0,
isBoosting = false,
}) {
const FakeFlame = useMemo(() => {
return shaderMaterial(
{

View File

@ -11,7 +11,13 @@ import FakeFlame from '../../ShaderMaterials/FakeFlame/FakeFlame'
import { useStore } from '../../store'
import gsap from 'gsap'
export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, shouldLaunch, ...props }) {
export function Mario({
currentSpeed,
steeringAngleWheels,
isBoosting,
shouldLaunch,
...props
}) {
const { nodes, materials } = useGLTF('./models/characters/mariokarttest.glb')
const frontLeftWheel = useRef()
@ -21,36 +27,31 @@ export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, shouldLau
const [scale, setScale] = React.useState(1)
const { actions } = useStore()
const [shouldSlow, setShouldSlow] = React.useState(false)
const mario = useRef();
const mario = useRef()
// isBoosting = true;
useFrame((_,delta) => {
useFrame((_, delta) => {
const rotation = currentSpeed / 100
frontLeftWheel.current.rotation.x += rotation
frontRightWheel.current.rotation.x += rotation
rearWheels.current.rotation.x += rotation
frontWheels.current.rotation.y = steeringAngleWheels
if (isBoosting){
if (isBoosting) {
setScale(Math.min(scale + 0.05 * 144 * delta, 1))
} else {
setScale(Math.max(scale - 0.03 * 144 * delta, 0))
}
setShouldSlow(actions.getShouldSlowDown());
setShouldSlow(actions.getShouldSlowDown())
})
useEffect(() => {
if (shouldLaunch) {
gsap.to(mario.current.rotation, {duration: 1.5, y: Math.PI * 3})
mario.current.rotation.set(0, 0, 0);
gsap.to(mario.current.rotation, { duration: 1.5, y: Math.PI * 3 })
mario.current.rotation.set(0, 0, 0)
}
}, [shouldLaunch])
return (
<group
{...props}
rotation={[0, Math.PI, 0]}
dispose={null}
ref={mario}
>
<group {...props} rotation={[0, Math.PI, 0]} dispose={null} ref={mario}>
<mesh
castShadow
receiveShadow
@ -91,10 +92,7 @@ export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, shouldLau
geometry={nodes.mt_mario.geometry}
material={materials.mt_mario}
/>
<Sphere
position={[0, 0.6, -1.2]}
scale={scale * 1.2}
>
<Sphere position={[0, 0.6, -1.2]} scale={scale * 1.2}>
<FakeGlowMaterial />
</Sphere>
@ -103,9 +101,8 @@ export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, shouldLau
position={[0.3, 0.65, -1.35]}
rotation={[Math.PI / 1.5, 0, 0]}
scale={scale}
>
<FakeFlame isBoosting={isBoosting}/>
<FakeFlame isBoosting={isBoosting} />
</Cylinder>
<Cylinder
@ -130,9 +127,8 @@ export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, shouldLau
rotation={[Math.PI / 1.5, 0, 0]}
scale={scale * 0.8}
>
<FakeFlame isBoosting={isBoosting}/>
<FakeFlame isBoosting={isBoosting} />
</Cylinder>
</group>
)
}

View File

@ -1,169 +0,0 @@
import { create } from "zustand";
export const playAudio = (path, callback) => {
const audio = new Audio(`./sounds/${path}.mp3`);
if (callback) {
audio.addEventListener("ended", callback);
}
audio.play();
};
export const items = [
"banana",
"shell",
]
export const useStore = create((set, get) => ({
gameStarted: false,
controls: "",
particles1: [],
particles2: [],
bodyPosition: [0, 0, 0],
bodyRotation: [0, 0, 0],
pastPositions: [],
shouldSlowdown: false,
bananas: [],
items: ["mushroom", "shell", "banana"],
item: "",
shells: [],
skids: [],
coins : 0,
players : [],
id : "",
joystickX: 0,
driftButton: false,
itemButton: false,
menuButton: false,
addPastPosition: (position) => {
set((state) => ({
pastPositions: [position, ...state.pastPositions.slice(0, 499)],
}));
},
actions: {
addParticle1: (particle) => {
set((state) => ({
particles1: [...state.particles1, particle],
}));
},
removeParticle1: (particle) => {
set((state) => ({
particles1: state.particles1.filter((p) => p.id !== particle.id),
}));
},
addParticle2: (particle) => {
set((state) => ({
particles2: [...state.particles2, particle],
}));
},
removeParticle2: (particle) => {
set((state) => ({
particles2: state.particles2.filter((p) => p.id !== particle.id),
}));
},
setBodyPosition: (position) => {
set({ bodyPosition: position });
},
setBodyRotation: (rotation) => {
set({ bodyRotation: rotation });
},
getBodyPosition: () => {
return get().bodyPosition;
},
getBodyRotation: () => {
return get().bodyRotation;
},
setShouldSlowDown: (shouldSlowdown) => {
set({ shouldSlowdown });
},
getShouldSlowDown: () => {
return get().shouldSlowdown;
},
addBanana: (banana) => {
set((state) => ({
bananas: [...state.bananas, banana],
}));
},
removeBanana: (banana) => {
set((state) => ({
bananas: state.bananas.filter((id) => id !== banana.id),
}));
},
getBananas: () => {
return get().bananas;
},
removeBananaById: (id) => {
set((state) => ({
bananas: state.bananas.filter((b) => b.id !== id),
}));
},
setBananas: (bananas) => {
set({ bananas });
},
setItem:() => {
set((state) => ({
item: state.items[Math.floor(Math.random() * state.items.length)],
}));
},
useItem:() => {
set((state) => ({
item: "",
}));
},
addShell: (shell) => {
set((state) => ({
shells: [...state.shells, shell],
}));
},
removeShell: (shell) => {
set((state) => ({
shells: state.shells.filter((s) => s.id !== shell.id),
}));
},
addSkid: (skid) => {
set((state) => ({
skids: [...state.skids, skid],
}));
},
addCoins : () => {
set((state) => ({
coins: state.coins + 1,
}));
},
looseCoins : () => {
set((state) => ({
coins: state.coins - 1,
}));
},
addPlayer : (player) => {
set((state) => ({
players: [...state.players, player],
}));
},
removePlayer : (player) => {
set((state) => ({
players: state.players.filter((p) => p.id !== player.id),
}));
},
setId : (id) => {
set({id});
},
setGameStarted: (gameStarted) => {
set({ gameStarted });
},
setControls: (controls) => {
set({ controls });
},
setJoystickX: (joystickX) => {
set({ joystickX });
},
setDriftButton: (driftButton) => {
set({ driftButton });
},
setItemButton: (itemButton) => {
set({ itemButton });
},
setMenuButton: (menuButton) => {
set({ menuButton });
},
},
}));

253
src/components/store.tsx Normal file
View File

@ -0,0 +1,253 @@
import { StoreApi, UseBoundStore, create } from 'zustand'
type CallbackFunction = () => void
export const playAudio = (path: string, callback?: CallbackFunction): void => {
const audio = new Audio(`./sounds/${path}.mp3`)
if (callback) {
audio.addEventListener('ended', callback)
}
audio.play()
}
export const items: string[] = ['banana', 'shell']
type Position = [number, number, number]
interface Particle {
id: string
// Other properties can be added here as needed
}
interface Banana {
id: string
// Other properties can be added here as needed
}
interface Shell {
id: string
// Other properties can be added here as needed
}
interface Skid {
// Define properties for Skid
}
interface Player {
id: string
// Other properties can be added here as needed
}
interface StoreState {
gameStarted: boolean
controls: string
particles1: Particle[]
particles2: Particle[]
bodyPosition: Position
bodyRotation: Position
pastPositions: Position[] // Define a more specific type if possible
shouldSlowdown: boolean
bananas: Banana[]
items: string[]
item: string
shells: Shell[]
skids: Skid[]
coins: number
players: Player[]
id: string
joystickX: number
driftButton: boolean
itemButton: boolean
menuButton: boolean
actions: {
addPastPosition: (position: Position) => void
addParticle1: (particle: Particle) => void
removeParticle1: (particle: Particle) => void
addParticle2: (particle: Particle) => void
removeParticle2: (particle: Particle) => void
setBodyPosition: (position: Position) => void
setBodyRotation: (rotation: Position) => void
getBodyPosition: () => Position
getBodyRotation: () => Position
setShouldSlowDown: (shouldSlowdown: boolean) => void
getShouldSlowDown: () => boolean
addBanana: (banana: Banana) => void
removeBanana: (banana: Banana) => void
getBananas: () => Banana[]
removeBananaById: (id: string) => void
setBananas: (bananas: Banana[]) => void
setItem: () => void
useItem: () => void
addShell: (shell: Shell) => void
removeShell: (shell: Shell) => void
addSkid: (skid: Skid) => void
addCoins: () => void
looseCoins: () => void
addPlayer: (player: Player) => void
removePlayer: (player: Player) => void
setId: (id: string) => void
setGameStarted: (gameStarted: boolean) => void
setControls: (controls: string) => void
setJoystickX: (joystickX: number) => void
setDriftButton: (driftButton: boolean) => void
setItemButton: (itemButton: boolean) => void
setMenuButton: (menuButton: boolean) => void
}
}
export const useStore: UseBoundStore<StoreApi<StoreState>> = create(
(set, get) => ({
gameStarted: false,
controls: '',
particles1: [],
particles2: [],
bodyPosition: [0, 0, 0],
bodyRotation: [0, 0, 0],
pastPositions: [],
shouldSlowdown: false,
bananas: [],
items: ['mushroom', 'shell', 'banana'],
item: '',
shells: [],
skids: [],
coins: 0,
players: [],
id: '',
joystickX: 0,
driftButton: false,
itemButton: false,
menuButton: false,
actions: {
addPastPosition: (position: Position) => {
set((state: StoreState) => ({
pastPositions: [position, ...state.pastPositions.slice(0, 499)],
}))
},
addParticle1: (particle: Particle) => {
set((state: StoreState) => ({
particles1: [...state.particles1, particle],
}))
},
removeParticle1: (particle: Particle) => {
set((state: StoreState) => ({
particles1: state.particles1.filter((p) => p.id !== particle.id),
}))
},
addParticle2: (particle: Particle) => {
set((state: StoreState) => ({
particles2: [...state.particles2, particle],
}))
},
removeParticle2: (particle: Particle) => {
set((state: StoreState) => ({
particles2: state.particles2.filter((p) => p.id !== particle.id),
}))
},
setBodyPosition: (position: Position) => {
set({ bodyPosition: position })
},
setBodyRotation: (rotation: Position) => {
set({ bodyRotation: rotation })
},
getBodyPosition: (): Position => {
return (get() as StoreState).bodyPosition
},
getBodyRotation: (): Position => {
return (get() as StoreState).bodyRotation
},
setShouldSlowDown: (shouldSlowdown: boolean) => {
set({ shouldSlowdown })
},
getShouldSlowDown: (): boolean => {
return (get() as StoreState).shouldSlowdown
},
addBanana: (banana: Banana) => {
set((state: StoreState) => ({
bananas: [...state.bananas, banana],
}))
},
removeBanana: (banana: Banana) => {
set((state: StoreState) => ({
bananas: state.bananas.filter((b) => b.id !== banana.id),
}))
},
getBananas: () => {
return (get() as StoreState).bananas
},
removeBananaById: (id: string) => {
set((state: StoreState) => ({
bananas: state.bananas.filter((b) => b.id !== id),
}))
},
setBananas: (bananas: Banana[]) => {
set({ bananas })
},
setItem: () => {
set((state: StoreState) => ({
item: state.items[Math.floor(Math.random() * state.items.length)],
}))
},
useItem: () => {
set((state: StoreState) => ({
item: '',
}))
},
addShell: (shell: Shell) => {
set((state: StoreState) => ({
shells: [...state.shells, shell],
}))
},
removeShell: (shell: Shell) => {
set((state: StoreState) => ({
shells: state.shells.filter((s) => s.id !== shell.id),
}))
},
addSkid: (skid: Skid) => {
set((state: StoreState) => ({
skids: [...state.skids, skid],
}))
},
addCoins: () => {
set((state: StoreState) => ({
coins: state.coins + 1,
}))
},
looseCoins: () => {
set((state: StoreState) => ({
coins: state.coins - 1,
}))
},
addPlayer: (player: Player) => {
set((state: StoreState) => ({
players: [...state.players, player],
}))
},
removePlayer: (player: Player) => {
set((state: StoreState) => ({
players: state.players.filter((p) => p.id !== player.id),
}))
},
setId: (id: string) => {
set({ id })
},
setGameStarted: (gameStarted: boolean) => {
set({ gameStarted })
},
setControls: (controls: string) => {
set({ controls })
},
setJoystickX: (joystickX: number) => {
set({ joystickX })
},
setDriftButton: (driftButton: boolean) => {
set({ driftButton })
},
setItemButton: (itemButton: boolean) => {
set({ itemButton })
},
setMenuButton: (menuButton: boolean) => {
set({ menuButton })
},
},
})
)

View File

@ -1,26 +0,0 @@
import React, { useState, useEffect } from 'react';
import * as THREE from 'three';
export const useCurvedPathPoints = (jsonPath) => {
const [points, setPoints] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const loadPointsFromJson = async () => {
try {
const response = await fetch(jsonPath);
const data = await response.json();
setPoints(data.points.map(point => new THREE.Vector3(point.x, point.y, point.z)));
setLoading(false);
} catch (err) {
setError(err);
setLoading(false);
}
};
loadPointsFromJson();
}, [jsonPath]);
return { points, loading, error };
};

View File

@ -0,0 +1,40 @@
import { useState, useEffect } from 'react'
import * as THREE from 'three'
interface Point {
x: number
y: number
z: number
}
interface PointsData {
points: Point[]
}
export const useCurvedPathPoints = (jsonPath: string) => {
const [points, setPoints] = useState<THREE.Vector3[]>([])
const [loading, setLoading] = useState<boolean>(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
const loadPointsFromJson = async () => {
try {
const response = await fetch(jsonPath)
const data: PointsData = await response.json()
setPoints(
data.points.map(
(point) => new THREE.Vector3(point.x, point.y, point.z)
)
)
setLoading(false)
} catch (err) {
setError(err as Error)
setLoading(false)
}
}
loadPointsFromJson()
}, [jsonPath])
return { points, loading, error }
}

View File

@ -1,68 +0,0 @@
import { useState, useEffect } from 'react';
export const useGamepad = () => {
const [gamepadInfo, setGamepadInfo] = useState({ connected: false, buttonA: false, buttonB :false, buttonX: false, buttonY:false, joystick: [0, 0], joystickRight : [0,0], RB: false, LB: false, RT: false, LT: false, start: false, select: false, up: false, down: false, left: false, right: false});
// Function to update gamepad state
const updateGamepadState = () => {
const gamepads = navigator.getGamepads ? navigator.getGamepads() : [];
const gamepad = gamepads[0]; // Assuming the first gamepad
if (gamepad) {
const newGamepadInfo = {
connected: true,
buttonA: gamepad.buttons[0].pressed,
buttonB: gamepad.buttons[1].pressed,
buttonX: gamepad.buttons[2].pressed,
buttonY: gamepad.buttons[3].pressed,
joystickRight: [gamepad.axes[2], gamepad.axes[3]],
LT: gamepad.buttons[6].pressed,
RT: gamepad.buttons[7].pressed,
LB: gamepad.buttons[4].pressed,
RB: gamepad.buttons[5].pressed,
start: gamepad.buttons[9].pressed,
select: gamepad.buttons[8].pressed,
up: gamepad.buttons[12].pressed,
down: gamepad.buttons[13].pressed,
left: gamepad.buttons[14].pressed,
right: gamepad.buttons[15].pressed,
joystick: [gamepad.axes[0], gamepad.axes[1]]
};
// Update state only if there's a change
if (JSON.stringify(newGamepadInfo) !== JSON.stringify(gamepadInfo)) {
setGamepadInfo(newGamepadInfo);
}
} else {
if (gamepadInfo.connected) {
setGamepadInfo({ connected: false, buttonA: false, buttonB :false, buttonX: false, buttonY:false, joystick: [0, 0], joystickRight : [0,0], RB: false, LB: false, RT: false, LT: false, start: false, select: false, up: false, down: false, left: false, right: false});
}
}
};
useEffect(() => {
const gamepadConnected = () => {
console.log('Gamepad connected!');
updateGamepadState();
};
const gamepadDisconnected = () => {
console.log('Gamepad disconnected!');
setGamepadInfo({ connected : false, buttonA: false, buttonB :false, buttonX: false, buttonY:false, joystick: [0, 0], joystickRight : [0,0], RB: false, LB: false, RT: false, LT: false, start: false, select: false, up: false, down: false, left: false, right: false});
};
window.addEventListener('gamepadconnected', gamepadConnected);
window.addEventListener('gamepaddisconnected', gamepadDisconnected);
const interval = setInterval(updateGamepadState, 100);
return () => {
window.removeEventListener('gamepadconnected', gamepadConnected);
window.removeEventListener('gamepaddisconnected', gamepadDisconnected);
clearInterval(interval);
};
}, [gamepadInfo]);
return gamepadInfo;
};

View File

@ -0,0 +1,122 @@
import { useState, useEffect } from 'react'
export const useGamepad = () => {
const [gamepadInfo, setGamepadInfo] = useState({
connected: false,
buttonA: false,
buttonB: false,
buttonX: false,
buttonY: false,
joystick: [0, 0],
joystickRight: [0, 0],
RB: false,
LB: false,
RT: false,
LT: false,
start: false,
select: false,
up: false,
down: false,
left: false,
right: false,
})
// Function to update gamepad state
const updateGamepadState = () => {
const gamepads = navigator.getGamepads ? navigator.getGamepads() : []
const gamepad = gamepads[0] // Assuming the first gamepad
if (gamepad) {
const newGamepadInfo = {
connected: true,
buttonA: gamepad.buttons[0].pressed,
buttonB: gamepad.buttons[1].pressed,
buttonX: gamepad.buttons[2].pressed,
buttonY: gamepad.buttons[3].pressed,
joystickRight: [gamepad.axes[2], gamepad.axes[3]],
LT: gamepad.buttons[6].pressed,
RT: gamepad.buttons[7].pressed,
LB: gamepad.buttons[4].pressed,
RB: gamepad.buttons[5].pressed,
start: gamepad.buttons[9].pressed,
select: gamepad.buttons[8].pressed,
up: gamepad.buttons[12].pressed,
down: gamepad.buttons[13].pressed,
left: gamepad.buttons[14].pressed,
right: gamepad.buttons[15].pressed,
joystick: [gamepad.axes[0], gamepad.axes[1]],
}
// Update state only if there's a change
if (JSON.stringify(newGamepadInfo) !== JSON.stringify(gamepadInfo)) {
setGamepadInfo(newGamepadInfo)
}
} else {
if (gamepadInfo.connected) {
setGamepadInfo({
connected: false,
buttonA: false,
buttonB: false,
buttonX: false,
buttonY: false,
joystick: [0, 0],
joystickRight: [0, 0],
RB: false,
LB: false,
RT: false,
LT: false,
start: false,
select: false,
up: false,
down: false,
left: false,
right: false,
})
}
}
}
useEffect(() => {
const gamepadConnected = () => {
console.log('Gamepad connected!')
updateGamepadState()
}
const gamepadDisconnected = () => {
console.log('Gamepad disconnected!')
setGamepadInfo({
connected: false,
buttonA: false,
buttonB: false,
buttonX: false,
buttonY: false,
joystick: [0, 0],
joystickRight: [0, 0],
RB: false,
LB: false,
RT: false,
LT: false,
start: false,
select: false,
up: false,
down: false,
left: false,
right: false,
})
}
window.addEventListener('gamepadconnected', gamepadConnected)
window.addEventListener('gamepaddisconnected', gamepadDisconnected)
const interval = setInterval(updateGamepadState, 100)
return () => {
window.removeEventListener('gamepadconnected', gamepadConnected)
window.removeEventListener('gamepaddisconnected', gamepadDisconnected)
clearInterval(interval)
}
}, [gamepadInfo])
return gamepadInfo
}

View File

@ -1,6 +1,6 @@
@import url("https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,100..900;1,100..900&display=swap");
@import url('https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,100..900;1,100..900&display=swap');
#root {
#app {
width: 100vw;
height: 100vh;
}
@ -88,7 +88,7 @@ body::-webkit-scrollbar {
border: 2px solid transparent;
border-radius: 50em;
.background {
background-image: url("./scanline.jpg");
background-image: url('./scanline.jpg');
background-position: center;
background-size: cover;
width: 100%;
@ -113,7 +113,7 @@ body::-webkit-scrollbar {
.drift {
right: 50px;
font-family: "Hanken Grotesk";
font-family: 'Hanken Grotesk';
border-radius: 100px;
background: rgba(255, 255, 255, 0.5);
height: 66.6667px;
@ -156,7 +156,7 @@ body::-webkit-scrollbar {
display: grid;
place-content: center;
z-index: 2;
font-family: "Hanken Grotesk";
font-family: 'Hanken Grotesk';
.logo {
img {
@ -201,114 +201,114 @@ body::-webkit-scrollbar {
}
}
.glassy {
width: 80vw;
height: 90vh;
background: #0000008f;
/* box-shadow: 0 8px 32px 0 rgba( 31, 38, 135, 0.37 ); */
backdrop-filter: blur(0px);
-webkit-backdrop-filter: blur(0px);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.18);
color: white;
position: relative;
.glassy {
width: 80vw;
height: 90vh;
background: #0000008f;
/* box-shadow: 0 8px 32px 0 rgba( 31, 38, 135, 0.37 ); */
backdrop-filter: blur(0px);
-webkit-backdrop-filter: blur(0px);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.18);
color: white;
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: column;
padding: 40px;
transition: all 0.5s ease 0s;
animation: froze 2s ease 1s both;
.articles {
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: column;
padding: 40px;
transition: all 0.5s ease 0s;
animation : froze 2s ease 1s both;
.articles {
align-items: flex-start;
width: 100%;
column-gap: 4%;
padding: 80px;
.article {
position: relative;
background: rgba(35, 35, 53, 0.685);
width: calc((100% - 3 * 4%) / 4);
aspect-ratio: 1;
border: 3px solid #ffffff;
border-radius: 10px;
display: flex;
justify-content: space-between;
align-items: flex-start;
width: 100%;
column-gap: 4%;
padding: 80px;
.article {
position: relative;
background: rgba(35, 35, 53, 0.685);
width: calc((100% - 3 * 4%) / 4);
aspect-ratio: 1;
border: 3px solid #ffffff;
border-radius: 10px;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: column;
padding: 20px;
transition: all 0.2s ease 0s;
cursor: pointer;
overflow: hidden;
&:hover {
background: rgba(216, 216, 216, 0.5);
transform: scale(1.05);
}
img {
max-width: 100%;
height: auto;
filter: drop-shadow(5px 5px 5px #0000008f);
}
}
.article.selected {
background: rgba(216, 216, 216, 0.7);
box-shadow: 0 0 20px 0 rgb(255, 255, 255);
}
.article.mobile {
img {
width: 100px;
}
}
.article_label {
position: absolute;
display: flex;
justify-content: center;
left: 0;
top: 80%;
background: rgba(6, 6, 6, 0.936);
border: 2px solid #272727;
width: 100%;
height: 100%;
overflow: hidden;
}
.article_label p {
margin-top: 6px;
font-size: calc(0.01 * (80vw - 400px) + 10px);
color: white;
}
}
.submit {
font-weight: 900;
padding: 10px;
color: rgba(255, 255, 255, 0.795);
border: 3px solid #ffffff;
background: rgba(35, 35, 53, 0.685);
border-radius: 10px;
font-size: 27px;
align-items: center;
flex-direction: column;
padding: 20px;
transition: all 0.2s ease 0s;
cursor: pointer;
overflow: hidden;
&:hover {
text-shadow: 0 0 40px rgba(255, 255, 255, 0.541);
color: white;
opacity: 1;
animation: none;
background: rgba(216, 216, 216, 0.5);
transform: scale(1.05);
}
button {
all: unset;
display: flex;
justify-content: center;
align-items: center;
display: inline-block;
img {
max-width: 100%;
height: auto;
filter: drop-shadow(5px 5px 5px #0000008f);
}
}
.article.selected {
background: rgba(216, 216, 216, 0.7);
box-shadow: 0 0 20px 0 rgb(255, 255, 255);
}
.article.mobile {
img {
width: 100px;
}
}
.article_label {
position: absolute;
display: flex;
justify-content: center;
left: 0;
top: 80%;
background: rgba(6, 6, 6, 0.936);
border: 2px solid #272727;
width: 100%;
height: 100%;
overflow: hidden;
}
.article_label p {
margin-top: 6px;
font-size: calc(0.01 * (80vw - 400px) + 10px);
color: white;
}
}
.submit {
font-weight: 900;
padding: 10px;
color: rgba(255, 255, 255, 0.795);
border: 3px solid #ffffff;
background: rgba(35, 35, 53, 0.685);
border-radius: 10px;
font-size: 27px;
transition: all 0.2s ease 0s;
cursor: pointer;
&:hover {
text-shadow: 0 0 40px rgba(255, 255, 255, 0.541);
color: white;
opacity: 1;
animation: none;
}
button {
all: unset;
display: flex;
justify-content: center;
align-items: center;
display: inline-block;
}
}
}
.disabled {
pointer-events: none;
cursor: not-allowed;
@ -334,12 +334,11 @@ body::-webkit-scrollbar {
100% {
backdrop-filter: blur(15px);
webkit-backdrop-filter: blur(15px);
}
}
.controls.itemButton {
right: 150px;
font-family: "Hanken Grotesk";
font-family: 'Hanken Grotesk';
border-radius: 100px;
background: rgba(255, 255, 255, 0.5);
height: 66.6667px;
@ -356,7 +355,7 @@ body::-webkit-scrollbar {
.controls.menuButton {
right: 50px;
top: 60px;
font-family: "Hanken Grotesk";
font-family: 'Hanken Grotesk';
border-radius: 100px;
background: rgba(255, 255, 255, 0.5);
height: 66.6667px;
@ -379,7 +378,7 @@ body::-webkit-scrollbar {
}
}
.start{
.start {
font-size: 30px;
}
@ -387,25 +386,25 @@ body::-webkit-scrollbar {
.glassy {
width: 80vw;
height: 90vh;
padding: 20px;
padding: 20px;
.articles {
display: flex;
flex-wrap: wrap;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
column-gap: 4%;
row-gap: 20px;
row-gap: 20px;
.article {
width: calc((100% - 3 * 4%) / 2 - 10px);
aspect-ratio: 1;
width: calc((100% - 3 * 4%) / 2 - 10px);
aspect-ratio: 1;
padding: 10px;
transition: transform 0.3s ease;
transition: transform 0.3s ease;
img {
width: 100%;
width: 100%;
height: auto;
}
&:hover {
transform: scale(1.05);
transform: scale(1.05);
}
}
}

View File

@ -1,15 +1,18 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import { HUD } from './HUD'
import { Landing } from './Landing'
import { useStore } from './components/store'
ReactDOM.createRoot(document.getElementById('root')).render(
import { createRoot } from 'react-dom/client'
const container = document.getElementById('app')
const root = createRoot(container!)
root.render(
<React.StrictMode>
<App />
<HUD />
<Landing />
</React.StrictMode>,
</React.StrictMode>
)

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

21
tsconfig.json Normal file
View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

10
tsconfig.node.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

1
vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -1,10 +1,10 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import viteTsconfigPaths from 'vite-tsconfig-paths'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
plugins: [react(), viteTsconfigPaths()],
optimizeDeps: {
exclude: ['js-big-decimal']
}
exclude: ['js-big-decimal'],
},
})