feat: sign interlude, window plate and a two-button control bay
The sign closes in over the road on a gantry truss, arcs into a wider window and hands off to a flat 2D plate that mirrors its face; content steps aside for the interlude and rises after. The bay keeps sound and settings; volume and Pause motion move into the settings popover.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import type { SectionId } from "@/content/types";
|
||||
import type { JourneySnapshot } from "@/journey/types";
|
||||
import { SIGN_ACCENTS } from "@/journey/sign-routes";
|
||||
|
||||
const props = defineProps<{
|
||||
items: ReadonlyArray<{ id: SectionId; label: string }>;
|
||||
@@ -9,6 +10,8 @@ const props = defineProps<{
|
||||
/** Exits passed in this browser (client-only memory). */
|
||||
visited?: ReadonlySet<SectionId>;
|
||||
allowMotion?: boolean;
|
||||
/** A returning visitor's last exit: the window offers it from the start. */
|
||||
resumeId?: SectionId | undefined;
|
||||
onSectionLinkClick: (event: MouseEvent, requestSign?: boolean) => void;
|
||||
}>();
|
||||
|
||||
@@ -93,18 +96,34 @@ onBeforeUnmount(() => {
|
||||
compactQuery = null;
|
||||
});
|
||||
|
||||
/** The gantry's sign window drives on to the next exit (or back to the
|
||||
* start from the destination), exactly like following that exit's link. */
|
||||
const nextIndex = computed(() =>
|
||||
activeIndex.value >= props.items.length - 1 ? 0 : activeIndex.value + 1,
|
||||
/** The gantry's sign window drives on to the next exit, resumes a returning
|
||||
* visitor's drive from the start, or goes back to the start from the
|
||||
* destination, exactly like following that exit's link. */
|
||||
const resumeIndex = computed(() =>
|
||||
props.resumeId === undefined || activeIndex.value !== 0
|
||||
? -1
|
||||
: props.items.findIndex((item) => item.id === props.resumeId),
|
||||
);
|
||||
const nextIndex = computed(() => {
|
||||
if (resumeIndex.value > 0) return resumeIndex.value;
|
||||
return activeIndex.value >= props.items.length - 1 ? 0 : activeIndex.value + 1;
|
||||
});
|
||||
const windowLabel = computed(() => {
|
||||
const next = props.items[nextIndex.value];
|
||||
if (next === undefined) return "Drive on";
|
||||
if (resumeIndex.value > 0)
|
||||
return `Resume the drive at ${next.label}, exit ${exitNumber(nextIndex.value)}`;
|
||||
return nextIndex.value === 0
|
||||
? `Back to the start: ${next.label}`
|
||||
? `Drive again from the start: ${next.label}`
|
||||
: `Drive to the next exit: ${next.label}`;
|
||||
});
|
||||
/** Decorative hover line under the window (CSS generated content). */
|
||||
const windowHint = computed(() => {
|
||||
if (resumeIndex.value > 0) return `RESUME EXIT ${exitNumber(nextIndex.value)} \u2192`;
|
||||
return nextIndex.value === 0
|
||||
? "DRIVE AGAIN \u21ba"
|
||||
: `DRIVE TO EXIT ${exitNumber(nextIndex.value)} \u2192`;
|
||||
});
|
||||
const driveOn = (): void => {
|
||||
list.value?.querySelectorAll<HTMLAnchorElement>("a")[nextIndex.value]?.click();
|
||||
};
|
||||
@@ -117,7 +136,7 @@ const navigate = (event: MouseEvent): void => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="site-navigation" aria-label="Portfolio sections" :data-enhanced="enhanced" :data-docked="docked" :data-motion="allowMotion === true" :style="{ '--nav-rest-scale': restScale, '--route-index': activeIndex, '--route-count': items.length }">
|
||||
<nav class="site-navigation" aria-label="Portfolio sections" :data-enhanced="enhanced" :data-docked="docked" :data-motion="allowMotion === true" :style="{ '--nav-rest-scale': restScale, '--route-index': activeIndex, '--route-count': items.length, '--sign-accent': SIGN_ACCENTS[items[activeIndex]?.id ?? 'start'] }">
|
||||
<details ref="disclosure" class="site-navigation__disclosure" open>
|
||||
<summary :data-exit="exitNumber(activeIndex)" :data-label="navigationLabels[items[activeIndex]?.id ?? 'start']">Navigate</summary>
|
||||
<ul ref="list">
|
||||
@@ -138,8 +157,7 @@ const navigate = (event: MouseEvent): void => {
|
||||
type="button"
|
||||
class="gantry-window"
|
||||
:aria-label="windowLabel"
|
||||
:title="windowLabel"
|
||||
@click="driveOn"
|
||||
></button>
|
||||
><span class="gantry-window__cone" aria-hidden="true"></span><span class="gantry-window__hint" aria-hidden="true" :data-hint="windowHint"></span></button>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user