rse refact
This commit is contained in:
28
rse/src/input.ts
Normal file
28
rse/src/input.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
export interface InputState {
|
||||||
|
targetX: number
|
||||||
|
targetY: number
|
||||||
|
smoothX: number
|
||||||
|
smoothY: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createInputState(): InputState {
|
||||||
|
return { targetX: 0, targetY: 0, smoothX: 0, smoothY: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupInputListeners(state: InputState): void {
|
||||||
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
state.targetX = (e.clientX / window.innerWidth - 0.5) * 2
|
||||||
|
state.targetY = (e.clientY / window.innerHeight - 0.5) * 2
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('touchmove', (e) => {
|
||||||
|
const t = e.touches[0]
|
||||||
|
state.targetX = (t.clientX / window.innerWidth - 0.5) * 2
|
||||||
|
state.targetY = (t.clientY / window.innerHeight - 0.5) * 2
|
||||||
|
}, { passive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function smoothInput(state: InputState): void {
|
||||||
|
state.smoothX += (state.targetX - state.smoothX) * 0.025
|
||||||
|
state.smoothY += (state.targetY - state.smoothY) * 0.025
|
||||||
|
}
|
||||||
38
rse/src/language.ts
Normal file
38
rse/src/language.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
function applyLang(lang: string): void {
|
||||||
|
document.querySelectorAll<HTMLElement>('[data-lang-en]').forEach(el => {
|
||||||
|
const text = el.getAttribute(`data-lang-${lang}`)
|
||||||
|
if (text) el.innerHTML = text
|
||||||
|
})
|
||||||
|
|
||||||
|
document.getElementById('lang-dropdown')
|
||||||
|
?.querySelectorAll('.lang-option')
|
||||||
|
.forEach(opt => {
|
||||||
|
opt.classList.toggle('selected', (opt as HTMLElement).dataset.lang === lang)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupLanguageSelector(): void {
|
||||||
|
let currentLang = localStorage.getItem('preferred-lang') || 'en'
|
||||||
|
|
||||||
|
const langBtn = document.getElementById('lang-tab')
|
||||||
|
const langDropdown = document.getElementById('lang-dropdown')
|
||||||
|
const menuDropdown = document.getElementById('menu-dropdown')
|
||||||
|
|
||||||
|
langBtn?.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
langDropdown?.classList.toggle('show')
|
||||||
|
menuDropdown?.classList.remove('show')
|
||||||
|
})
|
||||||
|
|
||||||
|
langDropdown?.querySelectorAll('.lang-option').forEach(opt => {
|
||||||
|
opt.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
currentLang = (opt as HTMLElement).dataset.lang || 'en'
|
||||||
|
localStorage.setItem('preferred-lang', currentLang)
|
||||||
|
applyLang(currentLang)
|
||||||
|
langDropdown?.classList.remove('show')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
applyLang(currentLang)
|
||||||
|
}
|
||||||
133
rse/src/main.ts
133
rse/src/main.ts
@@ -1,41 +1,30 @@
|
|||||||
import './style.css'
|
import './style.css'
|
||||||
import * as THREE from 'three'
|
import * as THREE from 'three'
|
||||||
import { createScene, updateScene } from './scene'
|
import { createScene, updateScene } from './scene'
|
||||||
|
import { createInputState, setupInputListeners, smoothInput } from './input'
|
||||||
|
import { setupNavigation } from './navigation'
|
||||||
|
import { setupLanguageSelector } from './language'
|
||||||
|
import { setupMenu } from './menu'
|
||||||
|
|
||||||
// Scene setup
|
// Scene
|
||||||
const canvas = document.getElementById('space-canvas') as HTMLCanvasElement
|
const canvas = document.getElementById('space-canvas') as HTMLCanvasElement
|
||||||
const sceneObjs = createScene(canvas)
|
const sceneObjs = createScene(canvas)
|
||||||
|
|
||||||
// Mouse / Touch
|
// Input
|
||||||
let targetX = 0
|
const input = createInputState()
|
||||||
let targetY = 0
|
setupInputListeners(input)
|
||||||
let smoothX = 0
|
|
||||||
let smoothY = 0
|
|
||||||
|
|
||||||
document.addEventListener('mousemove', (e) => {
|
|
||||||
targetX = (e.clientX / window.innerWidth - 0.5) * 2
|
|
||||||
targetY = (e.clientY / window.innerHeight - 0.5) * 2
|
|
||||||
})
|
|
||||||
|
|
||||||
document.addEventListener('touchmove', (e) => {
|
|
||||||
const t = e.touches[0]
|
|
||||||
targetX = (t.clientX / window.innerWidth - 0.5) * 2
|
|
||||||
targetY = (t.clientY / window.innerHeight - 0.5) * 2
|
|
||||||
}, { passive: true })
|
|
||||||
|
|
||||||
// Animation loop
|
// Animation loop
|
||||||
let time = 0
|
|
||||||
const clock = new THREE.Clock()
|
const clock = new THREE.Clock()
|
||||||
|
let time = 0
|
||||||
|
|
||||||
function animate() {
|
function animate(): void {
|
||||||
requestAnimationFrame(animate)
|
requestAnimationFrame(animate)
|
||||||
const dt = clock.getDelta()
|
const dt = clock.getDelta()
|
||||||
time += dt
|
time += dt
|
||||||
|
|
||||||
smoothX += (targetX - smoothX) * 0.025
|
smoothInput(input)
|
||||||
smoothY += (targetY - smoothY) * 0.025
|
updateScene(sceneObjs, time, dt, input.smoothX, input.smoothY)
|
||||||
|
|
||||||
updateScene(sceneObjs, time, dt, smoothX, smoothY)
|
|
||||||
sceneObjs.renderer.render(sceneObjs.scene, sceneObjs.camera)
|
sceneObjs.renderer.render(sceneObjs.scene, sceneObjs.camera)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,99 +37,9 @@ window.addEventListener('resize', () => {
|
|||||||
sceneObjs.renderer.setSize(window.innerWidth, window.innerHeight)
|
sceneObjs.renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Page navigation
|
// UI
|
||||||
const pageVideo = document.getElementById('page-video')
|
setupNavigation()
|
||||||
const pageLogo = document.getElementById('page-logo')
|
setupLanguageSelector()
|
||||||
const pageMessage = document.getElementById('page-message')
|
setupMenu()
|
||||||
const pageMessage2 = document.getElementById('page-message2')
|
|
||||||
const pageAbout = document.getElementById('page-about')
|
|
||||||
const pageTitle = document.getElementById('page-title')
|
|
||||||
const menuBtn = document.getElementById('menu-btn')
|
|
||||||
const menuDropdown = document.getElementById('menu-dropdown')
|
|
||||||
|
|
||||||
let currentPage: HTMLElement | null = null
|
|
||||||
|
|
||||||
const siteHeader = document.getElementById('site-header')
|
|
||||||
const siteFooter = document.getElementById('site-footer')
|
|
||||||
const subpages = [pageAbout]
|
|
||||||
const fullpages = [pageTitle]
|
|
||||||
|
|
||||||
function showPage(show: HTMLElement | null, hide: HTMLElement | null) {
|
|
||||||
if (hide) {
|
|
||||||
hide.classList.remove('visible')
|
|
||||||
hide.classList.add('page-hidden')
|
|
||||||
}
|
|
||||||
if (show) {
|
|
||||||
show.classList.remove('page-hidden')
|
|
||||||
show.classList.add('visible')
|
|
||||||
}
|
|
||||||
currentPage = show
|
|
||||||
const isFull = fullpages.includes(show)
|
|
||||||
if (siteHeader) siteHeader.style.display = isFull ? 'none' : ''
|
|
||||||
if (siteFooter) {
|
|
||||||
siteFooter.style.display = isFull ? 'none' : ''
|
|
||||||
siteFooter.classList.toggle('footer-solid', subpages.includes(show))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main page navigation
|
|
||||||
document.getElementById('btn-next')?.addEventListener('click', () => showPage(pageLogo, pageVideo))
|
|
||||||
document.getElementById('btn-logo-back')?.addEventListener('click', () => showPage(pageVideo, pageLogo))
|
|
||||||
document.getElementById('btn-logo-next')?.addEventListener('click', () => showPage(pageMessage, pageLogo))
|
|
||||||
document.getElementById('btn-msg-back')?.addEventListener('click', () => showPage(pageLogo, pageMessage))
|
|
||||||
document.getElementById('btn-msg-next')?.addEventListener('click', () => showPage(pageMessage2, pageMessage))
|
|
||||||
document.getElementById('btn-msg2-back')?.addEventListener('click', () => showPage(pageMessage, pageMessage2))
|
|
||||||
document.getElementById('btn-msg2-next')?.addEventListener('click', () => showPage(pageAbout, pageMessage2))
|
|
||||||
document.getElementById('btn-about-back')?.addEventListener('click', () => showPage(pageMessage2, pageAbout))
|
|
||||||
document.getElementById('btn-about-next')?.addEventListener('click', () => showPage(pageTitle, pageAbout))
|
|
||||||
pageTitle?.addEventListener('click', () => showPage(pageAbout, pageTitle))
|
|
||||||
|
|
||||||
// Menu dropdown
|
|
||||||
menuBtn?.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
menuDropdown?.classList.toggle('show')
|
|
||||||
langDropdown?.classList.remove('show')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Language selector
|
|
||||||
let currentLang = localStorage.getItem('preferred-lang') || 'en'
|
|
||||||
const langBtn = document.getElementById('lang-tab')
|
|
||||||
const langDropdown = document.getElementById('lang-dropdown')
|
|
||||||
|
|
||||||
function applyLang(lang: string) {
|
|
||||||
document.querySelectorAll<HTMLElement>('[data-lang-en]').forEach(el => {
|
|
||||||
const text = el.getAttribute(`data-lang-${lang}`)
|
|
||||||
if (text) el.innerHTML = text
|
|
||||||
})
|
|
||||||
langDropdown?.querySelectorAll('.lang-option').forEach(opt => {
|
|
||||||
opt.classList.toggle('selected', (opt as HTMLElement).dataset.lang === lang)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
langBtn?.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
langDropdown?.classList.toggle('show')
|
|
||||||
menuDropdown?.classList.remove('show')
|
|
||||||
})
|
|
||||||
|
|
||||||
langDropdown?.querySelectorAll('.lang-option').forEach(opt => {
|
|
||||||
opt.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
currentLang = (opt as HTMLElement).dataset.lang || 'en'
|
|
||||||
localStorage.setItem('preferred-lang', currentLang)
|
|
||||||
applyLang(currentLang)
|
|
||||||
langDropdown?.classList.remove('show')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
document.addEventListener('click', () => {
|
|
||||||
langDropdown?.classList.remove('show')
|
|
||||||
menuDropdown?.classList.remove('show')
|
|
||||||
})
|
|
||||||
|
|
||||||
applyLang(currentLang)
|
|
||||||
|
|
||||||
// Show first page immediately
|
|
||||||
pageVideo?.classList.add('visible')
|
|
||||||
currentPage = pageVideo
|
|
||||||
document.body.style.opacity = '1'
|
document.body.style.opacity = '1'
|
||||||
|
|||||||
16
rse/src/menu.ts
Normal file
16
rse/src/menu.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export function setupMenu(): void {
|
||||||
|
const menuBtn = document.getElementById('menu-btn')
|
||||||
|
const menuDropdown = document.getElementById('menu-dropdown')
|
||||||
|
const langDropdown = document.getElementById('lang-dropdown')
|
||||||
|
|
||||||
|
menuBtn?.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
menuDropdown?.classList.toggle('show')
|
||||||
|
langDropdown?.classList.remove('show')
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('click', () => {
|
||||||
|
langDropdown?.classList.remove('show')
|
||||||
|
menuDropdown?.classList.remove('show')
|
||||||
|
})
|
||||||
|
}
|
||||||
76
rse/src/navigation.ts
Normal file
76
rse/src/navigation.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
interface NavigationElements {
|
||||||
|
siteHeader: HTMLElement | null
|
||||||
|
siteFooter: HTMLElement | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageId = 'page-video' | 'page-logo' | 'page-message' | 'page-message2' | 'page-about' | 'page-title'
|
||||||
|
|
||||||
|
interface PageRoute {
|
||||||
|
btnId: string
|
||||||
|
from: PageId
|
||||||
|
to: PageId
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes: PageRoute[] = [
|
||||||
|
{ btnId: 'btn-next', from: 'page-video', to: 'page-logo' },
|
||||||
|
{ btnId: 'btn-logo-back', from: 'page-logo', to: 'page-video' },
|
||||||
|
{ btnId: 'btn-logo-next', from: 'page-logo', to: 'page-message' },
|
||||||
|
{ btnId: 'btn-msg-back', from: 'page-message', to: 'page-logo' },
|
||||||
|
{ btnId: 'btn-msg-next', from: 'page-message', to: 'page-message2' },
|
||||||
|
{ btnId: 'btn-msg2-back', from: 'page-message2', to: 'page-message' },
|
||||||
|
{ btnId: 'btn-msg2-next', from: 'page-message2', to: 'page-about' },
|
||||||
|
{ btnId: 'btn-about-back', from: 'page-about', to: 'page-message2' },
|
||||||
|
{ btnId: 'btn-about-next', from: 'page-about', to: 'page-title' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const subpageIds: PageId[] = ['page-about']
|
||||||
|
const fullpageIds: PageId[] = ['page-title']
|
||||||
|
|
||||||
|
function getPage(id: PageId): HTMLElement | null {
|
||||||
|
return document.getElementById(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPage(
|
||||||
|
show: HTMLElement | null,
|
||||||
|
hide: HTMLElement | null,
|
||||||
|
showId: PageId,
|
||||||
|
nav: NavigationElements,
|
||||||
|
): void {
|
||||||
|
if (hide) {
|
||||||
|
hide.classList.remove('visible')
|
||||||
|
hide.classList.add('page-hidden')
|
||||||
|
}
|
||||||
|
if (show) {
|
||||||
|
show.classList.remove('page-hidden')
|
||||||
|
show.classList.add('visible')
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFull = fullpageIds.includes(showId)
|
||||||
|
if (nav.siteHeader) nav.siteHeader.style.display = isFull ? 'none' : ''
|
||||||
|
if (nav.siteFooter) {
|
||||||
|
nav.siteFooter.style.display = isFull ? 'none' : ''
|
||||||
|
nav.siteFooter.classList.toggle('footer-solid', subpageIds.includes(showId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupNavigation(): void {
|
||||||
|
const nav: NavigationElements = {
|
||||||
|
siteHeader: document.getElementById('site-header'),
|
||||||
|
siteFooter: document.getElementById('site-footer'),
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const route of routes) {
|
||||||
|
document.getElementById(route.btnId)?.addEventListener('click', () => {
|
||||||
|
showPage(getPage(route.to), getPage(route.from), route.to, nav)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title page click returns to about
|
||||||
|
getPage('page-title')?.addEventListener('click', () => {
|
||||||
|
showPage(getPage('page-about'), getPage('page-title'), 'page-about', nav)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Show first page
|
||||||
|
const firstPage = getPage('page-video')
|
||||||
|
firstPage?.classList.add('visible')
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user