Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Features } from "@/components/sections/features";
import { Architecture } from "@/components/sections/architecture";
import { TechStack } from "@/components/sections/tech-stack";
import { Roadmap } from "@/components/sections/roadmap";
import { AlgorithmChecklist } from "@/components/sections/algorithm-checklist";
import { FAQ } from "@/components/sections/faq";
import { OpenSource } from "@/components/sections/open-source";
import { Footer } from "@/components/sections/footer";
Expand All @@ -24,6 +25,7 @@ export default function Home() {
<Architecture />
<TechStack />
<Roadmap />
<AlgorithmChecklist />
<FAQ />
<OpenSource />
</main>
Expand Down
42 changes: 36 additions & 6 deletions apps/web/src/components/animations/array-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,40 @@
import { motion } from "framer-motion";
import { useEffect, useState } from "react";

const array = [2, 5, 8, 11, 12, 16, 23, 28, 38, 56, 60, 67, 72, 81, 91, 99, 123, 212];
const DESKTOP_ARRAY = [2, 5, 8, 11, 12, 16, 23, 28, 38, 56, 60, 67, 72, 81, 91, 99, 123, 212];
const MOBILE_ARRAY = [67, 69, 70, 77, 81, 98];

export function ArraySearch() {
const [array, setArray] = useState(DESKTOP_ARRAY);
const target = 67;
const [currentIndex, setCurrentIndex] = useState(-1);
const [left, setLeft] = useState(0);
const [right, setRight] = useState(array.length - 1);
const [right, setRight] = useState(DESKTOP_ARRAY.length - 1);
const [found, setFound] = useState(false);

// Handle responsive array
useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 768) {
setArray(MOBILE_ARRAY);
} else {
setArray(DESKTOP_ARRAY);
}
};

// Initial check
handleResize();

window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);

useEffect(() => {
let isCancelled = false;

const animateBinarySearch = async () => {
if (isCancelled) return;

setCurrentIndex(-1);
setLeft(0);
setRight(array.length - 1);
Expand All @@ -23,6 +46,8 @@ export function ArraySearch() {
let r = array.length - 1;

while (l <= r) {
if (isCancelled) return;

const mid = Math.floor((l + r) / 2);
setCurrentIndex(mid);
setLeft(l);
Expand All @@ -39,14 +64,19 @@ export function ArraySearch() {
}
}

await new Promise((resolve) => setTimeout(resolve, 2500));
if (!isCancelled) {
await new Promise((resolve) => setTimeout(resolve, 2500));
}
};

const interval = setInterval(animateBinarySearch, 8000);
animateBinarySearch();

return () => clearInterval(interval);
}, []);
return () => {
isCancelled = true;
clearInterval(interval);
};
}, [array]); // Restart animation when array changes

