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 * as THREE from 'three'
|
||||
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 sceneObjs = createScene(canvas)
|
||||
|
||||
// Mouse / Touch
|
||||
let targetX = 0
|
||||
let targetY = 0
|
||||
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 })
|
||||
// Input
|
||||
const input = createInputState()
|
||||
setupInputListeners(input)
|
||||
|
||||
// Animation loop
|
||||
let time = 0
|
||||
const clock = new THREE.Clock()
|
||||
let time = 0
|
||||
|
||||
function animate() {
|
||||
function animate(): void {
|
||||
requestAnimationFrame(animate)
|
||||
const dt = clock.getDelta()
|
||||
time += dt
|
||||
|
||||
smoothX += (targetX - smoothX) * 0.025
|
||||
smoothY += (targetY - smoothY) * 0.025
|
||||
|
||||
updateScene(sceneObjs, time, dt, smoothX, smoothY)
|
||||
smoothInput(input)
|
||||
updateScene(sceneObjs, time, dt, input.smoothX, input.smoothY)
|
||||
sceneObjs.renderer.render(sceneObjs.scene, sceneObjs.camera)
|
||||
}
|
||||
|
||||
@@ -48,99 +37,9 @@ window.addEventListener('resize', () => {
|
||||
sceneObjs.renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
})
|
||||
|
||||
// Page navigation
|
||||
const pageVideo = document.getElementById('page-video')
|
||||
const pageLogo = document.getElementById('page-logo')
|
||||
const pageMessage = document.getElementById('page-message')
|
||||
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')
|
||||
// UI
|
||||
setupNavigation()
|
||||
setupLanguageSelector()
|
||||
setupMenu()
|
||||
|
||||
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'
|
||||
|
||||
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