diff --git a/package-lock.json b/package-lock.json
index 0741d21..3b03c64 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,7 +14,7 @@
"gray-matter": "^4.0.3",
"gsap": "^3.14.2",
"lucide-react": "^0.563.0",
- "motion": "^12.33.0",
+ "motion": "^12.41.0",
"next": "16.1.6",
"ogl": "^1.0.11",
"radix-ui": "^1.4.3",
@@ -6960,13 +6960,13 @@
}
},
"node_modules/framer-motion": {
- "version": "12.33.0",
- "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.33.0.tgz",
- "integrity": "sha512-ca8d+rRPcDP5iIF+MoT3WNc0KHJMjIyFAbtVLvM9eA7joGSpeqDfiNH/kCs1t4CHi04njYvWyj0jS4QlEK/rJQ==",
+ "version": "12.41.0",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.41.0.tgz",
+ "integrity": "sha512-OHAMNiCEON1RDBlRGuulsN5AD8ptMjvk5QWfFmYmBLPZ3zFGIJe60kQucQQf4cez1OzQmjYBWDY+dYfISkUdqg==",
"license": "MIT",
"dependencies": {
- "motion-dom": "^12.33.0",
- "motion-utils": "^12.29.2",
+ "motion-dom": "^12.41.0",
+ "motion-utils": "^12.39.0",
"tslib": "^2.4.0"
},
"peerDependencies": {
@@ -9544,12 +9544,12 @@
}
},
"node_modules/motion": {
- "version": "12.33.0",
- "resolved": "https://registry.npmjs.org/motion/-/motion-12.33.0.tgz",
- "integrity": "sha512-TcND7PijsrTeIA9SRVUB8TOJQ+6mJnJ5K4a9oAJZvyI0Zy47Gq5oofU+VkTxbLcvDoKXnHspQcII2mnk3TbFsQ==",
+ "version": "12.41.0",
+ "resolved": "https://registry.npmjs.org/motion/-/motion-12.41.0.tgz",
+ "integrity": "sha512-avEDKE22rFPJqDr3Ttk7gMQpeaOmNik60NoJ5T0tj+RBCNvz21D3ArY3l4uitoeQ7eIpDqueWaO3pPYFv8JOVA==",
"license": "MIT",
"dependencies": {
- "framer-motion": "^12.33.0",
+ "framer-motion": "^12.41.0",
"tslib": "^2.4.0"
},
"peerDependencies": {
@@ -9570,18 +9570,18 @@
}
},
"node_modules/motion-dom": {
- "version": "12.33.0",
- "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.33.0.tgz",
- "integrity": "sha512-XRPebVypsl0UM+7v0Hr8o9UAj0S2djsQWRdHBd5iVouVpMrQqAI0C/rDAT3QaYnXnHuC5hMcwDHCboNeyYjPoQ==",
+ "version": "12.41.0",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.41.0.tgz",
+ "integrity": "sha512-Lk3J39fOGg6xNr1KRZsN6usDyBf8aP7MEbUPez1VCughHt79OrP7VGqNrPyFL0riaT7WS8t9DRw1M3BHtM/xKw==",
"license": "MIT",
"dependencies": {
- "motion-utils": "^12.29.2"
+ "motion-utils": "^12.39.0"
}
},
"node_modules/motion-utils": {
- "version": "12.29.2",
- "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz",
- "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==",
+ "version": "12.39.0",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
+ "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==",
"license": "MIT"
},
"node_modules/ms": {
diff --git a/package.json b/package.json
index 48ab1f6..ac741ca 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
"gray-matter": "^4.0.3",
"gsap": "^3.14.2",
"lucide-react": "^0.563.0",
- "motion": "^12.33.0",
+ "motion": "^12.41.0",
"next": "16.1.6",
"ogl": "^1.0.11",
"radix-ui": "^1.4.3",
@@ -26,13 +26,13 @@
"tailwind-merge": "^3.4.0"
},
"devDependencies": {
+ "@netlify/plugin-nextjs": "^5.0.0",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.1.6",
- "@netlify/plugin-nextjs": "^5.0.0",
"shadcn": "^3.8.4",
"tailwindcss": "^4",
"tw-animate-css": "^1.4.0",
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 4e882be..54fe6bc 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -13,6 +13,7 @@ import Features from "@/components/Features";
import TechMarquee from "@/components/TechMarquee";
import TerminalWindow from "@/components/TerminalWindow";
import { ArrowRight, Star, Zap } from "lucide-react";
+import ShinyText from "@/components/ShinyText";
export default function Home() {
return (
@@ -72,9 +73,22 @@ export default function Home() {
Build faster with a
-
- real developer community
+
+
+
CodeVerse Hub is your always-on Discord for code reviews, open
source teams, mentorship, and instant help — designed for
@@ -94,11 +108,11 @@ export default function Home() {
}}
>
-
+
Join the Discord
diff --git a/src/components/ContactSection.tsx b/src/components/ContactSection.tsx
index 8f20c07..810b083 100644
--- a/src/components/ContactSection.tsx
+++ b/src/components/ContactSection.tsx
@@ -3,240 +3,253 @@
import { useState } from "react";
import { Mail, ArrowRight, Github, Send, CheckCircle } from "lucide-react";
import GradientText from "./GradientText";
+import ShinyText from "./ShinyText";
const contactMethods = [
- {
- icon: Mail,
- label: "Email Us",
- value: "contact@thecodeversehub.tech",
- href: "mailto:contact@thecodeversehub.tech",
- },
- {
- icon: () => (
-
-
-
- ),
- label: "Join Community",
- value: "The CodeVerse Hub Discord",
- href: "https://discord.gg/3xKFvKhuGR",
- },
- {
- icon: Github,
- label: "Follow Development",
- value: "@TheCodeVerseHub",
- href: "https://github.com/TheCodeVerseHub/",
- },
- {
- icon: Github,
- label: "Contribute to CVH Web",
- value: "Open-source website on GitHub",
- href: "https://github.com/TheCodeVerseHub/cvh_web",
- },
+ {
+ icon: Mail,
+ label: "Email Us",
+ value: "contact@thecodeversehub.tech",
+ href: "mailto:contact@thecodeversehub.tech",
+ },
+ {
+ icon: () => (
+
+
+
+ ),
+ label: "Join Community",
+ value: "The CodeVerse Hub Discord",
+ href: "https://discord.gg/3xKFvKhuGR",
+ },
+ {
+ icon: Github,
+ label: "Follow Development",
+ value: "@TheCodeVerseHub",
+ href: "https://github.com/TheCodeVerseHub/",
+ },
+ {
+ icon: Github,
+ label: "Contribute to CVH Web",
+ value: "Open-source website on GitHub",
+ href: "https://github.com/TheCodeVerseHub/cvh_web",
+ },
];
export default function ContactSection() {
- const [submitted, setSubmitted] = useState(false);
- const [isSubmitting, setIsSubmitting] = useState(false);
+ const [submitted, setSubmitted] = useState(false);
+ const [isSubmitting, setIsSubmitting] = useState(false);
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- setIsSubmitting(true);
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setIsSubmitting(true);
- const form = e.currentTarget;
- const formData = new FormData(form);
+ const form = e.currentTarget;
+ const formData = new FormData(form);
- try {
- await fetch("https://formspree.io/f/mdkvwgln", {
- method: "POST",
- body: formData,
- headers: {
- Accept: "application/json",
- },
- });
- setSubmitted(true);
- form.reset();
- } catch {
- console.error("Form submission error");
- } finally {
- setIsSubmitting(false);
- }
- };
+ try {
+ await fetch("https://formspree.io/f/mdkvwgln", {
+ method: "POST",
+ body: formData,
+ headers: {
+ Accept: "application/json",
+ },
+ });
+ setSubmitted(true);
+ form.reset();
+ } catch {
+ console.error("Form submission error");
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
- return (
-
-
- Get in{" "}
-
- Touch
-
-
-
- Have a question, want to collaborate, or interested in contributing to
- our open-source projects? Our inbox is always open.
-
+ return (
+
+
+ Get in{" "}
+
+
+
+
+
+ Have a question, want to collaborate, or interested in contributing to
+ our open-source projects? Our inbox is always open.
+
-
-
-
- Community Support
-
-
- Get help with CodeVerse Hub, bots, and community tools.
-
-
-
-
- Partnerships
-
-
- Reach out for events, collaborations, and sponsorships.
-
-
-
-
- Open Source
-
-
- Ask about contributing to our GitHub projects, including this site.
-
-
-
+
+
+
+ Community Support
+
+
+ Get help with CodeVerse Hub, bots, and community tools.
+
+
+
+
+ Partnerships
+
+
+ Reach out for events, collaborations, and sponsorships.
+
+
+
+
+ Open Source
+
+
+ Ask about contributing to our GitHub projects, including this site.
+
+
+
-
-
+
+
-
- {submitted ? (
-
-
-
- Message Sent!
-
-
- We'll get back to you as soon as possible.
-
-
setSubmitted(false)}
- className="mt-6 text-violet-400 hover:text-violet-300 transition-colors"
- >
- Send another message
-
-
- ) : (
-
- )}
-
+
+ {submitted ? (
+
+
+
+ Message Sent!
+
+
+ We'll get back to you as soon as possible.
+
+
setSubmitted(false)}
+ className="mt-6 text-violet-400 hover:text-violet-300 transition-colors"
+ >
+ Send another message
+
-
- );
+ ) : (
+
+
+
+ Name
+
+
+
+
+
+ Email
+
+
+
+
+
+ Subject
+
+
+
+
+
+ Message
+
+
+
+
+ {isSubmitting ? (
+ "Sending..."
+ ) : (
+ <>
+ Send Message
+
+ >
+ )}
+
+
+ )}
+
+
+
+ );
}
diff --git a/src/components/Features.tsx b/src/components/Features.tsx
index c85b4ea..a67cd15 100644
--- a/src/components/Features.tsx
+++ b/src/components/Features.tsx
@@ -40,7 +40,7 @@ const features = [
icon: Rocket,
title: "Project Showcase",
description:
- "Share what you're building. Get feedback, testers, and maybe even collaborators from 1100+ engaged developers.",
+ "Share what you're building. Get feedback, testers, and maybe even collaborators from 1700+ engaged developers.",
color: "#F59E0B",
tag: "#project-showcase",
},
@@ -91,7 +91,8 @@ export default function Features() {
Everything a dev needs
- From daily debugging to landing your first job — we have a channel, a community, and a resource for every step.
+ From daily debugging to landing your first job — we have a channel,
+ a community, and a resource for every step.
@@ -119,7 +120,10 @@ export default function Features() {
{/* Icon */}
@@ -136,7 +140,10 @@ export default function Features() {
{feature.tag}
diff --git a/src/components/JoinCTA.tsx b/src/components/JoinCTA.tsx
index 2e54f4c..d94c09d 100644
--- a/src/components/JoinCTA.tsx
+++ b/src/components/JoinCTA.tsx
@@ -1,73 +1,107 @@
"use client";
+import ShinyText from "./ShinyText";
+
export default function JoinCTA() {
- return (
-
- {/* Background glow */}
-
+ return (
+
+ {/* Background glow */}
+
-
- {/* Card */}
-
- {/* Inner glow */}
-
+
+ {/* Card */}
+
+ {/* Inner glow */}
+
- {/* Discord server icon simulation */}
-
+ {/* Discord server icon simulation */}
+
-
-
- Your next breakthrough
-
-
- starts in a chat.
-
-
+
+
+ Your next breakthrough
+
+
+
+
+
-
- 1100+ developers helping each other ship code, review PRs, learn new skills, and build careers. All for free, all on Discord.
-
+
+ 1700+ developers helping each other ship code, review PRs, learn new
+ skills, and build careers. All for free, all on Discord.
+
-
+
- {/* Member avatars row */}
-
-
- {["#8B5CF6","#EC4899","#06B6D4","#10B981","#F59E0B"].map((c, i) => (
-
- {String.fromCharCode(65 + i)}
-
- ))}
-
-
1100+ devs waiting for you
-
-
+ {/* Member avatars row */}
+
+
+ {[
+ "https://cdn.discordapp.com/avatars/955695820999639120/cf296ec1b2af5b10746bb89dbd24fc38.webp?size=96",
+ "https://cdn.discordapp.com/avatars/380987045008506880/4544bf7a183195600feefa78596578eb.webp?size=40",
+ "https://cdn.discordapp.com/avatars/847851585706393652/1eae2121f1537af4a3c3c4836d9c9bfc.webp?size=96",
+ "https://cdn.discordapp.com/avatars/927288268054229062/349d2011d76e055e12de1fc71644ef6f.webp?size=96",
+ "https://cdn.discordapp.com/avatars/903540976449097749/1a6aad52b59261e32fa66a96c915adcb.webp?size=96",
+ "https://cdn.discordapp.com/avatars/1418496947240697876/83c7517e2f0e45d91edf4aee0cfd91b5.webp?size=96",
+ ].map((c, i) => (
+
+ {/* {String.fromCharCode(65 + i)} */}
+
+ ))}
-
- );
+
+ 1700+ devs waiting for you
+
+
+
+
+
+ );
}
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
index 789bcc1..b5bada7 100644
--- a/src/components/Navbar.tsx
+++ b/src/components/Navbar.tsx
@@ -5,158 +5,162 @@ import Link from "next/link";
import { ChevronDown, Terminal } from "lucide-react";
const navLinks = [
- { href: "/", label: "Home" },
- { href: "/pages/rules", label: "Rules" },
- { href: "/pages/faq", label: "FAQ" },
- { href: "/resources", label: "Resources" },
- { href: "/pages/hall-of-fame", label: "Hall of Fame" },
+ { href: "/", label: "Home" },
+ { href: "/pages/rules", label: "Rules" },
+ { href: "/pages/faq", label: "FAQ" },
+ { href: "/resources", label: "Resources" },
+ { href: "/pages/hall-of-fame", label: "Hall of Fame" },
];
const moreLinks = [
- { href: "/pages/code-of-conduct", label: "Code of Conduct" },
- { href: "/pages/how-to-ask", label: "How to Ask for Help" },
- { href: "/pages/how-to-help", label: "How to Help" },
- { href: "/pages/join", label: "Join Guide" },
- { href: "/pages/contributing", label: "Contributing" },
- { href: "/pages/moderation-guide", label: "Moderation Guide" },
- { href: "/pages/server-info", label: "Server Info" },
- { href: "/pages/privacy-policy", label: "Privacy Policy" },
- { href: "/pages/security-notice", label: "Security Notice" },
- { href: "/pages/staff-roles", label: "Staff Roles" },
- { href: "/pages/tags", label: "Tags Reference" },
- { href: "/pages/acknowledgements", label: "Acknowledgements" },
+ { href: "/pages/code-of-conduct", label: "Code of Conduct" },
+ { href: "/pages/how-to-ask", label: "How to Ask for Help" },
+ { href: "/pages/how-to-help", label: "How to Help" },
+ { href: "/pages/join", label: "Join Guide" },
+ { href: "/pages/contributing", label: "Contributing" },
+ { href: "/pages/moderation-guide", label: "Moderation Guide" },
+ { href: "/pages/server-info", label: "Server Info" },
+ { href: "/pages/privacy-policy", label: "Privacy Policy" },
+ { href: "/pages/security-notice", label: "Security Notice" },
+ { href: "/pages/staff-roles", label: "Staff Roles" },
+ { href: "/pages/tags", label: "Tags Reference" },
+ { href: "/pages/acknowledgements", label: "Acknowledgements" },
];
export default function Navbar() {
- const [isOpen, setIsOpen] = useState(false);
- const [isMoreOpen, setIsMoreOpen] = useState(false);
+ const [isOpen, setIsOpen] = useState(false);
+ const [isMoreOpen, setIsMoreOpen] = useState(false);
- return (
-
-
-
-
-
-
-
- CodeVerse Hub
-
-
+ return (
+
+
+
+
+
+
+
+ CodeVerse Hub
+
+
-
+
-
setIsOpen(!isOpen)}
- className="md:hidden flex flex-col justify-center items-center w-10 h-10 gap-1.5"
- aria-label="Toggle menu"
- >
-
-
-
-
-
+ setIsOpen(!isOpen)}
+ className="md:hidden flex flex-col justify-center items-center w-10 h-10 gap-1.5"
+ aria-label="Toggle menu"
+ >
+
+
+
+
+
-
-
- );
+ Join Discord
+
+
+
+
+
+ );
}
diff --git a/src/components/ScrollVelocity.tsx b/src/components/ScrollVelocity.tsx
new file mode 100644
index 0000000..2547ff5
--- /dev/null
+++ b/src/components/ScrollVelocity.tsx
@@ -0,0 +1,209 @@
+import React, { useRef, useLayoutEffect, useState } from "react";
+import {
+ motion,
+ useScroll,
+ useSpring,
+ useTransform,
+ useMotionValue,
+ useVelocity,
+ useAnimationFrame,
+} from "motion/react";
+
+interface VelocityMapping {
+ input: [number, number];
+ output: [number, number];
+}
+
+interface VelocityTextProps {
+ children: React.ReactNode;
+ baseVelocity: number;
+ scrollContainerRef?: React.RefObject;
+ className?: string;
+ damping?: number;
+ stiffness?: number;
+ numCopies?: number;
+ velocityMapping?: VelocityMapping;
+ parallaxClassName?: string;
+ scrollerClassName?: string;
+ parallaxStyle?: React.CSSProperties;
+ scrollerStyle?: React.CSSProperties;
+}
+
+interface ScrollVelocityProps {
+ scrollContainerRef?: React.RefObject;
+ texts: React.ReactNode[];
+ velocity?: number;
+ className?: string;
+ damping?: number;
+ stiffness?: number;
+ numCopies?: number;
+ velocityMapping?: VelocityMapping;
+ parallaxClassName?: string;
+ scrollerClassName?: string;
+ parallaxStyle?: React.CSSProperties;
+ scrollerStyle?: React.CSSProperties;
+}
+
+function useElementWidth(
+ ref: React.RefObject,
+): number {
+ const [width, setWidth] = useState(0);
+
+ useLayoutEffect(() => {
+ function updateWidth() {
+ if (ref.current) {
+ setWidth(ref.current.offsetWidth);
+ }
+ }
+ updateWidth();
+ window.addEventListener("resize", updateWidth);
+ return () => window.removeEventListener("resize", updateWidth);
+ }, [ref]);
+
+ return width;
+}
+
+export const ScrollVelocity: React.FC = ({
+ scrollContainerRef,
+ texts = [],
+ velocity = 100,
+ className = "",
+ damping = 50,
+ stiffness = 400,
+ numCopies = 6,
+ velocityMapping = { input: [0, 1000], output: [0, 5] },
+ parallaxClassName,
+ scrollerClassName,
+ parallaxStyle,
+ scrollerStyle,
+}) => {
+ function VelocityText({
+ children,
+ baseVelocity = velocity,
+ scrollContainerRef,
+ className = "",
+ damping,
+ stiffness,
+ numCopies,
+ velocityMapping,
+ parallaxClassName,
+ scrollerClassName,
+ parallaxStyle,
+ scrollerStyle,
+ }: VelocityTextProps) {
+ const baseX = useMotionValue(0);
+ const scrollOptions = scrollContainerRef
+ ? { container: scrollContainerRef }
+ : {};
+ const { scrollY } = useScroll(scrollOptions);
+ const scrollVelocity = useVelocity(scrollY);
+ const smoothVelocity = useSpring(scrollVelocity, {
+ damping: damping ?? 50,
+ stiffness: stiffness ?? 400,
+ });
+ const velocityFactor = useTransform(
+ smoothVelocity,
+ velocityMapping?.input || [0, 1000],
+ velocityMapping?.output || [0, 5],
+ { clamp: false },
+ );
+
+ const copyRef = useRef(null);
+ const parallaxRef = useRef(null);
+ const copyWidth = useElementWidth(copyRef);
+ const containerWidth = useElementWidth(parallaxRef);
+
+ function wrap(min: number, max: number, v: number): number {
+ const range = max - min;
+ const mod = (((v - min) % range) + range) % range;
+ return mod + min;
+ }
+
+ const x = useTransform(baseX, (v) => {
+ if (copyWidth === 0) return "0px";
+ return `${wrap(-copyWidth, 0, v)}px`;
+ });
+
+ const directionFactor = useRef(1);
+ useAnimationFrame((t, delta) => {
+ let moveBy = directionFactor.current * baseVelocity * (delta / 1000);
+
+ if (velocityFactor.get() < 0) {
+ directionFactor.current = -1;
+ } else if (velocityFactor.get() > 0) {
+ directionFactor.current = 1;
+ }
+
+ moveBy += directionFactor.current * moveBy * velocityFactor.get();
+ baseX.set(baseX.get() + moveBy);
+ });
+
+ // Determine how many copies we need so there are no gaps.
+ let finalCopies = numCopies ?? 6;
+ if (copyWidth > 0 && containerWidth > 0) {
+ const needed = Math.ceil(containerWidth / copyWidth) + 2;
+ finalCopies = Math.max(finalCopies, needed);
+ }
+
+ const spans = [];
+ for (let i = 0; i < finalCopies; i++) {
+ spans.push(
+
+ {children}
+ ,
+ );
+ }
+
+ // When the measured width changes, wrap baseX into the new range to avoid jumps.
+ React.useLayoutEffect(() => {
+ if (copyWidth > 0) {
+ baseX.set(wrap(-copyWidth, 0, baseX.get()));
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [copyWidth, containerWidth]);
+
+ return (
+
+
+ {spans}
+
+
+ );
+ }
+
+ return (
+
+ {texts.map((text, index) => (
+
+ {text}
+
+ ))}
+
+ );
+};
+
+export default ScrollVelocity;
diff --git a/src/components/ShinyText.tsx b/src/components/ShinyText.tsx
new file mode 100644
index 0000000..c7bb96c
--- /dev/null
+++ b/src/components/ShinyText.tsx
@@ -0,0 +1,131 @@
+import React, { useState, useCallback, useEffect, useRef } from 'react';
+import { motion, useMotionValue, useAnimationFrame, useTransform } from 'motion/react';
+
+interface ShinyTextProps {
+ text: string;
+ disabled?: boolean;
+ speed?: number;
+ className?: string;
+ color?: string;
+ shineColor?: string;
+ spread?: number;
+ yoyo?: boolean;
+ pauseOnHover?: boolean;
+ direction?: 'left' | 'right';
+ delay?: number;
+}
+
+const ShinyText: React.FC = ({
+ text,
+ disabled = false,
+ speed = 2,
+ className = '',
+ color = '#b5b5b5',
+ shineColor = '#ffffff',
+ spread = 120,
+ yoyo = false,
+ pauseOnHover = false,
+ direction = 'left',
+ delay = 0
+}) => {
+ const [isPaused, setIsPaused] = useState(false);
+ const progress = useMotionValue(0);
+ const elapsedRef = useRef(0);
+ const lastTimeRef = useRef(null);
+ const directionRef = useRef(direction === 'left' ? 1 : -1);
+
+ const animationDuration = speed * 1000;
+ const delayDuration = delay * 1000;
+
+ useAnimationFrame(time => {
+ if (disabled || isPaused) {
+ lastTimeRef.current = null;
+ return;
+ }
+
+ if (lastTimeRef.current === null) {
+ lastTimeRef.current = time;
+ return;
+ }
+
+ const deltaTime = time - lastTimeRef.current;
+ lastTimeRef.current = time;
+
+ elapsedRef.current += deltaTime;
+
+ // Animation goes from 0 to 100
+ if (yoyo) {
+ const cycleDuration = animationDuration + delayDuration;
+ const fullCycle = cycleDuration * 2;
+ const cycleTime = elapsedRef.current % fullCycle;
+
+ if (cycleTime < animationDuration) {
+ // Forward animation: 0 -> 100
+ const p = (cycleTime / animationDuration) * 100;
+ progress.set(directionRef.current === 1 ? p : 100 - p);
+ } else if (cycleTime < cycleDuration) {
+ // Delay at end
+ progress.set(directionRef.current === 1 ? 100 : 0);
+ } else if (cycleTime < cycleDuration + animationDuration) {
+ // Reverse animation: 100 -> 0
+ const reverseTime = cycleTime - cycleDuration;
+ const p = 100 - (reverseTime / animationDuration) * 100;
+ progress.set(directionRef.current === 1 ? p : 100 - p);
+ } else {
+ // Delay at start
+ progress.set(directionRef.current === 1 ? 0 : 100);
+ }
+ } else {
+ const cycleDuration = animationDuration + delayDuration;
+ const cycleTime = elapsedRef.current % cycleDuration;
+
+ if (cycleTime < animationDuration) {
+ // Animation phase: 0 -> 100
+ const p = (cycleTime / animationDuration) * 100;
+ progress.set(directionRef.current === 1 ? p : 100 - p);
+ } else {
+ // Delay phase - hold at end (shine off-screen)
+ progress.set(directionRef.current === 1 ? 100 : 0);
+ }
+ }
+ });
+
+ useEffect(() => {
+ directionRef.current = direction === 'left' ? 1 : -1;
+ elapsedRef.current = 0;
+ progress.set(0);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [direction]);
+
+ // Transform: p=0 -> 150% (shine off right), p=100 -> -50% (shine off left)
+ const backgroundPosition = useTransform(progress, p => `${150 - p * 2}% center`);
+
+ const handleMouseEnter = useCallback(() => {
+ if (pauseOnHover) setIsPaused(true);
+ }, [pauseOnHover]);
+
+ const handleMouseLeave = useCallback(() => {
+ if (pauseOnHover) setIsPaused(false);
+ }, [pauseOnHover]);
+
+ const gradientStyle: React.CSSProperties = {
+ backgroundImage: `linear-gradient(${spread}deg, ${color} 0%, ${color} 35%, ${shineColor} 50%, ${color} 65%, ${color} 100%)`,
+ backgroundSize: '200% auto',
+ WebkitBackgroundClip: 'text',
+ backgroundClip: 'text',
+ WebkitTextFillColor: 'transparent'
+ };
+
+ return (
+
+ {text}
+
+ );
+};
+
+export default ShinyText;
diff --git a/src/components/Stats.tsx b/src/components/Stats.tsx
index 4d6bd66..ec1674c 100644
--- a/src/components/Stats.tsx
+++ b/src/components/Stats.tsx
@@ -3,93 +3,99 @@
import { useEffect, useRef, useState } from "react";
interface StatItemProps {
- value: number;
- suffix: string;
- label: string;
- inView: boolean;
+ value: number;
+ suffix: string;
+ label: string;
+ inView: boolean;
}
function useCountUp(end: number, inView: boolean, duration: number = 2000) {
- const [count, setCount] = useState(0);
- const hasAnimated = useRef(false);
+ const [count, setCount] = useState(0);
+ const hasAnimated = useRef(false);
- useEffect(() => {
- if (!inView || hasAnimated.current) return;
- hasAnimated.current = true;
+ useEffect(() => {
+ if (!inView || hasAnimated.current) return;
+ hasAnimated.current = true;
- let startTime: number;
- const animate = (currentTime: number) => {
- if (!startTime) startTime = currentTime;
- const progress = Math.min((currentTime - startTime) / duration, 1);
- const easeOut = 1 - Math.pow(1 - progress, 3);
- setCount(Math.floor(easeOut * end));
-
- if (progress < 1) {
- requestAnimationFrame(animate);
- } else {
- setCount(end);
- }
- };
+ let startTime: number;
+ const animate = (currentTime: number) => {
+ if (!startTime) startTime = currentTime;
+ const progress = Math.min((currentTime - startTime) / duration, 1);
+ const easeOut = 1 - Math.pow(1 - progress, 3);
+ setCount(Math.floor(easeOut * end));
+ if (progress < 1) {
requestAnimationFrame(animate);
- }, [end, inView, duration]);
+ } else {
+ setCount(end);
+ }
+ };
+
+ requestAnimationFrame(animate);
+ }, [end, inView, duration]);
- return count;
+ return count;
}
function StatItem({ value, suffix, label, inView }: StatItemProps) {
- const count = useCountUp(value, inView);
+ const count = useCountUp(value, inView);
- return (
-
-
- {count}{suffix}
-
-
- {label}
-
-
- );
+ return (
+
+
+ {count}
+ {suffix}
+
+
+ {label}
+
+
+ );
}
export default function Stats() {
- const [inView, setInView] = useState(false);
- const sectionRef = useRef(null);
-
- useEffect(() => {
- const observer = new IntersectionObserver(
- ([entry]) => {
- if (entry.isIntersecting) {
- setInView(true);
- }
- },
- { threshold: 0.3 }
- );
+ const [inView, setInView] = useState(false);
+ const sectionRef = useRef(null);
- if (sectionRef.current) {
- observer.observe(sectionRef.current);
+ useEffect(() => {
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry.isIntersecting) {
+ setInView(true);
}
+ },
+ { threshold: 0.3 },
+ );
- return () => observer.disconnect();
- }, []);
+ if (sectionRef.current) {
+ observer.observe(sectionRef.current);
+ }
- return (
-
-
- The Server Stats?
-
-
- Check the numbers out!
-
-
-
-
-
-
-
-
- );
+ return () => observer.disconnect();
+ }, []);
+
+ return (
+
+
+ The Server Stats?
+
+
+ Check the numbers out!
+
+
+
+
+
+
+
+
+ );
}
diff --git a/src/components/TechMarquee.tsx b/src/components/TechMarquee.tsx
index f6cdc73..95c833f 100644
--- a/src/components/TechMarquee.tsx
+++ b/src/components/TechMarquee.tsx
@@ -1,5 +1,7 @@
"use client";
+import ScrollVelocity from "./ScrollVelocity";
+
const techRow1 = [
{ name: "Python", color: "#3B82F6" },
{ name: "TypeScript", color: "#8B5CF6" },
@@ -34,8 +36,32 @@ const techRow2 = [
{ name: "Bun", color: "#F9F1E1" },
];
-function MarqueeTrack({ items, reverse = false }: { items: typeof techRow1; reverse?: boolean }) {
- const duplicated = [...items, ...items];
+function MarqueeTrack({
+ items,
+ reverse = false,
+}: {
+ items: typeof techRow1;
+ reverse?: boolean;
+}) {
+ // Build a single row (one repetition) — ScrollVelocity will duplicate this to create the continuous loop.
+ const row = (
+
+ {items.map((tech, i) => (
+
+ ))}
+
+ );
return (
@@ -43,26 +69,13 @@ function MarqueeTrack({ items, reverse = false }: { items: typeof techRow1; reve
-
- {duplicated.map((tech, i) => (
-
- ))}
-
+
);
}
@@ -78,7 +91,8 @@ export default function TechMarquee() {
No matter what you code in
- We have developers who work with every major language and framework. You'll always find someone who gets it.
+ We have developers who work with every major language and framework.
+ You'll always find someone who gets it.
diff --git a/src/components/TerminalWindow.tsx b/src/components/TerminalWindow.tsx
index 7aea7fd..3123f09 100644
--- a/src/components/TerminalWindow.tsx
+++ b/src/components/TerminalWindow.tsx
@@ -3,209 +3,232 @@
import { useEffect, useState, useRef } from "react";
const snippets = [
- {
- lang: "python",
- label: "Python",
- color: "#3B82F6",
- lines: [
- { text: "# Welcome to CodeVerse Hub!", type: "comment" },
- { text: "def join_community(developer):", type: "code" },
- { text: ' skills = developer["skills"]', type: "code" },
- { text: ' return f"Welcome, {developer[\'name\']}! 🚀"', type: "code" },
- { text: "", type: "blank" },
- { text: "me = {", type: "code" },
- { text: ' "name": "your_username",', type: "code" },
- { text: ' "skills": ["Python", "JS", "Rust"]', type: "code" },
- { text: "}", type: "code" },
- { text: "print(join_community(me))", type: "code" },
- { text: "# → Welcome, your_username! 🚀", type: "output" },
- ],
- },
- {
- lang: "typescript",
- label: "TypeScript",
- color: "#8B5CF6",
- lines: [
- { text: "// CodeVerse Hub — Build together", type: "comment" },
- { text: "interface Developer {", type: "code" },
- { text: " name: string;", type: "code" },
- { text: " stack: string[];", type: "code" },
- { text: " community: 'CodeVerse Hub';", type: "code" },
- { text: "}", type: "code" },
- { text: "", type: "blank" },
- { text: "const you: Developer = {", type: "code" },
- { text: ' name: "awesome_dev",', type: "code" },
- { text: ' stack: ["React", "Node", "TS"],', type: "code" },
- { text: ' community: "CodeVerse Hub"', type: "code" },
- { text: "};", type: "code" },
- ],
- },
- {
- lang: "rust",
- label: "Rust",
- color: "#F97316",
- lines: [
- { text: "// Blazingly fast community 🦀", type: "comment" },
- { text: "struct Developer {", type: "code" },
- { text: " name: String,", type: "code" },
- { text: " joined_cvh: bool,", type: "code" },
- { text: "}", type: "code" },
- { text: "", type: "blank" },
- { text: "fn main() {", type: "code" },
- { text: " let dev = Developer {", type: "code" },
- { text: ' name: String::from("you"),', type: "code" },
- { text: " joined_cvh: true,", type: "code" },
- { text: " };", type: "code" },
- { text: ' println!("Hello, {}!", dev.name);', type: "code" },
- { text: "}", type: "code" },
- ],
- },
+ {
+ lang: "python",
+ label: "Python",
+ color: "#3B82F6",
+ lines: [
+ { text: "# Welcome to CodeVerse Hub!", type: "comment" },
+ { text: "def join_community(developer):", type: "code" },
+ { text: ' skills = developer["skills"]', type: "code" },
+ {
+ text: " return f\"Welcome, {developer['name']}! 🚀\"",
+ type: "code",
+ },
+ { text: "", type: "blank" },
+ { text: "me = {", type: "code" },
+ { text: ' "name": "your_username",', type: "code" },
+ { text: ' "skills": ["Python", "JS", "Rust"]', type: "code" },
+ { text: "}", type: "code" },
+ { text: "print(join_community(me))", type: "code" },
+ { text: "# → Welcome, your_username! 🚀", type: "output" },
+ ],
+ },
+ {
+ lang: "typescript",
+ label: "TypeScript",
+ color: "#8B5CF6",
+ lines: [
+ { text: "// CodeVerse Hub — Build together", type: "comment" },
+ { text: "interface Developer {", type: "code" },
+ { text: " name: string;", type: "code" },
+ { text: " stack: string[];", type: "code" },
+ { text: " community: 'CodeVerse Hub';", type: "code" },
+ { text: "}", type: "code" },
+ { text: "", type: "blank" },
+ { text: "const you: Developer = {", type: "code" },
+ { text: ' name: "awesome_dev",', type: "code" },
+ { text: ' stack: ["React", "Node", "TS"],', type: "code" },
+ { text: ' community: "CodeVerse Hub"', type: "code" },
+ { text: "};", type: "code" },
+ ],
+ },
+ {
+ lang: "rust",
+ label: "Rust",
+ color: "#F97316",
+ lines: [
+ { text: "// Blazingly fast community 🦀", type: "comment" },
+ { text: "struct Developer {", type: "code" },
+ { text: " name: String,", type: "code" },
+ { text: " joined_cvh: bool,", type: "code" },
+ { text: "}", type: "code" },
+ { text: "", type: "blank" },
+ { text: "fn main() {", type: "code" },
+ { text: " let dev = Developer {", type: "code" },
+ { text: ' name: String::from("you"),', type: "code" },
+ { text: " joined_cvh: true,", type: "code" },
+ { text: " };", type: "code" },
+ { text: ' println!("Hello, {}!", dev.name);', type: "code" },
+ { text: "}", type: "code" },
+ ],
+ },
];
function getColor(type: string, lang: string) {
- if (type === "comment") return "text-white/30 italic";
- if (type === "output") return "text-emerald-400";
- if (lang === "python") return "text-blue-200";
- if (lang === "typescript") return "text-violet-200";
- if (lang === "rust") return "text-orange-200";
- return "text-slate-200";
+ if (type === "comment") return "text-white/30 italic";
+ if (type === "output") return "text-emerald-400";
+ if (lang === "python") return "text-blue-200";
+ if (lang === "typescript") return "text-violet-200";
+ if (lang === "rust") return "text-orange-200";
+ return "text-slate-200";
}
export default function TerminalWindow() {
- const [snippetIdx, setSnippetIdx] = useState(0);
- const [visibleLines, setVisibleLines] = useState(0);
- const [charIdx, setCharIdx] = useState(0);
- const [displayedLines, setDisplayedLines] = useState([]);
- const intervalRef = useRef | null>(null);
+ const [snippetIdx, setSnippetIdx] = useState(0);
+ const [visibleLines, setVisibleLines] = useState(0);
+ const [charIdx, setCharIdx] = useState(0);
+ const [displayedLines, setDisplayedLines] = useState([]);
+ const intervalRef = useRef | null>(null);
- const snippet = snippets[snippetIdx];
+ const snippet = snippets[snippetIdx];
- useEffect(() => {
- setVisibleLines(0);
- setCharIdx(0);
- setDisplayedLines([]);
- }, [snippetIdx]);
+ useEffect(() => {
+ setVisibleLines(0);
+ setCharIdx(0);
+ setDisplayedLines([]);
+ }, [snippetIdx]);
- useEffect(() => {
- if (visibleLines >= snippet.lines.length) {
- // Pause then move to next snippet
- const timeout = setTimeout(() => {
- setSnippetIdx((prev) => (prev + 1) % snippets.length);
- }, 2800);
- return () => clearTimeout(timeout);
- }
+ useEffect(() => {
+ if (visibleLines >= snippet.lines.length) {
+ // Pause then move to next snippet
+ const timeout = setTimeout(() => {
+ setSnippetIdx((prev) => (prev + 1) % snippets.length);
+ }, 2800);
+ return () => clearTimeout(timeout);
+ }
- const currentLine = snippet.lines[visibleLines];
- const fullText = currentLine.text;
+ const currentLine = snippet.lines[visibleLines];
+ const fullText = currentLine.text;
- if (charIdx <= fullText.length) {
- if (intervalRef.current) clearInterval(intervalRef.current);
- const delay = currentLine.type === "blank" ? 80 : 18;
- intervalRef.current = setInterval(() => {
- setCharIdx((prev) => {
- if (prev >= fullText.length) {
- clearInterval(intervalRef.current!);
- setDisplayedLines((lines) => [...lines, fullText]);
- setVisibleLines((v) => v + 1);
- setCharIdx(0);
- return 0;
- }
- return prev + 1;
- });
- }, delay);
- }
+ if (charIdx <= fullText.length) {
+ if (intervalRef.current) clearInterval(intervalRef.current);
+ const delay = currentLine.type === "blank" ? 80 : 18;
+ intervalRef.current = setInterval(() => {
+ setCharIdx((prev) => {
+ if (prev >= fullText.length) {
+ clearInterval(intervalRef.current!);
+ setDisplayedLines((lines) => [...lines, fullText]);
+ setVisibleLines((v) => v + 1);
+ setCharIdx(0);
+ return 0;
+ }
+ return prev + 1;
+ });
+ }, delay);
+ }
- return () => {
- if (intervalRef.current) clearInterval(intervalRef.current);
- };
- }, [visibleLines, snippet]);
+ return () => {
+ if (intervalRef.current) clearInterval(intervalRef.current);
+ };
+ }, [visibleLines, snippet]);
- const currentLineText =
- visibleLines < snippet.lines.length
- ? snippet.lines[visibleLines].text.slice(0, charIdx)
- : null;
+ const currentLineText =
+ visibleLines < snippet.lines.length
+ ? snippet.lines[visibleLines].text.slice(0, charIdx)
+ : null;
- return (
-
- {/* Glow effect behind terminal */}
-
+ return (
+
+ {/* Glow effect behind terminal */}
+
- {/* Terminal window */}
-
- {/* Title bar */}
-
-
-
-
-
-
- {snippet.label}
-
-
-
- {snippets.map((s, i) => (
- setSnippetIdx(i)}
- className="flex items-center gap-1 px-2 py-1 rounded-full text-[10px] font-mono cursor-pointer transition-all duration-200 hover:opacity-100"
- style={{
- backgroundColor: i === snippetIdx ? s.color + "30" : "rgba(255,255,255,0.07)",
- color: i === snippetIdx ? s.color : "rgba(255,255,255,0.35)",
- border: `1px solid ${i === snippetIdx ? s.color + "60" : "transparent"}`,
- opacity: i === snippetIdx ? 1 : 0.7,
- }}
- >
-
- {s.label}
-
- ))}
-
-
-
- {/* Code area */}
-
- {displayedLines.map((line, i) => {
- const lineData = snippet.lines[i];
- return (
-
-
- {i + 1}
-
-
- {line || "\u00A0"}
-
-
- );
- })}
- {currentLineText !== null && (
-
-
- {displayedLines.length + 1}
-
-
- {currentLineText}
-
-
-
- )}
-
+ {/* Terminal window */}
+
+ {/* Title bar */}
+
+
+
+
+
+
+ {snippet.label}
+
+
+
+ {snippets.map((s, i) => (
+ setSnippetIdx(i)}
+ className="flex items-center gap-1 px-2 py-1 rounded-full text-[10px] font-mono cursor-pointer transition-all duration-200 hover:opacity-100"
+ style={{
+ backgroundColor:
+ i === snippetIdx
+ ? s.color + "30"
+ : "rgba(255,255,255,0.07)",
+ color: i === snippetIdx ? s.color : "rgba(255,255,255,0.35)",
+ border: `1px solid ${i === snippetIdx ? s.color + "60" : "transparent"}`,
+ opacity: i === snippetIdx ? 1 : 0.7,
+ }}
+ >
+
+ {s.label}
+
+ ))}
+
+
- {/* Bottom bar */}
-
-
-
cvh ~ dev session
-
1100+ members online
-
+ {/* Code area */}
+
+ {displayedLines.map((line, i) => {
+ const lineData = snippet.lines[i];
+ return (
+
+
+ {i + 1}
+
+
+ {line || "\u00A0"}
+
+
+ );
+ })}
+ {currentLineText !== null && (
+
+
+ {displayedLines.length + 1}
+
+
+ {currentLineText}
+
+
+ )}
+
+
+ {/* Bottom bar */}
+
+
+
+ cvh ~ dev session
+
+
+ 1700+ members online
+
- );
+
+
+ );
}