feat: add Night Shift layer (boot intro, ops strip, headlight, chapter numerals, film grain)
This commit is contained in:
50
src/composables/useHeadlight.ts
Normal file
50
src/composables/useHeadlight.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { onMounted, onUnmounted, 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);
|
||||
};
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user