feat: wayfinding system across chapters, HUD and project plates; cut boot log, headlight and ops strip

This commit is contained in:
2026-10-10 17:46:17 +03:00
parent 8a77fc02b2
commit 629535ba04
16 changed files with 345 additions and 401 deletions

View File

@@ -1,56 +0,0 @@
import { onMounted, onUnmounted, watch, type Ref } from 'vue';
const SURFACES = '.project-case-study, .project-card, .contact-destination, .infrastructure-flow-figure';
/**
* Pointer light for fine pointers only. Writes `--hx/--hy` (px) on the root for the
* headlight beam and `--mx/--my` (%) on the hovered surface for its spotlight.
*/
export function useHeadlight(enabled: Ref<boolean>): void {
let frame = 0;
let pending: PointerEvent | undefined;
let active = false;
const flush = (): void => {
frame = 0;
const event = pending;
pending = undefined;
if (event === undefined) return;
const root = document.documentElement;
root.style.setProperty('--hx', `${event.clientX}px`);
root.style.setProperty('--hy', `${event.clientY}px`);
const surface = (event.target as Element | null)?.closest<HTMLElement>(SURFACES);
if (surface !== null && surface !== undefined) {
const box = surface.getBoundingClientRect();
surface.style.setProperty('--mx', `${((event.clientX - box.left) / box.width) * 100}%`);
surface.style.setProperty('--my', `${((event.clientY - box.top) / box.height) * 100}%`);
}
};
const onMove = (event: PointerEvent): void => {
if (!enabled.value || event.pointerType === 'touch') return;
if (!active) {
active = true;
document.documentElement.dataset.headlight = 'on';
}
pending = event;
if (frame === 0) frame = requestAnimationFrame(flush);
};
watch(enabled, (value) => {
if (value) return;
active = false;
delete document.documentElement.dataset.headlight;
});
onMounted(() => {
if (!window.matchMedia('(pointer: fine)').matches) return;
window.addEventListener('pointermove', onMove, { passive: true });
});
onUnmounted(() => {
window.removeEventListener('pointermove', onMove);
if (frame !== 0) cancelAnimationFrame(frame);
delete document.documentElement.dataset.headlight;
});
}