Improved animation on the inventory tools

This commit is contained in:
2026-08-23 22:11:29 +02:00
parent 0484a07fff
commit dfe2cdf666
+48 -3
View File
@@ -53,7 +53,9 @@ function ToolRack({ index }: { index: number }) {
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(34, 1, 0.1, 100)
camera.position.set(0.5, 0.45, 3.3)
// Pulled back along the same view direction (was z 3.3) so the tools sit smaller in
// frame — full-screen models were hard to read as objects.
camera.position.set(0.63, 0.57, 8.15)
camera.lookAt(0, 0, 0)
scene.add(new THREE.AmbientLight(0x40483a, 0.9))
const key = new THREE.DirectionalLight(0xfff4e0, 1.1); key.position.set(2, 3, 3); scene.add(key)
@@ -63,11 +65,41 @@ function ToolRack({ index }: { index: number }) {
const resize = () => { const w = stage.clientWidth, h = stage.clientHeight; if (w && h) { camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h, false) } }
resize(); const ro = new ResizeObserver(resize); ro.observe(stage)
// Drag to spin: a swipe rotates the holder 1:1 with the pointer and hands its speed
// to `spin`, which the idle loop keeps applying and eases back to the resting rate —
// so interfering with a flick sends it spinning faster, then it settles.
const BASE_SPIN = 0.008
let spin = BASE_SPIN
let dragging = false
let lastX = 0
const dom = renderer.domElement
dom.style.touchAction = 'none'
dom.style.cursor = 'grab'
const onDown = (event: PointerEvent) => { dragging = true; lastX = event.clientX; dom.style.cursor = 'grabbing'; dom.setPointerCapture(event.pointerId) }
const onMove = (event: PointerEvent) => {
if (!dragging) return
const delta = (event.clientX - lastX) * 0.01
lastX = event.clientX
holder.rotation.y += delta
spin = THREE.MathUtils.clamp(delta, -0.35, 0.35) // carry the swipe's speed as momentum
}
const onUp = (event: PointerEvent) => { dragging = false; dom.style.cursor = 'grab'; try { dom.releasePointerCapture(event.pointerId) } catch { /* pointer already released */ } }
dom.addEventListener('pointerdown', onDown)
dom.addEventListener('pointermove', onMove)
dom.addEventListener('pointerup', onUp)
dom.addEventListener('pointercancel', onUp)
let raf = 0
const tick = () => { holder.rotation.y += 0.008; renderer.render(scene, camera); raf = requestAnimationFrame(tick) }
const tick = () => {
if (!dragging) { holder.rotation.y += spin; spin += (BASE_SPIN - spin) * 0.04 } // momentum eases back to idle
renderer.render(scene, camera); raf = requestAnimationFrame(tick)
}
tick()
return () => {
cancelAnimationFrame(raf); ro.disconnect(); renderer.dispose(); renderer.domElement.remove()
cancelAnimationFrame(raf); ro.disconnect()
dom.removeEventListener('pointerdown', onDown); dom.removeEventListener('pointermove', onMove)
dom.removeEventListener('pointerup', onUp); dom.removeEventListener('pointercancel', onUp)
renderer.dispose(); renderer.domElement.remove()
scene.traverse(o => { if (o instanceof THREE.Mesh) { o.geometry.dispose(); (o.material as THREE.Material).dispose() } })
holderRef.current = null
}
@@ -89,6 +121,19 @@ export function Inventory({ onClose, session, onTearToBoard }: { onClose?: () =>
const [index, setIndex] = useState(0)
const [active, setActive] = useState<string | null>(null)
// Keyboard rack controls: ← / → cycle tools, Space uses the selected one. Inert once a
// tool is open, so its own inputs (e.g. the notebook textarea) keep the keys.
useEffect(() => {
if (active) return
const onKey = (event: KeyboardEvent) => {
if (event.key === 'ArrowLeft') { event.preventDefault(); setIndex(i => (i - 1 + TOOLS.length) % TOOLS.length) }
else if (event.key === 'ArrowRight') { event.preventDefault(); setIndex(i => (i + 1) % TOOLS.length) }
else if (event.key === ' ') { event.preventDefault(); setActive(TOOLS[index].key) }
}
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
}, [index, active])
if (active) return <div className="inv-tool">
<button className="inv-back" onClick={() => setActive(null)}> TOOLS</button>
{onClose && <button className="inv-exit" onClick={onClose}>EXIT </button>}