perf: enforce journey budgets and reference traces
This commit is contained in:
@@ -20,6 +20,9 @@ import type { PortfolioContent, SectionId } from '@/content/types';
|
||||
import type { SceneHostState } from '@/composables/useSceneController';
|
||||
import type { MotionPolicy, QualityTier } from '@/journey/motion-policy';
|
||||
|
||||
declare const __PORTFOLIO_TEST_BUILD__: boolean;
|
||||
const TEST_BUILD = typeof __PORTFOLIO_TEST_BUILD__ !== 'undefined' && __PORTFOLIO_TEST_BUILD__;
|
||||
|
||||
const props = defineProps<{ content: Readonly<PortfolioContent> }>();
|
||||
|
||||
const canonicalSectionIds = [
|
||||
@@ -57,7 +60,13 @@ const adaptive = useAdaptiveQuality({
|
||||
sceneState,
|
||||
pageLifecycle,
|
||||
});
|
||||
const actualQuality = computed(() => adaptive.snapshot.value.actual);
|
||||
const requestedPerformanceTier = TEST_BUILD
|
||||
? new URL(window.location.href).searchParams.get('__portfolio_tier')
|
||||
: null;
|
||||
const pinnedPerformanceTier = requestedPerformanceTier === 'high' || requestedPerformanceTier === 'low'
|
||||
? requestedPerformanceTier
|
||||
: null;
|
||||
const actualQuality = computed(() => pinnedPerformanceTier ?? adaptive.snapshot.value.actual);
|
||||
const activeSectionLabel = computed(() => sectionLabel(journey.value.activeSectionId));
|
||||
const serverDefaultPolicy: MotionPolicy = {
|
||||
allowSceneRequest: false,
|
||||
@@ -70,6 +79,16 @@ const displayedQuality = computed<QualityTier>(() => controlsReady.value ? actua
|
||||
const displayedSceneState = computed(() => controlsReady.value ? sceneState.value : 'running');
|
||||
let removePageListeners = (): void => {};
|
||||
|
||||
const sampleSceneFrame = (start: number, end: number): void => {
|
||||
if (pinnedPerformanceTier === null) adaptive.sampleFrame(start, end);
|
||||
if (TEST_BUILD) window.__PORTFOLIO_PERFORMANCE__?.recordSceneFrame(start, end);
|
||||
};
|
||||
|
||||
const updateSceneState = (state: SceneHostState): void => {
|
||||
sceneState.value = state;
|
||||
if (TEST_BUILD) window.__PORTFOLIO_PERFORMANCE__?.recordSceneState(state);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => journey.value.activeSectionId,
|
||||
(id) => navigation.notePassiveSection(id),
|
||||
@@ -100,6 +119,28 @@ const enableMotion = (): void => {
|
||||
const resumeMotion = (): void => {
|
||||
motion.setPaused(false);
|
||||
adaptive.setMaximum(motion.policy.value.maximumTier, 'manual');
|
||||
if (TEST_BUILD) window.__PORTFOLIO_PERFORMANCE__?.recordActionMutation('resume');
|
||||
};
|
||||
|
||||
const pauseMotion = (): void => {
|
||||
motion.setPaused(true);
|
||||
if (TEST_BUILD) window.__PORTFOLIO_PERFORMANCE__?.recordActionMutation('pause');
|
||||
};
|
||||
|
||||
const startDrive = (): void => {
|
||||
audio.startFromGesture();
|
||||
if (TEST_BUILD) window.__PORTFOLIO_PERFORMANCE__?.recordActionMutation('start');
|
||||
};
|
||||
|
||||
const toggleAudioMuted = (): void => {
|
||||
const action = audio.muted.value ? 'unmute' : 'mute';
|
||||
audio.toggleMuted();
|
||||
if (TEST_BUILD) window.__PORTFOLIO_PERFORMANCE__?.recordActionMutation(action);
|
||||
};
|
||||
|
||||
const updateAudioVolume = (value: number): void => {
|
||||
audio.setVolume(value);
|
||||
if (TEST_BUILD && value === 0.35) window.__PORTFOLIO_PERFORMANCE__?.recordActionMutation('volume');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
@@ -126,8 +167,8 @@ onUnmounted(() => removePageListeners());
|
||||
:policy="motion.policy.value"
|
||||
:quality="actualQuality"
|
||||
:scene-description="content.sceneDescription"
|
||||
:sample-frame="adaptive.sampleFrame"
|
||||
@state-change="sceneState = $event"
|
||||
:sample-frame="sampleSceneFrame"
|
||||
@state-change="updateSceneState"
|
||||
/>
|
||||
<JourneyHud
|
||||
:journey="journey"
|
||||
@@ -203,13 +244,13 @@ onUnmounted(() => removePageListeners());
|
||||
:audio-state="audio.state.value"
|
||||
:audio-muted="audio.muted.value"
|
||||
:audio-volume="controlsReady ? motion.volume.value : 0.4"
|
||||
@pause="motion.setPaused(true)"
|
||||
@pause="pauseMotion"
|
||||
@resume="resumeMotion"
|
||||
@enable="enableMotion"
|
||||
@quality-change="setQuality"
|
||||
@audio-start="audio.startFromGesture"
|
||||
@audio-start="startDrive"
|
||||
@audio-resume="audio.resumeFromGesture"
|
||||
@audio-mute-toggle="audio.toggleMuted"
|
||||
@audio-volume-change="audio.setVolume"
|
||||
@audio-mute-toggle="toggleAudioMuted"
|
||||
@audio-volume-change="updateAudioVolume"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -86,9 +86,6 @@ const changeVolume = (event: Event): void => {
|
||||
type="button"
|
||||
@click="emit('audioResume')"
|
||||
>Resume sound</button>
|
||||
<button type="button" @click="emit('audioMuteToggle')">
|
||||
{{ audioMuted ? 'Unmute sound' : 'Mute sound' }}
|
||||
</button>
|
||||
<label>
|
||||
<span>Sound volume</span>
|
||||
<input
|
||||
@@ -101,6 +98,9 @@ const changeVolume = (event: Event): void => {
|
||||
@input="changeVolume"
|
||||
>
|
||||
</label>
|
||||
<button type="button" @click="emit('audioMuteToggle')">
|
||||
{{ audioMuted ? 'Unmute sound' : 'Mute sound' }}
|
||||
</button>
|
||||
</div>
|
||||
<label>
|
||||
<span>Quality</span>
|
||||
|
||||
@@ -7,6 +7,17 @@ const props = defineProps<{
|
||||
journey: JourneySnapshot;
|
||||
onSectionLinkClick: (event: MouseEvent) => void;
|
||||
}>();
|
||||
|
||||
const compactLabels: Readonly<Record<SectionId, string>> = {
|
||||
start: 'Start',
|
||||
profile: 'Profile',
|
||||
experience: 'Career',
|
||||
infrastructure: 'Infra',
|
||||
'selected-builds': 'Builds',
|
||||
'additional-work': 'More work',
|
||||
toolkit: 'Toolkit',
|
||||
contact: 'Contact',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -15,11 +26,13 @@ const props = defineProps<{
|
||||
<li v-for="item in items" :key="item.id">
|
||||
<a
|
||||
:href="`#${item.id}`"
|
||||
:aria-label="item.label"
|
||||
:data-compact-label="compactLabels[item.id]"
|
||||
:aria-current="
|
||||
props.journey.activeSectionId === item.id ? 'page' : undefined
|
||||
"
|
||||
@click="props.onSectionLinkClick"
|
||||
>{{ item.label }}</a
|
||||
><span class="site-navigation__full-label" aria-hidden="true">{{ item.label }}</span></a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user