upatde: add prettier
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
@@ -7,30 +7,32 @@ Built with React, Three.js, and Vite. Runs in the browser, no installation requi
|
|||||||
## 📦 Tech Stack
|
## 📦 Tech Stack
|
||||||
|
|
||||||
### Build & Language
|
### Build & Language
|
||||||
| Package | Doc |
|
|
||||||
|--------|-----|
|
| Package | Doc |
|
||||||
|
| -------------------------------------------------- | ------------------------------------ |
|
||||||
| [TypeScript](https://www.typescriptlang.org/docs/) | https://www.typescriptlang.org/docs/ |
|
| [TypeScript](https://www.typescriptlang.org/docs/) | https://www.typescriptlang.org/docs/ |
|
||||||
| [React](https://react.dev/learn) | https://react.dev/learn |
|
| [React](https://react.dev/learn) | https://react.dev/learn |
|
||||||
| [Vite](https://vite.dev/guide/) | https://vite.dev/guide/ |
|
| [Vite](https://vite.dev/guide/) | https://vite.dev/guide/ |
|
||||||
| [ESLint](https://eslint.org/docs/latest/) | https://eslint.org/docs/latest/ |
|
| [ESLint](https://eslint.org/docs/latest/) | https://eslint.org/docs/latest/ |
|
||||||
| [Prettier](https://prettier.io/docs/) | https://prettier.io/docs/ |
|
| [Prettier](https://prettier.io/docs/) | https://prettier.io/docs/ |
|
||||||
|
|
||||||
### 3D Engine
|
### 3D Engine
|
||||||
| Package | Doc |
|
|
||||||
|--------|-----|
|
| Package | Doc |
|
||||||
| [Three.js](https://threejs.org/docs/) | https://threejs.org/docs/ |
|
| ----------------------------------------------------------------------------------------- | ---------------------------------------------- |
|
||||||
| [@react-three/fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction) | https://docs.pmnd.rs/react-three-fiber |
|
| [Three.js](https://threejs.org/docs/) | https://threejs.org/docs/ |
|
||||||
| [@react-three/drei](https://pmndrs.github.io/drei) | https://pmndrs.github.io/drei |
|
| [@react-three/fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction) | https://docs.pmnd.rs/react-three-fiber |
|
||||||
| [@react-three/rapier](https://rapier.rs/docs/) | https://rapier.rs/docs/user_guides/javascript/ |
|
| [@react-three/drei](https://pmndrs.github.io/drei) | https://pmndrs.github.io/drei |
|
||||||
| [@react-three/postprocessing](https://github.com/pmndrs/postprocessing) | https://github.com/pmndrs/postprocessing |
|
| [@react-three/rapier](https://rapier.rs/docs/) | https://rapier.rs/docs/user_guides/javascript/ |
|
||||||
| [GSAP](https://gsap.com/docs/v3/Installation/) | https://gsap.com/docs/v3/ |
|
| [@react-three/postprocessing](https://github.com/pmndrs/postprocessing) | https://github.com/pmndrs/postprocessing |
|
||||||
|
| [GSAP](https://gsap.com/docs/v3/Installation/) | https://gsap.com/docs/v3/ |
|
||||||
|
|
||||||
### Performance & Effects
|
### Performance & Effects
|
||||||
| Package | Doc |
|
|
||||||
|--------|-----|
|
|
||||||
| [r3f-perf](https://github.com/utsuboco/r3f-perf) | https://github.com/utsuboco/r3f-perf |
|
|
||||||
| [AnimationMixer](https://threejs.org/docs/#api/en/animation/AnimationMixer) | https://threejs.org/docs/#api/en/animation/AnimationMixer |
|
|
||||||
|
|
||||||
|
| Package | Doc |
|
||||||
|
| --------------------------------------------------------------------------- | --------------------------------------------------------- |
|
||||||
|
| [r3f-perf](https://github.com/utsuboco/r3f-perf) | https://github.com/utsuboco/r3f-perf |
|
||||||
|
| [AnimationMixer](https://threejs.org/docs/#api/en/animation/AnimationMixer) | https://threejs.org/docs/#api/en/animation/AnimationMixer |
|
||||||
|
|
||||||
## 🗂 Project Structure
|
## 🗂 Project Structure
|
||||||
|
|
||||||
@@ -123,6 +125,7 @@ Consistency matters, but the codebase does **not** force the same lifecycle patt
|
|||||||
Only cross-cutting services use the singleton pattern.
|
Only cross-cutting services use the singleton pattern.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- `GameManager`
|
- `GameManager`
|
||||||
- `CinematicManager`
|
- `CinematicManager`
|
||||||
- `AudioManager`
|
- `AudioManager`
|
||||||
@@ -135,30 +138,30 @@ These services must exist once, be accessible from anywhere, and coordinate the
|
|||||||
```ts
|
```ts
|
||||||
// stateManager/GameManager.ts
|
// stateManager/GameManager.ts
|
||||||
export class GameManager {
|
export class GameManager {
|
||||||
private static _instance: GameManager | null = null
|
private static _instance: GameManager | null = null;
|
||||||
|
|
||||||
cinematic!: CinematicManager
|
cinematic!: CinematicManager;
|
||||||
audio!: AudioManager
|
audio!: AudioManager;
|
||||||
zone!: ZoneManager
|
zone!: ZoneManager;
|
||||||
|
|
||||||
static getInstance(): GameManager {
|
static getInstance(): GameManager {
|
||||||
if (!GameManager._instance) {
|
if (!GameManager._instance) {
|
||||||
GameManager._instance = new GameManager()
|
GameManager._instance = new GameManager();
|
||||||
}
|
}
|
||||||
return GameManager._instance
|
return GameManager._instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.cinematic = CinematicManager.getInstance()
|
this.cinematic = CinematicManager.getInstance();
|
||||||
this.audio = AudioManager.getInstance()
|
this.audio = AudioManager.getInstance();
|
||||||
this.zone = ZoneManager.getInstance()
|
this.zone = ZoneManager.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.cinematic.destroy()
|
this.cinematic.destroy();
|
||||||
this.audio.destroy()
|
this.audio.destroy();
|
||||||
this.zone.destroy()
|
this.zone.destroy();
|
||||||
GameManager._instance = null
|
GameManager._instance = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -166,8 +169,8 @@ export class GameManager {
|
|||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const game = GameManager.getInstance()
|
const game = GameManager.getInstance();
|
||||||
game.startMission('workshop')
|
game.startMission("workshop");
|
||||||
```
|
```
|
||||||
|
|
||||||
**Important:** scene objects such as `Map`, `WorkshopZone`, `Lighting`, or `Environment` are **not** singletons and must remain standard React components.
|
**Important:** scene objects such as `Map`, `WorkshopZone`, `Lighting`, or `Environment` are **not** singletons and must remain standard React components.
|
||||||
@@ -179,6 +182,7 @@ game.startMission('workshop')
|
|||||||
All 3D scene objects are implemented as **declarative React components**.
|
All 3D scene objects are implemented as **declarative React components**.
|
||||||
|
|
||||||
This includes:
|
This includes:
|
||||||
|
|
||||||
- maps
|
- maps
|
||||||
- lights
|
- lights
|
||||||
- environments
|
- environments
|
||||||
@@ -193,34 +197,35 @@ Example:
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// world/zones/WorkshopZone.tsx
|
// world/zones/WorkshopZone.tsx
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from "react";
|
||||||
import * as THREE from 'three'
|
import * as THREE from "three";
|
||||||
import { useFrame } from '@react-three/fiber'
|
import { useFrame } from "@react-three/fiber";
|
||||||
import { useGLTF } from '@react-three/drei'
|
import { useGLTF } from "@react-three/drei";
|
||||||
|
|
||||||
export function WorkshopZone() {
|
export function WorkshopZone() {
|
||||||
const root = useRef<THREE.Group>(null)
|
const root = useRef<THREE.Group>(null);
|
||||||
const gltf = useGLTF('/models/workshop/ebike.glb')
|
const gltf = useGLTF("/models/workshop/ebike.glb");
|
||||||
const mixer = useRef<THREE.AnimationMixer | null>(null)
|
const mixer = useRef<THREE.AnimationMixer | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mixer.current = new THREE.AnimationMixer(gltf.scene)
|
mixer.current = new THREE.AnimationMixer(gltf.scene);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
mixer.current?.stopAllAction()
|
mixer.current?.stopAllAction();
|
||||||
mixer.current = null
|
mixer.current = null;
|
||||||
}
|
};
|
||||||
}, [gltf.scene])
|
}, [gltf.scene]);
|
||||||
|
|
||||||
useFrame((_, delta) => {
|
useFrame((_, delta) => {
|
||||||
mixer.current?.update(delta)
|
mixer.current?.update(delta);
|
||||||
})
|
});
|
||||||
|
|
||||||
return <primitive ref={root} object={gltf.scene.clone()} />
|
return <primitive ref={root} object={gltf.scene.clone()} />;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Per-frame values such as movement, interpolation, camera smoothing, and physics must stay in:
|
Per-frame values such as movement, interpolation, camera smoothing, and physics must stay in:
|
||||||
|
|
||||||
- `useRef`
|
- `useRef`
|
||||||
- `useFrame`
|
- `useFrame`
|
||||||
- Rapier bodies
|
- Rapier bodies
|
||||||
@@ -241,89 +246,90 @@ High-frequency values such as movement, camera interpolation, or physics never g
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
// stateManager/GameManager.ts
|
// stateManager/GameManager.ts
|
||||||
type Phase = 'loading' | 'intro' | 'exploring' | 'cinematic' | 'outro'
|
type Phase = "loading" | "intro" | "exploring" | "cinematic" | "outro";
|
||||||
type ZoneId = 'workshop' | 'powerGrid' | 'farm' | null
|
type ZoneId = "workshop" | "powerGrid" | "farm" | null;
|
||||||
|
|
||||||
type GameSnapshot = {
|
type GameSnapshot = {
|
||||||
phase: Phase
|
phase: Phase;
|
||||||
activeZone: ZoneId
|
activeZone: ZoneId;
|
||||||
missionId: string | null
|
missionId: string | null;
|
||||||
missionStep: number
|
missionStep: number;
|
||||||
inputLocked: boolean
|
inputLocked: boolean;
|
||||||
dialogueId: string | null
|
dialogueId: string | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
export class GameManager {
|
export class GameManager {
|
||||||
private static _instance: GameManager | null = null
|
private static _instance: GameManager | null = null;
|
||||||
private listeners = new Set<() => void>()
|
private listeners = new Set<() => void>();
|
||||||
|
|
||||||
private state: GameSnapshot = {
|
private state: GameSnapshot = {
|
||||||
phase: 'loading',
|
phase: "loading",
|
||||||
activeZone: null,
|
activeZone: null,
|
||||||
missionId: null,
|
missionId: null,
|
||||||
missionStep: 0,
|
missionStep: 0,
|
||||||
inputLocked: false,
|
inputLocked: false,
|
||||||
dialogueId: null,
|
dialogueId: null,
|
||||||
}
|
};
|
||||||
|
|
||||||
static getInstance(): GameManager {
|
static getInstance(): GameManager {
|
||||||
if (!GameManager._instance) {
|
if (!GameManager._instance) {
|
||||||
GameManager._instance = new GameManager()
|
GameManager._instance = new GameManager();
|
||||||
}
|
}
|
||||||
return GameManager._instance
|
return GameManager._instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
getState(): GameSnapshot {
|
getState(): GameSnapshot {
|
||||||
return this.state
|
return this.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(listener: () => void): () => void {
|
subscribe(listener: () => void): () => void {
|
||||||
this.listeners.add(listener)
|
this.listeners.add(listener);
|
||||||
return () => this.listeners.delete(listener)
|
return () => this.listeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private emit(): void {
|
private emit(): void {
|
||||||
this.listeners.forEach((cb) => cb())
|
this.listeners.forEach((cb) => cb());
|
||||||
}
|
}
|
||||||
|
|
||||||
setPhase(phase: Phase): void {
|
setPhase(phase: Phase): void {
|
||||||
this.state.phase = phase
|
this.state.phase = phase;
|
||||||
this.emit()
|
this.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveZone(zone: ZoneId): void {
|
setActiveZone(zone: ZoneId): void {
|
||||||
this.state.activeZone = zone
|
this.state.activeZone = zone;
|
||||||
this.emit()
|
this.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
startMission(id: string): void {
|
startMission(id: string): void {
|
||||||
this.state.missionId = id
|
this.state.missionId = id;
|
||||||
this.state.missionStep = 0
|
this.state.missionStep = 0;
|
||||||
this.emit()
|
this.emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// hooks/useGameState.ts
|
// hooks/useGameState.ts
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from "react";
|
||||||
import { GameManager } from '@/stateManager/GameManager'
|
import { GameManager } from "@/stateManager/GameManager";
|
||||||
|
|
||||||
export function useGameState() {
|
export function useGameState() {
|
||||||
const game = GameManager.getInstance()
|
const game = GameManager.getInstance();
|
||||||
const [state, setState] = useState(game.getState())
|
const [state, setState] = useState(game.getState());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return game.subscribe(() => {
|
return game.subscribe(() => {
|
||||||
setState({ ...game.getState() })
|
setState({ ...game.getState() });
|
||||||
})
|
});
|
||||||
}, [game])
|
}, [game]);
|
||||||
|
|
||||||
return state
|
return state;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This keeps the architecture simple:
|
This keeps the architecture simple:
|
||||||
|
|
||||||
- **GameManager** owns durable gameplay state
|
- **GameManager** owns durable gameplay state
|
||||||
- **other managers** handle side effects
|
- **other managers** handle side effects
|
||||||
- **React components** render that state
|
- **React components** render that state
|
||||||
@@ -336,6 +342,7 @@ This keeps the architecture simple:
|
|||||||
Managers other than `GameManager` should not become secondary state stores.
|
Managers other than `GameManager` should not become secondary state stores.
|
||||||
|
|
||||||
Their role is to manage side effects and specialized runtime logic, such as:
|
Their role is to manage side effects and specialized runtime logic, such as:
|
||||||
|
|
||||||
- GSAP timelines
|
- GSAP timelines
|
||||||
- audio playback
|
- audio playback
|
||||||
- zone entry detection
|
- zone entry detection
|
||||||
@@ -369,6 +376,7 @@ However, the project does **not** blindly deep-dispose every object on unmount.
|
|||||||
Only resources explicitly created and owned by the current component or manager should be disposed.
|
Only resources explicitly created and owned by the current component or manager should be disposed.
|
||||||
|
|
||||||
This includes things like:
|
This includes things like:
|
||||||
|
|
||||||
- custom materials
|
- custom materials
|
||||||
- render targets
|
- render targets
|
||||||
- postprocessing passes
|
- postprocessing passes
|
||||||
@@ -380,33 +388,33 @@ Shared or cached assets must **not** be blindly disposed.
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
// utils/Dispose.ts
|
// utils/Dispose.ts
|
||||||
import * as THREE from 'three'
|
import * as THREE from "three";
|
||||||
|
|
||||||
export class Dispose {
|
export class Dispose {
|
||||||
static material(material: THREE.Material): void {
|
static material(material: THREE.Material): void {
|
||||||
for (const value of Object.values(material)) {
|
for (const value of Object.values(material)) {
|
||||||
if (value instanceof THREE.Texture) {
|
if (value instanceof THREE.Texture) {
|
||||||
value.dispose()
|
value.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
material.dispose()
|
material.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
static mesh(mesh: THREE.Mesh): void {
|
static mesh(mesh: THREE.Mesh): void {
|
||||||
mesh.geometry?.dispose()
|
mesh.geometry?.dispose();
|
||||||
|
|
||||||
const materials = Array.isArray(mesh.material)
|
const materials = Array.isArray(mesh.material)
|
||||||
? mesh.material
|
? mesh.material
|
||||||
: [mesh.material]
|
: [mesh.material];
|
||||||
|
|
||||||
for (const material of materials) {
|
for (const material of materials) {
|
||||||
if (material) this.material(material)
|
if (material) this.material(material);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static renderTarget(rt: THREE.WebGLRenderTarget): void {
|
static renderTarget(rt: THREE.WebGLRenderTarget): void {
|
||||||
rt.texture.dispose()
|
rt.texture.dispose();
|
||||||
rt.dispose()
|
rt.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -418,14 +426,14 @@ useEffect(() => {
|
|||||||
const material = new THREE.ShaderMaterial({
|
const material = new THREE.ShaderMaterial({
|
||||||
vertexShader,
|
vertexShader,
|
||||||
fragmentShader,
|
fragmentShader,
|
||||||
})
|
});
|
||||||
|
|
||||||
meshRef.current.material = material
|
meshRef.current.material = material;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
Dispose.material(material)
|
Dispose.material(material);
|
||||||
}
|
};
|
||||||
}, [])
|
}, []);
|
||||||
```
|
```
|
||||||
|
|
||||||
**Rule:** disposal is ownership-based, not automatic and not blind.
|
**Rule:** disposal is ownership-based, not automatic and not blind.
|
||||||
@@ -443,29 +451,29 @@ Do not scatter debug checks across the codebase.
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
// utils/Debug.ts
|
// utils/Debug.ts
|
||||||
import GUI from 'lil-gui'
|
import GUI from "lil-gui";
|
||||||
|
|
||||||
export class Debug {
|
export class Debug {
|
||||||
private static _instance: Debug | null = null
|
private static _instance: Debug | null = null;
|
||||||
|
|
||||||
readonly active: boolean
|
readonly active: boolean;
|
||||||
gui: GUI | null = null
|
gui: GUI | null = null;
|
||||||
|
|
||||||
static getInstance(): Debug {
|
static getInstance(): Debug {
|
||||||
if (!Debug._instance) Debug._instance = new Debug()
|
if (!Debug._instance) Debug._instance = new Debug();
|
||||||
return Debug._instance
|
return Debug._instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.active = new URLSearchParams(window.location.search).has('debug')
|
this.active = new URLSearchParams(window.location.search).has("debug");
|
||||||
if (this.active) {
|
if (this.active) {
|
||||||
this.gui = new GUI({ title: 'La-Fabrik Debug' })
|
this.gui = new GUI({ title: "La-Fabrik Debug" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.gui?.destroy()
|
this.gui?.destroy();
|
||||||
Debug._instance = null
|
Debug._instance = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -473,10 +481,10 @@ export class Debug {
|
|||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const debug = Debug.getInstance()
|
const debug = Debug.getInstance();
|
||||||
|
|
||||||
if (debug.active) {
|
if (debug.active) {
|
||||||
debug.gui!.add(params, 'bloomIntensity', 0, 3).name('Bloom')
|
debug.gui!.add(params, "bloomIntensity", 0, 3).name("Bloom");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -492,7 +500,6 @@ npm run dev
|
|||||||
Open `http://localhost:5173` — standard experience.
|
Open `http://localhost:5173` — standard experience.
|
||||||
Open `http://localhost:5173?debug` — debug panel + r3f-perf overlay.
|
Open `http://localhost:5173?debug` — debug panel + r3f-perf overlay.
|
||||||
|
|
||||||
|
|
||||||
## 📜 License
|
## 📜 License
|
||||||
|
|
||||||
See [LICENSE](./LICENSE) file.
|
See [LICENSE](./LICENSE) file.
|
||||||
+9
-9
@@ -1,14 +1,14 @@
|
|||||||
import js from '@eslint/js'
|
import js from "@eslint/js";
|
||||||
import globals from 'globals'
|
import globals from "globals";
|
||||||
import reactHooks from 'eslint-plugin-react-hooks'
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
import reactRefresh from "eslint-plugin-react-refresh";
|
||||||
import tseslint from 'typescript-eslint'
|
import tseslint from "typescript-eslint";
|
||||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
globalIgnores(['dist']),
|
globalIgnores(["dist"]),
|
||||||
{
|
{
|
||||||
files: ['**/*.{ts,tsx}'],
|
files: ["**/*.{ts,tsx}"],
|
||||||
extends: [
|
extends: [
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
tseslint.configs.recommended,
|
tseslint.configs.recommended,
|
||||||
@@ -20,4 +20,4 @@ export default defineConfig([
|
|||||||
globals: globals.browser,
|
globals: globals.browser,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
]);
|
||||||
|
|||||||
Generated
+1215
-79
File diff suppressed because it is too large
Load Diff
+18
-3
@@ -1,17 +1,28 @@
|
|||||||
{
|
{
|
||||||
"name": "la-fabrik",
|
"name": "la-fabrik",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"lint:fix": "eslint . --fix",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format:check": "prettier --check .",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-three/drei": "^10.7.7",
|
||||||
|
"@react-three/fiber": "^9.6.0",
|
||||||
|
"@react-three/postprocessing": "^3.0.4",
|
||||||
|
"@react-three/rapier": "^2.2.0",
|
||||||
|
"gsap": "^3.15.0",
|
||||||
|
"r3f-perf": "^7.2.3",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4"
|
"react-dom": "^19.2.4",
|
||||||
|
"three": "^0.183.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.4",
|
"@eslint/js": "^9.39.4",
|
||||||
@@ -20,9 +31,13 @@
|
|||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "^9.39.4",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-prettier": "^5.5.5",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"globals": "^17.4.0",
|
"globals": "^17.4.0",
|
||||||
|
"lil-gui": "^0.21.0",
|
||||||
|
"prettier": "^3.8.2",
|
||||||
"typescript": "~6.0.2",
|
"typescript": "~6.0.2",
|
||||||
"typescript-eslint": "^8.58.0",
|
"typescript-eslint": "^8.58.0",
|
||||||
"vite": "^8.0.4"
|
"vite": "^8.0.4"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
# public/models/farm/*
|
# public/models/farm/\*
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
# public/models/map/*
|
# public/models/map/\*
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
# public/models/powerGrid/*
|
# public/models/powerGrid/\*
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
# public/sounds/*
|
# public/sounds/\*
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
# public/textures/*
|
# public/textures/\*
|
||||||
|
|||||||
+1
-1
@@ -167,7 +167,7 @@
|
|||||||
|
|
||||||
&::before,
|
&::before,
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -4.5px;
|
top: -4.5px;
|
||||||
border: 5px solid transparent;
|
border: 5px solid transparent;
|
||||||
|
|||||||
+8
-8
@@ -1,11 +1,11 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from "react";
|
||||||
import reactLogo from './assets/react.svg'
|
import reactLogo from "./assets/react.svg";
|
||||||
import viteLogo from './assets/vite.svg'
|
import viteLogo from "./assets/vite.svg";
|
||||||
import heroImg from './assets/hero.png'
|
import heroImg from "./assets/hero.png";
|
||||||
import './App.css'
|
import "./App.css";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [count, setCount] = useState(0)
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -115,7 +115,7 @@ function App() {
|
|||||||
<div className="ticks"></div>
|
<div className="ticks"></div>
|
||||||
<section id="spacer"></section>
|
<section id="spacer"></section>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App
|
export default App;
|
||||||
|
|||||||
+2
-2
@@ -11,8 +11,8 @@
|
|||||||
--shadow:
|
--shadow:
|
||||||
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
|
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
|
||||||
|
|
||||||
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
|
--sans: system-ui, "Segoe UI", Roboto, sans-serif;
|
||||||
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
--heading: system-ui, "Segoe UI", Roboto, sans-serif;
|
||||||
--mono: ui-monospace, Consolas, monospace;
|
--mono: ui-monospace, Consolas, monospace;
|
||||||
|
|
||||||
font: 18px/145% var(--sans);
|
font: 18px/145% var(--sans);
|
||||||
|
|||||||
+6
-6
@@ -1,10 +1,10 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from "react-dom/client";
|
||||||
import './index.css'
|
import "./index.css";
|
||||||
import App from './App.tsx'
|
import App from "./App.tsx";
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
);
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Suspense, lazy } from 'react'
|
||||||
|
const Perf = lazy(() => import('r3f-perf').then((m) => ({ default: m.Perf })))
|
||||||
|
export function DebugPerf() {
|
||||||
|
const debug = new URLSearchParams(window.location.search).has('debug')
|
||||||
|
if (!debug) return null
|
||||||
|
return (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<Perf position="top-left" />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import react from '@vitejs/plugin-react'
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
})
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user