diff --git a/docs/overnight/polish/speedo-1280-destination-bay.webp b/docs/overnight/polish/speedo-1280-destination-bay.webp new file mode 100644 index 0000000..761dadb Binary files /dev/null and b/docs/overnight/polish/speedo-1280-destination-bay.webp differ diff --git a/docs/overnight/polish/speedo-1280-destination.webp b/docs/overnight/polish/speedo-1280-destination.webp new file mode 100644 index 0000000..1305a16 Binary files /dev/null and b/docs/overnight/polish/speedo-1280-destination.webp differ diff --git a/docs/overnight/polish/speedo-1280-flash.webp b/docs/overnight/polish/speedo-1280-flash.webp new file mode 100644 index 0000000..7864743 Binary files /dev/null and b/docs/overnight/polish/speedo-1280-flash.webp differ diff --git a/docs/overnight/polish/speedo-1280-hover.webp b/docs/overnight/polish/speedo-1280-hover.webp new file mode 100644 index 0000000..00ed7ce Binary files /dev/null and b/docs/overnight/polish/speedo-1280-hover.webp differ diff --git a/docs/overnight/polish/speedo-1280-idle-bay.webp b/docs/overnight/polish/speedo-1280-idle-bay.webp new file mode 100644 index 0000000..1ec322f Binary files /dev/null and b/docs/overnight/polish/speedo-1280-idle-bay.webp differ diff --git a/docs/overnight/polish/speedo-1280-idle.webp b/docs/overnight/polish/speedo-1280-idle.webp new file mode 100644 index 0000000..9328e3b Binary files /dev/null and b/docs/overnight/polish/speedo-1280-idle.webp differ diff --git a/docs/overnight/polish/speedo-1280-ignition-strip.webp b/docs/overnight/polish/speedo-1280-ignition-strip.webp new file mode 100644 index 0000000..122f5cc Binary files /dev/null and b/docs/overnight/polish/speedo-1280-ignition-strip.webp differ diff --git a/docs/overnight/polish/speedo-1280-jump-strip.webp b/docs/overnight/polish/speedo-1280-jump-strip.webp new file mode 100644 index 0000000..3707493 Binary files /dev/null and b/docs/overnight/polish/speedo-1280-jump-strip.webp differ diff --git a/docs/overnight/polish/speedo-1280-jump.webp b/docs/overnight/polish/speedo-1280-jump.webp new file mode 100644 index 0000000..7ed9599 Binary files /dev/null and b/docs/overnight/polish/speedo-1280-jump.webp differ diff --git a/docs/overnight/polish/speedo-1280-scroll-strip.webp b/docs/overnight/polish/speedo-1280-scroll-strip.webp new file mode 100644 index 0000000..3bbfa32 Binary files /dev/null and b/docs/overnight/polish/speedo-1280-scroll-strip.webp differ diff --git a/docs/overnight/polish/speedo-1280-scrolling.webp b/docs/overnight/polish/speedo-1280-scrolling.webp new file mode 100644 index 0000000..6ac716a Binary files /dev/null and b/docs/overnight/polish/speedo-1280-scrolling.webp differ diff --git a/docs/overnight/polish/speedo-1440-destination-bay.webp b/docs/overnight/polish/speedo-1440-destination-bay.webp new file mode 100644 index 0000000..8daa2e4 Binary files /dev/null and b/docs/overnight/polish/speedo-1440-destination-bay.webp differ diff --git a/docs/overnight/polish/speedo-1440-destination.webp b/docs/overnight/polish/speedo-1440-destination.webp new file mode 100644 index 0000000..a143512 Binary files /dev/null and b/docs/overnight/polish/speedo-1440-destination.webp differ diff --git a/docs/overnight/polish/speedo-1440-flash.webp b/docs/overnight/polish/speedo-1440-flash.webp new file mode 100644 index 0000000..126f220 Binary files /dev/null and b/docs/overnight/polish/speedo-1440-flash.webp differ diff --git a/docs/overnight/polish/speedo-1440-hover.webp b/docs/overnight/polish/speedo-1440-hover.webp new file mode 100644 index 0000000..6f5a498 Binary files /dev/null and b/docs/overnight/polish/speedo-1440-hover.webp differ diff --git a/docs/overnight/polish/speedo-1440-idle-bay.webp b/docs/overnight/polish/speedo-1440-idle-bay.webp new file mode 100644 index 0000000..fc8f151 Binary files /dev/null and b/docs/overnight/polish/speedo-1440-idle-bay.webp differ diff --git a/docs/overnight/polish/speedo-1440-idle.webp b/docs/overnight/polish/speedo-1440-idle.webp new file mode 100644 index 0000000..64e06ae Binary files /dev/null and b/docs/overnight/polish/speedo-1440-idle.webp differ diff --git a/docs/overnight/polish/speedo-1440-ignition-strip.webp b/docs/overnight/polish/speedo-1440-ignition-strip.webp new file mode 100644 index 0000000..e41fd9f Binary files /dev/null and b/docs/overnight/polish/speedo-1440-ignition-strip.webp differ diff --git a/docs/overnight/polish/speedo-1440-jump-strip.webp b/docs/overnight/polish/speedo-1440-jump-strip.webp new file mode 100644 index 0000000..1e6bba4 Binary files /dev/null and b/docs/overnight/polish/speedo-1440-jump-strip.webp differ diff --git a/docs/overnight/polish/speedo-1440-jump.webp b/docs/overnight/polish/speedo-1440-jump.webp new file mode 100644 index 0000000..c866bdd Binary files /dev/null and b/docs/overnight/polish/speedo-1440-jump.webp differ diff --git a/docs/overnight/polish/speedo-1440-scroll-strip.webp b/docs/overnight/polish/speedo-1440-scroll-strip.webp new file mode 100644 index 0000000..dcca9bd Binary files /dev/null and b/docs/overnight/polish/speedo-1440-scroll-strip.webp differ diff --git a/docs/overnight/polish/speedo-1440-scrolling.webp b/docs/overnight/polish/speedo-1440-scrolling.webp new file mode 100644 index 0000000..b73cfb3 Binary files /dev/null and b/docs/overnight/polish/speedo-1440-scrolling.webp differ diff --git a/docs/overnight/polish/speedo-lamps-and-focus.webp b/docs/overnight/polish/speedo-lamps-and-focus.webp new file mode 100644 index 0000000..84a223c Binary files /dev/null and b/docs/overnight/polish/speedo-lamps-and-focus.webp differ diff --git a/docs/overnight/polish/speedo-reduced-motion-bay.webp b/docs/overnight/polish/speedo-reduced-motion-bay.webp new file mode 100644 index 0000000..06ae214 Binary files /dev/null and b/docs/overnight/polish/speedo-reduced-motion-bay.webp differ diff --git a/scripts/lib/bundle-budgets.mjs b/scripts/lib/bundle-budgets.mjs index 155fd47..9d920fd 100644 --- a/scripts/lib/bundle-budgets.mjs +++ b/scripts/lib/bundle-budgets.mjs @@ -48,6 +48,8 @@ export const classifyArtifact = (path, source = '') => { if (/^assets\/traffic-pass-[^/]+\.js$/u.test(path)) return 'deferred-non-video'; // PARKING LIGHTS: the favicon renderer, loaded after mount. if (/^assets\/tab-icon-[^/]+\.js$/u.test(path)) return 'deferred-non-video'; + // SPEEDOMETER: the gantry dial's model and DOM driver, loaded after mount. + if (/^assets\/speedometer-dial-[^/]+\.js$/u.test(path)) return 'deferred-non-video'; if (/^assets\/nightdrive-(?:grand-tourer|districts)-[^/]+\.glb$/u.test(path)) return 'first-live-scene'; if (/\.(?:woff2?|ttf|otf)$/u.test(path)) return 'initial-fonts'; if (/^assets\/hero-[^/]+\.(?:avif|webp|png|jpe?g)$/u.test(path)) return 'hero-poster'; diff --git a/src/components/AppShell.vue b/src/components/AppShell.vue index b11e01d..cb1441f 100644 --- a/src/components/AppShell.vue +++ b/src/components/AppShell.vue @@ -317,6 +317,10 @@ onUnmounted(() => removePageListeners()); :resume-id="resume?.id" :allow-motion="motion.policy.value.allowSceneRequest" :on-section-link-click="navigation.onSectionLinkClick" + :frames="journeyBinding" + :navigation="navigation.state.value" + :sample-beam="headlight.sampleBeam" + :animated="motion.policy.value.reason === 'full'" /> diff --git a/src/components/SiteNavigation.vue b/src/components/SiteNavigation.vue index 9993ac7..e825372 100644 --- a/src/components/SiteNavigation.vue +++ b/src/components/SiteNavigation.vue @@ -2,6 +2,10 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue"; import type { SectionId } from "@/content/types"; import type { JourneySnapshot } from "@/journey/types"; +import type { NavigationState } from "@/journey/navigation-controller"; +import type { BeamSample } from "@/journey/headlight-beam"; +import type { JourneySectionsBinding } from "@/composables/useJourneySections"; +import { useSpeedometer } from "@/composables/useSpeedometer"; import { SIGN_ACCENTS } from "@/journey/sign-routes"; const props = defineProps<{ @@ -13,6 +17,12 @@ const props = defineProps<{ /** A returning visitor's last exit: the window offers it from the start. */ resumeId?: SectionId | undefined; onSectionLinkClick: (event: MouseEvent, requestSign?: boolean) => void; + /** SPEEDOMETER (left bay): the journey's frame loop, the navigator and the beam. */ + frames: Pick; + navigation: NavigationState; + sampleBeam: (now: number) => BeamSample | null; + /** False snaps the needle: reduced motion, or motion paused. */ + animated?: boolean; }>(); const navigationLabels: Readonly> = { @@ -128,6 +138,31 @@ const driveOn = (): void => { list.value?.querySelectorAll("a")[nextIndex.value]?.click(); }; +/* SPEEDOMETER: a 240-degree dial and a digital readout in the left bay, + driven by the drive (journey/speedometer.ts). Hover or focus names the + route, the exit and the time on the road; a click switches km/h and mph. */ +const speedo = ref(null); +const speedometer = useSpeedometer({ + root: speedo, + navigation: () => props.navigation, + frames: props.frames, + sampleBeam: (now) => props.sampleBeam(now), + animated: () => props.animated === true, + motionAllowed: () => props.allowMotion === true, + detail: () => { + const next = props.items[activeIndex.value + 1]; + const route = `ROUTE ${Math.round(Math.min(1, Math.max(0, props.journey.routeProgress)) * 100)}%`; + const exit = `EXIT ${exitNumber(activeIndex.value)}/${exitNumber(props.items.length - 1)}`; + const ahead = next === undefined + ? "END OF ROUTE" + : `NEXT ${exitNumber(activeIndex.value + 1)} ${navigationLabels[next.id]}`.toUpperCase(); + return `${route} \u00b7 ${exit} \u00b7 ${ahead}`; + }, +}); +const speedoLabel = computed(() => (speedometer.units.value === "mph" + ? "Speedometer in miles per hour. Switch to kilometres per hour" + : "Speedometer in kilometres per hour. Switch to miles per hour")); + const navigate = (event: MouseEvent): void => { props.onSectionLinkClick(event, true); if (window.matchMedia("(max-width: 767px)").matches) @@ -137,6 +172,37 @@ const navigate = (event: MouseEvent): void => {