diff --git a/app/globals.css b/app/globals.css index d7d9bb1..f20ed62 100644 --- a/app/globals.css +++ b/app/globals.css @@ -126,3 +126,21 @@ body { @apply bg-background text-foreground; } } + +@keyframes grid { + 0% { transform: translateY(-50%); } + 100% { transform: translateY(0); } +} +.animate-grid { + animation: grid 15s linear infinite; +} + + +@keyframes shine { + 0% { transform: translateX(-150%) skewX(-12deg); } + 100% { transform: translateX(150%) skewX(-12deg); } +} +.animate-shine { + animation: shine 3s infinite linear; +} + diff --git a/app/pages/[slug]/page.tsx b/app/pages/[slug]/page.tsx index afce002..613a573 100644 --- a/app/pages/[slug]/page.tsx +++ b/app/pages/[slug]/page.tsx @@ -2,6 +2,7 @@ import { getPageBySlug, getAllPages } from '@/lib/pages'; import ReactMarkdown from 'react-markdown'; import { notFound } from 'next/navigation'; import { LandingFooter } from "@/features/landing-page/components/footer"; +import { TextReveal } from '@/components/framer/text-reveal'; // This is required for static site generation with dynamic routes export async function generateStaticParams() { @@ -23,12 +24,12 @@ export default async function Page({ params }: { params: Promise<{ slug: string return ( <> -
-

- {page.title} -

