Before the doublework change

This commit is contained in:
2026-08-23 22:40:43 +02:00
parent dfe2cdf666
commit 95b17e945c
8 changed files with 170 additions and 4 deletions
+78
View File
@@ -0,0 +1,78 @@
import type { FC } from 'react'
import * as THREE from 'three'
import { SpinningModel } from './model3d'
// The Barricelli Luggage: Nils Aall Barricelli's patented suitcase-commode. Two wooden
// beams that taper upward carry three suitcases slung between them as drawers, so the
// whole thing reads as a rudimentary chest of drawers built from luggage.
const WOOD = new THREE.MeshStandardMaterial({ color: 0x6b4a2f, roughness: 0.85, metalness: 0.04, flatShading: true })
const BRASS = new THREE.MeshStandardMaterial({ color: 0xc9a24a, roughness: 0.45, metalness: 0.6, flatShading: true })
const STRAP = new THREE.MeshStandardMaterial({ color: 0x2f2118, roughness: 0.8, flatShading: true })
const LEATHERS = [0x8a5a34, 0x6f4632, 0x9a6b3f].map(color => new THREE.MeshStandardMaterial({ color, roughness: 0.8, metalness: 0.05, flatShading: true }))
// One suitcase drawer: leather body, a lid seam, two wrap-around straps, brass latches,
// corner caps, and a front pull (it is a drawer now, so the handle faces the viewer).
function buildSuitcase(width: number, height: number, depth: number, leather: THREE.Material) {
const suitcase = new THREE.Group()
const body = new THREE.Mesh(new THREE.BoxGeometry(width, height, depth), leather); suitcase.add(body)
const seam = new THREE.Mesh(new THREE.BoxGeometry(width + 0.02, 0.04, depth + 0.02), STRAP); suitcase.add(seam)
for (const sign of [-1, 1]) {
const strap = new THREE.Mesh(new THREE.BoxGeometry(0.07, height + 0.02, depth + 0.04), STRAP)
strap.position.x = sign * width * 0.3; suitcase.add(strap)
const latch = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.07, 0.03), BRASS)
latch.position.set(sign * width * 0.3, 0.02, depth / 2 + 0.01); suitcase.add(latch)
}
for (const sx of [-1, 1]) for (const sy of [-1, 1]) { // brass corner caps on the front face
const cap = new THREE.Mesh(new THREE.BoxGeometry(0.07, 0.07, 0.06), BRASS)
cap.position.set(sx * (width / 2 - 0.035), sy * (height / 2 - 0.035), depth / 2)
suitcase.add(cap)
}
const pull = new THREE.Mesh(new THREE.BoxGeometry(0.26, 0.05, 0.05), BRASS)
pull.position.set(0, -height * 0.16, depth / 2 + 0.05); suitcase.add(pull)
for (const sign of [-1, 1]) { const stem = new THREE.Mesh(new THREE.BoxGeometry(0.03, 0.03, 0.05), BRASS); stem.position.set(sign * 0.1, -height * 0.16, depth / 2 + 0.02); suitcase.add(stem) }
return suitcase
}
function buildBarricelliLuggage() {
const frame = new THREE.Group()
const postHeight = 2.1
const suitcase = { w: 1.05, h: 0.44, d: 0.62 }
const levels = [0.62, 0, -0.62] // three stacked drawers
for (const sign of [-1, 1]) { // two beams, each tapering upward, leaning slightly inward
const post = new THREE.Mesh(new THREE.CylinderGeometry(0.085, 0.12, postHeight, 4), WOOD)
post.rotation.y = Math.PI / 4 // square the prism to face front
post.rotation.z = -sign * 0.05 // splay the feet, converge the tops
post.position.x = sign * 0.66
frame.add(post)
const foot = new THREE.Mesh(new THREE.BoxGeometry(0.24, 0.09, 0.32), WOOD)
foot.position.set(sign * 0.7, -postHeight / 2 - 0.02, 0); frame.add(foot)
}
// Top cap and a low stretcher tie the beams into a stand.
const cap = new THREE.Mesh(new THREE.BoxGeometry(1.44, 0.11, 0.5), WOOD); cap.position.y = postHeight / 2 + 0.02; frame.add(cap)
const stretcher = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.08, 0.18), WOOD); stretcher.position.y = -postHeight / 2 + 0.14; frame.add(stretcher)
levels.forEach((y, i) => {
const drawer = buildSuitcase(suitcase.w, suitcase.h, suitcase.d, LEATHERS[i % LEATHERS.length])
drawer.position.y = y; frame.add(drawer)
for (const sign of [-1, 1]) { // small wooden brackets mounting each case to the beams
const bracket = new THREE.Mesh(new THREE.BoxGeometry(0.16, 0.08, 0.3), WOOD)
bracket.position.set(sign * (suitcase.w / 2 + 0.05), y, 0); frame.add(bracket)
}
})
frame.traverse(obj => { if (obj instanceof THREE.Mesh) obj.castShadow = true })
return frame
}
export const BarricelliLuggageMerit: FC<{ label: string; onComplete: () => void }> = ({ label, onComplete }) => (
<div className="cutscene-card merit-card">
<div className="title-card-inner">
<small className="merit-eyebrow"> MERIT AWARDED </small>
<SpinningModel build={buildBarricelliLuggage} className="merit-stage" cameraPos={[0.9, 0.35, 4.9]} />
<h1>{label}</h1>
<button className="cutscene-begin" onClick={event => { event.stopPropagation(); onComplete() }}>Accept </button>
</div>
</div>
)