feat: SPA routing, projects/resume sections, deployment infra
- Add client-side router with History API pushState (router.ts, pages.ts) - Extend overlay with projects grid/category/detail views and resume section - Add Gitea CI/CD workflow and webhook deployment server - Improve Docker Compose with nginx reverse proxy config - Add deployment script and .env.example
This commit is contained in:
63
src/app.ts
63
src/app.ts
@@ -1,7 +1,8 @@
|
||||
import { Renderer, UiState } from './engine/renderer'
|
||||
import { Input } from './ui/input'
|
||||
import { DomOverlay } from './ui/overlay'
|
||||
import { SCENES } from './data/scenes'
|
||||
import { Router, Route } from './router'
|
||||
import { PAGES, PROJECTS_SCENE, RESUME_SCENE } from './data/pages'
|
||||
|
||||
const DPR_CAP = 2
|
||||
|
||||
@@ -10,6 +11,7 @@ export class App {
|
||||
private renderer = new Renderer()
|
||||
private input!: Input
|
||||
private overlay!: DomOverlay
|
||||
private router = new Router()
|
||||
|
||||
private dpr = 1
|
||||
private res = new Float32Array(2)
|
||||
@@ -28,11 +30,23 @@ export class App {
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
this.input = new Input()
|
||||
this.overlay = new DomOverlay(SCENES, {
|
||||
onNavClick: (s) => { this.scene = s },
|
||||
onButtonClick: () => { this.scene = (this.scene + 1) % 5 }
|
||||
|
||||
this.overlay = new DomOverlay(PAGES, {
|
||||
onNavClick: (s) => this.router.navigate(Router.sceneToBasePath(s)),
|
||||
onButtonClick: () => {
|
||||
const next = (this.scene + 1) % PAGES.length
|
||||
this.router.navigate(Router.sceneToBasePath(next))
|
||||
},
|
||||
onNavigate: (path) => this.router.navigate(path),
|
||||
})
|
||||
this.overlay.setScene(this.scene)
|
||||
|
||||
// Initial route from current URL
|
||||
const route = this.router.start()
|
||||
this.applyRoute(route)
|
||||
|
||||
// Subsequent navigation (popstate or pushState)
|
||||
this.router.onChange((r) => this.applyRoute(r))
|
||||
|
||||
this.resize()
|
||||
const ok = await this.renderer.init(this.canvas)
|
||||
if (!ok) return false
|
||||
@@ -47,6 +61,31 @@ export class App {
|
||||
cancelAnimationFrame(this.rafId)
|
||||
}
|
||||
|
||||
// ---- routing ----
|
||||
|
||||
private applyRoute(route: Route): void {
|
||||
this.scene = route.scene
|
||||
this.overlay.setScene(this.scene)
|
||||
|
||||
if (route.scene === PROJECTS_SCENE && 'view' in route) {
|
||||
if (route.view === 'category' && 'catId' in route && route.catId) {
|
||||
this.overlay.showCategory(route.catId)
|
||||
} else if (
|
||||
route.view === 'detail' &&
|
||||
'catId' in route && route.catId &&
|
||||
'projectId' in route && route.projectId
|
||||
) {
|
||||
this.overlay.showProjectDetail(route.projectId, route.catId)
|
||||
} else {
|
||||
this.overlay.showCategoryGrid()
|
||||
}
|
||||
} else if (route.scene === RESUME_SCENE) {
|
||||
this.overlay.showResume()
|
||||
}
|
||||
}
|
||||
|
||||
// ---- rendering ----
|
||||
|
||||
private resize(): void {
|
||||
this.dpr = Math.min(window.devicePixelRatio || 1, DPR_CAP)
|
||||
const w = Math.floor(window.innerWidth * this.dpr)
|
||||
@@ -62,23 +101,16 @@ export class App {
|
||||
|
||||
private computeStemMultipliers(time: number): void {
|
||||
const sm = this.stemMultipliers
|
||||
// scene-based color identity and speed
|
||||
const colorBase = [0.0, 0.6, 1.2, 1.8, 2.4]
|
||||
const speedBase = [0.0, 0.2, 0.1, 0.0, 0.15]
|
||||
const scene = this.scene
|
||||
|
||||
// [0] = (camera speed, 0, color offset, twist/flash)
|
||||
sm[0] = speedBase[scene]
|
||||
sm[2] = colorBase[scene] * 0.2
|
||||
sm[3] = 0.3 + 0.3 * Math.sin(time * 0.5)
|
||||
|
||||
// [1] = (color offset, 0, 0, 0)
|
||||
sm[4] = colorBase[scene] * 0.4
|
||||
|
||||
// [2] = (size scale, 0, 0, 0)
|
||||
sm[8] = 0.1// + 0.4 * Math.sin(time * 0.3 + scene * 3.0)
|
||||
|
||||
// rest of the array stays zero
|
||||
sm[8] = 0.1
|
||||
}
|
||||
|
||||
private loop = (): void => {
|
||||
@@ -97,7 +129,6 @@ export class App {
|
||||
|
||||
if (this.scene !== this.prevScene) {
|
||||
this.prevScene = this.scene
|
||||
this.overlay.setScene(this.scene)
|
||||
}
|
||||
|
||||
this.computeStemMultipliers(time)
|
||||
@@ -107,8 +138,8 @@ export class App {
|
||||
time,
|
||||
scene: this.scene,
|
||||
mouse: this.mouse,
|
||||
stemMultipliers: this.stemMultipliers
|
||||
stemMultipliers: this.stemMultipliers,
|
||||
}
|
||||
this.renderer.render(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user