feat: wayfinding system across chapters, HUD and project plates; cut boot log, headlight and ops strip
This commit is contained in:
@@ -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<HTMLElement>('#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"
|
||||
/>
|
||||
<BootSequence :allow-motion="motion.policy.value.allowSceneRequest" />
|
||||
<OperationsStrip />
|
||||
<JourneyHud
|
||||
:journey="journey"
|
||||
:section-label="activeSectionLabel"
|
||||
:exit="exitFor(journey.activeSectionId)"
|
||||
/>
|
||||
<header class="site-header">
|
||||
<SiteNavigation
|
||||
@@ -235,7 +251,7 @@ onUnmounted(() => removePageListeners());
|
||||
<main id="portfolio-content" class="portfolio-main" tabindex="-1">
|
||||
<HeroJourney :identity="content.identity" :allow-motion="motion.policy.value.allowSceneRequest && pageLifecycle === 'active'" @explore="navigation.onSectionLinkClick" @contact="navigation.onSectionLinkClick" />
|
||||
|
||||
<PortfolioSection id="profile" :layout="sectionLayouts.profile" :heading="sectionLabel('profile')">
|
||||
<PortfolioSection id="profile" :exit="exitFor('profile')" :next-label="nextLabelFor('profile')" :layout="sectionLayouts.profile" :heading="sectionLabel('profile')">
|
||||
<p>{{ content.profile.introduction }}</p>
|
||||
<dl class="profile-facts">
|
||||
<template v-for="fact in content.profile.screeningFacts" :key="fact.label">
|
||||
@@ -245,19 +261,21 @@ onUnmounted(() => removePageListeners());
|
||||
</dl>
|
||||
</PortfolioSection>
|
||||
|
||||
<PortfolioSection id="experience" :layout="sectionLayouts.experience" :heading="sectionLabel('experience')">
|
||||
<PortfolioSection id="experience" :exit="exitFor('experience')" :next-label="nextLabelFor('experience')" :layout="sectionLayouts.experience" :heading="sectionLabel('experience')">
|
||||
<ExperienceTimeline :experience="content.experience" :systems-operations="content.systemsOperations" />
|
||||
</PortfolioSection>
|
||||
|
||||
<PortfolioSection id="infrastructure" :layout="sectionLayouts.infrastructure" :heading="sectionLabel('infrastructure')">
|
||||
<PortfolioSection id="infrastructure" :exit="exitFor('infrastructure')" :next-label="nextLabelFor('infrastructure')" :layout="sectionLayouts.infrastructure" :heading="sectionLabel('infrastructure')">
|
||||
<InfrastructureDiagram :infrastructure="content.infrastructure" />
|
||||
</PortfolioSection>
|
||||
|
||||
<PortfolioSection id="selected-builds" :layout="sectionLayouts['selected-builds']" eyebrow="Selected work" :heading="sectionLabel('selected-builds')">
|
||||
<PortfolioSection id="selected-builds" :exit="exitFor('selected-builds')" :next-label="nextLabelFor('selected-builds')" :layout="sectionLayouts['selected-builds']" eyebrow="Selected work" :heading="sectionLabel('selected-builds')">
|
||||
<template #lead>
|
||||
<ProjectCaseStudy
|
||||
v-if="content.projects.selected[0]"
|
||||
:project="content.projects.selected[0]"
|
||||
:exit="exitFor('selected-builds')"
|
||||
:index="0"
|
||||
priority="primary"
|
||||
:allow-automatic-video="motion.policy.value.allowAutomaticVideo"
|
||||
/>
|
||||
@@ -267,28 +285,32 @@ onUnmounted(() => removePageListeners());
|
||||
v-for="(project, index) in content.projects.selected.slice(1)"
|
||||
:key="project.id"
|
||||
:project="project"
|
||||
:exit="exitFor('selected-builds')"
|
||||
:index="index + 1"
|
||||
:priority="index === 0 ? 'primary' : 'supporting'"
|
||||
:allow-automatic-video="motion.policy.value.allowAutomaticVideo"
|
||||
/>
|
||||
</div>
|
||||
</PortfolioSection>
|
||||
|
||||
<PortfolioSection id="additional-work" :layout="sectionLayouts['additional-work']" :heading="sectionLabel('additional-work')">
|
||||
<PortfolioSection id="additional-work" :exit="exitFor('additional-work')" :next-label="nextLabelFor('additional-work')" :layout="sectionLayouts['additional-work']" :heading="sectionLabel('additional-work')">
|
||||
<div class="compact-projects">
|
||||
<ProjectCard
|
||||
v-for="project in content.projects.additional"
|
||||
v-for="(project, index) in content.projects.additional"
|
||||
:key="project.id"
|
||||
:project="project"
|
||||
:exit="exitFor('additional-work')"
|
||||
:index="index"
|
||||
:allow-automatic-video="motion.policy.value.allowAutomaticVideo"
|
||||
/>
|
||||
</div>
|
||||
</PortfolioSection>
|
||||
|
||||
<PortfolioSection id="toolkit" :layout="sectionLayouts.toolkit" :heading="sectionLabel('toolkit')">
|
||||
<PortfolioSection id="toolkit" :exit="exitFor('toolkit')" :next-label="nextLabelFor('toolkit')" :layout="sectionLayouts.toolkit" :heading="sectionLabel('toolkit')">
|
||||
<ToolkitGrid :groups="content.toolkit" />
|
||||
</PortfolioSection>
|
||||
|
||||
<PortfolioSection id="contact" :layout="sectionLayouts.contact" eyebrow="Destination" :heading="sectionLabel('contact')">
|
||||
<PortfolioSection id="contact" :exit="exitFor('contact')" :next-label="nextLabelFor('contact')" :layout="sectionLayouts.contact" eyebrow="Destination" :heading="sectionLabel('contact')">
|
||||
<footer>
|
||||
<ContactDestination :contact="content.contact" />
|
||||
</footer>
|
||||
|
||||
Reference in New Issue
Block a user