- Inbound pending requests render as blinking yellow point-globes on an orbit outside the peers, dotted-linked to self, with their own dashed guide ring and a Request legend chip. - Tapping one opens a black-glass popover (name, request message, Accept/Reject) centred over the scene; tap-away or ✕ dismisses. - Reject: the node swells and pops out of existence, taking its link. - Accept: green burst ring, the point cloud/link morph to the trusted colour and the globe glides inward onto the peer orbit, then the data refresh replaces it with the real peer node. - Wired to the same approve/reject RPCs as the pending panel; new motionTokens.color.pending (#facc15) token. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
72 lines
2.6 KiB
TypeScript
72 lines
2.6 KiB
TypeScript
/**
|
|
* 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',
|
|
/** Pending/attention — inbound peer requests awaiting a decision. */
|
|
pending: '#facc15',
|
|
/** 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 }
|