1
0

rse refact

This commit is contained in:
2026-03-06 17:20:19 +09:00
parent afaf1b853e
commit 4e56237396
5 changed files with 174 additions and 117 deletions

28
rse/src/input.ts Normal file
View 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
View 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)
}

View File

@@ -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
View 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
View 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')
}