+
+
+ +
-
+
{page.content}
diff --git a/app/pages/layout.tsx b/app/pages/layout.tsx index d3f7dee..2f1ddd5 100644 --- a/app/pages/layout.tsx +++ b/app/pages/layout.tsx @@ -3,6 +3,12 @@ import Link from 'next/link'; import { LandingFooter } from "@/features/landing-page/components/footer"; import { SidebarNav } from "@/components/sidebar-nav"; import { ScrollArea } from "@/components/ui/scroll-area"; +import RetroGrid from "@/components/framer/retro-grid"; +import DecryptedText from '@/components/framer/decrypted-text'; +import { Button } from "@/components/ui/button"; +import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; +import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; +import { Menu } from "lucide-react"; export default function PagesLayout({ children, @@ -23,26 +29,72 @@ export default function PagesLayout({ })); return ( -
-
- -
-
- {children} -
-
- +
+
+ +
+ + {/* Mobile Header */} +
+ + + + + +
+

+ +

+ +
+ +
+
+
+
+
+
Docs
+
+ + {/* Desktop Resizable Layout */} +
+ + + +
+

+ +

+ +
+
+
+ + + +
+
+
+ {children} +
+
+ +
+
+
+
- + + {/* Mobile Content Fallback */} +
+
+ {children} +
+ +
); } + diff --git a/app/pages/page.tsx b/app/pages/page.tsx index b68c21f..ee4aec8 100644 --- a/app/pages/page.tsx +++ b/app/pages/page.tsx @@ -1,6 +1,7 @@ import { getAllPages } from '@/lib/pages'; import Link from 'next/link'; -import { LandingFooter } from "@/features/landing-page/components/footer"; +import { TextReveal } from "@/components/framer/text-reveal"; +import SpotlightCard from "@/components/framer/spotlight"; export default function PagesIndex() { const pages = getAllPages(); @@ -8,9 +9,9 @@ export default function PagesIndex() { return ( <>
-

- Documentation -

+
+ +

Welcome to the CodeVerse Hub documentation. Select a topic from the sidebar to get started.

@@ -20,12 +21,16 @@ export default function PagesIndex() { -

- {page.title || page.slug} -

- {/* You could extract an excerpt here if you wanted */} + +

+ {page.title || page.slug} +

+

+ Explore the {page.title || page.slug} section. +

+
))}
diff --git a/components/framer/decrypted-text.tsx b/components/framer/decrypted-text.tsx new file mode 100644 index 0000000..a5a9663 --- /dev/null +++ b/components/framer/decrypted-text.tsx @@ -0,0 +1,73 @@ +'use client'; + +import { useEffect, useState, useRef } from 'react'; +import { motion } from 'motion/react'; + +const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+"; + +interface DecryptedTextProps { + text: string; + speed?: number; + maxIterations?: number; + className?: string; + animateOnHover?: boolean; + revealDirection?: "start" | "end" | "center"; +} + +export default function DecryptedText({ + text, + speed = 50, + maxIterations = 20, + className, + animateOnHover = true, + revealDirection = "start" +}: DecryptedTextProps) { + const [displayText, setDisplayText] = useState(text); + const [isScrambling, setIsScrambling] = useState(false); + const intervalRef = useRef(null); + + const scramble = () => { + if (isScrambling) return; + setIsScrambling(true); + + let iteration = 0; + + clearInterval(intervalRef.current); + + intervalRef.current = setInterval(() => { + setDisplayText( + text + .split("") + .map((letter, index) => { + if (index < iteration) { + return text[index]; + } + return letters[Math.floor(Math.random() * letters.length)]; + }) + .join("") + ); + + if (iteration >= text.length) { + clearInterval(intervalRef.current); + setIsScrambling(false); + } + + iteration += 1 / 3; + }, speed); + }; + + useEffect(() => { + // Optional: Scramble on mount + // scramble(); + return () => clearInterval(intervalRef.current); + }, []); + + return ( + + {displayText} + + ); +} diff --git a/components/framer/magnet.tsx b/components/framer/magnet.tsx new file mode 100644 index 0000000..34bed22 --- /dev/null +++ b/components/framer/magnet.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { useRef, useState } from 'react'; +import { motion } from 'motion/react'; + +interface MagnetProps { + children: React.ReactNode; + magnetStrength?: number; + active?: boolean; +} + +export default function Magnet({ children, magnetStrength = 2, active = false }: MagnetProps) { + const [position, setPosition] = useState({ x: 0, y: 0 }); + const ref = useRef(null); + + const handleMouseMove = (e: React.MouseEvent) => { + const { clientX, clientY } = e; + const { height, width, left, top } = ref.current!.getBoundingClientRect(); + const middleX = clientX - (left + width / 2); + const middleY = clientY - (top + height / 2); + setPosition({ x: middleX, y: middleY }); + }; + + const reset = () => { + setPosition({ x: 0, y: 0 }); + }; + + const { x, y } = position; + return ( + + {children} + + ); +} diff --git a/components/framer/retro-grid.tsx b/components/framer/retro-grid.tsx new file mode 100644 index 0000000..4bf34d5 --- /dev/null +++ b/components/framer/retro-grid.tsx @@ -0,0 +1,37 @@ +import { cn } from "@/lib/utils"; + +export default function RetroGrid({ + className, + angle = 65, +}: { + className?: string; + angle?: number; +}) { + return ( +
+ {/* Grid */} +
+
+
+ + {/* Background Gradient */} +
+
+ ); +} diff --git a/components/framer/spotlight.tsx b/components/framer/spotlight.tsx new file mode 100644 index 0000000..e5ee6d8 --- /dev/null +++ b/components/framer/spotlight.tsx @@ -0,0 +1,74 @@ +'use client'; +import React, { useRef, useState,MouseEvent } from 'react'; +import { motion } from 'motion/react'; +import { cn } from '@/lib/utils'; + +interface SpotlightCardProps extends React.HTMLAttributes { + children: React.ReactNode; + className?: string; + spotlightColor?: string; +} + +export default function SpotlightCard({ + children, + className = "", + spotlightColor = "rgba(255, 255, 255, 0.25)", + ...props +}: SpotlightCardProps) { + const divRef = useRef(null); + const [isFocused, setIsFocused] = useState(false); + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [opacity, setOpacity] = useState(0); + + const handleMouseMove = (e: MouseEvent) => { + if (!divRef.current || isFocused) return; + + const div = divRef.current; + const rect = div.getBoundingClientRect(); + + setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top }); + }; + + const handleFocus = () => { + setIsFocused(true); + setOpacity(1); + }; + + const handleBlur = () => { + setIsFocused(false); + setOpacity(0); + }; + + const handleMouseEnter = () => { + setOpacity(1); + }; + + const handleMouseLeave = () => { + setOpacity(0); + }; + + return ( +
+
+ {children} +
+ ); +} diff --git a/components/framer/text-reveal.tsx b/components/framer/text-reveal.tsx new file mode 100644 index 0000000..8fed5cc --- /dev/null +++ b/components/framer/text-reveal.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { motion } from 'motion/react'; +import { cn } from '@/lib/utils'; + +interface TextRevealProps { + text: string; + className?: string; + delay?: number; +} + +export const TextReveal = ({ text, className, delay = 0 }: TextRevealProps) => { + const words = text.split(' '); + + const container = { + hidden: { opacity: 0 }, + visible: (i = 1) => ({ + opacity: 1, + transition: { staggerChildren: 0.12, delayChildren: 0.04 * i + delay }, + }), + }; + + const child = { + visible: { + opacity: 1, + y: 0, + filter: "blur(0px)", + transition: { + type: "spring" as const, + damping: 12, + stiffness: 100, + }, + }, + hidden: { + opacity: 0, + y: 20, + filter: "blur(10px)", + transition: { + type: "spring" as const, + damping: 12, + stiffness: 100, + }, + }, + }; + + return ( + + {words.map((word, index) => ( + + {word} + + ))} + + ); +}; diff --git a/components/sidebar-nav.tsx b/components/sidebar-nav.tsx index 0838b32..594b210 100644 --- a/components/sidebar-nav.tsx +++ b/components/sidebar-nav.tsx @@ -3,7 +3,8 @@ import { usePathname } from "next/navigation"; import Link from "next/link"; import { cn } from "@/lib/utils"; -import { buttonVariants } from "@/components/ui/button"; +import Magnet from "@/components/framer/magnet"; +import { motion } from "motion/react"; interface SidebarNavProps extends React.HTMLAttributes { items: { @@ -18,26 +19,49 @@ export function SidebarNav({ className, items, ...props }: SidebarNavProps) { return ( ); } + diff --git a/components/ui/resizable.tsx b/components/ui/resizable.tsx new file mode 100644 index 0000000..f4bc558 --- /dev/null +++ b/components/ui/resizable.tsx @@ -0,0 +1,45 @@ +"use client" + +import { GripVertical } from "lucide-react" +import * as ResizablePrimitive from "react-resizable-panels" + +import { cn } from "@/lib/utils" + +const ResizablePanelGroup = ({ + className, + ...props +}: React.ComponentProps) => ( + +) + +const ResizablePanel = ResizablePrimitive.Panel + +const ResizableHandle = ({ + withHandle, + className, + ...props +}: React.ComponentProps & { + withHandle?: boolean +}) => ( + div]:rotate-90", + className + )} + {...props} + > + {withHandle && ( +
+ +
+ )} +
+) + +export { ResizablePanelGroup, ResizablePanel, ResizableHandle } diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx new file mode 100644 index 0000000..6a74e34 --- /dev/null +++ b/components/ui/sheet.tsx @@ -0,0 +1,140 @@ +"use client" + +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = SheetPrimitive.Portal + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + + + Close + + {children} + + +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +}