diff --git a/apps/web/app/new/page.tsx b/apps/web/app/new/page.tsx index d03b9dc9..ee953abb 100644 --- a/apps/web/app/new/page.tsx +++ b/apps/web/app/new/page.tsx @@ -9,10 +9,10 @@ export default function NewPage() { return (
-
+
-
+
diff --git a/apps/web/components/chat-loader.tsx b/apps/web/components/chat-loader.tsx new file mode 100644 index 00000000..788ec88d --- /dev/null +++ b/apps/web/components/chat-loader.tsx @@ -0,0 +1,227 @@ +'use client'; + +import { + motion, + useMotionValue, + useTransform, + animate, + useReducedMotion, +} from 'motion/react'; + +import { useEffect, useMemo } from 'react'; + +import * as flubber from 'flubber'; + +type ChatLoaderProps = { + size?: number; + colorClassName?: string; + label?: string; + className?: string; +}; + +const LEFT_PATHS = [ + 'M12.6984 9.02793V3.52344H10.6523V9.49591C10.6523 10.1302 10.9028 10.7395 11.3479 11.1883L16.5188 16.4032L17.9655 14.9441L14.1463 11.0926H19.0324V9.02914L12.6984 9.02793Z', // 0 + 'M12.6984 9.02793V3.52344H10.6523V9.49591C10.6523 10.1302 10.9028 10.7395 11.3479 11.1883L16.5188 16.4032L17.9655 14.9441L14.1463 11.0926H14.149L12.699 9.02914L12.6984 9.02793Z', // 1 + 'M12.6985 9.02793V3.52344H10.6524V9.49591C10.6524 10.1302 10.6516 10.7381 10.6532 11.0926L10.6524 16.4075H12.6985L12.6991 11.0926V9.02914L12.6985 9.02793Z', // 2 + 'M14.5653 7.14453V7.1485H10.6528V8.0394C10.6528 9.25237 10.6512 10.4147 10.6542 11.0925L10.6528 11.0887H14.5653L14.5664 11.0925V7.14684L14.5653 7.14453Z', // 3 + 'M19.0304 8.51562V8.51963H15.0776V9.41971C15.0776 10.6452 15.076 11.8194 15.0791 12.5043L15.0776 12.5004H19.0304L19.0315 12.5043V8.51796L19.0304 8.51562Z', // 4 + 'M19.0304 8.51562V8.51963H15.0776V9.41971C15.0776 10.6452 15.076 11.8194 15.0791 12.5043L15.0776 12.5004H19.0304L19.0315 12.5043V8.51796L19.0304 8.51562Z', // 5 + 'M19.0304 8.51562V8.51963H15.0776V9.41971C15.0776 10.6452 15.076 11.8194 15.0791 12.5043L15.0776 12.5004H19.0304L19.0315 12.5043V8.51796L19.0304 8.51562Z', // 6 + 'M19.0304 6.51562V6.51963H15.0776V7.41971C15.0776 8.64517 15.076 9.81944 15.0791 10.5043L15.0776 10.5004H19.0304L19.0315 10.5043V6.51796L19.0304 6.51562Z', // 7 + 'M19.0304 8.51562V8.51963H15.0776V9.41971C15.0776 10.6452 15.076 11.8194 15.0791 12.5043L15.0776 12.5004H19.0304L19.0315 12.5043V8.51796L19.0304 8.51562Z', // 8 + 'M19.0304 8.51562V8.51963H15.0776V9.41971C15.0776 10.6452 15.076 11.8194 15.0791 12.5043L15.0776 12.5004H19.0304L19.0315 12.5043V8.51796L19.0304 8.51562Z', // 9 + 'M19.0304 6.51562V6.51963H15.0776V7.41971C15.0776 8.64517 15.076 9.81944 15.0791 10.5043L15.0776 10.5004H19.0304L19.0315 10.5043V6.51796L19.0304 6.51562Z', // 10 + 'M19.0304 8.51562V8.51963H15.0776V9.41971C15.0776 10.6452 15.076 11.8194 15.0791 12.5043L15.0776 12.5004H19.0304L19.0315 12.5043V8.51796L19.0304 8.51562Z', // 11 + 'M19.0304 8.51562V8.51963H15.0776V9.41971C15.0776 10.6452 15.076 11.8194 15.0791 12.5043L15.0776 12.5004H19.0304L19.0315 12.5043V8.51796L19.0304 8.51562Z', // 12 + 'M19.0304 6.51562V6.51963H15.0776V7.41971C15.0776 8.64517 15.076 9.81944 15.0791 10.5043L15.0776 10.5004H19.0304L19.0315 10.5043V6.51796L19.0304 6.51562Z', // 13 + 'M19.0304 8.51562V8.51963H15.0776V9.41971C15.0776 10.6452 15.076 11.8194 15.0791 12.5043L15.0776 12.5004H19.0304L19.0315 12.5043V8.51796L19.0304 8.51562Z', // 14 + 'M14.5653 7.14453V7.1485H10.6528V8.0394C10.6528 9.25237 10.6512 10.4147 10.6542 11.0925L10.6528 11.0887H14.5653L14.5664 11.0925V7.14684L14.5653 7.14453Z', // 15 + 'M12.6985 9.02793V3.52344H10.6524V9.49591C10.6524 10.1302 10.6516 10.7381 10.6532 11.0926L10.6524 16.4075H12.6985L12.6991 11.0926V9.02914L12.6985 9.02793Z', // 16 + 'M12.6984 9.02793V3.52344H10.6523V9.49591C10.6523 10.1302 10.9028 10.7395 11.3479 11.1883L16.5188 16.4032L17.9655 14.9441L14.1463 11.0926H14.149L12.699 9.02914L12.6984 9.02793Z', // 17 + 'M12.6984 9.02793V3.52344H10.6523V9.49591C10.6523 10.1302 10.9028 10.7395 11.3479 11.1883L16.5188 16.4032L17.9655 14.9441L14.1463 11.0926H19.0324V9.02914L12.6984 9.02793Z', // 18 +]; + +const MIDDLE_PATHS = [ + 'M6.60156 9.46875L6.60171 11.5326L6.60231 11.5286H8.64841V11.0646C8.64841 10.4302 8.64841 10.1053 8.64841 9.46911L6.60156 9.46875Z', // 0 + 'M6.60156 9.46875L6.60171 11.5326L6.60231 11.5286H8.64841V11.0646C8.64841 10.4302 8.64841 10.1053 8.64841 9.46911L6.60156 9.46875Z', // 1 + 'M6.60156 9.46875L6.60171 11.5326L6.60231 11.5286H8.64841V11.0646C8.64841 10.4302 8.64841 10.1053 8.64841 9.46911L6.60156 9.46875Z', // 2 + 'M6.60156 9.46875L6.60171 11.5326L6.60231 11.5286H8.64841V11.0646C8.64841 10.4302 8.64841 10.1053 8.64841 9.46911L6.60156 9.46875Z', // 3 + 'M1.96875 8.50391L1.96904 12.4909L1.9702 12.4833H5.92302V11.5868C5.92302 10.3613 5.92302 9.73358 5.92302 8.50459L1.96875 8.50391Z', // 4 + 'M1.96875 6.50391L1.96904 10.4909L1.9702 10.4833H5.92302V9.58685C5.92302 8.3613 5.92302 7.73358 5.92302 6.50459L1.96875 6.50391Z', // 5 + 'M1.96875 8.50391L1.96904 12.4909L1.9702 12.4833H5.92302V11.5868C5.92302 10.3613 5.92302 9.73358 5.92302 8.50459L1.96875 8.50391Z', // 6 + 'M1.96875 8.50391L1.96904 12.4909L1.9702 12.4833H5.92302V11.5868C5.92302 10.3613 5.92302 9.73358 5.92302 8.50459L1.96875 8.50391Z', // 7 + 'M1.96875 6.50391L1.96904 10.4909L1.9702 10.4833H5.92302V9.58685C5.92302 8.3613 5.92302 7.73358 5.92302 6.50459L1.96875 6.50391Z', // 8 + 'M1.96875 8.50391L1.96904 12.4909L1.9702 12.4833H5.92302V11.5868C5.92302 10.3613 5.92302 9.73358 5.92302 8.50459L1.96875 8.50391Z', // 9 + 'M1.96875 8.50391L1.96904 12.4909L1.9702 12.4833H5.92302V11.5868C5.92302 10.3613 5.92302 9.73358 5.92302 8.50459L1.96875 8.50391Z', // 10 + 'M1.96875 6.50391L1.96904 10.4909L1.9702 10.4833H5.92302V9.58685C5.92302 8.3613 5.92302 7.73358 5.92302 6.50459L1.96875 6.50391Z', // 11 + 'M1.96875 8.50391L1.96904 12.4909L1.9702 12.4833H5.92302V11.5868C5.92302 10.3613 5.92302 9.73358 5.92302 8.50459L1.96875 8.50391Z', // 12 + 'M1.96875 8.50391L1.96904 12.4909L1.9702 12.4833H5.92302V11.5868C5.92302 10.3613 5.92302 9.73358 5.92302 8.50459L1.96875 8.50391Z', // 13 + 'M1.96875 8.50391L1.96904 12.4909L1.9702 12.4833H5.92302V11.5868C5.92302 10.3613 5.92302 9.73358 5.92302 8.50459L1.96875 8.50391Z', // 14 + 'M1.96875 8.50391L1.96904 12.4909L1.9702 12.4833H5.92302V11.5868C5.92302 10.3613 5.92302 9.73358 5.92302 8.50459L1.96875 8.50391Z', // 15 + 'M6.60156 9.46875L6.60171 11.5326L6.60231 11.5286H8.64841V11.0646C8.64841 10.4302 8.64841 10.1053 8.64841 9.46911L6.60156 9.46875Z', // 16 + 'M6.60156 9.46875L6.60171 11.5326L6.60231 11.5286H8.64841V11.0646C8.64841 10.4302 8.64841 10.1053 8.64841 9.46911L6.60156 9.46875Z', // 17 + 'M6.60156 9.46875L6.60171 11.5326L6.60231 11.5286H8.64841V11.0646C8.64841 10.4302 8.64841 10.1053 8.64841 9.46911L6.60156 9.46875Z', // 18 +]; + +const RIGHT_PATHS = [ + 'M3.03472 6.05861L6.8539 9.91021H1.96777V11.9737H8.3006V17.4781H10.3467V11.5057C10.3467 10.8713 10.0963 10.2621 9.65119 9.81327L4.48145 4.59961L3.03472 6.05861Z', // 0 + 'M3.03516 6.05861L6.85434 9.91021H6.85044L8.30044 11.9737L8.30104 17.4781H10.3471V11.5057C10.3471 10.8713 10.0967 10.2621 9.65162 9.81327L4.48188 4.59961L3.03516 6.05861Z', // 1 + 'M8.30024 4.58789L8.2998 9.91036L8.30039 11.9738L8.30099 17.4783H10.3471V11.5058C10.3471 10.8714 10.3471 10.5465 10.3471 9.91036V4.58789H8.30024Z', // 2 + 'M6.42383 9.9082L6.42412 13.8633L6.42527 13.8557H10.3464V12.9664C10.3464 11.7507 10.3464 11.128 10.3464 9.90888L6.42383 9.9082Z', // 3 + 'M8.52051 8.50391L8.5208 12.4909L8.52196 12.4833H12.4748V11.5868C12.4748 10.3613 12.4748 9.73358 12.4748 8.50459L8.52051 8.50391Z', // 4 + 'M8.52051 8.50391L8.5208 12.4909L8.52196 12.4833H12.4748V11.5868C12.4748 10.3613 12.4748 9.73358 12.4748 8.50459L8.52051 8.50391Z', // 5 + 'M8.52051 6.50391L8.5208 10.4909L8.52196 10.4833H12.4748V9.58685C12.4748 8.3613 12.4748 7.73358 12.4748 6.50459L8.52051 6.50391Z', // 6 + 'M8.52051 8.50391L8.5208 12.4909L8.52196 12.4833H12.4748V11.5868C12.4748 10.3613 12.4748 9.73358 12.4748 8.50459L8.52051 8.50391Z', // 7 + 'M8.52051 8.50391L8.5208 12.4909L8.52196 12.4833H12.4748V11.5868C12.4748 10.3613 12.4748 9.73358 12.4748 8.50459L8.52051 8.50391Z', // 8 + 'M8.52051 6.50391L8.5208 10.4909L8.52196 10.4833H12.4748V9.58685C12.4748 8.3613 12.4748 7.73358 12.4748 6.50459L8.52051 6.50391Z', // 9 + 'M8.52051 8.50391L8.5208 12.4909L8.52196 12.4833H12.4748V11.5868C12.4748 10.3613 12.4748 9.73358 12.4748 8.50459L8.52051 8.50391Z', // 10 + 'M8.52051 8.50391L8.5208 12.4909L8.52196 12.4833H12.4748V11.5868C12.4748 10.3613 12.4748 9.73358 12.4748 8.50459L8.52051 8.50391Z', // 11 + 'M8.52051 6.50391L8.5208 10.4909L8.52196 10.4833H12.4748V9.58685C12.4748 8.3613 12.4748 7.73358 12.4748 6.50459L8.52051 6.50391Z', // 12 + 'M8.52051 8.50391L8.5208 12.4909L8.52196 12.4833H12.4748V11.5868C12.4748 10.3613 12.4748 9.73358 12.4748 8.50459L8.52051 8.50391Z', // 13 + 'M8.52051 8.50391L8.5208 12.4909L8.52196 12.4833H12.4748V11.5868C12.4748 10.3613 12.4748 9.73358 12.4748 8.50459L8.52051 8.50391Z', // 14 + 'M6.42383 9.9082L6.42412 13.8633L6.42527 13.8557H10.3464V12.9664C10.3464 11.7507 10.3464 11.128 10.3464 9.90888L6.42383 9.9082Z', // 15 + 'M8.30024 4.58789L8.2998 9.91036L8.30039 11.9738L8.30099 17.4783H10.3471V11.5058C10.3471 10.8714 10.3471 10.5465 10.3471 9.91036V4.58789H8.30024Z', // 16 + 'M3.03516 6.05861L6.85434 9.91021H6.85044L8.30044 11.9737L8.30104 17.4781H10.3471V11.5057C10.3471 10.8713 10.0967 10.2621 9.65162 9.81327L4.48188 4.59961L3.03516 6.05861Z', // 17 + 'M3.03472 6.05861L6.8539 9.91021H1.96777V11.9737H8.3006V17.4781H10.3467V11.5057C10.3467 10.8713 10.0963 10.2621 9.65119 9.81327L4.48145 4.59961L3.03472 6.05861Z', // 18 +]; + +export function ChatLoader({ + size = 80, + colorClassName = 'text-white', + label = '', + className = '', +}: ChatLoaderProps) { + const prefersReducedMotion = useReducedMotion(); + + const t = useMotionValue(0); + + const loopDuration = 3.6; // full cycle + + const makeMultiInterp = (paths: string[]) => { + if (!paths || paths.length === 0) { + return (_t: number) => ''; + } + + if (paths.length === 1) { + const only = paths[0]; + return (_t: number) => only; + } + + const options: { maxSegmentLength?: number } = { maxSegmentLength: 0.5 }; + + const interpolateFn = (flubber as any).interpolate as ( + from: string, + to: string, + options?: { maxSegmentLength?: number } + ) => (t: number) => string; + + const segmentInterpolators: Array<(t: number) => string> = []; + + for (let i = 0; i < paths.length - 1; i++) { + segmentInterpolators.push(interpolateFn(paths[i], paths[i + 1], options)); + } + + const segmentCount = segmentInterpolators.length; + + return (t: number) => { + if (t <= 0) return paths[0] || ''; + if (t >= 1) return paths[paths.length - 1] || ''; + + const scaled = t * segmentCount; + const segIndex = Math.min(Math.floor(scaled), segmentCount - 1); + const localT = scaled - segIndex; + + return segmentInterpolators[segIndex](localT); + }; + }; + + const leftInterp = useMemo( + () => (LEFT_PATHS.length ? makeMultiInterp(LEFT_PATHS) : null), + [] + ); + + const middleInterp = useMemo( + () => (MIDDLE_PATHS.length ? makeMultiInterp(MIDDLE_PATHS) : null), + [] + ); + + const rightInterp = useMemo( + () => (RIGHT_PATHS.length ? makeMultiInterp(RIGHT_PATHS) : null), + [] + ); + + // Turn scalar t into d strings + const leftD = useTransform(t, v => + leftInterp ? leftInterp(v) : LEFT_PATHS[0] || '' + ); + + const middleD = useTransform(t, v => + middleInterp ? middleInterp(v) : MIDDLE_PATHS[0] || '' + ); + + const rightD = useTransform(t, v => + rightInterp ? rightInterp(v) : RIGHT_PATHS[0] || '' + ); + + const middleOpacity = useTransform(t, v => { + if (v < 0.2) return 0; + if (v < 0.3) return (v - 0.2) / 0.1; // fade in + if (v < 0.8) return 1; + if (v < 0.9) return 1 - (v - 0.8) / 0.1; // fade out + return 0; + }); + + useEffect(() => { + if (prefersReducedMotion) { + t.set(0); + return; + } + + const controls = animate(t, [0, 1], { + duration: loopDuration, + ease: 'linear', + repeat: Infinity, + repeatType: 'loop', + repeatDelay: 0.4, // ⬅️ wait 2 seconds at the end before restarting + }); + + return () => controls.stop(); + }, [t, prefersReducedMotion, loopDuration]); + + return ( +
+ + {leftInterp && } + {rightInterp && } + {middleInterp && ( + + )} + + + {label && ( + + {label} + + )} +
+ ); +} + diff --git a/apps/web/components/new/memories-grid.tsx b/apps/web/components/new/memories-grid.tsx index 74116e4f..19b11feb 100644 --- a/apps/web/components/new/memories-grid.tsx +++ b/apps/web/components/new/memories-grid.tsx @@ -7,8 +7,8 @@ import { useInfiniteQuery } from "@tanstack/react-query" import { useCallback, memo, useMemo } from "react" import type { z } from "zod" import { Masonry, useInfiniteLoader } from "masonic" -import { Sparkles } from "lucide-react" import { dmSansClassName } from "@/utils/fonts" +import { SuperLoader } from "@/components/superloader" import { cn } from "@lib/utils" import { Button } from "@ui/components/button" import { useProject } from "@/stores" @@ -158,10 +158,7 @@ export function MemoriesGrid() {
) : isPending ? (
-
- - Loading memory list... -
+
) : documents.length === 0 && !isPending ? (
@@ -186,10 +183,7 @@ export function MemoriesGrid() { {isLoadingMore && (
-
- - Loading more memories... -
+
)}
diff --git a/apps/web/components/superloader.tsx b/apps/web/components/superloader.tsx new file mode 100644 index 00000000..55998d1a --- /dev/null +++ b/apps/web/components/superloader.tsx @@ -0,0 +1,98 @@ +'use client'; + +import { motion, useReducedMotion, Variants } from 'motion/react'; + +type NovaPathLoaderProps = { + size?: number; // px + colorClassName?: string; + label?: string; + className?: string; +}; + +// full SVG path data from nova-2d-anim.svg +const PATH_RIGHT = + 'M3.03472 6.05861L6.8539 9.91021H1.96777V11.9737H8.3006V17.4781H10.3467V11.5057C10.3467 10.8713 10.0963 10.2621 9.65119 9.81327L4.48145 4.59961L3.03472 6.05861Z'; + +const PATH_LEFT = + 'M12.6994 9.02793V3.52344H10.6533V9.49591C10.6533 10.1302 10.9037 10.7395 11.3488 11.1883L16.5197 16.4032L17.9665 14.9441L14.1473 11.0926H19.0334V9.02914L12.6994 9.02793Z'; + +// animation for stroke draw +const strokeVariants: Variants = { + hidden: { pathLength: 0, opacity: 0.2 }, + visible: (i: number) => ({ + pathLength: [0, 1], + opacity: [0.2, 1], + transition: { + duration: 0.9, + repeat: Infinity, + repeatType: 'reverse', + ease: 'easeInOut', + delay: i * 0.18, + }, + }), + static: { pathLength: 1, opacity: 0.7 }, +}; + +export function SuperLoader({ + size = 42, + colorClassName = 'text-sky-400', + label = 'Loading...', + className = '', +}: NovaPathLoaderProps) { + const prefersReducedMotion = useReducedMotion(); + + const animateVariant = prefersReducedMotion ? 'static' : 'visible'; + + return ( +
+ + {/* Right path */} + + + {/* Left path */} + + + + + {label} + +
+ ); +} + diff --git a/apps/web/package.json b/apps/web/package.json index 3590aba4..dc4a5772 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -58,6 +58,7 @@ "dotenv": "^16.6.0", "embla-carousel-autoplay": "^8.6.0", "embla-carousel-react": "^8.6.0", + "flubber": "^0.4.2", "framer-motion": "^12.23.12", "idb-keyval": "^6.2.2", "is-hotkey": "^0.2.0", diff --git a/packages/ui/pages/login.tsx b/packages/ui/pages/login.tsx index 8b9eeff1..971fb746 100644 --- a/packages/ui/pages/login.tsx +++ b/packages/ui/pages/login.tsx @@ -16,6 +16,7 @@ import { useState, useEffect } from "react"; import { motion } from "framer-motion"; import { dmSansClassName } from "../../../apps/web/utils/fonts" import { cn } from "@lib/utils"; +import { Logo } from "@ui/assets/Logo"; function AnimatedGradientBackground() { return ( @@ -419,17 +420,17 @@ export function LoginPage() {