1- import React , { ReactNode , useState , useEffect } from "react" ;
1+ import React , { ReactNode , useState , useEffect , useRef } from "react" ;
22import Layout from "@theme/Layout" ;
33import Link from "@docusaurus/Link" ;
44import useDocusaurusContext from "@docusaurus/useDocusaurusContext" ;
55import { motion , useAnimation , useInView } from "framer-motion" ;
6+ import Head from '@docusaurus/Head' ;
67import styles from "./styles.module.css" ;
7- import Head from "@docusaurus/Head" ;
88
99// Type definitions
1010interface AnimatedSectionProps {
@@ -30,15 +30,18 @@ interface LearningPathProps {
3030 description : string ;
3131 icon : string ;
3232 color : string ;
33+ isCompleted ?: boolean ;
34+ onToggleComplete ?: ( ) => void ;
35+ index ?: number ;
3336}
3437
3538// Animated component for staggered animations
3639const AnimatedSection = ( { children, delay = 0 } : AnimatedSectionProps ) => {
3740 const controls = useAnimation ( ) ;
38- const ref = React . useRef < HTMLDivElement > ( null ) ;
41+ const ref = useRef < HTMLDivElement > ( null ) ;
3942 const isInView = useInView ( ref , { once : true } ) ;
4043
41- React . useEffect ( ( ) => {
44+ useEffect ( ( ) => {
4245 if ( isInView ) {
4346 controls . start ( {
4447 opacity : 1 ,
@@ -256,10 +259,10 @@ function Feature({ title, description, icon, color }: FeatureProps) {
256259 const [ isHovered , setIsHovered ] = useState ( false ) ;
257260
258261 // Create a ref to apply the custom property
259- const featureRef = React . useRef < HTMLElement > ( null ) ;
262+ const featureRef = useRef < HTMLElement > ( null ) ;
260263
261264 // Apply the color as a CSS variable when the component mounts or color changes
262- React . useEffect ( ( ) => {
265+ useEffect ( ( ) => {
263266 if ( featureRef . current ) {
264267 featureRef . current . style . setProperty ( '--feature-color' , color ) ;
265268 }
@@ -354,7 +357,15 @@ function StatCard({ value, label, index = 0 }: StatCardProps) {
354357 ) ;
355358}
356359
357- const LearningPath = ( { title, description, icon, color, index } : LearningPathProps & { index : number } ) => {
360+ const LearningPath = ( {
361+ title,
362+ description,
363+ icon,
364+ color,
365+ index = 0 ,
366+ isCompleted = false ,
367+ onToggleComplete = ( ) => { }
368+ } : LearningPathProps ) => {
358369 const isEven = index % 2 === 0 ;
359370 const delay = index * 0.15 ;
360371
@@ -363,7 +374,7 @@ const LearningPath = ({ title, description, icon, color, index }: LearningPathPr
363374
364375 return (
365376 < motion . article
366- className = { `${ styles . pathCard } ${ isEven ? styles . left : styles . right } group` }
377+ className = { `${ styles . pathCard } ${ isEven ? styles . left : styles . right } group relative ` }
367378 style = { {
368379 '--card-color' : color || '#3b82f6' ,
369380 '--card-color-light' : color ? `${ color } 20` : 'rgba(59, 130, 246, 0.2)' ,
@@ -373,10 +384,14 @@ const LearningPath = ({ title, description, icon, color, index }: LearningPathPr
373384 transform : isEven ? 'none' : 'translateX(50%)' ,
374385 marginLeft : isEven ? '0' : 'auto' ,
375386 marginRight : isEven ? 'auto' : '0' ,
387+ opacity : isCompleted ? 0.9 : 1 ,
388+ filter : isCompleted ? 'saturate(0.9)' : 'none' ,
389+ transition : 'all 0.3s ease-in-out' ,
390+ border : isCompleted ? `1px solid ${ color } 40` : '1px solid rgba(255, 255, 255, 0.1)'
376391 } as React . CSSProperties }
377392 initial = { { opacity : 0 , x : isEven ? - 100 : 100 , y : 20 } }
378393 animate = { {
379- opacity : 1 ,
394+ opacity : 1 ,
380395 x : 0 ,
381396 y : 0 ,
382397 transition : {
@@ -389,14 +404,15 @@ const LearningPath = ({ title, description, icon, color, index }: LearningPathPr
389404 >
390405 < div className = "flex items-start gap-4" >
391406 < motion . div
392- className = "flex-shrink-0 w-14 h-14 flex items-center justify-center rounded-xl text-2xl"
407+ className = "flex-shrink-0 w-14 h-14 flex items-center justify-center rounded-xl text-2xl relative "
393408 style = { {
394409 background : `linear-gradient(135deg, ${ color || '#3b82f6' } , ${ color ? `${ color } 80` : '#2563eb' } )` ,
395- boxShadow : `0 4px 15px -5px ${ color || '#3b82f6' } 80`
410+ boxShadow : `0 4px 15px -5px ${ color || '#3b82f6' } 80` ,
411+ opacity : isCompleted ? 0.9 : 1
396412 } }
397413 whileHover = { {
398414 scale : 1.1 ,
399- rotate : 5 ,
415+ rotate : isCompleted ? 0 : 5 ,
400416 transition : { duration : 0.2 }
401417 } }
402418 >
@@ -405,33 +421,131 @@ const LearningPath = ({ title, description, icon, color, index }: LearningPathPr
405421 < div className = "flex-1" >
406422 < h3 className = "text-xl font-bold mb-2 text-white" > { title } </ h3 >
407423 < p className = "text-gray-300 mb-4 leading-relaxed" > { description } </ p >
408- < Link
409- to = "/docs"
410- className = "inline-flex items-center text-sm font-medium text-blue-400 hover:text-blue-300 transition-colors group-hover:underline"
411- >
412- Start Learning
413- < svg
414- className = "w-4 h-4 ml-2 transition-transform duration-300 group-hover:translate-x-1"
415- fill = "none"
416- viewBox = "0 0 24 24"
417- stroke = "currentColor"
424+
425+ < div className = "flex items-center justify-between mt-4 pt-3 border-t border-gray-700/50" >
426+ < Link
427+ to = "/docs"
428+ className = "inline-flex items-center text-sm font-medium text-blue-400 hover:text-blue-300 transition-colors group-hover:underline"
418429 >
419- < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M9 5l7 7-7 7" />
420- </ svg >
421- </ Link >
430+ { isCompleted ? 'Continue Learning' : 'Start Learning' }
431+ < svg
432+ className = "w-4 h-4 ml-2 transition-transform duration-300 group-hover:translate-x-1"
433+ fill = "none"
434+ viewBox = "0 0 24 24"
435+ stroke = "currentColor"
436+ >
437+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M9 5l7 7-7 7" />
438+ </ svg >
439+ </ Link >
440+
441+ { /* Completion Toggle */ }
442+ < button
443+ onClick = { ( e ) => {
444+ e . preventDefault ( ) ;
445+ onToggleComplete ( ) ;
446+ } }
447+ className = { `relative inline-flex items-center h-6 rounded-full w-11 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 ${
448+ isCompleted
449+ ? 'bg-green-500 focus:ring-green-500'
450+ : 'bg-gray-600 focus:ring-blue-500'
451+ } `}
452+ aria-pressed = { isCompleted }
453+ aria-label = { isCompleted ? 'Mark as incomplete' : 'Mark as complete' }
454+ >
455+ < span className = "sr-only" >
456+ { isCompleted ? 'Mark as incomplete' : 'Mark as complete' }
457+ </ span >
458+ < span
459+ className = { `${
460+ isCompleted ? 'translate-x-6' : 'translate-x-1'
461+ } inline-block w-4 h-4 transform bg-white rounded-full transition-transform duration-200 ease-in-out`}
462+ >
463+ { isCompleted && (
464+ < svg
465+ className = "w-3 h-3 text-green-500 mx-auto mt-0.5"
466+ fill = "currentColor"
467+ viewBox = "0 0 20 20"
468+ xmlns = "http://www.w3.org/2000/svg"
469+ >
470+ < path
471+ fillRule = "evenodd"
472+ d = "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
473+ clipRule = "evenodd"
474+ />
475+ </ svg >
476+ ) }
477+ </ span >
478+ </ button >
479+ </ div >
422480 </ div >
423481 </ div >
482+
483+ { /* Progress bar background */ }
484+ < div className = "absolute bottom-0 left-0 right-0 h-1 bg-gray-700/50 rounded-b-lg overflow-hidden" >
485+ < motion . div
486+ className = "h-full bg-green-500"
487+ initial = { { width : '0%' } }
488+ animate = { { width : isCompleted ? '100%' : '0%' } }
489+ transition = { { duration : 0.5 , ease : 'easeOut' } }
490+ />
491+ </ div >
492+
493+ { /* Completion badge */ }
494+ { isCompleted && (
495+ < div className = "absolute -top-2 -right-2 w-8 h-8 bg-green-500 rounded-full flex items-center justify-center shadow-lg" >
496+ < svg className = "w-4 h-4 text-white" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" >
497+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 3 } d = "M5 13l4 4L19 7" />
498+ </ svg >
499+ </ div >
500+ ) }
424501 </ motion . article >
425502 ) ;
426503} ;
427504
428505export default function GetStarted ( ) {
429506 const { siteConfig } = useDocusaurusContext ( ) ;
430- const currentYear = new Date ( ) . getFullYear ( ) ;
507+ type CompletedPaths = Record < string , boolean > ;
508+
509+ const [ completedPaths , setCompletedPaths ] = useState < CompletedPaths > ( ( ) => {
510+ if ( typeof window !== 'undefined' ) {
511+ try {
512+ const saved = localStorage . getItem ( 'completedPaths' ) ;
513+ return saved ? JSON . parse ( saved ) : { } ;
514+ } catch ( e ) {
515+ console . error ( 'Failed to parse completedPaths from localStorage' , e ) ;
516+ return { } ;
517+ }
518+ }
519+ return { } ;
520+ } ) ;
521+
522+ // Toggle completion status for a path
523+ const togglePathCompletion = React . useCallback ( ( pathTitle : string ) => {
524+ setCompletedPaths ( prev => ( {
525+ ...prev ,
526+ [ pathTitle ] : ! prev [ pathTitle ]
527+ } ) ) ;
528+ } , [ ] ) ;
529+
530+ // Calculate completion percentage
531+ const completionPercentage = Math . round (
532+ ( Object . values ( completedPaths ) . filter ( Boolean ) . length / learningPaths . length ) * 100
533+ ) || 0 ;
534+
535+ // Save to localStorage whenever completedPaths changes
536+ useEffect ( ( ) => {
537+ if ( typeof window !== 'undefined' ) {
538+ try {
539+ localStorage . setItem ( 'completedPaths' , JSON . stringify ( completedPaths ) ) ;
540+ } catch ( e ) {
541+ console . error ( 'Failed to save completedPaths to localStorage' , e ) ;
542+ }
543+ }
544+ } , [ completedPaths ] ) ;
431545
432546 return (
433547 < Layout
434- title = { `Get Started | ${ siteConfig . title } ` }
548+ title = { `Get Started | ${ siteConfig ? .title || 'Recode Hive' } ` }
435549 description = "Start your coding journey with Recode Hive. Learn to code with our interactive platform and structured learning paths."
436550 >
437551 < Head >
@@ -517,19 +631,83 @@ export default function GetStarted() {
517631 </ motion . h2 >
518632 </ div >
519633
520- < div className = { styles . timelineContainer } >
521- < div className = { styles . timeline } > </ div >
522- < div className = { styles . pathsContainer } >
523- { learningPaths . map ( ( path , idx ) => (
524- < LearningPath
525- key = { path . title }
526- title = { path . title }
527- description = { path . description }
528- icon = { path . icon }
529- color = { path . color }
530- index = { idx }
634+ < div className = "relative" >
635+ { /* Progress Overview */ }
636+ < motion . div
637+ className = "mb-12 p-6 bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-gray-700/50 shadow-lg"
638+ initial = { { opacity : 0 , y : 20 } }
639+ whileInView = { { opacity : 1 , y : 0 } }
640+ viewport = { { once : true , margin : "-50px 0px -50px 0px" } }
641+ transition = { { duration : 0.5 , delay : 0.2 } }
642+ >
643+ < div className = "flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-4" >
644+ < div >
645+ < h3 className = "text-xl font-bold text-white mb-1" > Your Learning Progress</ h3 >
646+ < p className = "text-gray-300 text-sm" >
647+ { completionPercentage } % Complete • { Object . values ( completedPaths ) . filter ( Boolean ) . length } of { learningPaths . length } paths started
648+ </ p >
649+ </ div >
650+ < div className = "text-right" >
651+ < span className = "text-2xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent" >
652+ { completionPercentage } %
653+ </ span >
654+ </ div >
655+ </ div >
656+
657+ { /* Progress Bar */ }
658+ < div className = "w-full h-3 bg-gray-700/50 rounded-full overflow-hidden" >
659+ < motion . div
660+ className = "h-full bg-gradient-to-r from-blue-500 to-purple-500 rounded-full relative"
661+ initial = { { width : 0 } }
662+ animate = { { width : `${ completionPercentage } %` } }
663+ transition = { { duration : 1 , ease : "easeOut" } }
664+ >
665+ < motion . div
666+ className = "absolute inset-0 bg-white/20"
667+ animate = { {
668+ left : [ '0%' , '100%' ]
669+ } }
670+ transition = { {
671+ duration : 1.5 ,
672+ repeat : Infinity ,
673+ repeatType : 'loop' ,
674+ ease : 'easeInOut' ,
675+ } }
676+ style = { {
677+ width : '20%' ,
678+ height : '100%' ,
679+ background : 'linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent)' ,
680+ transform : 'skewX(-20deg)'
681+ } }
682+ />
683+ </ motion . div >
684+ </ div >
685+ </ motion . div >
686+
687+ < div className = { styles . timelineContainer } >
688+ < div className = { styles . timeline } >
689+ { /* Animated progress indicator on the timeline */ }
690+ < motion . div
691+ className = "absolute left-0 top-0 w-1 bg-gradient-to-b from-blue-400 to-purple-500 h-0"
692+ initial = { { height : 0 } }
693+ animate = { { height : `${ ( completionPercentage / 100 ) * 100 } %` } }
694+ transition = { { duration : 1.5 , ease : 'easeInOut' } }
531695 />
532- ) ) }
696+ </ div >
697+ < div className = { styles . pathsContainer } >
698+ { learningPaths . map ( ( path , idx ) => (
699+ < LearningPath
700+ key = { path . title }
701+ title = { path . title }
702+ description = { path . description }
703+ icon = { path . icon }
704+ color = { path . color }
705+ index = { idx }
706+ isCompleted = { ! ! completedPaths [ path . title ] }
707+ onToggleComplete = { ( ) => togglePathCompletion ( path . title ) }
708+ />
709+ ) ) }
710+ </ div >
533711 </ div >
534712 </ div >
535713
0 commit comments