close
Skip to content

Commit 5c4c367

Browse files
committed
feat: add corridor wayfinding cues
1 parent 9473c3f commit 5c4c367

3 files changed

Lines changed: 149 additions & 0 deletions

File tree

‎src/app/surf/DevWebSurf.tsx‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,7 @@ export default function DevWebSurf() {
763763
const [locked, setLocked] = useState(false)
764764
const [currentSection, setCurrentSection] =
765765
useState<LibrarySection>('atrium')
766+
const [wayfindingCue, setWayfindingCue] = useState<string | null>(null)
766767
const [routeTargetId, setRouteTargetId] = useState<string | null>(null)
767768
const [travelRequest, setTravelRequest] = useState<{
768769
id: string
@@ -1381,6 +1382,7 @@ export default function DevWebSurf() {
13811382
onHover={setHovered}
13821383
onPointerLockChange={setLocked}
13831384
onZoneChange={setCurrentSection}
1385+
onWayfindingCueChange={setWayfindingCue}
13841386
currentFloor={currentFloor}
13851387
floorRequest={floorRequest}
13861388
onFloorChange={setCurrentFloor}
@@ -1516,6 +1518,12 @@ export default function DevWebSurf() {
15161518
</small>
15171519
</nav>
15181520

1521+
{wayfindingCue && (
1522+
<aside className={styles.aheadHud} aria-live="polite">
1523+
{wayfindingCue}
1524+
</aside>
1525+
)}
1526+
15191527
<button
15201528
type="button"
15211529
className={[

‎src/app/surf/DevWebSurf3D.tsx‎

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type Props = {
3636
onHover: (node: SurfNode | null) => void
3737
onPointerLockChange: (locked: boolean) => void
3838
onZoneChange: (section: LibrarySection) => void
39+
onWayfindingCueChange: (cue: string | null) => void
3940
currentFloor: number
4041
floorRequest: FloorRequest
4142
onFloorChange: (floor: number) => void
@@ -148,6 +149,16 @@ const SECTION_ACCENTS: Record<LibrarySection, number> = {
148149
archive: 0x8b96a8,
149150
}
150151

152+
const WAYFINDING_DESTINATIONS: Record<LibrarySection, string> = {
153+
atrium: 'Central Atrium',
154+
featured: 'Featured Shelves',
155+
latest: 'New Arrivals',
156+
topics: 'Topic Wings',
157+
creators: 'Creator Studies',
158+
search: 'Card Catalog',
159+
archive: 'Deep Archive',
160+
}
161+
151162
const KIND_GEOMETRY: Record<SurfNodeKind, () => THREE.BufferGeometry> = {
152163
home: () => new THREE.CylinderGeometry(.8, 1.05, .72, 8),
153164
section: () => new THREE.CylinderGeometry(.09, .13, 1.05, 8),
@@ -542,6 +553,7 @@ export default function DevWebSurf3D({
542553
onHover,
543554
onPointerLockChange,
544555
onZoneChange,
556+
onWayfindingCueChange,
545557
currentFloor,
546558
floorRequest,
547559
onFloorChange,
@@ -560,6 +572,7 @@ export default function DevWebSurf3D({
560572
const hoverRef = useRef(onHover)
561573
const lockRef = useRef(onPointerLockChange)
562574
const zoneRef = useRef(onZoneChange)
575+
const wayfindingCueRef = useRef(onWayfindingCueChange)
563576
const currentFloorRef = useRef(currentFloor)
564577
const floorRequestRef = useRef(floorRequest)
565578
const floorChangeRef = useRef(onFloorChange)
@@ -577,6 +590,7 @@ export default function DevWebSurf3D({
577590
hoverRef.current = onHover
578591
lockRef.current = onPointerLockChange
579592
zoneRef.current = onZoneChange
593+
wayfindingCueRef.current = onWayfindingCueChange
580594
currentFloorRef.current = currentFloor
581595
floorRequestRef.current = floorRequest
582596
floorChangeRef.current = onFloorChange
@@ -4201,6 +4215,44 @@ export default function DevWebSurf3D({
42014215
})
42024216
architecturalGeometries.push(floorStripGeometry)
42034217

4218+
// A single transparent gradient gives otherwise-neutral transit runs a
4219+
// gentle pull toward the next threshold without adding lights or routes.
4220+
const corridorPullCanvas = document.createElement('canvas')
4221+
corridorPullCanvas.width = 16
4222+
corridorPullCanvas.height = 256
4223+
const corridorPullContext = corridorPullCanvas.getContext('2d')
4224+
if (corridorPullContext) {
4225+
const gradient = corridorPullContext.createLinearGradient(0, 256, 0, 0)
4226+
gradient.addColorStop(0, 'rgba(255,255,255,0)')
4227+
gradient.addColorStop(.45, 'rgba(255,255,255,.025)')
4228+
gradient.addColorStop(1, 'rgba(255,255,255,.22)')
4229+
corridorPullContext.fillStyle = gradient
4230+
corridorPullContext.fillRect(0, 0, 16, 256)
4231+
}
4232+
const corridorPullTexture = new THREE.CanvasTexture(corridorPullCanvas)
4233+
corridorPullTexture.minFilter = THREE.LinearFilter
4234+
corridorPullTexture.magFilter = THREE.LinearFilter
4235+
corridorPullTexture.generateMipmaps = false
4236+
labelsToDispose.push(corridorPullTexture)
4237+
const corridorPullGeometry = new THREE.PlaneGeometry(.82, 1)
4238+
const corridorPullMaterial = new THREE.MeshBasicMaterial({
4239+
color: SECTION_ACCENTS.featured,
4240+
map: corridorPullTexture,
4241+
transparent: true,
4242+
opacity: .46,
4243+
blending: THREE.AdditiveBlending,
4244+
depthWrite: false,
4245+
})
4246+
const corridorPull = new THREE.Mesh(
4247+
corridorPullGeometry,
4248+
corridorPullMaterial,
4249+
)
4250+
corridorPull.rotation.x = -Math.PI / 2
4251+
corridorPull.visible = false
4252+
scene.add(corridorPull)
4253+
architecturalGeometries.push(corridorPullGeometry)
4254+
architecturalMaterials.push(corridorPullMaterial)
4255+
42044256
const arrowShape = new THREE.Shape()
42054257
arrowShape.moveTo(0, .28)
42064258
arrowShape.lineTo(.16, .02)
@@ -4259,6 +4311,7 @@ export default function DevWebSurf3D({
42594311
const sceneRevealStartedAt = performance.now()
42604312
let debugFrameCount = 0
42614313
let debugWindowStartedAt = performance.now()
4314+
let displayedWayfindingCue: string | null = null
42624315

42634316
function updateArticleLods(now: number) {
42644317
if (now - lastLodUpdate < .22) return
@@ -5259,6 +5312,66 @@ export default function DevWebSurf3D({
52595312
zoneRef.current(nearestSection)
52605313
}
52615314

5315+
const floorBase = currentFloorIndex * LIBRARY_FLOOR_HEIGHT
5316+
const nearbyHubs: Array<{
5317+
section: LibrarySection
5318+
position: THREE.Vector3
5319+
}> =
5320+
currentFloorIndex === 0
5321+
? (Object.keys(SECTION_DOORWAYS) as LibrarySection[])
5322+
.filter((section) => section !== currentSection)
5323+
.map((section) => ({
5324+
section,
5325+
position: SECTION_DOORWAYS[section],
5326+
}))
5327+
: [
5328+
{
5329+
section: 'atrium',
5330+
position: new THREE.Vector3(0, floorBase + .09, 7),
5331+
},
5332+
]
5333+
camera.getWorldDirection(tempDirection)
5334+
let nextHubIndex = -1
5335+
let nextHubDistance = Number.POSITIVE_INFINITY
5336+
nearbyHubs.forEach((hub, index) => {
5337+
const dx = hub.position.x - camera.position.x
5338+
const dz = hub.position.z - camera.position.z
5339+
const distance = Math.hypot(dx, dz)
5340+
if (distance < 3 || distance > 18) return
5341+
const ahead =
5342+
(dx * tempDirection.x + dz * tempDirection.z) /
5343+
Math.max(.001, distance)
5344+
if (ahead < .58 || distance >= nextHubDistance) return
5345+
nextHubIndex = index
5346+
nextHubDistance = distance
5347+
})
5348+
const nextHub =
5349+
nextHubIndex >= 0 ? nearbyHubs[nextHubIndex] : null
5350+
5351+
const nextCue = nextHub
5352+
? 'AHEAD: ' + WAYFINDING_DESTINATIONS[nextHub.section]
5353+
: null
5354+
if (nextCue !== displayedWayfindingCue) {
5355+
displayedWayfindingCue = nextCue
5356+
wayfindingCueRef.current(nextCue)
5357+
}
5358+
5359+
if (nextHub) {
5360+
const dx = nextHub.position.x - camera.position.x
5361+
const dz = nextHub.position.z - camera.position.z
5362+
corridorPull.visible = true
5363+
corridorPull.position.set(
5364+
camera.position.x + dx / 2,
5365+
floorBase + .043,
5366+
camera.position.z + dz / 2,
5367+
)
5368+
corridorPull.rotation.set(-Math.PI / 2, Math.atan2(dx, dz), 0)
5369+
corridorPull.scale.set(1, nextHubDistance, 1)
5370+
corridorPullMaterial.color.setHex(SECTION_ACCENTS[nextHub.section])
5371+
} else {
5372+
corridorPull.visible = false
5373+
}
5374+
52625375
deskGlow.rotation.z += delta * .12
52635376
deskGlowMaterial.opacity =
52645377
.28 + Math.max(0, Math.sin(now * .7)) * .13

‎src/app/surf/surf.module.css‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2698,6 +2698,26 @@
26982698
white-space: nowrap;
26992699
}
27002700

2701+
.aheadHud {
2702+
position: absolute;
2703+
z-index: 62;
2704+
top: 153px;
2705+
right: 22px;
2706+
max-width: 180px;
2707+
overflow: hidden;
2708+
padding: 5px 8px 5px 10px;
2709+
color: #aeb8c8;
2710+
background: linear-gradient(90deg, rgba(13,16,22,.72), rgba(13,16,22,.18));
2711+
border-left: 1px solid rgba(83,211,255,.32);
2712+
font-size: .46rem;
2713+
font-weight: 760;
2714+
letter-spacing: .1em;
2715+
text-overflow: ellipsis;
2716+
text-transform: uppercase;
2717+
white-space: nowrap;
2718+
pointer-events: none;
2719+
}
2720+
27012721
@media (max-width: 1050px) {
27022722
.floorRail {
27032723
right: 14px;
@@ -2707,6 +2727,10 @@
27072727
.floorRail small {
27082728
display: none;
27092729
}
2730+
2731+
.aheadHud {
2732+
right: 14px;
2733+
}
27102734
}
27112735

27122736
@media (max-width: 720px) {
@@ -2716,6 +2740,10 @@
27162740
top: 108px;
27172741
}
27182742

2743+
.aheadHud {
2744+
display: none;
2745+
}
2746+
27192747
.directoryToggle {
27202748
top: 146px;
27212749
}

0 commit comments

Comments
 (0)