return (
<div className="space-y-3">
Expand All @@ -58,7 +88,7 @@ export function ArraySearch() {

return (
<motion.div
key={index}
key={`${array.length}-${index}`} // Key changes when array changes to force re-render
className={`w-8 h-8 flex items-center justify-center rounded-md text-xs font-semibold border transition-colors ${isFound
? "bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))] border-[hsl(var(--primary))]"
: isCurrent
Expand Down
272 changes: 272 additions & 0 deletions apps/web/src/components/sections/algorithm-checklist.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
"use client";

import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
import { ChevronDown, ChevronRight, Check, Circle, Terminal, Sparkles, Rocket } from "lucide-react";
import { categories, getTotalCount, getCategoryCount, type Category, type AlgorithmTopic } from "@/lib/algorithms-data";

interface SubcategoryProps {
name: string;
topics: AlgorithmTopic[];
}

function Subcategory({ name, topics }: SubcategoryProps) {
const [isOpen, setIsOpen] = useState(false);
const implementedCount = topics.filter(t => t.implemented).length;

return (
<div className="space-y-1">
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full text-left text-[hsl(var(--foreground))]/80 text-xs sm:text-sm font-medium flex items-center gap-2 hover:text-[hsl(var(--foreground))] transition-colors py-1"
>
{isOpen ? (
<ChevronDown className="h-3 w-3 text-[hsl(var(--muted-foreground))]" />
) : (
<ChevronRight className="h-3 w-3 text-[hsl(var(--muted-foreground))]" />
)}
<span className="cursor-pointer">{name}</span>
<span className="text-[10px] font-mono text-[hsl(var(--muted-foreground))] ml-auto">
{implementedCount}/{topics.length}
</span>
</button>

<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.2 }}
className="ml-5 space-y-0.5 overflow-hidden"
>
{topics.map((item, index) => (
<div
key={index}
className={`group flex items-center justify-between text-xs transition-colors duration-200 py-0.5 ${item.implemented
? "text-[hsl(var(--primary))]"
: "text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))]"
}`}
>
<span className="flex items-center gap-2 cursor-default">
<span className="text-[hsl(var(--muted-foreground))]/50 ">
{index === topics.length - 1 ? "└─" : "├─"}
</span>
{item.name}
</span>
<span title={item.implemented ? "Implemented" : "Pending"}>
{item.implemented ? (
<Check className="h-3 w-3" />
) : (
<Circle className="h-3 w-3" />
)}
</span>
</div>
))}
</motion.div>
)}
</AnimatePresence>
</div>
);
}

interface CategorySectionProps {
category: Category;
visible: boolean;
setVisible: (visible: boolean) => void;
}

function CategorySection({ category, visible, setVisible }: CategorySectionProps) {
const { total, implemented } = getCategoryCount(category);

return (
<div className="w-full">
<button
className="w-full text-left text-[hsl(var(--primary))] text-sm sm:text-base font-semibold mb-4 cursor-pointer hover:text-[hsl(var(--primary))]/80 flex items-center gap-2 transition-colors"
onClick={() => setVisible(!visible)}
>
{visible ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)}
<span>{category.title}</span>
<span className="text-xs font-mono text-[hsl(var(--muted-foreground))] ml-auto">
{implemented}/{total}
</span>
</button>

<AnimatePresence>
{visible && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
className="ml-4 space-y-2 overflow-hidden"
>
{Object.entries(category.items).map(([topic, { topics }]) => (
<Subcategory key={topic} name={topic} topics={topics} />
))}
</motion.div>
)}
</AnimatePresence>
</div>
);
}

export function AlgorithmChecklist() {
const [dsVisible, setDsVisible] = useState(true);
const [algoVisible, setAlgoVisible] = useState(true);
const { total, implemented } = getTotalCount();
const progress = Math.round((implemented / total) * 100);

return (
<section className="relative py-24 sm:py-32 overflow-hidden">
{/* Background */}
<div className="absolute inset-0 bg-[hsl(var(--background))]/90" />
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-[hsl(var(--primary))]/5 to-transparent" />
<div className="absolute inset-0 grid-pattern opacity-20" />

{/* Noise overlay */}
<div className="noise absolute inset-0">
<div className="absolute inset-0" />
</div>

<div className="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* Section header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="text-center mb-12 sm:mb-16"
>
<div className="flex items-center justify-center gap-2 mb-6">
<Rocket className="h-4 w-4 text-[hsl(var(--primary))]" />
<span className="mono-label">/ Implementation Roadmap</span>
</div>

<h2 className="text-3xl sm:text-4xl lg:text-5xl font-extrabold tracking-tight text-[hsl(var(--foreground))]">
THE <span className="text-[hsl(var(--primary))]">AMBITIOUS</span> LIST
</h2>

<p className="mt-4 text-sm sm:text-base text-[hsl(var(--muted-foreground))] max-w-2xl mx-auto">
{total}+ algorithms and data structures. This is what we&apos;re building together.
Pick one and contribute, or suggest new ones. ML/DL, algorithms from other fields, and visualizers are all welcome.
</p>

{/* Progress bar */}
<div className="mt-8 max-w-md mx-auto">
<div className="flex items-center justify-between text-xs font-mono text-[hsl(var(--muted-foreground))] mb-2">
<span>Progress</span>
<span>{implemented}/{total} ({progress}%)</span>
</div>
<div className="h-2 w-full rounded-full bg-[hsl(var(--secondary))] overflow-hidden">
<motion.div
className="h-full rounded-full bg-[hsl(var(--primary))]"
initial={{ width: 0 }}
whileInView={{ width: `${progress}%` }}
viewport={{ once: true }}
transition={{ duration: 1, delay: 0.3 }}
/>
</div>
</div>
</motion.div>

{/* Terminal-style container */}
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
className="rounded-2xl border border-[hsl(var(--border))] bg-[hsl(0_0%_4%)]/90 overflow-hidden shadow-2xl shadow-black/50"
>
{/* Terminal header */}
<div className="flex items-center justify-between px-4 py-3 border-b border-[hsl(var(--border))] bg-[hsl(0_0%_6%)]">
<div className="flex items-center gap-2">
<div className="flex gap-1.5">
<div className="h-3 w-3 rounded-full bg-red-500/80" />
<div className="h-3 w-3 rounded-full bg-yellow-500/80" />
<div className="h-3 w-3 rounded-full bg-green-500/80" />
</div>
<Terminal className="h-4 w-4 text-[hsl(var(--muted-foreground))] ml-2" />
</div>
<span className="text-xs font-mono text-[hsl(var(--muted-foreground))]">
opendsa@roadmap ~ $
</span>
</div>

{/* Terminal content */}
<div className="p-4 sm:p-6">
{/* Header banner */}
<div className="text-center mb-6 pb-4 border-b border-[hsl(var(--border))]/50">
<div className="flex items-center justify-center gap-2 text-[hsl(var(--primary))] font-bold text-lg sm:text-xl">
<Sparkles className="h-5 w-5" />
OpenDSA Implementation Roadmap
<Sparkles className="h-5 w-5" />
</div>
<p className="text-xs text-[hsl(var(--muted-foreground))] mt-2">
Click on any category to expand/collapse
</p>
</div>

{/* Two columns */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-8">
<div className="lg:border-r lg:border-[hsl(var(--border))]/30 lg:pr-8">
<CategorySection
category={categories.dataStructures}
visible={dsVisible}
setVisible={setDsVisible}
/>
</div>

<div>
<CategorySection
category={categories.algorithms}
visible={algoVisible}
setVisible={setAlgoVisible}
/>
</div>
</div>

{/* Legend */}
<div className="mt-6 pt-4 border-t border-[hsl(var(--border))]/50 flex flex-wrap items-center gap-4 text-xs text-[hsl(var(--muted-foreground))]">
<span>Status:</span>
<span className="flex items-center gap-1.5 text-[hsl(var(--primary))]">
<Check className="h-3 w-3" /> Implemented
</span>
<span className="flex items-center gap-1.5">
<Circle className="h-3 w-3" /> Pending
</span>
</div>

{/* Cursor */}
<div className="mt-4 text-[hsl(var(--primary))] font-mono text-sm flex items-center gap-1">
<span>$</span>
<motion.span
animate={{ opacity: [1, 0] }}
transition={{ repeat: Infinity, duration: 0.8 }}
className="inline-block w-2 h-4 bg-[hsl(var(--primary))]"
/>
</div>
</div>
</motion.div>

{/* Future plans */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.4 }}
className="mt-8 text-center"
>
<p className="text-xs sm:text-sm text-[hsl(var(--muted-foreground))]">
<span className="text-[hsl(var(--primary))] font-semibold">Open to Contributions:</span>{" "}
Machine Learning & Deep Learning Algorithm Visualizers etc...
</p>
</motion.div>
</div>
</section>
);
}
5 changes: 3 additions & 2 deletions apps/web/src/components/sections/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const footerLinks = {
{ label: "GitHub", href: "https://github.com/soloshun/opendsa" },
{ label: "Discord", href: "#" },
{ label: "Twitter", href: "#" },
{ label: "Support Us ☕", href: "https://docs.opendsa.dev/sponsors" },
],
};

Expand All @@ -46,10 +47,10 @@ export function Footer() {
{/* Gradient from bottom */}
<div className="absolute inset-0 bg-gradient-to-t from-[hsl(var(--primary))]/10 via-transparent to-transparent" />
<div className="absolute bottom-0 left-0 right-0 h-[300px] bg-[radial-gradient(ellipse_at_bottom,_hsl(var(--primary))_0%,_transparent_60%)] opacity-[0.08]" />

{/* Background */}
<div className="absolute inset-0 bg-[hsl(var(--card))]/80" />

{/* Noise overlay */}
<div className="noise absolute inset-0">
<div className="absolute inset-0" />
Expand Down
Loading