feat(federation): GSAP-powered 3D orbital node map, fills viewport to bottom
- Add gsap 3.15 + design-system-aware motion module (src/utils/motion.ts): shared colour/duration/ease tokens mirrored from style.css, gsap.defaults, live prefers-reduced-motion check. - Replace the d3 force NetworkMap with NetworkMap3D.vue: peers on projected 3D orbital rings around the self node, cinematic intro (camera dolly + staggered fly-in + ring draw), idle rotation with drag-to-orbit inertia, depth-sorted painter's order, trust-colour palette, online/offline states, sonar pulse on self, tap-a-node opens the detail modal. - Map view now fills the dashboard panel to the bottom edge on desktop, mobile and companion: .dashboard-scroll-panel:has(.node-map-stage) turns the panel into a column (tab-bar/safe-area/audio-player aware padding) instead of leaving the old dead bottom margin. - Reduced motion: intro/idle skipped, scene renders static. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
7fbdabf136
commit
fa14f6ebaa
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Design-system-aware GSAP setup — the single place animation code pulls
|
||||
* timing, easing, and colour tokens from, so every GSAP-driven surface moves
|
||||
* (and is coloured) like the rest of the glass UI instead of inventing its
|
||||
* own physics per component.
|
||||
*
|
||||
* Usage: `import { gsap, motionTokens, prefersReducedMotion } from '@/utils/motion'`
|
||||
* — never `import gsap from 'gsap'` directly, or the shared defaults are lost.
|
||||
*/
|
||||
import { gsap } from 'gsap'
|
||||
|
||||
/** Colour tokens mirrored from style.css / tailwind.config.js. The UI is
|
||||
* dark-only (style.css pins `color-scheme: dark`), so these are constants,
|
||||
* not theme-dependent lookups. */
|
||||
export const motionTokens = {
|
||||
color: {
|
||||
/** Brand accent — the orange used for focus glows and highlights
|
||||
* (tailwind orange-400, e.g. `.glass-button:focus-visible`). */
|
||||
accent: '#fb923c',
|
||||
/** Trust-level palette — matches NodeList / trust badges. */
|
||||
trusted: '#4ade80',
|
||||
observer: '#fb923c',
|
||||
untrusted: '#ef4444',
|
||||
neutral: '#9ca3af',
|
||||
/** Text/line opacities on the dark glass ground. */
|
||||
textPrimary: 'rgba(255, 255, 255, 0.95)',
|
||||
textSecondary: 'rgba(255, 255, 255, 0.7)',
|
||||
textFaint: 'rgba(255, 255, 255, 0.45)',
|
||||
line: 'rgba(255, 255, 255, 0.18)',
|
||||
lineFaint: 'rgba(255, 255, 255, 0.08)',
|
||||
glassDark: 'rgba(0, 0, 0, 0.35)',
|
||||
glassDarker: 'rgba(0, 0, 0, 0.6)',
|
||||
},
|
||||
/** Durations (seconds) — align with the CSS transitions already shipped
|
||||
* (modal 0.3s, press feedback 0.1s). */
|
||||
duration: {
|
||||
fast: 0.18,
|
||||
base: 0.3,
|
||||
slow: 0.6,
|
||||
/** Scene-setting intros (map fly-in, hero moments). */
|
||||
cinematic: 1.4,
|
||||
},
|
||||
ease: {
|
||||
/** Default UI ease — matches the snappy glass feel. */
|
||||
out: 'power3.out',
|
||||
inOut: 'power2.inOut',
|
||||
/** Playful overshoot for elements "arriving" (node pop-ins). */
|
||||
arrive: 'back.out(1.6)',
|
||||
/** Springy attention pulse. */
|
||||
pulse: 'sine.inOut',
|
||||
},
|
||||
} as const
|
||||
|
||||
// Shared defaults: any tween that doesn't say otherwise moves like the rest
|
||||
// of the design system.
|
||||
gsap.defaults({
|
||||
ease: motionTokens.ease.out,
|
||||
duration: motionTokens.duration.base,
|
||||
})
|
||||
|
||||
/** Live reduced-motion check. Query at animation-build time (not module
|
||||
* scope) so OS-level toggles apply without a reload. Callers should skip
|
||||
* intros / idle loops and jump to end state when this is true. */
|
||||
export function prefersReducedMotion(): boolean {
|
||||
return typeof window !== 'undefined'
|
||||
&& window.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true
|
||||
}
|
||||
|
||||
export { gsap }
|
||||
Reference in New Issue
Block a user