mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(mascot): typecheck the clip metadata and keep the client ratchets green
The tuple cast on clips.meta.json failed tsc. The clip lookup and the 3D opt-out move into mascotClips.ts so the component file only exports the component, the crop offset is allowed as a deliberate physical side, and MDancingTrek leaves the format baseline.
This commit is contained in:
@@ -1277,7 +1277,6 @@
|
||||
"src/mobile/components/MBottomNav.test.tsx",
|
||||
"src/mobile/components/MBottomNav.tsx",
|
||||
"src/mobile/components/MChip.tsx",
|
||||
"src/mobile/components/MDancingTrek.tsx",
|
||||
"src/mobile/components/MDayImpactList.tsx",
|
||||
"src/mobile/components/MDropdownPanel.tsx",
|
||||
"src/mobile/components/MFab.tsx",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"pages/tripPlanner/PlanMapStage.tsx": 4
|
||||
"pages/tripPlanner/PlanMapStage.tsx": 4,
|
||||
"mobile/components/trek3d/MascotClip.tsx": 1
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import MascotClip, { findClip, mascot3dEnabled } from './trek3d/MascotClip';
|
||||
import MascotClip from './trek3d/MascotClip';
|
||||
import { findClip, mascot3dEnabled } from './trek3d/mascotClips';
|
||||
|
||||
/**
|
||||
* The TREK mascot — an empty-state character that IS the TREK mark: the logo is
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
||||
import meta from './clips.meta.json';
|
||||
import { CLIPS } from './mascotClips';
|
||||
|
||||
/**
|
||||
* The 3D TREK mascot as pre-rendered clips. Each clip is a plain H.264 MP4 with
|
||||
@@ -17,48 +18,12 @@ import meta from './clips.meta.json';
|
||||
* inside the full 88:96 box so every scene keeps the same scale. Only URLs are
|
||||
* bundled; the files load when a mascot actually renders.
|
||||
*/
|
||||
const CLIPS = import.meta.glob('./clips/*.mp4', { query: '?url', import: 'default', eager: true }) as Record<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
const META = meta as Record<string, { poke: number; crop: [number, number, number, number] }>;
|
||||
const META: Record<string, { poke: number; crop: number[] }> = meta;
|
||||
|
||||
const FADE_MS = 140;
|
||||
// The clips frame the figure with more air than the SVG, so they render at 1.6 times the requested size
|
||||
const CLIP_SCALE = 1.6;
|
||||
|
||||
function clipKey(scene: string, mood: string, sceneMood: string) {
|
||||
return mood === sceneMood ? scene : `${scene}--${mood}`;
|
||||
}
|
||||
|
||||
export function findClip(scene: string, mood: string, sceneMood: string): string | null {
|
||||
const key = clipKey(scene, mood, sceneMood);
|
||||
return CLIPS[`./clips/${key}-light-loop.mp4`] ? key : null;
|
||||
}
|
||||
|
||||
let webgl: boolean | null = null;
|
||||
function hasWebgl(): boolean {
|
||||
if (webgl === null) {
|
||||
try {
|
||||
webgl = !!document.createElement('canvas').getContext('webgl');
|
||||
} catch {
|
||||
webgl = false;
|
||||
}
|
||||
}
|
||||
return webgl;
|
||||
}
|
||||
|
||||
/** Opt-out for comparing against the SVG: localStorage `trek-mascot-3d` = `0`. */
|
||||
export function mascot3dEnabled(): boolean {
|
||||
try {
|
||||
if (localStorage.getItem('trek-mascot-3d') === '0') return false;
|
||||
} catch {
|
||||
/* storage blocked: keep the default */
|
||||
}
|
||||
if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return false;
|
||||
return hasWebgl();
|
||||
}
|
||||
|
||||
function useIsDark() {
|
||||
const read = () => document.documentElement.classList.contains('dark');
|
||||
const [dark, setDark] = useState(read);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/** Clip lookup and the 3D opt-out, kept apart from the component so Fast Refresh keeps working. */
|
||||
export const CLIPS = import.meta.glob('./clips/*.mp4', { query: '?url', import: 'default', eager: true }) as Record<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
|
||||
function clipKey(scene: string, mood: string, sceneMood: string) {
|
||||
return mood === sceneMood ? scene : `${scene}--${mood}`;
|
||||
}
|
||||
|
||||
export function findClip(scene: string, mood: string, sceneMood: string): string | null {
|
||||
const key = clipKey(scene, mood, sceneMood);
|
||||
return CLIPS[`./clips/${key}-light-loop.mp4`] ? key : null;
|
||||
}
|
||||
|
||||
let webgl: boolean | null = null;
|
||||
function hasWebgl(): boolean {
|
||||
if (webgl === null) {
|
||||
try {
|
||||
webgl = !!document.createElement('canvas').getContext('webgl');
|
||||
} catch {
|
||||
webgl = false;
|
||||
}
|
||||
}
|
||||
return webgl;
|
||||
}
|
||||
|
||||
/** Opt-out for comparing against the SVG: localStorage `trek-mascot-3d` = `0`. */
|
||||
export function mascot3dEnabled(): boolean {
|
||||
try {
|
||||
if (localStorage.getItem('trek-mascot-3d') === '0') return false;
|
||||
} catch {
|
||||
/* storage blocked: keep the default */
|
||||
}
|
||||
if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return false;
|
||||
return hasWebgl();
|
||||
}
|
||||
Reference in New Issue
Block a user