diff --git a/frontend/public/Feature_images/Analytics_Dashboard.png b/frontend/public/Feature_images/Analytics_Dashboard.png new file mode 100644 index 0000000..180fc94 Binary files /dev/null and b/frontend/public/Feature_images/Analytics_Dashboard.png differ diff --git a/frontend/public/Feature_images/Auto_Commit_AI_Scheduling.png b/frontend/public/Feature_images/Auto_Commit_AI_Scheduling.png new file mode 100644 index 0000000..3f9bbb0 Binary files /dev/null and b/frontend/public/Feature_images/Auto_Commit_AI_Scheduling.png differ diff --git a/frontend/public/Feature_images/Contribution_Analytics.png b/frontend/public/Feature_images/Contribution_Analytics.png new file mode 100644 index 0000000..81c9359 Binary files /dev/null and b/frontend/public/Feature_images/Contribution_Analytics.png differ diff --git a/frontend/public/Feature_images/Enterprise_Security.png b/frontend/public/Feature_images/Enterprise_Security.png new file mode 100644 index 0000000..64c2d62 Binary files /dev/null and b/frontend/public/Feature_images/Enterprise_Security.png differ diff --git a/frontend/public/Feature_images/Github_README_AI.png b/frontend/public/Feature_images/Github_README_AI.png new file mode 100644 index 0000000..4787cfb Binary files /dev/null and b/frontend/public/Feature_images/Github_README_AI.png differ diff --git a/frontend/public/Feature_images/Goal_Tracking.png b/frontend/public/Feature_images/Goal_Tracking.png new file mode 100644 index 0000000..363989a Binary files /dev/null and b/frontend/public/Feature_images/Goal_Tracking.png differ diff --git a/frontend/public/Feature_images/Multi-Repository_Github_Tools.png b/frontend/public/Feature_images/Multi-Repository_Github_Tools.png new file mode 100644 index 0000000..5772fe2 Binary files /dev/null and b/frontend/public/Feature_images/Multi-Repository_Github_Tools.png differ diff --git a/frontend/public/Feature_images/README_Generation.png b/frontend/public/Feature_images/README_Generation.png new file mode 100644 index 0000000..3fb3e78 Binary files /dev/null and b/frontend/public/Feature_images/README_Generation.png differ diff --git a/frontend/public/Feature_images/Smart Notifications.png b/frontend/public/Feature_images/Smart Notifications.png new file mode 100644 index 0000000..f4d8392 Binary files /dev/null and b/frontend/public/Feature_images/Smart Notifications.png differ diff --git a/frontend/public/Implemenatation.txt b/frontend/public/Implemenatation.txt index eae62b5..8bd43b5 100644 --- a/frontend/public/Implemenatation.txt +++ b/frontend/public/Implemenatation.txt @@ -11,713 +11,143 @@ Determine the default path for components and styles. If default path for components is not /components/ui, provide instructions on why it's important to create this folder Copy-paste this component to /components/ui folder: ```tsx -compare.tsx +container-scroll-animation.tsx "use client"; -import React, { useState, useEffect, useRef, useCallback } from "react"; -import { SparklesCore } from "@/components/ui/sparkles"; -import { AnimatePresence, motion } from "framer-motion"; -import { cn } from "@/lib/utils"; -import { IconDotsVertical } from "@tabler/icons-react"; - -interface CompareProps { - firstImage?: string; - secondImage?: string; - className?: string; - firstImageClassName?: string; - secondImageClassname?: string; - initialSliderPercentage?: number; - slideMode?: "hover" | "drag"; - showHandlebar?: boolean; - autoplay?: boolean; - autoplayDuration?: number; -} -export const Compare = ({ - firstImage = "", - secondImage = "", - className, - firstImageClassName, - secondImageClassname, - initialSliderPercentage = 50, - slideMode = "hover", - showHandlebar = true, - autoplay = false, - autoplayDuration = 5000, -}: CompareProps) => { - const [sliderXPercent, setSliderXPercent] = useState(initialSliderPercentage); - const [isDragging, setIsDragging] = useState(false); - - const sliderRef = useRef(null); - - const [isMouseOver, setIsMouseOver] = useState(false); - - const autoplayRef = useRef(null); - - const startAutoplay = useCallback(() => { - if (!autoplay) return; - - const startTime = Date.now(); - const animate = () => { - const elapsedTime = Date.now() - startTime; - const progress = - (elapsedTime % (autoplayDuration * 2)) / autoplayDuration; - const percentage = progress <= 1 ? progress * 100 : (2 - progress) * 100; - - setSliderXPercent(percentage); - autoplayRef.current = setTimeout(animate, 16); // ~60fps +import React, { useRef } from "react"; +import { useScroll, useTransform, motion, MotionValue } from "framer-motion"; + +export const ContainerScroll = ({ + titleComponent, + children, +}: { + titleComponent: string | React.ReactNode; + children: React.ReactNode; +}) => { + const containerRef = useRef(null); + const { scrollYProgress } = useScroll({ + target: containerRef, + }); + const [isMobile, setIsMobile] = React.useState(false); + + React.useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth <= 768); + }; + checkMobile(); + window.addEventListener("resize", checkMobile); + return () => { + window.removeEventListener("resize", checkMobile); }; - - animate(); - }, [autoplay, autoplayDuration]); - - const stopAutoplay = useCallback(() => { - if (autoplayRef.current) { - clearTimeout(autoplayRef.current); - autoplayRef.current = null; - } }, []); - useEffect(() => { - startAutoplay(); - return () => stopAutoplay(); - }, [startAutoplay, stopAutoplay]); - - function mouseEnterHandler() { - setIsMouseOver(true); - stopAutoplay(); - } - - function mouseLeaveHandler() { - setIsMouseOver(false); - if (slideMode === "hover") { - setSliderXPercent(initialSliderPercentage); - } - if (slideMode === "drag") { - setIsDragging(false); - } - startAutoplay(); - } - - const handleStart = useCallback( - (clientX: number) => { - if (slideMode === "drag") { - setIsDragging(true); - } - }, - [slideMode] - ); - - const handleEnd = useCallback(() => { - if (slideMode === "drag") { - setIsDragging(false); - } - }, [slideMode]); + const scaleDimensions = () => { + return isMobile ? [0.7, 0.9] : [1.05, 1]; + }; - const handleMove = useCallback( - (clientX: number) => { - if (!sliderRef.current) return; - if (slideMode === "hover" || (slideMode === "drag" && isDragging)) { - const rect = sliderRef.current.getBoundingClientRect(); - const x = clientX - rect.left; - const percent = (x / rect.width) * 100; - requestAnimationFrame(() => { - setSliderXPercent(Math.max(0, Math.min(100, percent))); - }); - } - }, - [slideMode, isDragging] - ); + const rotate = useTransform(scrollYProgress, [0, 1], [20, 0]); + const scale = useTransform(scrollYProgress, [0, 1], scaleDimensions()); + const translate = useTransform(scrollYProgress, [0, 1], [0, -100]); - const handleMouseDown = useCallback( - (e: React.MouseEvent) => handleStart(e.clientX), - [handleStart] - ); - const handleMouseUp = useCallback(() => handleEnd(), [handleEnd]); - const handleMouseMove = useCallback( - (e: React.MouseEvent) => handleMove(e.clientX), - [handleMove] - ); - - const handleTouchStart = useCallback( - (e: React.TouchEvent) => { - if (!autoplay) { - handleStart(e.touches[0].clientX); - } - }, - [handleStart, autoplay] + return ( +
+
+
+ + {children} + +
+
); +}; - const handleTouchEnd = useCallback(() => { - if (!autoplay) { - handleEnd(); - } - }, [handleEnd, autoplay]); - - const handleTouchMove = useCallback( - (e: React.TouchEvent) => { - if (!autoplay) { - handleMove(e.touches[0].clientX); - } - }, - [handleMove, autoplay] +export const Header = ({ translate, titleComponent }: any) => { + return ( + + {titleComponent} + ); +}; +export const Card = ({ + rotate, + scale, + children, +}: { + rotate: MotionValue; + scale: MotionValue; + translate: MotionValue; + children: React.ReactNode; +}) => { return ( -
- - -
-
-
- -
- {showHandlebar && ( -
- -
- )} - - -
- - {firstImage ? ( - - first image - - ) : null} - +
+ {children}
- - - {secondImage ? ( - - ) : null} - -
+ ); }; -const MemoizedSparklesCore = React.memo(SparklesCore); - demo.tsx +"use client"; import React from "react"; -import { Compare } from "@/components/ui/compare"; +import { ContainerScroll } from "@/components/ui/container-scroll-animation"; +import Image from "next/image"; -export function CompareDemo() { +export function HeroScrollDemo() { return ( -
- +
+ +

+ Unleash the power of
+ + Scroll Animations + +

+ + } + > + hero +
); } ``` -Copy-paste these files for dependencies: -```tsx -aceternity/sparkles -"use client"; -import React, { useId, useMemo } from "react"; -import { useEffect, useState } from "react"; -import Particles, { initParticlesEngine } from "@tsparticles/react"; -import type { Container, SingleOrMultiple } from "@tsparticles/engine"; -import { loadSlim } from "@tsparticles/slim"; -import { cn } from "@/lib/utils"; -import { motion, useAnimation } from "framer-motion"; - -type ParticlesProps = { - id?: string; - className?: string; - background?: string; - particleSize?: number; - minSize?: number; - maxSize?: number; - speed?: number; - particleColor?: string; - particleDensity?: number; -}; -export const SparklesCore = (props: ParticlesProps) => { - const { - id, - className, - background, - minSize, - maxSize, - speed, - particleColor, - particleDensity, - } = props; - const [init, setInit] = useState(false); - useEffect(() => { - initParticlesEngine(async (engine) => { - await loadSlim(engine); - }).then(() => { - setInit(true); - }); - }, []); - const controls = useAnimation(); - - const particlesLoaded = async (container?: Container) => { - if (container) { - controls.start({ - opacity: 1, - transition: { - duration: 1, - }, - }); - } - }; - - const generatedId = useId(); - return ( - - {init && ( - | undefined, - }, - groups: {}, - move: { - angle: { - offset: 0, - value: 90, - }, - attract: { - distance: 200, - enable: false, - rotate: { - x: 3000, - y: 3000, - }, - }, - center: { - x: 50, - y: 50, - mode: "percent", - radius: 0, - }, - decay: 0, - distance: {}, - direction: "none", - drift: 0, - enable: true, - gravity: { - acceleration: 9.81, - enable: false, - inverse: false, - maxSpeed: 50, - }, - path: { - clamp: true, - delay: { - value: 0, - }, - enable: false, - options: {}, - }, - outModes: { - default: "out", - }, - random: false, - size: false, - speed: { - min: 0.1, - max: 1, - }, - spin: { - acceleration: 0, - enable: false, - }, - straight: false, - trail: { - enable: false, - length: 10, - fill: {}, - }, - vibrate: false, - warp: false, - }, - number: { - density: { - enable: true, - width: 400, - height: 400, - }, - limit: { - mode: "delete", - value: 0, - }, - value: particleDensity || 120, - }, - opacity: { - value: { - min: 0.1, - max: 1, - }, - animation: { - count: 0, - enable: true, - speed: speed || 4, - decay: 0, - delay: 0, - sync: false, - mode: "auto", - startValue: "random", - destroy: "none", - }, - }, - reduceDuplicates: false, - shadow: { - blur: 0, - color: { - value: "#000", - }, - enable: false, - offset: { - x: 0, - y: 0, - }, - }, - shape: { - close: true, - fill: true, - options: {}, - type: "circle", - }, - size: { - value: { - min: minSize || 1, - max: maxSize || 3, - }, - animation: { - count: 0, - enable: false, - speed: 5, - decay: 0, - delay: 0, - sync: false, - mode: "auto", - startValue: "random", - destroy: "none", - }, - }, - stroke: { - width: 0, - }, - zIndex: { - value: 0, - opacityRate: 1, - sizeRate: 1, - velocityRate: 1, - }, - destroy: { - bounds: {}, - mode: "none", - split: { - count: 1, - factor: { - value: 3, - }, - rate: { - value: { - min: 4, - max: 9, - }, - }, - sizeOffset: true, - }, - }, - roll: { - darken: { - enable: false, - value: 0, - }, - enable: false, - enlighten: { - enable: false, - value: 0, - }, - mode: "vertical", - speed: 25, - }, - tilt: { - value: 0, - animation: { - enable: false, - speed: 0, - decay: 0, - sync: false, - }, - direction: "clockwise", - enable: false, - }, - twinkle: { - lines: { - enable: false, - frequency: 0.05, - opacity: 1, - }, - particles: { - enable: false, - frequency: 0.05, - opacity: 1, - }, - }, - wobble: { - distance: 5, - enable: false, - speed: { - angle: 50, - move: 10, - }, - }, - life: { - count: 0, - delay: { - value: 0, - sync: false, - }, - duration: { - value: 0, - sync: false, - }, - }, - rotate: { - value: 0, - animation: { - enable: false, - speed: 0, - decay: 0, - sync: false, - }, - direction: "clockwise", - path: false, - }, - orbit: { - animation: { - count: 0, - enable: false, - speed: 1, - decay: 0, - delay: 0, - sync: false, - }, - enable: false, - opacity: 1, - rotation: { - value: 45, - }, - width: 1, - }, - links: { - blink: false, - color: { - value: "#fff", - }, - consent: false, - distance: 100, - enable: false, - frequency: 1, - opacity: 1, - shadow: { - blur: 5, - color: { - value: "#000", - }, - enable: false, - }, - triangles: { - enable: false, - frequency: 1, - }, - width: 1, - warp: false, - }, - repulse: { - value: 0, - enabled: false, - distance: 1, - duration: 1, - factor: 1, - speed: 1, - }, - }, - detectRetina: true, - }} - /> - )} - - ); -}; - -``` - Install NPM dependencies: ```bash -framer-motion, @tabler/icons-react, @tsparticles/slim, @tsparticles/react, @tsparticles/engine +framer-motion ``` Implementation Guidelines diff --git a/frontend/public/images/After.png b/frontend/public/images/After.png index c40dffd..9a6efa3 100644 Binary files a/frontend/public/images/After.png and b/frontend/public/images/After.png differ diff --git a/frontend/public/images/Before.png b/frontend/public/images/Before.png index 9a6efa3..c40dffd 100644 Binary files a/frontend/public/images/Before.png and b/frontend/public/images/Before.png differ diff --git a/frontend/public/images/Framemobile.png b/frontend/public/images/Framemobile.png new file mode 100644 index 0000000..d77baf6 Binary files /dev/null and b/frontend/public/images/Framemobile.png differ diff --git a/frontend/public/images/Frameweb.png b/frontend/public/images/Frameweb.png new file mode 100644 index 0000000..7b12763 Binary files /dev/null and b/frontend/public/images/Frameweb.png differ diff --git a/frontend/public/images/MobileAfter.png b/frontend/public/images/MobileAfter.png index cf29bfd..840e2e0 100644 Binary files a/frontend/public/images/MobileAfter.png and b/frontend/public/images/MobileAfter.png differ diff --git a/frontend/public/images/MobileBefore.png b/frontend/public/images/MobileBefore.png index 840e2e0..cf29bfd 100644 Binary files a/frontend/public/images/MobileBefore.png and b/frontend/public/images/MobileBefore.png differ diff --git a/frontend/src/components/ui/HeroScrollDemo.jsx b/frontend/src/components/ui/HeroScrollDemo.jsx new file mode 100644 index 0000000..c04c847 --- /dev/null +++ b/frontend/src/components/ui/HeroScrollDemo.jsx @@ -0,0 +1,40 @@ +"use client"; +import React from "react"; +import { ContainerScroll } from "./container-scroll-animation"; + +export function HeroScrollDemo() { + return ( +
+ +

+ Experience the power of
+ + AutoMaxLib + +

+

+ Transform your GitHub presence with intelligent automation, beautiful analytics, and professional README generation. +

+ + } + > + {/* Desktop Image */} + AutoMaxLib dashboard showing GitHub analytics, commit automation, and README generation tools + {/* Mobile Image */} + AutoMaxLib mobile dashboard +
+
+ ); +} diff --git a/frontend/src/components/ui/ScrollytellingFeatureShowcase.jsx b/frontend/src/components/ui/ScrollytellingFeatureShowcase.jsx new file mode 100644 index 0000000..d9ee07f --- /dev/null +++ b/frontend/src/components/ui/ScrollytellingFeatureShowcase.jsx @@ -0,0 +1,304 @@ +import React, { useState, useEffect, useRef, forwardRef } from 'react' +import { motion, AnimatePresence } from 'framer-motion' + +const ScrollytellingFeatureShowcase = forwardRef(({ features, title, subtitle }, ref) => { + const [activeFeature, setActiveFeature] = useState(0) + const [imageLoaded, setImageLoaded] = useState({}) + const containerRef = useRef(null) + const featureRefs = useRef([]) + const isScrolling = useRef(false) + const lastScrollTime = useRef(0) + const scrollTimeout = useRef(null) + const isContinuousScrolling = useRef(false) + + // Map features to their corresponding images (exact order from LandingPage.jsx) + const getFeatureImage = (index) => { + const imageMap = { + 0: '/Feature_images/Auto_Commit_AI_Scheduling.png', // Auto Commit AI Scheduling ✅ + 1: '/Feature_images/Multi-Repository_Github_Tools.png', // Multi-Repository GitHub Tools ✅ + 2: '/Feature_images/Github_README_AI.png', // GitHub README AI ✅ + 3: '/Feature_images/Contribution_Analytics.png', // Contribution Analytics ✅ + 4: '/Feature_images/Enterprise_Security.png', // Enterprise Security ✅ + 5: '/Feature_images/Goal_Tracking.png', // Goal Tracking ✅ + 6: '/Feature_images/Analytics_Dashboard.png', // Analytics Dashboard ✅ + 7: '/Feature_images/Smart Notifications.png', // Smart Notifications ✅ + 8: '/Feature_images/README_Generation.png', // README Generation ✅ + } + return imageMap[index] || '/Feature_images/Analytics_Dashboard.png' // Default fallback + } + + // Preload images + useEffect(() => { + features.forEach((_, index) => { + const img = new Image() + img.src = getFeatureImage(index) + img.onload = () => { + setImageLoaded(prev => ({ ...prev, [index]: true })) + } + }) + }, [features]) + + useEffect(() => { + const observers = [] + + featureRefs.current.forEach((featureRef, index) => { + if (featureRef) { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setActiveFeature(index) + } + }, + { + root: null, + rootMargin: '-30% 0px -30% 0px', + threshold: [0.3, 0.7] + } + ) + + observer.observe(featureRef) + observers.push(observer) + } + }) + + // Scroll snap behavior for both wheel and keyboard + const scrollToFeature = (index) => { + if (featureRefs.current[index]) { + featureRefs.current[index].scrollIntoView({ + behavior: 'smooth', + block: 'center' + }) + } + } + + const handleWheel = (e) => { + const now = Date.now() + const timeSinceLastScroll = now - lastScrollTime.current + + // Detect if user is continuously scrolling (fast) or deliberate scrolling (slow) + if (timeSinceLastScroll < 150) { + isContinuousScrolling.current = true + // Allow normal scrolling for continuous/fast scrolling + return + } + + // For deliberate/slow scrolling, use snap behavior + if (isScrolling.current) return + + e.preventDefault() + isScrolling.current = true + lastScrollTime.current = now + isContinuousScrolling.current = false + + const direction = e.deltaY > 0 ? 1 : -1 + const nextIndex = Math.max(0, Math.min(features.length - 1, activeFeature + direction)) + + if (nextIndex !== activeFeature) { + scrollToFeature(nextIndex) + } + + setTimeout(() => { + isScrolling.current = false + }, 800) + } + + // Auto-snap when user stops scrolling + const handleScroll = () => { + if (isScrolling.current) return + + clearTimeout(scrollTimeout.current) + + scrollTimeout.current = setTimeout(() => { + if (!isContinuousScrolling.current) return + + // Find the closest feature to current scroll position + let closestIndex = 0 + let minDistance = Infinity + + featureRefs.current.forEach((ref, index) => { + if (ref) { + const rect = ref.getBoundingClientRect() + const distance = Math.abs(rect.top + rect.height / 2 - window.innerHeight / 2) + if (distance < minDistance) { + minDistance = distance + closestIndex = index + } + } + }) + + // Auto-snap to closest feature + if (closestIndex !== activeFeature) { + scrollToFeature(closestIndex) + } + + isContinuousScrolling.current = false + }, 500) // Wait 500ms after user stops scrolling + } + + const handleKeyDown = (e) => { + if (isScrolling.current) return + + let nextIndex = activeFeature + + if (e.key === 'ArrowDown' || e.key === 'PageDown') { + e.preventDefault() + nextIndex = Math.min(features.length - 1, activeFeature + 1) + } else if (e.key === 'ArrowUp' || e.key === 'PageUp') { + e.preventDefault() + nextIndex = Math.max(0, activeFeature - 1) + } else { + return + } + + if (nextIndex !== activeFeature) { + isScrolling.current = true + scrollToFeature(nextIndex) + + setTimeout(() => { + isScrolling.current = false + }, 600) + } + } + + const container = containerRef.current + if (container) { + container.addEventListener('wheel', handleWheel, { passive: false }) + window.addEventListener('scroll', handleScroll, { passive: true }) + document.addEventListener('keydown', handleKeyDown) + } + + return () => { + observers.forEach(observer => observer.disconnect()) + clearTimeout(scrollTimeout.current) + if (container) { + container.removeEventListener('wheel', handleWheel) + window.removeEventListener('scroll', handleScroll) + document.removeEventListener('keydown', handleKeyDown) + } + } + }, [activeFeature, features.length]) + + return ( +
+
+ {/* Header */} +
+

+ {title} +

+

+ {subtitle} +

+
+ + {/* Two Column Layout */} +
+ {/* Content Column - Scrollable */} +
+ {features.map((feature, index) => ( +
featureRefs.current[index] = el} + className={`transition-all duration-700 ease-in-out transform ${ + activeFeature === index + ? 'opacity-100 scale-100 translate-y-0' + : 'opacity-30 scale-95 translate-y-4' + }`} + > +
+
+
+ +
+
+

+ {feature.title} +

+

+ {feature.description} +

+
+
+
+
+ ))} +
+ + {/* Visual Column - Sticky */} +
+
+ {/* Render all images with opacity transitions */} + {features.map((feature, index) => ( + { + console.error('Failed to load feature image:', getFeatureImage(index)) + e.target.style.display = 'none' + }} + /> + ))} + + {/* Loading fallback */} + {!imageLoaded[activeFeature] && ( +
+
+
+ {React.createElement(features[activeFeature]?.icon, { + className: "w-12 h-12 text-white", + strokeWidth: 1.5 + })} +
+

+ {features[activeFeature]?.title} +

+

+ Loading preview... +

+
+
+ )} +
+
+
+
+
+ ) +}) + +ScrollytellingFeatureShowcase.displayName = 'ScrollytellingFeatureShowcase' + +export default ScrollytellingFeatureShowcase diff --git a/frontend/src/components/ui/animated-testimonials.jsx b/frontend/src/components/ui/animated-testimonials.jsx new file mode 100644 index 0000000..8343499 --- /dev/null +++ b/frontend/src/components/ui/animated-testimonials.jsx @@ -0,0 +1,156 @@ +"use client"; + +import { IconArrowLeft, IconArrowRight } from "@tabler/icons-react"; +import { motion, AnimatePresence } from "framer-motion"; +import { useEffect, useState } from "react"; +import { cn } from "../../utils/cn"; + +export const AnimatedTestimonials = ({ + testimonials, + autoplay = false, + className, +}) => { + const [active, setActive] = useState(0); + + const handleNext = () => { + setActive((prev) => (prev + 1) % testimonials.length); + }; + + const handlePrev = () => { + setActive((prev) => (prev - 1 + testimonials.length) % testimonials.length); + }; + + const isActive = (index) => { + return index === active; + }; + + useEffect(() => { + if (autoplay) { + const interval = setInterval(handleNext, 5000); + return () => clearInterval(interval); + } + }, [autoplay]); + + const randomRotateY = () => { + return Math.floor(Math.random() * 21) - 10; + }; + + return ( +
+
+
+
+ + {testimonials.map((testimonial, index) => ( + + {testimonial.name} + + ))} + +
+
+
+ +

+ {testimonials[active].name} +

+

+ {testimonials[active].designation} +

+ + {testimonials[active].quote.split(" ").map((word, index) => ( + + {word}  + + ))} + +
+
+ + +
+
+
+
+ ); +}; diff --git a/frontend/src/components/ui/container-scroll-animation.jsx b/frontend/src/components/ui/container-scroll-animation.jsx new file mode 100644 index 0000000..7e49f19 --- /dev/null +++ b/frontend/src/components/ui/container-scroll-animation.jsx @@ -0,0 +1,87 @@ +"use client"; +import React, { useRef } from "react"; +import { useScroll, useTransform, motion } from "framer-motion"; + +export const ContainerScroll = ({ + titleComponent, + children, +}) => { + const containerRef = useRef(null); + const { scrollYProgress } = useScroll({ + target: containerRef, + }); + const [isMobile, setIsMobile] = React.useState(false); + + React.useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth <= 768); + }; + checkMobile(); + window.addEventListener("resize", checkMobile); + return () => { + window.removeEventListener("resize", checkMobile); + }; + }, []); + + const scaleDimensions = () => { + return isMobile ? [0.7, 0.9] : [1.05, 1]; + }; + + const rotate = useTransform(scrollYProgress, [0, 1], [20, 0]); + const scale = useTransform(scrollYProgress, [0, 1], scaleDimensions()); + const translate = useTransform(scrollYProgress, [0, 1], [0, -100]); + + return ( +
+
+
+ + {children} + +
+
+ ); +}; + +export const Header = ({ translate, titleComponent }) => { + return ( + + {titleComponent} + + ); +}; + +export const Card = ({ + rotate, + scale, + children, +}) => { + return ( + +
+ {children} +
+
+ ); +}; diff --git a/frontend/src/components/ui/pricing.jsx b/frontend/src/components/ui/pricing.jsx index 7804f08..612cb2c 100644 --- a/frontend/src/components/ui/pricing.jsx +++ b/frontend/src/components/ui/pricing.jsx @@ -78,8 +78,8 @@ export const PricingCard = ({ size="lg" className={cn( "w-full group transition-all duration-300 text-sm font-medium py-3 px-6", - buttonVariant === "default" && "bg-indigo-600 hover:bg-indigo-700 text-white border-0 hover:scale-[1.02] shadow-lg", - buttonVariant === "outline" && "border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800 hover:scale-[1.02]" + buttonVariant === "default" && "bg-indigo-600 hover:bg-indigo-700 text-white dark:text-white border-0 hover:scale-[1.02] shadow-lg dark:bg-indigo-600 dark:hover:bg-indigo-700", + buttonVariant === "outline" && "bg-indigo-600 hover:bg-indigo-700 text-white dark:text-white border-0 hover:scale-[1.02] shadow-lg dark:bg-indigo-600 dark:hover:bg-indigo-700" )} > diff --git a/frontend/src/index.css b/frontend/src/index.css index 202b89c..f920d6a 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1064,6 +1064,23 @@ animation-delay: calc(var(--stagger-delay, 0) * 100ms); } + /* Continuous arrow bounce animation */ + .animate-arrow-bounce { + animation: arrowBounce 2s ease-in-out infinite; + } + + @keyframes arrowBounce { + 0%, 20%, 50%, 80%, 100% { + transform: translateX(0); + } + 40% { + transform: translateX(4px); + } + 60% { + transform: translateX(2px); + } + } + /* Enhanced focus states */ .focus-ring-inset { @apply focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500; diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index 258fa8e..3db70c9 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -3,7 +3,9 @@ import { useAuth } from '@clerk/clerk-react' import { useEffect, useRef, useState } from 'react' import FeatureShowcase from '../components/FeatureShowcase' import { PricingCard } from '../components/ui/pricing' -import { CircularTestimonials } from '../components/ui/circular-testimonials' +import { AnimatedTestimonials } from '../components/ui/animated-testimonials' +import ScrollytellingFeatureShowcase from '../components/ui/ScrollytellingFeatureShowcase' +import { HeroScrollDemo } from '../components/ui/HeroScrollDemo' import { useScrollAnimation } from '../hooks/useScrollAnimation' import { CompareSection } from '../components/CompareShowcase' import SEOHead from '../components/SEOHead' @@ -40,6 +42,7 @@ const LandingPage = () => { const { isSignedIn } = useAuth() const [repositoryUrl, setRepositoryUrl] = useState('') const [validationError, setValidationError] = useState('') + const [counters, setCounters] = useState({ 0: 0, 1: 0, 2: 0, 3: 0 }) // Scroll animation refs const heroRef = useScrollAnimation() @@ -125,13 +128,85 @@ const LandingPage = () => { } ] + const scrollytellingSteps = [ + { + id: "smart-automation", + title: "Smart Commit Automation", + description: "Our AI analyzes your coding patterns and generates meaningful commits that maintain your GitHub streak. Never worry about breaking your consistency again - AutoMaxLib intelligently schedules commits based on your workflow and preferences.", + visualAsset: { + type: "placeholder", + src: "/placeholder-feature-1.jpg", + altText: "Smart commit automation dashboard" + } + }, + { + id: "flexible-scheduling", + title: "Flexible Scheduling", + description: "Set your preferred commit times and let AutoMaxLib adapt to your schedule. Whether you're a night owl or early bird, our intelligent scheduling ensures your commits happen when it makes sense for your workflow.", + visualAsset: { + type: "placeholder", + src: "/placeholder-feature-2.jpg", + altText: "Flexible scheduling interface" + } + }, + { + id: "advanced-analytics", + title: "Advanced Analytics", + description: "Track your coding patterns, streak statistics, and productivity metrics with beautiful, detailed insights. Understand your development habits and optimize your workflow with data-driven decisions.", + visualAsset: { + type: "placeholder", + src: "/placeholder-feature-3.jpg", + altText: "Advanced analytics dashboard" + } + }, + { + id: "secure-reliable", + title: "Secure & Reliable", + description: "Enterprise-grade security with 99.9% uptime ensures your automation runs smoothly. Your code and data are protected with industry-standard encryption and security practices.", + visualAsset: { + type: "placeholder", + src: "/placeholder-feature-4.jpg", + altText: "Security and reliability features" + } + } + ] + const stats = [ - { number: "100+", label: "Active Developers", icon: Users }, - { number: "1000+", label: "Commits Generated", icon: GitBranch }, - { number: "99.9%", label: "Uptime Reliability", icon: Shield }, - { number: "365", label: "Days Automated", icon: Calendar } + { number: "100+", label: "Active Developers", icon: Users, target: 100 }, + { number: "1000+", label: "Commits Generated", icon: GitBranch, target: 1000 }, + { number: "99.9%", label: "Uptime Reliability", icon: Shield, target: 99.9 }, + { number: "365", label: "Days Automated", icon: Calendar, target: 365 } ] + // Count animation effect + useEffect(() => { + const animateCounters = () => { + stats.forEach((stat, index) => { + const duration = 2000 // 2 seconds + const steps = 60 + const increment = stat.target / steps + let current = 0 + + const timer = setInterval(() => { + current += increment + if (current >= stat.target) { + current = stat.target + clearInterval(timer) + } + + setCounters(prev => ({ + ...prev, + [index]: current + })) + }, duration / steps) + }) + } + + // Start animation after component mounts with delay + const timeout = setTimeout(animateCounters, 1000) + return () => clearTimeout(timeout) + }, []) + const testimonials = [ { quote: "AutoMaxLib has been a game-changer for maintaining my coding consistency. The intelligent scheduling means I never have to worry about breaking my streak, even during busy periods.", @@ -161,7 +236,7 @@ const LandingPage = () => { period: "15 days", description: "Perfect for testing the automated commit experience", buttonText: "Start 15-Day Free Trial", - buttonVariant: "outline", + buttonVariant: "default", cta: "Start 15-Day Free Trial", popular: false, features: [ @@ -244,8 +319,10 @@ const LandingPage = () => { />
{/* Hero Section */} -
-
+
+ {/* Left Side Gradient Glow */} +
+
{/* Main Headline */}

@@ -263,10 +340,10 @@ const LandingPage = () => { {isSignedIn ? ( Go to Dashboard - + ) : ( <> @@ -296,8 +373,10 @@ const LandingPage = () => { className="text-center transform transition-all duration-300 hover:scale-105 animate-fade-in-up" style={{ animationDelay: `${0.8 + index * 0.1}s` }} > -
- {stat.number} +
+ {index === 2 ? `${counters[index].toFixed(1)}%` : + index === 0 || index === 1 ? `${Math.floor(counters[index])}+` : + Math.floor(counters[index])}
{stat.label} @@ -310,46 +389,16 @@ const LandingPage = () => {

- {/* Features Section */} -
-
-
-

- Everything You Need to Succeed -

-

- From intelligent automation to beautiful analytics, AutoMaxLib provides all the tools - you need to maintain consistency and build a professional developer profile. -

-
+ {/* Features Section - Scrollytelling */} + -
- {features.map((feature, index) => ( -
-
-
-
- -
-
-

- {feature.title} -

-

- {feature.description} -

-
-
-
-
- ))} -
-
-
+ {/* Hero Scroll Animation Section */} + {/* Repository README Generation Section */}
@@ -504,7 +553,7 @@ const LandingPage = () => { {/* Testimonials Section */}
-
+
What Developers Say @@ -519,25 +568,10 @@ const LandingPage = () => {

-
@@ -631,7 +665,7 @@ const LandingPage = () => { Sign In diff --git a/query b/query new file mode 100644 index 0000000..df37c12 --- /dev/null +++ b/query @@ -0,0 +1 @@ +MongoDB