From 629535ba04047ba133529ef2d4fb574bd7377e51 Mon Sep 17 00:00:00 2001 From: LucaP Date: Sat, 10 Oct 2026 17:46:17 +0300 Subject: [PATCH] feat: wayfinding system across chapters, HUD and project plates; cut boot log, headlight and ops strip --- src/App.vue | 2 +- src/components/AppShell.vue | 50 ++++-- src/components/BootSequence.vue | 64 -------- src/components/HeroJourney.vue | 2 +- src/components/JourneyHud.vue | 16 +- src/components/OperationsStrip.vue | 34 ---- src/components/PortfolioSection.vue | 6 +- src/components/ProjectCard.vue | 10 +- src/components/ProjectCaseStudy.vue | 6 +- src/composables/useHeadlight.ts | 56 ------- src/lib/operations.ts | 29 +--- src/lib/route-plate.ts | 15 ++ src/styles/nightshift.css | 184 --------------------- src/styles/wayfinding.css | 240 ++++++++++++++++++++++++++++ tests/unit/operations.test.ts | 18 +-- tests/unit/route-plate.test.ts | 14 ++ 16 files changed, 345 insertions(+), 401 deletions(-) delete mode 100644 src/components/BootSequence.vue delete mode 100644 src/components/OperationsStrip.vue delete mode 100644 src/composables/useHeadlight.ts create mode 100644 src/lib/route-plate.ts delete mode 100644 src/styles/nightshift.css create mode 100644 src/styles/wayfinding.css create mode 100644 tests/unit/route-plate.test.ts diff --git a/src/App.vue b/src/App.vue index eeb7cd5..8914b64 100644 --- a/src/App.vue +++ b/src/App.vue @@ -10,7 +10,7 @@ import './styles/evolution.css'; import './styles/chromatic.css'; import './styles/refinements.css'; import './styles/title-navigation.css'; -import './styles/nightshift.css'; +import './styles/wayfinding.css'; import './styles/print.css'; defineProps<{ inputs: Readonly }>(); diff --git a/src/components/AppShell.vue b/src/components/AppShell.vue index 024f596..7496192 100644 --- a/src/components/AppShell.vue +++ b/src/components/AppShell.vue @@ -2,12 +2,10 @@ import { computed, onMounted, onUnmounted, shallowRef, watch } from 'vue'; import ContactDestination from './ContactDestination.vue'; import DriveControls from './DriveControls.vue'; -import BootSequence from './BootSequence.vue'; import ExperienceTimeline from './ExperienceTimeline.vue'; import HeroJourney from './HeroJourney.vue'; import InfrastructureDiagram from './InfrastructureDiagram.vue'; import JourneyHud from './JourneyHud.vue'; -import OperationsStrip from './OperationsStrip.vue'; import PortfolioSection from './PortfolioSection.vue'; import ProjectCard from './ProjectCard.vue'; import ProjectCaseStudy from './ProjectCaseStudy.vue'; @@ -18,10 +16,10 @@ import { useAdaptiveQuality } from '@/composables/useAdaptiveQuality'; import { useAudioEngine } from '@/composables/useAudioEngine'; import { useAudioIntent } from '@/composables/useAudioIntent'; import { useJourneySections } from '@/composables/useJourneySections'; -import { useHeadlight } from '@/composables/useHeadlight'; import { useKeyboardNavigation } from '@/composables/useKeyboardNavigation'; import { useMotionPreferences } from '@/composables/useMotionPreferences'; import { assetUrl } from '@/content/asset-url'; +import { signRouteFor } from '@/journey/sign-routes'; import type { PortfolioContent, PublicAssetManifest, SectionId, SectionLayout } from '@/content/types'; import type { SceneHostState } from '@/composables/useSceneController'; import type { MotionPolicy } from '@/journey/motion-policy'; @@ -60,9 +58,16 @@ const sectionLabel = (id: SectionId): string => { return item.label; }; +/** One wayfinding vocabulary: chapter plates reuse the roadside sign markers. */ +const exitFor = (id: SectionId): string => signRouteFor(id).marker; +const nextLabelFor = (id: SectionId): string | undefined => { + const index = canonicalSectionIds.indexOf(id); + const next = canonicalSectionIds[index + 1]; + return next === undefined ? undefined : sectionLabel(next); +}; + const canonicalNavigation = computed(() => canonicalSectionIds.map((id) => ({ id, label: sectionLabel(id) }))); const motion = useMotionPreferences(); -useHeadlight(computed(() => motion.policy.value.allowSceneRequest)); const navigation = useKeyboardNavigation(canonicalSectionIds); const journeyBinding = useJourneySections( canonicalSectionIds, @@ -139,6 +144,18 @@ watch( (id) => navigation.notePassiveSection(id), ); +// Light the current chapter's plate once per arrival (attribute only, no per-frame work). +watch( + () => [journey.value.activeSectionId, controlsReady.value] as const, + ([id, ready]) => { + if (!ready) return; + for (const section of document.querySelectorAll('#portfolio-content > section[id]')) { + section.toggleAttribute('data-current', section.id === id); + } + }, + { flush: 'post' }, +); + watch( () => motion.policy.value.maximumTier, (tier, previous) => { @@ -215,11 +232,10 @@ onUnmounted(() => removePageListeners()); :sample-frame="sampleSceneFrame" @state-change="updateSceneState" /> - -