Initial CSE website content integration

This commit is contained in:
Neeldhara Misra 2026-06-01 16:31:25 +05:30
commit dfe66b9002
260 changed files with 83153 additions and 0 deletions

View file

@ -0,0 +1,94 @@
---
// Import the global.css file here so that it is included on
// all pages through the use of the <BaseHead /> component.
import '@/styles/global.css';
import { SITE_TITLE, SITE_METADATA } from '../consts';
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description, image } = Astro.props;
const finalTitle = title || SITE_METADATA.title.default;
const finalDescription = description || SITE_METADATA.description;
const finalImage = image || SITE_METADATA.openGraph.images[0].url;
const imageURL = new URL(finalImage, Astro.url);
---
<!-- Global Metadata -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta
name="robots"
content={`${SITE_METADATA.robots.index ? 'index' : 'noindex'}, ${SITE_METADATA.robots.follow ? 'follow' : 'nofollow'}`}
/>
<meta name="keywords" content={SITE_METADATA.keywords.join(', ')} />
<meta name="author" content={SITE_METADATA.authors[0].name} />
<meta name="creator" content={SITE_METADATA.creator} />
<meta name="publisher" content={SITE_METADATA.publisher} />
<!-- Theme Script -->
<script is:inline>
const savedTheme = localStorage.getItem('theme');
const initialTheme = savedTheme || 'light';
document.documentElement.classList.toggle('dark', initialTheme === 'dark');
</script>
<!-- Favicon -->
{
SITE_METADATA.icons.icon.map((icon) => (
<link rel="icon" type={icon.type} sizes={icon.sizes} href={icon.url} />
))
}
{
SITE_METADATA.icons.apple.map((icon) => (
<link rel="apple-touch-icon" sizes={icon.sizes} href={icon.url} />
))
}
{
SITE_METADATA.icons.shortcut.map((icon) => (
<link rel="shortcut icon" href={icon.url} />
))
}
<link rel="sitemap" href="/sitemap-index.xml" />
<link
rel="alternate"
type="application/rss+xml"
title={SITE_TITLE}
href={new URL('rss.xml', Astro.site)}
/>
<meta name="generator" content={Astro.generator} />
<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />
<!-- Primary Meta Tags -->
<title>{finalTitle}</title>
<meta name="title" content={finalTitle} />
<meta name="description" content={finalDescription} />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={Astro.url} />
<meta property="og:site_name" content={SITE_METADATA.openGraph.siteName} />
<meta property="og:title" content={finalTitle} />
<meta property="og:description" content={finalDescription} />
<meta property="og:image" content={imageURL} />
<meta
property="og:image:width"
content={SITE_METADATA.openGraph.images[0].width.toString()}
/>
<meta
property="og:image:height"
content={SITE_METADATA.openGraph.images[0].height.toString()}
/>
<meta property="og:image:alt" content={SITE_METADATA.openGraph.images[0].alt} />
<!-- Twitter -->
<meta property="twitter:card" content={SITE_METADATA.twitter.card} />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:title" content={finalTitle} />
<meta property="twitter:description" content={finalDescription} />
<meta property="twitter:image" content={imageURL} />
<meta property="twitter:creator" content={SITE_METADATA.twitter.creator} />

View file

@ -0,0 +1,411 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { ArrowRight, Plus } from "lucide-react";
import { Fragment, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import z from "zod";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import {
Field,
FieldDescription,
FieldError,
FieldTitle,
} from "@/components/ui/field";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
import { Separator } from "@/components/ui/separator";
import { useMediaQuery } from "@/hooks/useMediaQuery";
import { cn } from "@/lib/utils";
type NewsletterData = {
title?: string;
description?: string;
};
type NewsletterFormProps = NewsletterData;
type FooterLink = {
text: string;
type?: string;
link?: string;
email?: string;
dividerAfter?: boolean;
};
type FooterLinksSection = {
title: string;
id: string;
items: FooterLink[];
};
type SocialIcon = {
title: string;
light: string;
dark: string;
};
type SocialLink = {
link: string;
icon: SocialIcon;
};
type QuickLink = {
label: string;
href: string;
};
interface EcommerceFooter2Props {
newsletter: NewsletterData;
footerLinks: FooterLinksSection[];
socialLinks: SocialLink[];
description: string;
quickLinks?: QuickLink[];
className?: string;
}
interface FooterLinksSectionProps {
sections: FooterLinksSection[];
}
interface SocialMediaSectionProps {
links: SocialLink[];
}
const NEWSLETTER_DATA = {
title: "Subscribe & Get 10% Off Your First Gadget",
description:
"Join our newsletter to receive exclusive deals, tech tips, product launches, and early access to the latest electronics.",
};
const FOOTER_LINKS: FooterLinksSection[] = [
{
title: "The Brand",
id: "the-brand",
items: [
{
text: "Our Story",
link: "#",
},
{
text: "Sustainability",
link: "#",
},
{
text: "Customer Reviews",
link: "#",
},
{
text: "Store Locator",
link: "#",
},
{
text: "Refer a Friend",
link: "#",
},
],
},
{
title: "Help",
id: "help",
items: [
{
text: "Contact Us",
link: "#",
},
{
text: "FAQs",
link: "#",
},
{
text: "Shipping & Tracking",
link: "#",
},
{
text: "Returns & Exchanges",
link: "#",
},
],
},
{
title: "Information",
id: "information",
items: [
{
text: "Terms and Conditions",
link: "#",
},
{
text: "Privacy Policy",
link: "#",
},
{
text: "Warranty Policy",
link: "#",
},
{
text: "Terms of Service",
link: "#",
},
],
},
{
title: "Contact",
id: "contact",
items: [
{
type: "email",
text: "support@techstore.com",
email: "support@techstore.com",
},
],
},
];
const SOCIAL_ICONS = {
facebook: {
title: "Facebook",
light:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/facebook-icon.svg",
dark: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/facebook-icon.svg",
},
x: {
title: "X",
light:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/x.svg",
dark: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/x.svg",
},
instagram: {
title: "Instagram",
light:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/instagram-icon.svg",
dark: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/instagram-icon.svg",
},
};
const SOCIAL_MEDIA_LINKS = [
{
icon: SOCIAL_ICONS.facebook,
link: "#",
},
{
icon: SOCIAL_ICONS.x,
link: "#",
},
{
icon: SOCIAL_ICONS.instagram,
link: "#",
},
];
const DESCRIPTION =
"Get the latest tech updates, exclusive discounts, product launches, and expert tips delivered straight to your inbox. Stay ahead with smarter gadgets.";
const EcommerceFooter2 = ({
newsletter = NEWSLETTER_DATA,
footerLinks = FOOTER_LINKS,
socialLinks = SOCIAL_MEDIA_LINKS,
description = DESCRIPTION,
quickLinks,
className,
}: EcommerceFooter2Props) => {
return (
<footer className={cn("px-0 py-10 md:px-7.5 md:py-12 lg:px-20", className)}>
<div className="container">
{/* Links in 4 columns */}
<div className="mb-10">
<FooterLinksSection sections={footerLinks} />
</div>
<Separator className="mb-10" />
{/* Bottom row: address + socials + newsletter */}
<div className="grid grid-cols-1 gap-10 lg:grid-cols-3">
<div>
<p className="leading-normal font-light max-lg:text-center">
{description}
</p>
</div>
<div className="space-y-5 max-lg:text-center">
<SocialMediaSection links={socialLinks} />
</div>
<div>
<NewsletterSection {...newsletter} />
</div>
</div>
</div>
</footer>
);
};
const newsletterFormSchema = z.object({
email: z.string().email(),
});
type newsletterFormType = z.infer<typeof newsletterFormSchema>;
const NewsletterSection = ({ title, description }: NewsletterFormProps) => {
const form = useForm({
resolver: zodResolver(newsletterFormSchema),
defaultValues: {
email: "",
},
});
const onSubmit = (data: newsletterFormType) => {
console.log(data);
};
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
<Controller
name="email"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<div className="space-y-5">
<div className="space-y-4">
<FieldTitle className="text-lg leading-none font-bold">
{title}
</FieldTitle>
<FieldDescription className="text-sm leading-normal font-light">
{description}
</FieldDescription>
</div>
<InputGroup
aria-invalid={fieldState.invalid}
className="rounded-full"
>
<InputGroupInput
{...field}
aria-invalid={fieldState.invalid}
placeholder="Email Address"
/>
<InputGroupAddon align="inline-end">
<InputGroupButton
type="submit"
size="icon-xs"
variant="default"
className="rounded-full"
>
<ArrowRight />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
</form>
);
};
const FooterLinksSection = ({ sections }: FooterLinksSectionProps) => {
const isDesktop = useMediaQuery("(min-width: 1024px)");
const [value, setValue] = useState("");
if (!sections?.length) return null;
const allAccordionIds = sections.map(({ id }) => id);
const handleOnValueChange = (value: string) => {
setValue(value);
};
if (isDesktop) {
return (
<Accordion
value={allAccordionIds}
type="multiple"
className="lg:grid lg:grid-cols-5 lg:gap-x-10 lg:gap-y-4"
>
<AccordionItems sections={sections} />
</Accordion>
);
} else {
return (
<Accordion
value={value}
type="single"
collapsible={true}
onValueChange={handleOnValueChange}
className="lg:grid lg:grid-cols-5 lg:gap-x-10 lg:gap-y-4"
>
<AccordionItems sections={sections} />
</Accordion>
);
}
};
const AccordionItems = ({ sections }: { sections: FooterLinksSection[] }) => {
return (
<Fragment>
{sections.map(({ id, title, items }) => (
<AccordionItem key={id} value={id} className="border-none">
<AccordionTrigger className="cursor-auto rounded-none pt-0 text-lg leading-none font-bold hover:no-underline max-lg:border-b max-lg:py-4 [&>svg]:hidden">
{title}
<div className="lg:hidden">
<Plus className="size-5" />
</div>
</AccordionTrigger>
<AccordionContent className="pb-0 max-lg:py-4 max-lg:pl-4">
<ul className="space-y-4 lg:space-y-2">
{items.map(({ link, text, type, email, dividerAfter }) => (
<li
className="text-sm leading-tight font-light"
key={`${id}-${text}-${link ?? email ?? type ?? ""}`}
>
{type === "email" && <p className="mb-1.5">Email us at:</p>}
<a
data-type={type}
href={type === "email" ? `mailto:${email}` : link}
className="data-[type=email]:underline data-[type=email]:underline-offset-2"
>
{text}
</a>
{dividerAfter && <hr className="border-border mt-3" />}
</li>
))}
</ul>
</AccordionContent>
</AccordionItem>
))}
</Fragment>
);
};
const SocialMediaSection = ({ links }: SocialMediaSectionProps) => {
return (
<ul className="flex flex-wrap gap-4 max-lg:justify-center">
{links.map(({ icon, link }) => (
<li key={`${icon.title}-${link}`}>
<a
href={link}
target="_blank"
rel="noopener noreferrer"
className="bg-muted hover:bg-muted/70 flex size-10 items-center justify-center rounded-full transition-colors"
>
<img
className="size-4.5 dark:invert"
alt={icon.title}
src={icon.light}
/>
</a>
</li>
))}
</ul>
);
};
export { EcommerceFooter2 };

View file

@ -0,0 +1,33 @@
'use client';
import { useEffect } from 'react';
export function NavigationProvider({
children,
currentPath,
}: {
children: React.ReactNode;
currentPath: string;
}) {
useEffect(() => {
// Reset transition state on initial load
document.documentElement.classList.remove('page-transition');
// Only apply view transitions if browser supports it
if (document.startViewTransition) {
// Remove transition class first to reset animations
document.documentElement.classList.remove('page-transition');
// Force browser to recalculate styles before adding the class back
window.requestAnimationFrame(() => {
document.documentElement.classList.add('page-transition');
document.startViewTransition(() => {
return Promise.resolve();
});
});
}
}, [currentPath]);
return <>{children}</>;
}

View file

@ -0,0 +1,16 @@
import React from 'react';
import { cn } from '@/lib/utils';
const Noise = ({ className }: { className?: string }) => {
return (
<div
className={cn(
'pointer-events-none absolute inset-0 bg-[url(/images/noise.webp)] bg-cover bg-center bg-no-repeat opacity-2',
className,
)}
/>
);
};
export default Noise;

View file

@ -0,0 +1,250 @@
'use client';
import { useEffect, useState, useRef } from 'react';
import { motion as m } from 'motion/react';
import { Button } from '@/components/ui/button';
interface ThemeToggleProps {
className?: string;
}
export function ThemeToggle({ className }: ThemeToggleProps = {}) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const buttonRef = useRef<HTMLButtonElement>(null);
const storageKey = 'theme';
useEffect(() => {
// Get initial theme from localStorage, default to 'dark' if none exists
const savedTheme = localStorage.getItem(storageKey) as
| 'light'
| 'dark'
| null;
const initialTheme = savedTheme || 'light';
setTheme(initialTheme);
document.documentElement.classList.toggle('dark', initialTheme === 'dark');
// Also set the data attribute for Starlight
document.documentElement.setAttribute('data-theme', initialTheme);
// Listen for theme changes
const handleStorageChange = () => {
const newTheme = localStorage.getItem(storageKey) as
| 'light'
| 'dark'
| null;
if (newTheme) {
setTheme(newTheme);
}
};
window.addEventListener('storage', handleStorageChange);
// Listen for direct DOM class and data-theme changes (for immediate updates)
const observer = new MutationObserver(() => {
const isDark = document.documentElement.classList.contains('dark');
const dataTheme = document.documentElement.getAttribute('data-theme');
const currentTheme = dataTheme || (isDark ? 'dark' : 'light');
setTheme(currentTheme as 'light' | 'dark');
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class', 'data-theme'],
});
return () => {
window.removeEventListener('storage', handleStorageChange);
observer.disconnect();
};
}, []);
const shineVariant = {
hidden: {
opacity: 0,
scale: 2,
strokeDasharray: '20, 1000',
strokeDashoffset: 0,
filter: 'blur(0px)',
},
visible: {
opacity: [0, 1, 0],
strokeDashoffset: [0, -50, -100],
filter: ['blur(2px)', 'blur(2px)', 'blur(0px)'],
transition: {
duration: 0.75,
},
},
};
const raysVariants = {
hidden: {
strokeOpacity: 0,
transition: {
staggerChildren: 0.05,
staggerDirection: -1,
},
},
visible: {
strokeOpacity: 1,
transition: {
staggerChildren: 0.05,
},
},
};
const rayVariant = {
hidden: {
pathLength: 0,
opacity: 0,
// Start from center of the circle
scale: 0,
},
visible: {
pathLength: 1,
opacity: 1,
scale: 1,
transition: {
duration: 0.5,
// Customize timing for each property
pathLength: { duration: 0.3 },
opacity: { duration: 0.2 },
scale: { duration: 0.3 },
},
},
};
const toggleTheme = () => {
if (document.startViewTransition) {
// Get the button's position using ref
const rect = buttonRef.current?.getBoundingClientRect();
if (rect) {
// Calculate position relative to viewport
const x = (rect.left + rect.right) / 2;
const y = (rect.top + rect.bottom) / 2;
// Set the CSS variables for the animation
document.documentElement.style.setProperty(
'--x',
`${(x / window.innerWidth) * 100}%`,
);
document.documentElement.style.setProperty(
'--y',
`${(y / window.innerHeight) * 100}%`,
);
}
// Remove page-transition class to avoid conflicts
document.documentElement.classList.remove('page-transition');
// Add theme-transition class
document.documentElement.classList.add('theme-transition');
document
.startViewTransition(() => {
const newTheme = theme === 'dark' ? 'light' : 'dark';
setTheme(newTheme);
document.documentElement.classList.toggle('dark');
// Set both data attribute for Starlight and localStorage
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem(storageKey, newTheme);
})
.finished.then(() => {
// Clean up theme-transition class after all animations complete
setTimeout(() => {
document.documentElement.classList.remove('theme-transition');
}, 600);
})
.catch(() => {
// Fallback cleanup in case of error
document.documentElement.classList.remove('theme-transition');
});
} else {
const newTheme = theme === 'dark' ? 'light' : 'dark';
setTheme(newTheme);
document.documentElement.classList.toggle('dark');
// Set both data attribute for Starlight and localStorage
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem(storageKey, newTheme);
}
};
const sunPath =
'M70 49.5C70 60.8218 60.8218 70 49.5 70C38.1782 70 29 60.8218 29 49.5C29 38.1782 38.1782 29 49.5 29C60 29 69.5 38 70 49.5Z';
const moonPath =
'M70 49.5C70 60.8218 60.8218 70 49.5 70C38.1782 70 29 60.8218 29 49.5C29 38.1782 38.1782 29 49.5 29C39 45 49.5 59.5 70 49.5Z';
return (
<Button
variant="ghost"
size="icon"
onClick={toggleTheme}
data-theme-toggle
ref={buttonRef}
className={className}
>
<m.svg
strokeWidth="4"
strokeLinecap="round"
width={100}
height={100}
viewBox="0 0 100 100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="relative"
>
<m.path
variants={shineVariant}
d={moonPath}
className={'stroke-chart-1 absolute top-0 left-0'}
initial="hidden"
animate={theme === 'dark' ? 'visible' : 'hidden'}
/>
<m.g
variants={raysVariants}
initial="hidden"
animate={theme === 'light' ? 'visible' : 'hidden'}
className="stroke-chart-3 stroke-6"
style={{ strokeLinecap: 'round' }}
>
<m.path
className="origin-center"
variants={rayVariant}
d="M50 2V11"
/>
<m.path variants={rayVariant} d="M85 15L78 22" />
<m.path variants={rayVariant} d="M98 50H89" />
<m.path variants={rayVariant} d="M85 85L78 78" />
<m.path variants={rayVariant} d="M50 98V89" />
<m.path variants={rayVariant} d="M23 78L16 84" />
<m.path variants={rayVariant} d="M11 50H2" />
<m.path variants={rayVariant} d="M23 23L16 16" />
</m.g>
<m.path
d={sunPath}
fill="transparent"
transition={{ duration: 1, type: 'spring' }}
initial={{ fillOpacity: 0, strokeOpacity: 0, d: sunPath }}
animate={{
d: theme === 'dark' ? moonPath : sunPath,
rotate: theme === 'dark' ? -360 : 0,
scale: theme === 'dark' ? 2 : 1,
stroke:
theme === 'dark'
? 'var(--color-chart-1)'
: 'var(--color-chart-3)',
fill:
theme === 'dark'
? 'var(--color-chart-1)'
: 'var(--color-chart-3)',
fillOpacity: 0.35,
strokeOpacity: 1,
transition: { delay: 0.1 },
}}
/>
</m.svg>
</Button>
);
}

View file

@ -0,0 +1,59 @@
'use client';
import { useEffect, useState } from 'react';
import { X } from 'lucide-react';
import { cn } from '@/lib/utils';
export const useBannerVisibility = () => {
const [isBannerVisible, setIsBannerVisible] = useState(true);
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
const bannerDismissed = localStorage.getItem('banner-dismissed');
setIsBannerVisible(bannerDismissed !== 'true');
}, []);
const dismissBanner = () => {
setIsBannerVisible(false);
localStorage.setItem('banner-dismissed', 'true');
};
return { isBannerVisible, dismissBanner, isClient };
};
const Banner = () => {
const { isBannerVisible, dismissBanner, isClient } = useBannerVisibility();
if (!isClient || !isBannerVisible) {
return null;
}
return (
<div className="bg-primary relative">
<div className="container flex items-center justify-between gap-4 py-2.5 pr-12">
<div className="flex flex-1 items-center justify-center gap-3 sm:gap-4">
<span className="text-primary-foreground text-center text-sm font-medium">
This website is under active development.
</span>
</div>
<button
onClick={dismissBanner}
className={cn(
'absolute top-1/2 right-4 -translate-y-1/2 rounded-sm p-1.5',
'text-primary-foreground/70 hover:text-primary-foreground',
'transition-all duration-200 hover:scale-110 hover:bg-white/10',
'focus:ring-2 focus:ring-white/30 focus:outline-none',
)}
aria-label="Close banner"
>
<X className="size-3.5" />
</button>
</div>
</div>
);
};
export default Banner;

View file

@ -0,0 +1,98 @@
'use client';
import { NAV_LINKS } from './navbar';
import { EcommerceFooter2 } from '@/components/ecommerce-footer2';
const EXCLUDED_FOOTER_SECTIONS = ['Careers', 'Updates'];
const footerLinks = NAV_LINKS.filter(
(link) => link.subitems && !EXCLUDED_FOOTER_SECTIONS.includes(link.label),
).map((link) => ({
title: link.label,
id: link.label.toLowerCase().replace(/\s+/g, '-'),
items: (link.subitems ?? []).map((sub) => ({
text: sub.label,
link: sub.href,
...(sub.label === 'Patents' && { dividerAfter: true }),
})),
}));
footerLinks.push({
title: 'Updates',
id: 'updates',
items: [
{ text: 'News', link: '/updates/news' },
{ text: 'Blog', link: '/updates/blog' },
{ text: 'Seminars', link: '/updates/seminars' },
{ text: 'Deadlines', link: '/updates/deadlines' },
{ text: 'Outreach', link: '/updates/outreach' },
{ text: 'Events', link: '/updates/events' },
],
});
const SOCIAL_ICONS = {
x: {
title: 'X',
light:
'https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/x.svg',
dark: 'https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/x.svg',
},
linkedin: {
title: 'LinkedIn',
light:
'https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/linkedin-icon.svg',
dark: 'https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/linkedin-icon.svg',
},
youtube: {
title: 'YouTube',
light:
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 17a24.12 24.12 0 0 1 0-10 2 2 0 0 1 1.4-1.4 49.56 49.56 0 0 1 16.2 0A2 2 0 0 1 21.5 7a24.12 24.12 0 0 1 0 10 2 2 0 0 1-1.4 1.4 49.55 49.55 0 0 1-16.2 0A2 2 0 0 1 2.5 17'/%3E%3Cpath d='m10 15 5-3-5-3z'/%3E%3C/svg%3E",
dark: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 17a24.12 24.12 0 0 1 0-10 2 2 0 0 1 1.4-1.4 49.56 49.56 0 0 1 16.2 0A2 2 0 0 1 21.5 7a24.12 24.12 0 0 1 0 10 2 2 0 0 1-1.4 1.4 49.55 49.55 0 0 1-16.2 0A2 2 0 0 1 2.5 17'/%3E%3Cpath d='m10 15 5-3-5-3z'/%3E%3C/svg%3E",
},
instagram: {
title: 'Instagram',
light:
'https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/instagram-icon.svg',
dark: 'https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/instagram-icon.svg',
},
};
const RSS_ICON = {
title: 'RSS',
light: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 11a9 9 0 0 1 9 9'/%3E%3Cpath d='M4 4a16 16 0 0 1 16 16'/%3E%3Ccircle cx='5' cy='19' r='1'/%3E%3C/svg%3E",
dark: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 11a9 9 0 0 1 9 9'/%3E%3Cpath d='M4 4a16 16 0 0 1 16 16'/%3E%3Ccircle cx='5' cy='19' r='1'/%3E%3C/svg%3E",
};
const socialLinks = [
{ icon: SOCIAL_ICONS.x, link: 'https://x.com/iaboratories' },
{ icon: SOCIAL_ICONS.linkedin, link: 'https://linkedin.com' },
{ icon: SOCIAL_ICONS.youtube, link: 'https://youtube.com' },
{ icon: SOCIAL_ICONS.instagram, link: 'https://instagram.com' },
{ icon: RSS_ICON, link: '/rss.xml' },
];
const Footer = () => {
return (
<div className="border-t">
<EcommerceFooter2
newsletter={{
title: 'Stay Connected',
description:
'Subscribe to hear about seminars, research highlights, and department news.',
}}
footerLinks={footerLinks}
socialLinks={socialLinks}
description="Department of Computer Science and Engineering, IIT Gandhinagar, Palaj, Gujarat 382355, India"
/>
<div className="container pb-6">
<p className="text-muted-foreground text-center text-xs">
&copy; {new Date().getFullYear()} Department of Computer Science and
Engineering, IIT Gandhinagar
</p>
</div>
</div>
);
};
export default Footer;

View file

@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="58"
height="58"
viewBox="0 0 58 58"
fill="none"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M26.3747 0.93378L29.156 29.1283L20.9373 2.01574C19.1305 2.547 17.4313 3.27176 15.8013 4.14077L29.156 29.1281L11.1806 7.2206C9.74734 8.38854 8.43558 9.71644 7.24819 11.153L29.1557 29.1284L4.16836 15.7574C3.29931 17.3874 2.57448 19.1055 2.04332 20.8934L29.1559 29.1284L0.961368 26.3471C0.872733 27.2686 0.820312 28.1901 0.820312 29.1284C0.820312 30.0667 0.872738 30.9881 0.961368 31.9097L29.1559 29.1284L2.04332 37.3634C2.57459 39.1507 3.29935 40.8694 4.16836 42.4994L29.1557 29.1284L7.24819 47.1038C8.43558 48.5371 9.74403 49.8656 11.1806 51.0362L29.156 29.1287L15.8013 54.116C17.4313 54.9851 19.1301 55.7099 20.9373 56.2411L29.156 29.1285L26.3747 57.323C27.2962 57.4117 28.2177 57.4641 29.156 57.4641C30.0943 57.4641 31.0157 57.4117 31.9373 57.323L29.156 29.1285L37.3747 56.2411C39.1815 55.7098 40.8807 54.9851 42.5107 54.116L29.156 29.1287L47.1314 51.0362C48.5646 49.8683 49.8764 48.5404 51.0638 47.1038L29.1563 29.1284L54.1436 42.4994C55.0127 40.8694 55.7375 39.1513 56.2687 37.3634L29.1561 29.1284L57.3506 31.9097C57.4393 30.9882 57.4917 30.0667 57.4917 29.1284C57.4917 28.1901 57.4393 27.2687 57.3506 26.3471L29.1561 29.1284L56.2687 20.8934C55.7374 19.1061 55.0126 17.3874 54.1436 15.7574L29.1563 29.1284L51.0638 11.153C49.8764 9.71976 48.568 8.39125 47.1314 7.2206L29.156 29.1281L42.5107 4.14077C40.8807 3.27172 39.1626 2.54689 37.3747 2.01574L29.156 29.1283L31.9373 0.93378C31.0158 0.845146 30.0943 0.792725 29.156 0.792725C28.2177 0.792725 27.2963 0.84515 26.3747 0.93378Z"
fill="oklch(0.2 0.024 254)"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,43 @@
import React from 'react';
import { cn } from '@/lib/utils';
interface LogoProps {
className?: string;
wrapperClassName?: string;
onlyLogo?: boolean;
}
const Logo: React.FC<LogoProps> = ({
className = '',
wrapperClassName = '',
onlyLogo = false,
}) => {
return (
<div className={cn('', wrapperClassName)}>
<a
href="/"
className={cn(
'flex items-center gap-2.5 font-semibold tracking-tight',
className,
)}
>
<div className="bg-primary text-primary-foreground grid size-8 place-items-center rounded-md text-xs font-bold">
CSE
</div>
{!onlyLogo && (
<div className="flex flex-col leading-none">
<span className="text-sm font-semibold">
Computer Science & Engineering
</span>
<span className="text-muted-foreground text-xs">
IIT Gandhinagar
</span>
</div>
)}
</a>
</div>
);
};
export default Logo;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,132 @@
'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
import { ArrowRight, FileText, Search, X } from 'lucide-react';
import { cn } from '@/lib/utils';
const PAGES = [
{ title: 'Faculty', href: '/people/faculty', section: 'People' },
{ title: 'Staff', href: '/people/staff', section: 'People' },
{ title: 'Research Scholars', href: '/people/students', section: 'People' },
{ title: 'Postdoctoral Researchers', href: '/people/postdocs', section: 'People' },
{ title: 'B.Tech Program', href: '/academics/btech', section: 'Academics' },
{ title: 'M.Tech Program', href: '/academics/mtech', section: 'Academics' },
{ title: 'PhD Program', href: '/academics/phd', section: 'Academics' },
{ title: 'Courses', href: '/academics/courses', section: 'Academics' },
{ title: 'Research Areas', href: '/research', section: 'Research' },
{ title: 'Labs & Groups', href: '/research/labs', section: 'Research' },
{ title: 'Publications', href: '/research/publications', section: 'Research' },
{ title: 'News', href: '/news', section: 'Updates' },
{ title: 'Events', href: '/events', section: 'Updates' },
{ title: 'Blog', href: '/blog', section: 'Updates' },
{ title: 'About', href: '/about', section: 'About' },
{ title: 'Leadership', href: '/about/leadership', section: 'About' },
{ title: 'Contact', href: '/about/contact', section: 'About' },
{ title: 'Careers', href: '/about/careers', section: 'Careers' },
];
interface SearchModalProps {
open: boolean;
onClose: () => void;
}
export function SearchModal({ open, onClose }: SearchModalProps) {
const [query, setQuery] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
const filtered = query.trim()
? PAGES.filter(
(p) =>
p.title.toLowerCase().includes(query.toLowerCase()) ||
p.section.toLowerCase().includes(query.toLowerCase()),
)
: PAGES;
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
},
[onClose],
);
useEffect(() => {
if (open) {
document.addEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'hidden';
setTimeout(() => inputRef.current?.focus(), 50);
} else {
document.body.style.overflow = '';
}
return () => {
document.removeEventListener('keydown', handleKeyDown);
document.body.style.overflow = '';
};
}, [open, handleKeyDown]);
useEffect(() => {
if (!open) setQuery('');
}, [open]);
if (!open) return null;
return (
<div className="fixed inset-0 z-[9999] flex items-start justify-center pt-[10vh]">
<div
className="absolute inset-0 bg-black/40 backdrop-blur-sm"
onClick={onClose}
/>
<div className="bg-background relative w-full max-w-xl overflow-hidden rounded-xl border shadow-2xl">
{/* Search input */}
<div className="flex items-center gap-3 border-b px-4 py-3">
<Search className="text-muted-foreground size-5 shrink-0" />
<input
ref={inputRef}
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search pages, people, programs..."
className="placeholder:text-muted-foreground flex-1 bg-transparent text-base outline-none"
/>
<button onClick={onClose} className="text-muted-foreground hover:text-foreground">
<X className="size-4" />
</button>
</div>
{/* Results */}
<div className="max-h-80 overflow-y-auto p-2">
{filtered.length === 0 ? (
<div className="text-muted-foreground px-3 py-8 text-center text-sm">
No results found for "{query}"
</div>
) : (
filtered.map((page) => (
<a
key={page.href}
href={page.href}
onClick={onClose}
className="hover:bg-muted flex items-center gap-3 rounded-lg px-3 py-2.5 transition-colors"
>
<FileText className="text-muted-foreground size-4 shrink-0" />
<div className="flex-1">
<p className="text-sm font-medium">{page.title}</p>
<p className="text-muted-foreground text-xs">{page.section}</p>
</div>
<ArrowRight className="text-muted-foreground size-3.5 opacity-0 transition-opacity group-hover:opacity-100" />
</a>
))
)}
</div>
{/* Footer hint */}
<div className="text-muted-foreground border-t px-4 py-2 text-xs">
<kbd className="bg-muted rounded px-1.5 py-0.5 font-mono text-[10px]">
Esc
</kbd>{' '}
to close
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,74 @@
import React from 'react';
import { cn } from '@/lib/utils';
interface MarqueeProps extends React.ComponentPropsWithoutRef<'div'> {
/**
* Optional CSS class name to apply custom styles
*/
className?: string;
/**
* Whether to reverse the animation direction
* @default false
*/
reverse?: boolean;
/**
* Whether to pause the animation on hover
* @default false
*/
pauseOnHover?: boolean;
/**
* Content to be displayed in the marquee
*/
children: React.ReactNode;
/**
* Whether to animate vertically instead of horizontally
* @default false
*/
vertical?: boolean;
/**
* Number of times to repeat the content
* @default 4
*/
repeat?: number;
}
export function Marquee({
className,
reverse = false,
pauseOnHover = false,
children,
vertical = false,
repeat = 4,
...props
}: MarqueeProps) {
return (
<div
{...props}
className={cn(
'group flex [gap:var(--gap)] overflow-hidden p-2 [--duration:40s] [--gap:1rem]',
{
'flex-row': !vertical,
'flex-col': vertical,
},
className,
)}
>
{Array(repeat)
.fill(0)
.map((_, i) => (
<div
key={i}
className={cn('flex shrink-0 justify-around [gap:var(--gap)]', {
'animate-marquee flex-row': !vertical,
'animate-marquee-vertical flex-col': vertical,
'group-hover:[animation-play-state:paused]': pauseOnHover,
'[animation-direction:reverse]': reverse,
})}
>
{children}
</div>
))}
</div>
);
}

802
src/components/navbar10.tsx Normal file
View file

@ -0,0 +1,802 @@
"use client";
import type { LucideIcon } from "lucide-react";
import {
Book,
ChevronRight,
Code,
Database,
Globe,
Layout,
MenuIcon,
Monitor,
Paintbrush,
Server,
Settings,
Shield,
Sparkles,
Terminal,
Users,
X,
} from "lucide-react";
import type { MouseEventHandler } from "react";
import { forwardRef, Fragment, useEffect, useRef, useState } from "react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { AspectRatio } from "@/components/ui/aspect-ratio";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu";
import { Separator } from "@/components/ui/separator";
import { Sheet, SheetContent, SheetTitle } from "@/components/ui/sheet";
import { cn } from "@/lib/utils";
interface MenuLink {
label?: string;
description?: string;
url: string;
icon?: LucideIcon;
image?: string;
background?: string;
company?: {
logo: string;
name: string;
};
}
interface MenuGroup {
title: string;
links: MenuLink[];
}
interface MenuItem {
id?: number;
title: string;
url?: string;
links?: MenuLink[];
featuredLinks?: MenuLink[];
imageLink?: MenuLink;
groupLinks?: MenuGroup[];
}
interface DesktopMenuItemProps {
item: MenuItem;
index: number;
}
interface MobileNavigationMenuProps {
open: boolean;
}
type NavLinkProps = {
link: MenuLink;
onMouseEnter?: MouseEventHandler<HTMLAnchorElement>;
onMouseLeave?: MouseEventHandler<HTMLAnchorElement>;
showDescription?: boolean;
};
const LOGO = {
url: "https://www.shadcnblocks.com",
src: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/block-1.svg",
alt: "logo",
title: "Shadcnblocks.com",
};
const NAVIGATION: MenuItem[] = [
{
title: "Products",
id: 1,
links: [
{
label: "Insights",
icon: Book,
description:
"Latest company news, updates, insights, and announcements",
url: "#",
image:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/photos/jeremy-bishop-iEjCQtcsVPY-unsplash.jpg",
},
{
label: "Engineering",
icon: Code,
description:
"Deep technical articles, tutorials, guides, and documentation",
url: "#",
image:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/photos/carles-rabada-f7UprkNqi08-unsplash.jpg",
},
{
label: "Culture",
icon: Users,
description: "Team values, experiences, stories, goals, and traditions",
url: "#",
image:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/photos/john-murphey-ZWUWSEY6OGk-unsplash.jpg",
},
{
label: "Press",
icon: Globe,
description:
"Mentions in media, interviews, articles, and publications",
url: "#",
image:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/photos/kevin-charit-1fL2Q1JcbNc-unsplash.jpg",
},
{
label: "API",
icon: Monitor,
description: "Programmatic access using our secure REST API",
url: "#",
image:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/photos/pat-whelen-gWfpmH0H2bM-unsplash.jpg",
},
{
label: "CLI",
icon: Terminal,
description: "Command line tools for automation and productivity",
url: "#",
image:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/photos/sam-wermut-FiUuNWxnb3k-unsplash.jpg",
},
{
label: "SDKs",
icon: Code,
description: "Software kits for easy and fast integration",
url: "#",
image:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/photos/vasilis-karkalas-qOaeVSKyhhE-unsplash.jpg",
},
],
},
{
title: "Solutions",
id: 2,
featuredLinks: [
{
label: "Icons",
icon: Sparkles,
description: "Lucide open-source icon library for developers",
background:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/photos/john-murphey-ZWUWSEY6OGk-unsplash.jpg",
url: "#",
},
{
label: "Themes",
icon: Paintbrush,
description: "Customizable UI themes, styles, and appearance presets",
background:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/photos/kevin-charit-1fL2Q1JcbNc-unsplash.jpg",
url: "#",
},
],
links: [
{
description:
"Tailored eCommerce solutions for growing online businesses",
url: "#",
company: {
logo: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/company/fictional-company-logo-1.svg",
name: "ARC",
},
},
{
description: "Optimized development tools for SaaS web platforms",
url: "#",
company: {
logo: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/company/fictional-company-logo-2.svg",
name: "descript",
},
},
{
description: "Bank-grade security for finance-based web applications",
url: "#",
company: {
logo: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/company/fictional-company-logo-3.svg",
name: "MERCURY",
},
},
{
description:
"Healthcare infrastructure built for medical tech platforms",
url: "#",
company: {
logo: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/logos/company/fictional-company-logo-4.svg",
name: "ramp",
},
},
],
},
{
title: "Platform",
id: 3,
imageLink: {
url: "#",
image:
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/photos/carles-rabada-f7UprkNqi08-unsplash.jpg",
label: "Explore New Components",
},
groupLinks: [
{
title: "Core Services",
links: [
{
label: "Hosting",
icon: Server,
description: "Global infrastructure hosting your scalable web apps",
url: "#",
},
{
label: "Auth",
icon: Shield,
description: "Secure authentication and role-based user access",
url: "#",
},
{
label: "Database",
icon: Database,
description:
"Reliable, scalable storage for application data needs",
url: "#",
},
],
},
{
title: "Design System",
links: [
{
label: "Components",
icon: Layout,
description:
"Reusable components built for consistent UI experiences",
url: "#",
},
{
label: "Tokens",
icon: Settings,
description:
"Design tokens standardizing consistent branding elements",
url: "#",
},
{
label: "Icons",
icon: Sparkles,
description: "Lucide icons used across multiple interface elements",
url: "#",
},
],
},
],
},
{
title: "Resources",
url: "#",
},
{
title: "Pricing",
url: "#",
},
];
const BUTTON = {
label: "Sign up",
isPrimary: true,
url: "#",
};
const MOBILE_BREAKPOINT = 1024;
interface Navbar10Props {
className?: string;
}
const Navbar10 = ({ className }: Navbar10Props) => {
const [open, setOpen] = useState<boolean>(false);
useEffect(() => {
const handleResize = () => {
if (window.innerWidth > MOBILE_BREAKPOINT) {
setOpen(false);
}
};
handleResize();
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
useEffect(() => {
document.body.style.overflow = open ? "hidden" : "auto";
}, [open]);
const handleMobileMenu = () => {
const nextOpen = !open;
setOpen(nextOpen);
};
return (
<Fragment>
<section
className={cn(
"pointer-events-auto absolute top-0 z-999 flex w-full items-center justify-center bg-background",
className,
)}
>
<NavigationMenu className="h-20 max-w-full after:absolute after:inset-0 after:z-998 after:block after:size-full after:bg-background after:content-[''] [&>div:last-child>div]:mt-0 [&>div:last-child>div]:animate-none [&>div:last-child>div]:rounded-none [&>div:last-child>div]:border-0 [&>div:last-child>div]:border-b [&>div:last-child>div]:!shadow-[0px_-1px_0px_0px_rgba(0,0,0,0.05),0px_0px_0px_1px_rgba(17,26,37,0.05),0px_2px_5px_0px_rgba(16,25,36,0.1),0px_5px_20px_0px_rgba(16,25,36,0.1)]">
<div className="relative z-999 container grid w-full grid-cols-2 items-center justify-between gap-8 xl:grid-cols-3">
<a
href={LOGO.url}
className="flex max-h-8 items-center gap-2 text-lg font-semibold tracking-tighter"
>
<img
src={LOGO.src}
alt={LOGO.alt}
className="inline-block size-6"
/>
<span className="hidden md:inline-block">{LOGO.title}</span>
</a>
<div className="hidden xl:flex">
<NavigationMenuList>
{NAVIGATION.map((item, index) => (
<DesktopMenuItem
key={`desktop-link-${index}`}
item={item}
index={index}
/>
))}
</NavigationMenuList>
</div>
<div className="justify-self-end">
<div className="hidden xl:block">
<Button variant="ghost">
<a href={BUTTON.url}>
{BUTTON.label}
<ChevronRight />
</a>
</Button>
</div>
<div className="xl:hidden">
<Button
className="size-11"
variant="ghost"
size="icon"
onClick={handleMobileMenu}
>
{open ? (
<X className="size-5.5 stroke-foreground" />
) : (
<MenuIcon className="size-5.5 stroke-foreground" />
)}
</Button>
</div>
</div>
</div>
</NavigationMenu>
</section>
<MobileNavigationMenu open={open} />
</Fragment>
);
};
const DesktopMenuItem = ({ item, index }: DesktopMenuItemProps) => {
if (item.links || item.featuredLinks || item.groupLinks) {
return (
<NavigationMenuItem key={`desktop-menu-item-${index}`} value={`${index}`}>
<NavigationMenuTrigger className="h-fit bg-transparent font-normal text-foreground/60">
{item.title}
</NavigationMenuTrigger>
<NavigationMenuContent className="hidden !rounded-xl !border-0 !p-0 xl:block">
<div className="w-dvw animate-[fade-in-slide-down_0.35s_cubic-bezier(0.33,1,0.68,1)_forwards] px-8 pt-6 pb-12">
<div className="container">
{item.id === 1 && <DropdownMenu1 links={item.links} />}
{item.id === 2 && (
<DropdownMenu2
featuredLinks={item.featuredLinks}
links={item.links}
/>
)}
{item.id === 3 && (
<DropdownMenu3
groupLinks={item.groupLinks}
imageLink={item.imageLink}
/>
)}
</div>
</div>
</NavigationMenuContent>
</NavigationMenuItem>
);
}
return (
<NavigationMenuItem key={`desktop-menu-item-${index}`} value={`${index}`}>
<NavigationMenuLink
href={item.url}
className={`${navigationMenuTriggerStyle()} h-fit bg-transparent font-normal text-foreground/60`}
>
{item.title}
</NavigationMenuLink>
</NavigationMenuItem>
);
};
const DropdownMenu1 = ({ links }: { links?: MenuLink[] }) => {
const linksRef = useRef<HTMLAnchorElement[]>([]);
const imageRefs = useRef<HTMLDivElement[]>([]);
const updateImageClasses = (activeIndex: number) => {
imageRefs.current.forEach((img, i) => {
if (!img) return;
const isActive = i === activeIndex;
img.classList.toggle("opacity-100", isActive);
img.classList.toggle("translate-y-0", isActive);
img.classList.toggle("opacity-0", !isActive);
img.classList.toggle("translate-y-20", !isActive);
img.classList.toggle("z-10", isActive);
});
};
const handleMouseEnter =
(index: number) => (event: React.MouseEvent<HTMLAnchorElement>) => {
linksRef.current.forEach((link) => {
if (link && link !== event.currentTarget) {
link.classList.add("opacity-50");
}
});
updateImageClasses(index);
};
const handleMouseLeave = () => {
linksRef.current.forEach((link) => {
link?.classList.remove("opacity-50");
});
updateImageClasses(0);
};
if (!links) return null;
return (
<div className="grid grid-cols-2 gap-8">
<ul className="grid grid-cols-2 gap-8">
{links.map((link, index) => (
<NavLink
key={`default-nav-link-${index}`}
link={link}
onMouseEnter={handleMouseEnter(index)}
onMouseLeave={handleMouseLeave}
ref={(el) => {
if (el) linksRef.current[index] = el;
}}
/>
))}
</ul>
<div className="relative !h-[16rem] w-full overflow-hidden rounded-lg bg-muted">
{links.map((link, index) => (
<div
key={`default-nav-link-img-${index}`}
ref={(el) => {
if (el) imageRefs.current[index] = el;
}}
className={`will-change-opacity absolute top-14 left-14 aspect-video w-[43.75rem] overflow-hidden rounded-tl-md border-t border-l transition-all duration-600 ease-in-out will-change-transform ${
index === 0
? "z-10 translate-y-0 opacity-100"
: "pointer-events-none z-0 translate-y-20 opacity-0"
}`}
>
<img
src={link.image}
alt={link.label}
className="size-full object-cover object-left-top"
/>
</div>
))}
</div>
</div>
);
};
const DropdownMenu2 = ({
links,
featuredLinks,
}: {
links?: MenuLink[];
featuredLinks?: MenuLink[];
}) => {
return (
<div>
<div className="flex gap-8 pb-8">
{featuredLinks &&
featuredLinks.map((link, index) => (
<FeaturedLink key={`desktop-featured-link-${index}`} link={link} />
))}
</div>
<Separator />
<div className="grid grid-cols-4 pt-8">
{links &&
links.map((link, index) => (
<NavLink key={`default-nav-link-${index}`} link={link} />
))}
</div>
</div>
);
};
const DropdownMenu3 = ({
groupLinks,
imageLink,
}: {
groupLinks?: MenuGroup[];
imageLink?: MenuLink;
}) => {
return (
<div className="grid grid-cols-2 gap-8">
<GroupLinks groupLinks={groupLinks} />
<FeaturedImageLink link={imageLink} />
</div>
);
};
const GroupLinks = ({ groupLinks }: { groupLinks?: MenuGroup[] }) => {
const linksRef = useRef<HTMLAnchorElement[]>([]);
const handleMouseEnter =
() => (event: React.MouseEvent<HTMLAnchorElement>) => {
linksRef.current.forEach((link) => {
if (link && link !== event.currentTarget) {
link.classList.add("opacity-50");
}
});
};
const handleMouseLeave = () => {
linksRef.current.forEach((link) => {
link?.classList.remove("opacity-50");
});
};
if (!groupLinks) return null;
let linkIndex = 0;
return (
<div className="grid grid-cols-2 gap-8">
{groupLinks.map((group, index1) => (
<div key={`group-link-${index1}`}>
<div className="mb-4 text-xs text-muted-foreground">
{group.title}
</div>
<ul className="flex flex-col gap-8">
{group.links.map((link, index2) => {
const index = linkIndex++;
return (
<li key={`group-link-${index1}-${index2}`}>
<NavLink
link={link}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
ref={(el) => {
if (el) linksRef.current[index] = el;
}}
/>
</li>
);
})}
</ul>
</div>
))}
</div>
);
};
const FeaturedImageLink = ({ link }: { link?: MenuLink }) => {
if (!link) return null;
return (
<div className="hidden xl:block">
<a href={link.url} className="w-full max-w-[36.875rem]">
<AspectRatio
ratio={1.77245509}
className="overflow-hidden rounded-[0.25rem] bg-muted"
>
<div className="size-full">
<Badge className="absolute top-2 left-2">New</Badge>
<div className="flex w-full flex-col items-center justify-center gap-8 pt-10">
<div className="text-2xl font-semibold">{link.label}</div>
<div className="w-[80%]">
<AspectRatio
ratio={1.5}
className="overflow-hidden rounded-[0.25rem] bg-muted"
>
<img
src={link.image}
alt={link.label}
className="size-full object-cover object-left-top"
/>
</AspectRatio>
</div>
</div>
</div>
</AspectRatio>
</a>
</div>
);
};
const FeaturedLink = ({ link }: { link: MenuLink }) => {
return (
<a
href={link.url}
className="group relative flex w-full overflow-hidden rounded-xl bg-muted px-8 py-7"
>
<div className="relative z-10 flex w-full items-center gap-6">
<div className="flex size-12 shrink-0 rounded-lg border bg-background shadow-lg">
{link.icon && (
<link.icon className="m-auto size-5 stroke-foreground" />
)}
</div>
<div className="flex flex-col gap-2">
<div className="text-lg font-semibold text-white">{link.label}</div>
<div className="font-medium text-white/80">{link.description}</div>
</div>
</div>
<img
src={link.background}
alt={link.label}
className="absolute top-0 left-0 size-full object-cover object-left-top opacity-90 transition-opacity duration-300 ease-in-out group-hover:opacity-100"
/>
</a>
);
};
const NavLink = forwardRef<HTMLAnchorElement, NavLinkProps>(
({ link, onMouseEnter, onMouseLeave }, ref) => {
return (
<a
ref={ref}
href={link.url}
className="flex w-full gap-2 transition-opacity duration-300"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{link.icon && (
<div className="flex size-6 shrink-0 rounded-md border shadow">
<link.icon className="m-auto size-3.5" />
</div>
)}
<div className="flex flex-col items-start gap-2">
{link.company && (
<div className="block text-base leading-normal xl:hidden">
{link.company.name}
</div>
)}
{link.company && (
<img
className="hidden h-6 xl:block"
src={link.company.logo}
alt={link.company.name}
/>
)}
{link.label && (
<div className="text-base leading-normal">{link.label}</div>
)}
<div className="text-sm leading-normal text-muted-foreground">
{link.description}
</div>
</div>
</a>
);
},
);
const MobileNavigationMenu = ({ open }: MobileNavigationMenuProps) => {
return (
<Sheet open={open}>
<SheetContent
aria-describedby={undefined}
side="top"
className="inset-0 z-998 h-dvh w-full bg-background pt-20 [&>button]:hidden"
>
<div className="flex-1 overflow-y-auto">
<div className="container py-8">
<div className="absolute -m-px h-px w-px overflow-hidden border-0 mask-clip-border p-0 text-nowrap whitespace-nowrap">
<SheetTitle className="text-primary">
Mobile Navigation
</SheetTitle>
</div>
<div className="flex min-h-full flex-col gap-6">
<Accordion type="multiple" className="w-full">
{NAVIGATION.map((item, index) =>
renderMobileMenuItem(item, index),
)}
</Accordion>
<Button asChild>
<a href={BUTTON.url}>{BUTTON.label}</a>
</Button>
</div>
</div>
</div>
</SheetContent>
</Sheet>
);
};
const renderMobileMenuItem = (item: MenuItem, index: number) => {
if (item.links || item.featuredLinks || item.groupLinks) {
return (
<AccordionItem
key={item.title}
value={`nav-${index}`}
className="border-b-0"
>
<AccordionTrigger className="h-[2.5rem] items-center text-base font-normal text-foreground hover:no-underline">
{item.title}
</AccordionTrigger>
<AccordionContent className="flex flex-col gap-6 p-2">
{item.featuredLinks && (
<div className="flex flex-col gap-2 p-2">
{item.featuredLinks.map((link, index) => (
<NavLink key={`default-nav-link-${index}`} link={link} />
))}
</div>
)}
{item.links && (
<div className="flex flex-col gap-2 p-2">
{item.links.map((link, index) => (
<NavLink key={`default-nav-link-${index}`} link={link} />
))}
</div>
)}
{item.groupLinks && (
<div className="flex flex-col gap-2 p-2">
{item.groupLinks.map((group, index1) => (
<div className="mb-8 last:mb-0" key={`group-link-${index1}`}>
<div className="mb-4 text-xs text-muted-foreground">
{group.title}
</div>
<ul className="flex flex-col gap-2">
{group.links.map((link, index2) => (
<li key={`group-link-${index1}-${index2}`}>
<NavLink link={link} />
</li>
))}
</ul>
</div>
))}
</div>
)}
</AccordionContent>
</AccordionItem>
);
}
return (
<a
key={item.title}
href={item.url}
className="flex h-[2.5rem] items-center rounded-md text-left text-base leading-[3.75] font-normal text-foreground ring-ring/10 outline-ring/50 transition-all focus-visible:ring-4 focus-visible:outline-1 nth-last-1:border-0"
>
{item.title}
</a>
);
};
export { Navbar10 };

View file

@ -0,0 +1,77 @@
import Noise from '@/components/elements/noise';
const stats = [
{ number: '21M', label: 'Global Reach of Users' },
{ number: '12+', label: 'Years of Expertise' },
{ number: '654', label: 'Projects Completed' },
{ number: '113k+', label: 'Monthly Active Users' },
{ number: '461k', label: 'Registered Accounts' },
{ number: '98+', label: 'Daily Users' },
];
export default function AboutHero() {
return (
<section className="section-padding relative">
<Noise />
<div className="bigger-container">
{/* Hero Content */}
<div className="text-center">
<h1 className="text-center text-4xl font-medium tracking-tighter md:text-6xl md:leading-none lg:text-7xl">
About Us
</h1>
<p className="text-muted-foreground mx-auto mt-3 hidden max-w-3xl text-lg leading-relaxed md:block lg:mt-4">
Discover how Paravel helps teams manage tasks with more clarity and
confidence. We streamline operations so you can focus on meaningful
progress.
</p>
</div>
{/* Hero Images */}
<div className="mt-8 grid gap-6 sm:grid-cols-12 lg:mt-12">
{/* First image - widest (spans 6 columns on desktop) */}
<div className="relative h-60 overflow-hidden rounded-xl border sm:col-span-5 md:h-80">
<img
src="https://images.unsplash.com/photo-1587825140708-dfaf72ae4b04?w=1200&h=1600&fit=crop"
alt="Modern workspace setup"
className="size-full object-cover"
/>
</div>
{/* Second image - medium width (spans 4 columns on desktop) */}
<div className="relative h-60 overflow-hidden rounded-xl border sm:col-span-4 md:h-80">
<img
src="https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=800&h=1066&fit=crop"
alt="Team collaboration"
className="size-full object-cover"
/>
</div>
{/* Third image - narrowest (spans 2 columns on desktop) */}
<div className="relative h-60 overflow-hidden rounded-xl border sm:col-span-3 md:h-80">
<img
src="https://images.unsplash.com/photo-1498050108023-c5249f4df085?w=600&h=800&fit=crop"
alt="Developer working"
className="size-full object-cover"
/>
</div>
</div>
<h2 className="mt-8 max-w-3xl text-4xl leading-none font-medium tracking-tight md:mt-12 lg:mt-16 lg:text-5xl">
We excel in our field, but skill isn&apos;t everything we offer.
</h2>
{/* Stats Grid - Left Aligned */}
<div className="mt-8 grid grid-cols-2 gap-8 md:grid-cols-3 lg:mt-12 lg:gap-12">
{stats.map((stat, index) => (
<div key={index} className="border-input border-b">
<div className="text-3xl font-medium md:text-4xl lg:text-5xl">
{stat.number}
</div>
<div className="text-muted-foreground my-6 text-sm md:text-base">
{stat.label}
</div>
</div>
))}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,83 @@
---
type Link = {
label: string;
href: string;
};
type Props = {
eyebrow?: string;
title: string;
description: string;
summary: string;
highlights: string[];
links: Link[];
};
const {
eyebrow = 'Academics',
title,
description,
summary,
highlights,
links,
} = Astro.props;
---
<section class="border-b bg-muted/40">
<div class="container py-14 md:py-18">
<p class="text-sm font-medium text-secondary">
Department of Computer Science &amp; Engineering, IIT Gandhinagar
</p>
<div class="mt-5 grid gap-8 lg:grid-cols-[minmax(0,1fr)_22rem] lg:items-start">
<div class="max-w-3xl">
<h1 class="text-3xl font-semibold leading-tight md:text-5xl">
{title}
</h1>
<p class="mt-5 text-lg leading-relaxed text-muted-foreground">
{description}
</p>
</div>
<aside class="rounded-lg border border-border bg-card p-5 shadow-sm">
<p class="font-semibold">More information</p>
<div class="mt-4 space-y-3">
{
links.map((link) => (
<a
href={link.href}
class="block text-sm font-medium text-primary underline-offset-4 hover:underline"
target={link.href.startsWith('http') ? '_blank' : undefined}
rel={link.href.startsWith('http') ? 'noreferrer' : undefined}
>
{link.label} -&gt;
</a>
))
}
</div>
</aside>
</div>
</div>
</section>
<section class="section-padding">
<div class="container">
<div class="max-w-3xl">
<p class="text-sm font-medium text-secondary">{eyebrow}</p>
<h2 class="mt-3 text-2xl font-semibold md:text-3xl">
Overview
</h2>
<p class="mt-3 leading-relaxed text-muted-foreground">
{summary}
</p>
</div>
<div class="mt-8 grid gap-4 md:grid-cols-2">
{
highlights.map((highlight) => (
<article class="rounded-lg border border-border bg-card p-5 shadow-sm">
<p class="leading-relaxed text-muted-foreground">{highlight}</p>
</article>
))
}
</div>
</div>
</section>

View file

@ -0,0 +1,168 @@
---
type Committee = {
title: string;
members: string[];
note?: string;
};
type CommitteeGroup = {
title: string;
description: string;
committees: Committee[];
};
const committeeGroups: CommitteeGroup[] = [
{
title: 'Academic Governance',
description:
'Representatives and coordinators for senate-facing academic processes, admissions, qualifying exams, and data science coordination.',
committees: [
{
title: 'SAPC Representative',
members: ['Manisha Padala'],
},
{
title: 'SAPEC Representative',
members: ['Balagopal Komarath'],
},
{
title: 'PG Admissions Coordinators',
members: ['Bireshwar Das', 'Sameer Kulkarni'],
note: 'Sameer Kulkarni also serves as focal point for the IITGN-SAC Collaborative PhD Programme.',
},
{
title: 'PhD Qualifying Exam Coordinator',
members: ['Balagopal Komarath'],
},
{
title: 'CDS Representative',
members: ['Mayank Singh'],
},
],
},
{
title: 'Courses and Programs',
description:
'Coordination roles for project courses, thesis or project courses, joint programs, and supervisor allocation.',
committees: [
{
title: 'UG Project Courses and Grades Coordinator',
members: ['Manoj Gupta'],
},
{
title: 'PG Thesis/Project Courses and Grades Coordinator',
members: ['Yogesh Kumar Meena'],
},
{
title: 'Joint PG Programs Coordinator',
members: ['Yogesh Kumar Meena'],
},
{
title: 'PG Supervisor Allocation Coordinators',
members: ['Abhishek Bichhawat', 'Yogesh Kumar Meena', 'Manoj Gupta'],
},
],
},
{
title: 'Department Planning and Outreach',
description:
'Coordinators for resources, department vision, recruitment, communication, and public visibility.',
committees: [
{
title: 'Resource Management and Planning Coordinators',
members: ['Mayank Singh', 'Yogesh Kumar Meena', 'Sameer Kulkarni'],
note: 'Covers budget, facilities, space, SIPC, and grants.',
},
{
title: 'Department Vision Document Coordinators',
members: ['Manoj Gupta', 'Yogesh Kumar Meena', 'Sameer Kulkarni'],
},
{
title: 'Faculty Recruitment Committee Representative',
members: ['Abhishek Bichhawat'],
},
{
title: 'Department Visibility and Perception Coordinators',
members: ['Neeldhara Misra', 'Anup Kalbalia', 'Manu Awasthi'],
note: 'Includes website, communications, and social media.',
},
],
},
];
---
<section class="border-b bg-muted/40">
<div class="container py-14 md:py-18">
<p class="text-secondary text-sm font-medium">
Department of Computer Science &amp; Engineering, IIT Gandhinagar
</p>
<div class="mt-5 grid gap-8 lg:grid-cols-[minmax(0,1fr)_22rem] lg:items-start">
<div class="max-w-3xl">
<h1 class="text-3xl font-semibold leading-tight md:text-5xl">
Administration
</h1>
<p class="text-muted-foreground mt-5 text-lg leading-relaxed">
Department-level committee assignments and administrative coordination
roles, consolidated from the current CSE office orders.
</p>
</div>
<aside class="rounded-lg border border-border bg-card p-5 shadow-sm">
<p class="font-semibold">Current term</p>
<p class="text-muted-foreground mt-3 text-sm leading-relaxed">
These assignments are constituted for department activities from
<span class="font-medium text-foreground">19 September 2025</span> to
<span class="font-medium text-foreground">31 August 2027</span>.
</p>
</aside>
</div>
</div>
</section>
<section class="section-padding">
<div class="container">
<div class="max-w-3xl">
<p class="text-secondary text-sm font-medium">Department Committees</p>
<h2 class="mt-3 text-2xl font-semibold md:text-3xl">
Roles and coordinators
</h2>
<p class="text-muted-foreground mt-3 leading-relaxed">
Faculty members in these committees may co-opt other department faculty
members as required for carrying out specific tasks.
</p>
</div>
<div class="mt-8 space-y-10">
{
committeeGroups.map((group) => (
<section class="scroll-mt-24">
<div class="mb-4 border-b pb-3">
<h3 class="text-xl font-semibold">{group.title}</h3>
<p class="text-muted-foreground mt-1 text-sm">
{group.description}
</p>
</div>
<div class="grid gap-4 md:grid-cols-2">
{group.committees.map((committee) => (
<article class="rounded-lg border border-border bg-card p-5 shadow-sm">
<h4 class="text-lg font-semibold">{committee.title}</h4>
<ul class="mt-4 flex flex-wrap gap-2">
{committee.members.map((member) => (
<li class="rounded-full border bg-background px-3 py-1 text-sm">
{member}
</li>
))}
</ul>
{committee.note && (
<p class="text-muted-foreground mt-4 text-sm leading-relaxed">
{committee.note}
</p>
)}
</article>
))}
</div>
</section>
))
}
</div>
</div>
</section>

View file

@ -0,0 +1,155 @@
"use client";
import { useMemo, useState } from "react";
import { ArrowRight, Calendar, Megaphone } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { CATEGORY_LABELS, homepageNewsItems } from "@/data/news";
import { seminarEntries } from "@/data/seminars";
const parseSeminarDate = (date: string) => new Date(`${date}T00:00:00`);
const formatSeminarMeta = (displayDate: string, time?: string) =>
time ? `${displayDate} · ${time}` : displayDate;
export default function Announcements() {
const [seminarView, setSeminarView] = useState<"upcoming" | "recent">(
"upcoming",
);
const today = useMemo(() => {
const date = new Date();
date.setHours(0, 0, 0, 0);
return date;
}, []);
const upcomingSeminars = useMemo(
() =>
[...seminarEntries]
.filter((item) => parseSeminarDate(item.date) >= today)
.sort(
(a, b) =>
parseSeminarDate(a.date).getTime() -
parseSeminarDate(b.date).getTime(),
)
.slice(0, 3),
[today],
);
const recentSeminars = useMemo(
() =>
[...seminarEntries]
.filter((item) => parseSeminarDate(item.date) < today)
.sort(
(a, b) =>
parseSeminarDate(b.date).getTime() -
parseSeminarDate(a.date).getTime(),
)
.slice(0, 3),
[today],
);
const activeSeminars =
seminarView === "upcoming" ? upcomingSeminars : recentSeminars;
return (
<section className="section-padding">
<div className="container">
<div className="grid gap-8 lg:grid-cols-[minmax(0,2fr)_minmax(18rem,1fr)]">
<div>
<div className="mb-4 flex items-center justify-between gap-4">
<h2 className="flex items-center gap-2 text-2xl">
<Megaphone className="text-secondary size-6" />
Announcements
</h2>
<Button variant="ghost" size="sm" asChild>
<a href="/updates/news">
View all <ArrowRight className="ml-1 size-3.5" />
</a>
</Button>
</div>
<div className="grid gap-2.5">
{homepageNewsItems.slice(0, 3).map((item) => (
<Card key={item.id} className="p-3.5">
<div className="mb-1.5 flex flex-wrap items-center gap-2">
<Badge variant="secondary" className="text-xs">
{CATEGORY_LABELS[item.category]}
</Badge>
<span className="text-muted-foreground flex items-center gap-1 text-xs">
<Calendar className="size-3" />
{item.displayDate}
</span>
</div>
<a
href={item.sourceUrl}
target="_blank"
rel="noreferrer"
className="hover:text-secondary text-base font-semibold transition-colors"
>
{item.title}
</a>
<p className="text-muted-foreground mt-1 line-clamp-1 text-sm">
{item.summary}
</p>
</Card>
))}
</div>
</div>
<aside>
<div className="mb-6 flex items-center gap-2">
<Calendar className="text-secondary size-6" />
<h2 className="text-2xl">Seminars</h2>
</div>
<div className="mb-5 inline-flex rounded-full bg-muted p-1">
{(["upcoming", "recent"] as const).map((view) => (
<button
key={view}
type="button"
className="rounded-full px-3 py-1 text-sm capitalize text-muted-foreground transition data-[active=true]:bg-background data-[active=true]:text-foreground data-[active=true]:shadow-sm"
data-active={seminarView === view}
onClick={() => setSeminarView(view)}
>
{view}
</button>
))}
</div>
<div className="space-y-4">
{activeSeminars.length > 0 ? (
activeSeminars.map((item) => (
<a
key={item.id}
href={`/updates/seminars#${item.id}`}
className="block border-l-2 border-secondary/50 py-1 pl-4 transition hover:border-secondary"
>
<p className="text-muted-foreground text-xs">
{formatSeminarMeta(item.displayDate, item.time)}
</p>
<p className="mt-1 text-sm font-medium leading-snug">
{item.title}
</p>
<p className="text-muted-foreground mt-0.5 line-clamp-1 text-xs">
{item.speaker}
</p>
</a>
))
) : (
<p className="text-muted-foreground border-l-2 border-secondary/50 py-1 pl-4 text-sm">
No upcoming seminars listed.
</p>
)}
</div>
<Button variant="outline" size="sm" className="mt-4 w-full" asChild>
<a href="/updates/seminars">Full Calendar</a>
</Button>
</aside>
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,159 @@
import Noise from '@/components/elements/noise';
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
const COLUMNS_DATA = [
{
image: '/images/benefits-showcase/1.webp',
imageAlt: 'Global Users',
cardTitle: '21M',
cardSubtitle: 'Global Users',
cardContent:
'Achieve greater accuracy and efficiency with our advanced toolkit.',
cardPosition: 'bottom' as const,
},
{
image: '/images/benefits-showcase/2.webp',
imageAlt: 'Open AI.',
cardTitle: 'Open AI.',
name: 'Grace Young',
title: 'CMO at Open AI.',
cardContent:
'Clear task ownership, fast collaboration, and better visibility lead to better decisions and faster delivery.',
cardPosition: 'inside' as const,
},
{
image: '/images/benefits-showcase/3.webp',
imageAlt: 'Minimized Errors',
cardTitle: '97%',
cardSubtitle: 'Minimized Errors',
cardContent:
'Achieve greater accuracy and efficiency with our advanced toolkit.',
cardPosition: 'top' as const,
},
];
export default function BenefitsShowcase() {
return (
<section className="section-padding relative">
<Noise />
<div className="bigger-container">
<h2 className="mb-12 text-center text-4xl leading-none font-medium tracking-tight lg:mb-16 lg:text-5xl">
See the Benefits Firsthand
</h2>
{/* Three Column Grid */}
<div className="mx-auto grid max-w-sm gap-6 lg:mx-0 lg:max-w-none lg:grid-cols-3 lg:gap-8">
{COLUMNS_DATA.map((column, index) => {
if (column.cardPosition === 'inside') {
return <InsideCardColumn key={index} {...column} />;
}
return <TopBottomCardColumn key={index} {...column} />;
})}
</div>
</div>
</section>
);
}
// Column Components
interface ColumnProps {
image: string;
imageAlt: string;
cardTitle?: string;
cardSubtitle?: string;
cardContent: string;
cardFooter?: string;
cardPosition: 'top' | 'inside' | 'bottom';
name?: string;
title?: string;
}
function TopBottomCardColumn({
image,
imageAlt,
cardTitle,
cardSubtitle,
cardContent,
cardPosition,
}: ColumnProps) {
return (
<div className="flex flex-col gap-6">
{cardPosition === 'bottom' && (
<div className="relative aspect-[389/384] w-full">
<img
src={image}
alt={imageAlt}
className="size-full rounded-xl border object-cover"
/>
</div>
)}
<Card className="">
<CardHeader>
<CardTitle className="text-4xl font-medium">{cardTitle}</CardTitle>
<p className="text-base font-semibold">{cardSubtitle}</p>
</CardHeader>
<CardContent>
<CardDescription className="">{cardContent}</CardDescription>
</CardContent>
</Card>
{cardPosition === 'top' && (
<div className="relative aspect-[389/384] w-full">
<img
src={image}
alt={imageAlt}
className="size-full rounded-xl border object-cover"
/>
</div>
)}
</div>
);
}
function InsideCardColumn({
image,
imageAlt,
cardTitle,
name,
title,
cardContent,
}: ColumnProps) {
return (
<div className="flex h-full flex-col">
<div className="relative aspect-[326/576] w-full flex-1 overflow-hidden rounded-xl border lg:aspect-auto">
<img src={image} alt={imageAlt} className="size-full object-cover" />
{/* Overlay card */}
<Card className="absolute right-6 bottom-6 left-6 gap-4">
<CardHeader className="gap-0">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
className="text-foreground"
>
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
</svg>
{cardTitle}
</CardTitle>
</CardHeader>
<CardContent>
<CardDescription className="">{cardContent}</CardDescription>
</CardContent>
<CardFooter className="mt-2 block">
<span className="font-medium">{name}</span>{' '}
<span className="text-sm">{title}</span>
</CardFooter>
</Card>
</div>
</div>
);
}

View file

@ -0,0 +1,161 @@
import type { CollectionEntry } from 'astro:content';
import { ChevronLeft, Facebook, Twitter, MessageCircle } from 'lucide-react';
import { motion } from 'motion/react';
import { BlogCard } from './blog-posts';
/**
* Calculate read time based on word count
* @param content - The markdown content to analyze
* @returns Formatted read time string (e.g., "5 min read")
*/
export function calculateReadTime(content: string): string {
// Remove markdown syntax and count words
const cleanContent = content
.replace(/#{1,6}\s+/g, '') // Remove headers
.replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold
.replace(/\*(.*?)\*/g, '$1') // Remove italic
.replace(/\[(.*?)\]\(.*?\)/g, '$1') // Remove links
.replace(/```[\s\S]*?```/g, '') // Remove code blocks
.replace(/`(.*?)`/g, '$1') // Remove inline code
.replace(/^\s*[-*+]\s+/gm, '') // Remove list markers
.replace(/^\s*\d+\.\s+/gm, '') // Remove numbered list markers
.replace(/---[\s\S]*?---/g, '') // Remove frontmatter
.trim();
const words = cleanContent.split(/\s+/).filter((word) => word.length > 0);
const wordCount = words.length;
// Average reading speed is 200-250 words per minute, using 225
const readingSpeed = 225;
const minutes = Math.ceil(wordCount / readingSpeed);
return `${Math.max(1, minutes)} min read`;
}
const BlogPost = ({
post,
relatedPosts,
children,
}: {
post: CollectionEntry<'blog'>[];
relatedPosts: CollectionEntry<'blog'>[];
children: React.ReactNode;
}) => {
const { title, description, date, coverImage, author } = post[0].data;
const { id, body } = post[0];
const readTime = calculateReadTime(body || '');
return (
<div className="section-padding container">
<div className="mx-auto max-w-4xl">
{/* Header */}
<header className="mb-8">
<a
href="/blog"
className="group text-muted-foreground hover:text-foreground mb-8 inline-flex items-center gap-2 text-sm transition-colors"
>
<ChevronLeft className="size-4 transition-transform group-hover:-translate-x-0.5" />
Back to Blog
</a>
{/* Title and Description */}
<h1 className="mb-4 text-4xl tracking-tight md:text-5xl lg:text-6xl">
{title}
</h1>
<p className="text-muted-foreground mb-8 text-lg md:text-xl">
{description}
</p>
</header>
<motion.div className="relative aspect-[16/9] overflow-hidden rounded-xl">
<img
src={coverImage}
alt={title}
className="size-full object-cover"
/>
</motion.div>
{/* Author Information */}
<div className="mt-8 mb-12 flex flex-wrap items-center justify-between gap-4">
<div className="flex items-center gap-4">
<div className="relative size-14 overflow-hidden rounded-full">
<img
src={author.image}
alt={author.name}
className="size-full object-cover"
/>
</div>
<div>
<div className="text-foreground text-2xl font-semibold">
{author.name}
</div>
<div className="text-muted-foreground">
{new Date(date).toLocaleDateString('en-GB', {
month: 'short',
day: 'numeric',
year: 'numeric',
})}{' '}
· {readTime}
</div>
</div>
</div>
{/* Social Share Icons */}
<div className="bg-background border-input [&_*]:border-input grid grid-cols-3 items-center divide-x rounded-sm border shadow-sm">
<a
href={author.facebookUrl}
className="flex items-center justify-center px-3 py-2.5 md:px-5"
>
<Facebook className="size-4 shrink-0 md:size-5" />
</a>
<a
href={author.twitterUrl}
className="flex items-center justify-center px-3 py-2.5 md:px-5"
>
<Twitter className="size-4 shrink-0 md:size-5" />
</a>
<a
href={author.linkedinUrl}
className="flex items-center justify-center px-3 py-2.5 md:px-5"
>
<MessageCircle className="size-4 shrink-0 md:size-5" />
</a>
</div>
</div>
{/* Article Content */}
<article className="prose lg:prose-lg prose-headings:font-weight-display dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed prose-li:leading-relaxed prose-img:rounded-xl prose-img:shadow-sm prose-a:text-primary prose-a:no-underline hover:prose-a:underline mx-auto max-w-none">
{children}
</article>
</div>
{relatedPosts.length > 0 && (
<section className="mt-20 lg:mt-24">
<h2 className="text-4xl tracking-tight lg:text-5xl">
Related Articles
</h2>
<p className="text-muted-foreground mt-3 text-lg leading-snug lg:mt-4">
More writing from the IITGN CSE community, grouped by shared
themes and tags.
</p>
<div className="mt-8 grid gap-6 sm:grid-cols-2 lg:mt-12 lg:grid-cols-3">
{relatedPosts.map((relatedPost) => {
return (
<a
key={relatedPost.id}
href={`/blog/${relatedPost.id}`}
className="group block h-full"
>
<BlogCard post={relatedPost} />
</a>
);
})}
</div>
</section>
)}
</div>
);
};
export { BlogPost };

View file

@ -0,0 +1,536 @@
'use client';
import { useEffect, useState } from 'react';
import { ArrowUpRight } from 'lucide-react';
import { AnimatePresence, motion, useScroll, useTransform } from 'motion/react';
import { Badge } from '@/components/ui/badge';
import {
Card,
CardContent,
CardFooter,
CardHeader,
} from '@/components/ui/card';
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from '@/components/ui/pagination';
import { ScrollArea } from '@/components/ui/scroll-area';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
import usePrefersReducedMotion from '@/hooks/usePrefersReducedMotion';
import { cn } from '@/lib/utils';
/**
* Calculate read time based on word count
* @param content - The markdown content to analyze
* @returns Formatted read time string (e.g., "5 min read")
*/
export function calculateReadTime(content: string): string {
// Remove markdown syntax and count words
const cleanContent = content
.replace(/#{1,6}\s+/g, '') // Remove headers
.replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold
.replace(/\*(.*?)\*/g, '$1') // Remove italic
.replace(/\[(.*?)\]\(.*?\)/g, '$1') // Remove links
.replace(/```[\s\S]*?```/g, '') // Remove code blocks
.replace(/`(.*?)`/g, '$1') // Remove inline code
.replace(/^\s*[-*+]\s+/gm, '') // Remove list markers
.replace(/^\s*\d+\.\s+/gm, '') // Remove numbered list markers
.replace(/---[\s\S]*?---/g, '') // Remove frontmatter
.trim();
const words = cleanContent.split(/\s+/).filter((word) => word.length > 0);
const wordCount = words.length;
// Average reading speed is 200-250 words per minute, using 225
const readingSpeed = 225;
const minutes = Math.ceil(wordCount / readingSpeed);
return `${Math.max(1, minutes)} min read`;
}
export type EnhancedBlogPost = import('astro:content').CollectionEntry<'blog'>;
interface BlogClientProps {
posts: EnhancedBlogPost[];
}
export default function BlogPosts({ posts }: BlogClientProps) {
const prefersReducedMotion = usePrefersReducedMotion();
const [selectedCategory, setSelectedCategory] = useState('View all');
const [sortBy, setSortBy] = useState('newest');
const [currentPage, setCurrentPage] = useState(1);
const [isScrolling, setIsScrolling] = useState(false);
const postsPerPage = 9;
const { scrollY } = useScroll();
const categories = [
'View all',
...Array.from(
new Set(
posts
.map((post) => post.data.tags?.[0])
.filter((tag): tag is string => Boolean(tag)),
),
),
];
// Scroll-based animations for featured card
const imageScale = useTransform(scrollY, [0, 600], [1, 1.15]);
const filteredPosts = posts
.filter(
(post) =>
selectedCategory === 'View all' ||
post.data.tags?.includes(selectedCategory),
)
.sort((a, b) => {
if (sortBy === 'newest') {
return (
new Date(b.data.date).getTime() - new Date(a.data.date).getTime()
);
} else {
return (
new Date(a.data.date).getTime() - new Date(b.data.date).getTime()
);
}
});
const featuredPost = posts[0];
const allRegularPosts = filteredPosts.slice(1);
// Pagination logic
const totalPages = Math.ceil(allRegularPosts.length / postsPerPage);
const startIndex = (currentPage - 1) * postsPerPage;
const endIndex = startIndex + postsPerPage;
const currentPosts = allRegularPosts.slice(startIndex, endIndex);
// Update isScrolling state when user scrolls
useEffect(() => {
const updateScrollState = () => {
if (window.scrollY > 5 && !isScrolling) {
setIsScrolling(true);
}
};
window.addEventListener('scroll', updateScrollState);
return () => window.removeEventListener('scroll', updateScrollState);
}, [isScrolling]);
// Reset to page 1 when category changes
const handleCategoryChange = (category: string) => {
setSelectedCategory(category);
setCurrentPage(1);
};
// Animation variants for featured card
const featuredContainer = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2,
delayChildren: 0.3,
},
},
};
const featuredItem = {
hidden: { opacity: 0, y: 30, filter: 'blur(2px)' },
visible: {
opacity: 1,
y: 0,
filter: 'blur(0px)',
transition: {
type: 'spring' as const,
stiffness: 60,
damping: 20,
},
},
};
// Animation variants for individual blog cards
const cardVariants = {
hidden: {
opacity: 0,
y: 20,
filter: 'blur(3px)',
scale: 0.98,
},
visible: {
opacity: 1,
y: 0,
filter: 'blur(0px)',
scale: 1,
transition: {
type: 'spring' as const,
stiffness: 120,
damping: 25,
mass: 1,
duration: 0.6,
},
},
};
return (
<section className="section-padding bigger-container overflow-hidden">
{/* Hero Section */}
<div className="">
<h1 className="text-center text-4xl font-medium tracking-tighter md:text-start md:text-6xl md:leading-none lg:text-7xl">
Blog
</h1>
<p className="text-muted-foreground mt-3 hidden text-lg leading-relaxed md:block lg:mt-4">
Read our latest articles
</p>
{featuredPost && (
<a href={`/blog/${featuredPost.id}`} className="group block">
<motion.div
className="mt-8 lg:mt-12"
variants={featuredContainer}
initial={prefersReducedMotion ? 'visible' : 'hidden'}
animate="visible"
>
{/* Image */}
<div className="text-border group relative aspect-[16/10] cursor-pointer overflow-hidden rounded-2xl lg:aspect-[16/9]">
<motion.div
className="absolute inset-0 rounded-2xl"
style={isScrolling ? { scale: imageScale } : {}}
>
<img
src={featuredPost.data.coverImage}
alt={featuredPost.data.title}
className="size-full rounded-2xl object-cover"
/>
<div className="from-foreground/70 absolute inset-0 hidden bg-gradient-to-t to-transparent to-40% md:block" />
</motion.div>
{/* Desktop overlay - only shows on md+ */}
<motion.div
className="absolute right-0 bottom-0 left-0 hidden p-8 md:block"
variants={featuredContainer}
initial={prefersReducedMotion ? 'visible' : 'hidden'}
animate="visible"
>
<motion.div
className="flex w-full items-center justify-between gap-2"
variants={featuredItem}
>
<h2 className="text-lg leading-tight font-medium md:text-2xl lg:text-3xl">
{featuredPost.data.title}
</h2>
<ArrowUpRight className="h-5 w-5 transition-transform duration-200 group-hover:translate-x-1 group-hover:-translate-y-1" />
</motion.div>
<motion.p
className="mt-1 text-sm md:text-base"
variants={featuredItem}
>
{featuredPost.data.description}
</motion.p>
<motion.div
className="mt-6 flex items-end justify-between"
variants={featuredItem}
>
<div className="flex gap-8 text-xs">
<div className="space-y-2">
<div className="font-medium">Written by</div>
<div className="flex items-center gap-2">
<img
src={featuredPost.data.author.image}
alt={featuredPost.data.author.name}
width={40}
height={40}
className="rounded-full object-cover"
/>
<span className="font-medium">
{featuredPost.data.author.name}
</span>
</div>
</div>
<div className="flex flex-col space-y-2">
<div className="font-medium">Published on</div>
<div className="flex flex-1 items-center justify-center text-sm font-medium">
{new Date(featuredPost.data.date).toLocaleDateString(
'en-GB',
{
month: 'short',
day: 'numeric',
year: 'numeric',
},
)}
</div>
</div>
</div>
<div className="flex flex-wrap gap-2">
{featuredPost.data.tags?.slice(0, 3).map((tag) => (
<Badge
key={tag}
className="bg-card-foreground capitalize"
>
{tag}
</Badge>
))}
</div>
</motion.div>
</motion.div>
</div>
{/* Mobile Content - Shows on mobile */}
<div className="mt-5 md:hidden">
<motion.div className="" variants={featuredItem}>
<div className="flex flex-wrap gap-2">
{featuredPost.data.tags?.slice(0, 3).map((tag) => (
<Badge
key={tag}
className="bg-card-foreground capitalize"
>
{tag}
</Badge>
))}
</div>
</motion.div>
<motion.div
className="mt-4 flex items-start justify-between gap-2"
variants={featuredItem}
>
<h2 className="text-xl leading-tight font-medium">
{featuredPost.data.title}
</h2>
<ArrowUpRight className="mt-1 h-5 w-5 flex-shrink-0 transition-transform duration-200 group-hover:translate-x-1 group-hover:-translate-y-1" />
</motion.div>
</div>
{/* Mobile Content - Shows below image on mobile */}
<div className="mt-2 md:hidden">
<motion.p
className="text-muted-foreground mb-6 text-sm"
variants={featuredItem}
>
{featuredPost.data.description}
</motion.p>
<motion.div
className="flex items-center gap-4 text-xs"
variants={featuredItem}
>
<div className="flex items-center gap-2">
<img
src={featuredPost.data.author.image}
alt={featuredPost.data.author.name}
width={40}
height={40}
className="rounded-full object-cover"
/>
<div>
<div className="font-medium">
{featuredPost.data.author.name}
</div>
<div className="text-muted-foreground">
{new Date(featuredPost.data.date).toLocaleDateString(
'en-GB',
{
month: 'short',
day: 'numeric',
year: 'numeric',
},
)}
</div>
</div>
</div>
</motion.div>
</div>
</motion.div>
</a>
)}
</div>
{/* Filter Controls */}
<div className="mt-14 mb-10 flex flex-col justify-between gap-4 md:mt-16 lg:mt-20 lg:flex-row">
<div className="w-full overflow-x-auto">
<Tabs value={selectedCategory} onValueChange={handleCategoryChange}>
<ScrollArea className="pb-2" orientation="horizontal">
<TabsList className="">
{categories.map((category) => (
<TabsTrigger
key={category}
value={category}
className="whitespace-nowrap"
>
{category}
</TabsTrigger>
))}
</TabsList>
</ScrollArea>
</Tabs>
</div>
<div>
<Select value={sortBy} onValueChange={setSortBy}>
<SelectTrigger className="bg-background w-full sm:w-[200px]">
<SelectValue placeholder="Sort" />
</SelectTrigger>
<SelectContent>
<SelectItem value="newest">Newest first</SelectItem>
<SelectItem value="oldest">Oldest first</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<h2 className="text-4xl font-medium tracking-tighter">
Read all articles
</h2>
{/* Regular Posts Grid */}
<div className="mt-6 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{currentPosts.map((post) => (
<motion.div
key={`${selectedCategory}-${sortBy}-${currentPage}-${post.id}`}
variants={cardVariants}
initial={prefersReducedMotion ? 'visible' : 'hidden'}
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
className="h-full"
>
<a href={`/blog/${post.id}`} className="group block h-full">
<BlogCard post={post} className="h-full" />
</a>
</motion.div>
))}
</div>
{/* Pagination */}
<AnimatePresence>
{totalPages > 1 && (
<motion.div
className="border-input/50 mt-12 flex justify-center border-t pt-4 md:mt-20"
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -6 }}
transition={{
duration: 0.35,
delay: 0.15,
ease: [0.22, 1, 0.36, 1],
}}
>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage > 1) setCurrentPage(currentPage - 1);
}}
className={
currentPage === 1 ? 'pointer-events-none opacity-50' : ''
}
/>
</PaginationItem>
{Array.from({ length: totalPages }, (_, i) => i + 1).map(
(page) => (
<PaginationItem key={page}>
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault();
setCurrentPage(page);
}}
isActive={currentPage === page}
>
{page}
</PaginationLink>
</PaginationItem>
),
)}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage < totalPages)
setCurrentPage(currentPage + 1);
}}
className={
currentPage === totalPages
? 'pointer-events-none opacity-50'
: ''
}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</motion.div>
)}
</AnimatePresence>
</section>
);
}
interface BlogCardProps {
post: EnhancedBlogPost;
featured?: boolean;
className?: string;
}
export function BlogCard({ post, featured = false, className }: BlogCardProps) {
if (featured) {
// Featured card is handled separately in the main component
return null;
}
return (
<Card
className={cn(
'group border-input h-full gap-4 overflow-hidden rounded-2xl py-5 shadow-none transition-all duration-200',
className,
)}
>
<CardHeader className="px-5">
<motion.div className="relative aspect-[4/3] overflow-hidden rounded-md">
<img
src={post.data.coverImage}
alt={post.data.title}
className="size-full object-cover transition-all duration-300 group-hover:scale-102"
/>
</motion.div>
</CardHeader>
<CardContent className="mb-8 px-5">
<div className="mb-3 flex items-center justify-between">
<span className="text-lg">{post.data.tags?.[0]}</span>
<span className="text-muted-foreground">
{new Date(post.data.date).toLocaleDateString('en-GB', {
month: 'short',
day: 'numeric',
year: 'numeric',
})}
</span>
</div>
<h3 className="text-xl">{post.data.title}</h3>
</CardContent>
<CardFooter className="mt-auto mb-0 flex flex-wrap items-center justify-between gap-2 justify-self-end px-5">
<span className="text-foreground text-sm font-medium group-hover:underline">
Read now
</span>
<div className="flex flex-wrap items-center gap-1">
{post.data.tags?.slice(0, 2).map((tag) => (
<Badge key={tag} className="bg-card-foreground capitalize">
{tag}
</Badge>
))}
</div>
</CardFooter>
</Card>
);
}

View file

@ -0,0 +1,953 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import {
ArrowDownZA,
ArrowUpAZ,
BookOpen,
Clock,
ExternalLink,
FileText,
Github,
Grid2X2,
Info,
List,
CirclePlay,
Search,
Users,
X,
Youtube,
} from "lucide-react";
import coursesData from "@/data/courses.json";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";
const ALL = "all";
const EXCLUDED_COURSE_IDS = new Set([
"cs-302-theory-of-computation",
"cs-602-theory-of-computation",
]);
type Course = (typeof coursesData.courses)[number];
type SortDirection = "asc" | "desc";
type ViewMode = "list" | "cards";
type FeaturedResource = {
courseId: string;
title: string;
eyebrow: string;
meta: string;
url: string;
thumbnail?: string;
type: "playlist" | "repository" | "notes";
description: string;
};
const FEATURED_RESOURCES: FeaturedResource[] = [
{
courseId: "es-335-machine-learning",
title: "Machine Learning @ IIT Gandhinagar 2024",
eyebrow: "ES 335",
meta: "33 lessons",
url: "https://www.youtube.com/watch?v=XuDrvFmvk2U&list=PLftoLyLEwECDqanAL7LkZNnZG9VdNCiJF",
thumbnail: "/images/course-resources/machine-learning.png",
type: "playlist",
description: "Recorded lectures by Nipun Batra.",
},
{
courseId: "es-661-probabilistic-machine-learning",
title: "Probabilistic Machine Learning",
eyebrow: "ES 661",
meta: "24 lessons",
url: "https://www.youtube.com/watch?v=UCYaVS9_jGU&list=PLftoLyLEwECBEJyfRBJoSBd0UaTjEcs3I",
thumbnail: "/images/course-resources/probabilistic-machine-learning.png",
type: "playlist",
description: "Recorded lectures by Nipun Batra.",
},
{
courseId: "cs-330-operating-systems",
title: "Operating Systems Fall 2018 IITGN",
eyebrow: "CS 330",
meta: "31 lessons",
url: "https://www.youtube.com/watch?v=whp34MZbG6o&list=PLftoLyLEwECB3NsNfQ1oxtt8IoBNRWcO5",
thumbnail: "/images/course-resources/operating-systems.png",
type: "playlist",
description: "Recorded lectures by Nipun Batra.",
},
{
courseId: "cs-201-theory-of-computing",
title: "balu/toc",
eyebrow: "CS 201",
meta: "GitHub repository",
url: "https://github.com/balu/toc",
thumbnail: "/images/course-resources/theory-of-computing.png",
type: "repository",
description:
"Repository of Theory of Computation course material and references by Balagopal Komarath.",
},
{
courseId: "cs-614-advanced-algorithms",
title: "Parameterized Algorithms",
eyebrow: "CS 614",
meta: "NPTEL playlist",
url: "https://www.youtube.com/watch?v=9x9pAn5DkZI&list=PLyqSpQzTE6M_0KLR2_FVBN4Rnvq_PlcrA",
thumbnail: "/images/course-resources/parameterized-algorithms.png",
type: "playlist",
description:
"NPTEL course on Parameterized Algorithms by Neeldhara Misra and Saket Saurabh, closely aligned with the Advanced Algorithms course.",
},
{
courseId:
"cs-392-i-special-topics-in-computer-science-introduction-to-competitive-programming",
title: "Introduction to Competitive Programming",
eyebrow: "CS 392-I",
meta: "NPTEL playlist",
url: "https://www.youtube.com/playlist?list=PLyqSpQzTE6M9p9pKxFGpskf4voY45T2DZ",
thumbnail: "/images/course-resources/competitive-programming.png",
type: "playlist",
description:
"NPTEL course on Competitive Programming by Neeldhara Misra and Arjun Arul, closely aligned with the Competitive Programming elective.",
},
{
courseId: "cs-328-introduction-to-data-science",
title: "Scalable Data Science",
eyebrow: "CS 328",
meta: "NPTEL playlist",
url: "https://www.youtube.com/playlist?list=PLbRMhDVUMngekIHyLt8b_3jQR7C0KUCul",
thumbnail: "/images/course-resources/scalable-data-science.png",
type: "playlist",
description:
"A NPTEL course by Anirban Dasgupta and Souransghu Bhattacharya, closely aligned with the Introduction to Data Science Course.",
},
{
courseId: "em-614-advanced-probability-and-statistics",
title: "Probability and Random Processes Lecture Notes",
eyebrow: "EM 614",
meta: "Lecture notes",
url: "https://shanmuga.people.iitgn.ac.in/Data/ProbabilityRPLectureNotes_Shanmuga.pdf",
thumbnail: "/images/course-resources/probability-lecture-notes.png",
type: "notes",
description: "Lecture notes by Shanmuganathan Raman.",
},
];
function getFeaturedResourcesForCourse(courseId: string) {
return FEATURED_RESOURCES.filter((resource) => resource.courseId === courseId);
}
function ResourceIcon({
type,
className,
}: {
type: FeaturedResource["type"];
className?: string;
}) {
if (type === "repository") return <Github className={className} />;
if (type === "notes") return <FileText className={className} />;
return <CirclePlay className={className} />;
}
function ResourceSourceIcon({
type,
className,
}: {
type: FeaturedResource["type"];
className?: string;
}) {
if (type === "repository") return <Github className={className} />;
if (type === "notes") return <FileText className={className} />;
return <Youtube className={className} />;
}
function resourceActionLabel(type: FeaturedResource["type"]) {
if (type === "notes") return "Open notes";
return type === "repository" ? "Open repository" : "Watch playlist";
}
function formatCredits(course: Course) {
if (!course.credits.length) return "Credits not specified";
return `${course.creditLabel} ${
course.credits.length === 1 && course.credits[0] === 1
? "credit"
: "credits"
}`;
}
function courseSearchText(course: Course) {
return [
course.code,
course.title,
course.creditLabel,
course.discipline,
course.program,
course.courseType,
course.level,
course.semester,
course.instructors.join(" "),
course.prerequisites.join(" "),
course.content,
course.textbooks,
]
.filter(Boolean)
.join(" ")
.toLowerCase();
}
function truncateText(value: string | null, maxLength: number) {
if (!value) return null;
if (value.length <= maxLength) return value;
return `${value.slice(0, maxLength).trimEnd()}...`;
}
function optionLabel(value: string, count?: number) {
return count === undefined ? value : `${value} (${count})`;
}
function programTone(program: string) {
if (program === "Data Science for Decision Making") {
return {
card: "border-l-sky-500/45 bg-sky-500/[0.025]",
badge: "border-sky-500/25 bg-sky-500/10 text-sky-700 dark:text-sky-300",
};
}
if (program === "Integrated Circuit Design and Technology") {
return {
card: "border-l-emerald-500/45 bg-emerald-500/[0.025]",
badge:
"border-emerald-500/25 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
};
}
return {
card: "border-l-primary/45 bg-primary/[0.025]",
badge: "border-primary/20 bg-primary/10 text-primary",
};
}
function countBy(courses: Course[], key: keyof Course) {
const counts = new Map<string, number>();
for (const course of courses) {
const value = course[key];
if (typeof value === "string" && value) {
counts.set(value, (counts.get(value) ?? 0) + 1);
}
}
return counts;
}
function CourseBadges({ course }: { course: Course }) {
return (
<div className="flex flex-wrap gap-2">
<Badge variant="secondary">{formatCredits(course)}</Badge>
<Badge variant="outline">{course.level}</Badge>
<Badge variant="outline">{course.courseType}</Badge>
{course.semester && <Badge variant="outline">{course.semester}</Badge>}
</div>
);
}
function CourseSummary({
course,
view,
onOpen,
}: {
course: Course;
view: ViewMode;
onOpen: (course: Course) => void;
}) {
const contentPreview =
truncateText(course.content, view === "cards" ? 180 : 260) ??
"Detailed syllabus text is not available in the current catalogue extract.";
const instructors = course.instructors.length
? course.instructors.join(", ")
: "To be announced";
return (
<Card
className={cn(
"hover:border-primary/40 gap-0 rounded-lg border-l-4 p-5 transition-colors",
programTone(course.program).card,
view === "cards" && "h-full",
)}
>
<div
className={cn(
"flex h-full gap-4",
view === "cards"
? "flex-col"
: "flex-col md:flex-row md:items-start md:justify-between",
)}
>
<div className="min-w-0 flex-1 space-y-3">
<div className="text-muted-foreground flex flex-wrap items-center gap-2 text-xs">
<span className="text-foreground inline-flex items-center gap-1 font-medium">
<BookOpen className="size-3.5" />
{course.code}
</span>
<span
className={cn(
"inline-flex max-w-full rounded-full border px-2 py-0.5 font-medium",
programTone(course.program).badge,
)}
>
<span className="truncate">{course.program}</span>
</span>
{course.ltpLabel && (
<span className="inline-flex items-center gap-1">
<Clock className="size-3.5" />
L-T-P {course.ltpLabel}
</span>
)}
</div>
<h2 className="text-lg leading-snug font-semibold">{course.title}</h2>
<CourseBadges course={course} />
<p className="text-muted-foreground text-sm leading-relaxed">
{contentPreview}
</p>
<div className="text-muted-foreground flex flex-wrap items-center gap-2 text-xs">
<Users className="size-3.5" />
<span className="min-w-0">{instructors}</span>
</div>
</div>
<div className={cn(view === "list" && "shrink-0")}>
<Button
type="button"
variant="outline"
size="sm"
className="w-full md:w-auto"
onClick={() => onOpen(course)}
>
<Info className="size-4" />
Details
</Button>
</div>
</div>
</Card>
);
}
function DetailRow({
label,
value,
}: {
label: string;
value: string | null | undefined;
}) {
return (
<>
<dt className="text-muted-foreground">{label}</dt>
<dd>{value || "Not specified"}</dd>
</>
);
}
function CourseDetailModal({
course,
onClose,
}: {
course: Course | undefined;
onClose: () => void;
}) {
useEffect(() => {
if (!course) return;
const previousOverflow = document.body.style.overflow;
function onKeyDown(event: KeyboardEvent) {
if (event.key === "Escape") onClose();
}
document.body.style.overflow = "hidden";
document.addEventListener("keydown", onKeyDown);
return () => {
document.body.style.overflow = previousOverflow;
document.removeEventListener("keydown", onKeyDown);
};
}, [course, onClose]);
if (!course) return null;
const resources = getFeaturedResourcesForCourse(course.id);
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div
className="bg-background/80 absolute inset-0 backdrop-blur-sm"
aria-hidden="true"
onClick={onClose}
/>
<div
role="dialog"
aria-modal="true"
aria-labelledby="course-detail-title"
className="bg-background relative z-10 flex max-h-[90vh] w-full max-w-4xl flex-col overflow-hidden rounded-lg border shadow-xl"
>
<div className="border-b p-5">
<div className="flex items-start justify-between gap-4">
<div className="min-w-0">
<p className="text-primary text-sm font-medium">{course.code}</p>
<h2
id="course-detail-title"
className="mt-1 text-2xl leading-tight font-semibold"
>
{course.title}
</h2>
</div>
<Button
type="button"
variant="ghost"
size="icon"
aria-label="Close course details"
onClick={onClose}
>
<X className="size-5" />
</Button>
</div>
<div className="mt-4">
<Badge
variant="outline"
className={cn("mb-3", programTone(course.program).badge)}
>
{course.program}
</Badge>
<CourseBadges course={course} />
</div>
</div>
<div className="overflow-y-auto p-5">
{resources.length > 0 && (
<section className="bg-primary/5 mb-6 rounded-lg border p-4">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="min-w-0">
<div className="text-primary mb-1 flex items-center gap-2 text-sm font-semibold">
<ResourceSourceIcon
type={resources[0]?.type ?? "playlist"}
className="size-4"
/>
Featured course resource
</div>
<p className="text-muted-foreground text-sm">
Public resources are available for this course.
</p>
</div>
<div className="flex flex-wrap gap-2">
{resources.map((resource) => (
<Button key={resource.url} asChild size="sm">
<a
href={resource.url}
target="_blank"
rel="noreferrer"
>
<ResourceIcon type={resource.type} className="size-4" />
{resourceActionLabel(resource.type)}
<ExternalLink className="size-3.5" />
</a>
</Button>
))}
</div>
</div>
</section>
)}
<dl className="grid gap-x-6 gap-y-3 text-sm md:grid-cols-[10rem_1fr_10rem_1fr]">
<DetailRow label="Program" value={course.program} />
<DetailRow label="Discipline" value={course.discipline} />
<DetailRow label="Credits" value={formatCredits(course)} />
<DetailRow
label="L-T-P"
value={
course.ltpLabel
? `${course.ltpLabel} (lecture-theory-practical)`
: null
}
/>
<DetailRow label="Semester" value={course.semester} />
<DetailRow label="Category" value={course.courseType} />
<DetailRow label="Level" value={course.level} />
<DetailRow
label="Potential instructors"
value={
course.instructors.length
? course.instructors.join(", ")
: undefined
}
/>
<DetailRow
label="Prerequisites"
value={
course.prerequisites.length
? course.prerequisites.join(", ")
: undefined
}
/>
</dl>
<div className="mt-8 grid gap-6 lg:grid-cols-2">
<section>
<div className="mb-2 flex items-center gap-2 font-semibold">
<FileText className="size-4" />
Course Content
</div>
<p className="text-muted-foreground text-sm leading-relaxed">
{course.content ||
"Detailed syllabus text is not available in the current catalogue extract."}
</p>
</section>
<section>
<div className="mb-2 flex items-center gap-2 font-semibold">
<BookOpen className="size-4" />
Textbooks and References
</div>
<p className="text-muted-foreground text-sm leading-relaxed">
{course.textbooks ||
"Textbook and reference details are not available in the current catalogue extract."}
</p>
</section>
</div>
</div>
</div>
</div>
);
}
function FeaturedResources({
courses,
onOpenCourse,
}: {
courses: Course[];
onOpenCourse: (course: Course) => void;
}) {
const coursesById = new Map(courses.map((course) => [course.id, course]));
return (
<section className="border-t bg-background py-8">
<div className="container">
<div className="mb-5 flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
<div>
<div className="text-primary mb-2 flex items-center gap-2 text-sm font-semibold">
<BookOpen className="size-4" />
Featured resources
</div>
<h2 className="text-2xl font-semibold">Featured course resources</h2>
</div>
<p className="text-muted-foreground max-w-2xl text-sm">
Selected public resources are linked to their corresponding course
catalogue entries.
</p>
</div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
{FEATURED_RESOURCES.map((resource) => {
const course = coursesById.get(resource.courseId);
if (!course) return null;
return (
<Card
key={resource.url}
className="group gap-0 overflow-hidden rounded-lg py-0 transition-colors hover:border-primary/40"
>
<a
href={resource.url}
target="_blank"
rel="noreferrer"
className="relative block aspect-video overflow-hidden bg-muted"
aria-label={`Open ${resource.title}`}
>
{resource.thumbnail ? (
<img
src={resource.thumbnail}
alt=""
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
loading="lazy"
/>
) : (
<div className="from-primary/20 to-muted flex h-full w-full items-center justify-center bg-gradient-to-br">
<ResourceSourceIcon
type={resource.type}
className="text-primary size-14"
/>
</div>
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/65 via-black/5 to-transparent" />
<div className="absolute right-3 bottom-3 inline-flex items-center gap-1 rounded-md bg-black/75 px-2 py-1 text-xs font-medium text-white">
<ResourceIcon type={resource.type} className="size-3.5" />
{resource.meta}
</div>
</a>
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="space-y-2">
<div className="flex flex-wrap items-center gap-2">
<Badge variant="secondary">{resource.eyebrow}</Badge>
<span className="text-muted-foreground text-xs">
{course.title}
</span>
</div>
<h3 className="text-base leading-snug font-semibold">
{resource.title}
</h3>
<p className="text-muted-foreground text-sm leading-relaxed">
{resource.description}
</p>
</div>
<div className="mt-auto flex flex-wrap gap-2">
<Button asChild size="sm">
<a
href={resource.url}
target="_blank"
rel="noreferrer"
>
<ResourceIcon type={resource.type} className="size-4" />
{resource.type === "playlist" ? "Watch" : "Open"}
</a>
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => onOpenCourse(course)}
>
<Info className="size-4" />
Course details
</Button>
</div>
</div>
</Card>
);
})}
</div>
</div>
</section>
);
}
export default function CourseCatalog() {
const courses = useMemo(
() =>
coursesData.courses.filter(
(course) => !EXCLUDED_COURSE_IDS.has(course.id),
),
[],
);
const [query, setQuery] = useState("");
const [selectedProgram, setSelectedProgram] = useState(ALL);
const [selectedLevel, setSelectedLevel] = useState(ALL);
const [selectedSemester, setSelectedSemester] = useState(ALL);
const [selectedCredit, setSelectedCredit] = useState(ALL);
const [sortDirection, setSortDirection] = useState<SortDirection>("asc");
const [view, setView] = useState<ViewMode>("list");
const [selectedCourseId, setSelectedCourseId] = useState<string | null>(null);
const searchIndex = useMemo(
() =>
new Map(courses.map((course) => [course.id, courseSearchText(course)])),
[courses],
);
const programCounts = useMemo(() => countBy(courses, "program"), [courses]);
const levelCounts = useMemo(() => countBy(courses, "level"), [courses]);
const semesterCounts = useMemo(() => countBy(courses, "semester"), [courses]);
const selectedCourse = useMemo(
() => courses.find((course) => course.id === selectedCourseId),
[courses, selectedCourseId],
);
const filteredCourses = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();
return courses.filter((course) => {
if (
normalizedQuery &&
!searchIndex.get(course.id)?.includes(normalizedQuery)
) {
return false;
}
if (selectedProgram !== ALL && course.program !== selectedProgram) {
return false;
}
if (selectedLevel !== ALL && course.level !== selectedLevel) {
return false;
}
if (selectedSemester !== ALL && course.semester !== selectedSemester) {
return false;
}
if (
selectedCredit !== ALL &&
!course.credits.some((credit) => String(credit) === selectedCredit)
) {
return false;
}
return true;
});
}, [
courses,
query,
searchIndex,
selectedCredit,
selectedLevel,
selectedProgram,
selectedSemester,
]);
const sortedCourses = useMemo(() => {
return [...filteredCourses].sort((a, b) => {
const titleOrder = a.title.localeCompare(b.title, "en", {
sensitivity: "base",
});
const codeOrder = a.code.localeCompare(b.code, "en", {
numeric: true,
sensitivity: "base",
});
const order = titleOrder || codeOrder;
return sortDirection === "asc" ? order : -order;
});
}, [filteredCourses, sortDirection]);
const filtersActive =
query ||
selectedProgram !== ALL ||
selectedLevel !== ALL ||
selectedSemester !== ALL ||
selectedCredit !== ALL;
function clearFilters() {
setQuery("");
setSelectedProgram(ALL);
setSelectedLevel(ALL);
setSelectedSemester(ALL);
setSelectedCredit(ALL);
}
const SortIcon = sortDirection === "asc" ? ArrowUpAZ : ArrowDownZA;
return (
<div>
<section className="from-primary/5 bg-gradient-to-b to-transparent py-14 md:py-18">
<div className="container">
<div className="max-w-3xl">
<div className="text-muted-foreground mb-4 inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs">
<BookOpen className="size-3.5" />
{coursesData.metadata.sourceLabel}
</div>
<h1 className="text-3xl md:text-4xl">Courses</h1>
<p className="text-muted-foreground mt-4 max-w-2xl">
Browse the CSE course catalogue by course code, title, level,
program, credit count, and semester format.
</p>
</div>
</div>
</section>
<FeaturedResources
courses={courses}
onOpenCourse={(course) => setSelectedCourseId(course.id)}
/>
<section className="bg-muted/20 border-y">
<div className="container py-5">
<div className="grid gap-3 lg:grid-cols-[minmax(16rem,1fr)_13rem_10rem_11rem_8rem_auto]">
<div className="relative">
<Search className="text-muted-foreground pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2" />
<Input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search code, title, instructor, content"
className="pl-9"
/>
</div>
<Select value={selectedProgram} onValueChange={setSelectedProgram}>
<SelectTrigger className="w-full" aria-label="Filter by program">
<SelectValue placeholder="Program" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL}>All Programs</SelectItem>
{coursesData.metadata.filters.programs.map((program) => (
<SelectItem key={program} value={program}>
{optionLabel(program, programCounts.get(program))}
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={selectedLevel} onValueChange={setSelectedLevel}>
<SelectTrigger className="w-full" aria-label="Filter by level">
<SelectValue placeholder="Level" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL}>All Levels</SelectItem>
{coursesData.metadata.filters.levels.map((level) => (
<SelectItem key={level} value={level}>
{optionLabel(level, levelCounts.get(level))}
</SelectItem>
))}
</SelectContent>
</Select>
<Select
value={selectedSemester}
onValueChange={setSelectedSemester}
>
<SelectTrigger className="w-full" aria-label="Filter by semester">
<SelectValue placeholder="Semester" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL}>All Semesters</SelectItem>
{coursesData.metadata.filters.semesters.map((semester) => (
<SelectItem key={semester} value={semester}>
{optionLabel(semester, semesterCounts.get(semester))}
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={selectedCredit} onValueChange={setSelectedCredit}>
<SelectTrigger className="w-full" aria-label="Filter by credits">
<SelectValue placeholder="Credits" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL}>All Credits</SelectItem>
{coursesData.metadata.filters.credits.map((credit) => (
<SelectItem key={credit} value={String(credit)}>
{credit} credits
</SelectItem>
))}
</SelectContent>
</Select>
<Button
type="button"
variant="outline"
className="w-full lg:w-auto"
onClick={clearFilters}
disabled={!filtersActive}
>
<X className="size-4" />
Clear
</Button>
</div>
<div className="mt-4 flex flex-wrap items-center justify-between gap-3">
<p className="text-muted-foreground text-sm">
Showing{" "}
<span className="text-foreground font-medium">
{filteredCourses.length}
</span>{" "}
of {courses.length} catalogue entries
</p>
<div className="flex flex-wrap items-center gap-2">
<Button
type="button"
variant="outline"
size="sm"
aria-label={`Sort by name ${
sortDirection === "asc" ? "descending" : "ascending"
}`}
onClick={() =>
setSortDirection((current) =>
current === "asc" ? "desc" : "asc",
)
}
>
<SortIcon className="size-4" />
{sortDirection === "asc" ? "Name A-Z" : "Name Z-A"}
</Button>
<div className="bg-background inline-flex rounded-md border p-1">
<Button
type="button"
variant={view === "list" ? "default" : "ghost"}
size="sm"
aria-pressed={view === "list"}
onClick={() => setView("list")}
>
<List className="size-4" />
List
</Button>
<Button
type="button"
variant={view === "cards" ? "default" : "ghost"}
size="sm"
aria-pressed={view === "cards"}
onClick={() => setView("cards")}
>
<Grid2X2 className="size-4" />
Cards
</Button>
</div>
</div>
</div>
</div>
</section>
<section className="section-padding">
<div className="container">
<div
className={cn(
view === "cards"
? "grid gap-4 md:grid-cols-2 xl:grid-cols-3"
: "space-y-3",
)}
>
{sortedCourses.map((course) => (
<CourseSummary
key={course.id}
course={course}
view={view}
onOpen={(nextCourse) => setSelectedCourseId(nextCourse.id)}
/>
))}
</div>
{sortedCourses.length === 0 && (
<div className="rounded-lg border border-dashed py-16 text-center">
<h2 className="font-semibold">No courses found</h2>
<p className="text-muted-foreground mt-2 text-sm">
Adjust the filters or search terms.
</p>
</div>
)}
</div>
</section>
<CourseDetailModal
course={selectedCourse}
onClose={() => setSelectedCourseId(null)}
/>
</div>
);
}

View file

@ -0,0 +1,29 @@
'use client';
const STATS = [
{ value: '20+', label: 'Faculty Members' },
{ value: '100+', label: 'PhD & M.Tech Students' },
{ value: '6', label: 'Research Labs' },
{ value: '50+', label: 'Publications/Year' },
];
export default function DeptStats() {
return (
<section className="border-y py-12">
<div className="container">
<div className="grid grid-cols-2 gap-8 md:grid-cols-4">
{STATS.map((stat, i) => (
<div key={i} className="text-center">
<p className="text-secondary text-3xl font-bold md:text-4xl">
{stat.value}
</p>
<p className="text-muted-foreground mt-1 text-sm">
{stat.label}
</p>
</div>
))}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,141 @@
'use client';
import { useState } from 'react';
import { ExternalLink, Filter } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import {
CATEGORY_LABELS,
CATEGORY_ORDER,
FACULTY,
type FacultyCategory,
type FacultyMember,
} from '@/data/faculty';
function FacultyCard({ member }: { member: FacultyMember }) {
const initials = member.name
.split(' ')
.map((n) => n[0])
.join('')
.slice(0, 2)
.toUpperCase();
return (
<Card className="group flex gap-4 p-4">
<div className="bg-primary/10 text-primary grid size-14 shrink-0 place-items-center rounded-lg text-lg font-semibold">
{initials}
</div>
<div className="min-w-0 flex-1">
<div className="flex items-start justify-between gap-2">
<div>
<h3 className="text-sm font-semibold leading-snug">
{member.name}
</h3>
<p className="text-muted-foreground text-xs">{member.designation}</p>
</div>
{member.homepage && (
<a
href={member.homepage}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-secondary shrink-0 transition-colors"
>
<ExternalLink className="size-3.5" />
</a>
)}
</div>
{member.secondaryDepartment && (
<p className="text-muted-foreground mt-1 text-xs">
Also affiliated with {member.secondaryDepartment}
</p>
)}
{member.affiliations && member.affiliations.length > 0 && (
<p className="text-muted-foreground mt-1 text-xs">
{member.affiliations.join(', ')}
</p>
)}
</div>
</Card>
);
}
export default function FacultyDirectory() {
const [activeFilter, setActiveFilter] = useState<FacultyCategory | 'all'>(
'all',
);
const filteredCategories =
activeFilter === 'all'
? CATEGORY_ORDER
: CATEGORY_ORDER.filter((c) => c === activeFilter);
return (
<div>
{/* Header */}
<section className="from-primary/5 bg-gradient-to-b to-transparent py-16 md:py-20">
<div className="container text-center">
<h1 className="text-3xl md:text-4xl">Faculty</h1>
<p className="text-muted-foreground mx-auto mt-3 max-w-lg">
Our faculty bring expertise across theoretical CS, AI, systems,
security, and interdisciplinary computing.
</p>
</div>
</section>
{/* Filters */}
<section className="border-b">
<div className="container flex flex-wrap items-center gap-2 py-4">
<Filter className="text-muted-foreground mr-1 size-4" />
<Button
variant={activeFilter === 'all' ? 'default' : 'outline'}
size="sm"
className="rounded-full text-xs"
onClick={() => setActiveFilter('all')}
>
All ({FACULTY.length})
</Button>
{CATEGORY_ORDER.map((cat) => {
const count = FACULTY.filter((f) => f.category === cat).length;
if (count === 0) return null;
return (
<Button
key={cat}
variant={activeFilter === cat ? 'default' : 'outline'}
size="sm"
className="rounded-full text-xs"
onClick={() => setActiveFilter(cat)}
>
{CATEGORY_LABELS[cat]} ({count})
</Button>
);
})}
</div>
</section>
{/* Faculty Grid */}
<section className="section-padding">
<div className="container">
{filteredCategories.map((category) => {
const members = FACULTY.filter((f) => f.category === category);
if (members.length === 0) return null;
return (
<div key={category} className="mb-12 last:mb-0">
<h2 className="mb-6 text-xl font-semibold">
{CATEGORY_LABELS[category]}
</h2>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{members.map((member) => (
<FacultyCard key={member.name} member={member} />
))}
</div>
</div>
);
})}
</div>
</section>
</div>
);
}

View file

@ -0,0 +1,117 @@
'use client';
import { ChevronRightIcon, MessageSquare } from 'lucide-react';
import Noise from '@/components/elements/noise';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/ui/accordion';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardFooter,
CardHeader,
} from '@/components/ui/card';
const faqData = [
{
id: 'lumen-who-is-it-for',
question: 'What is Lumen and who is it for?',
answer:
'Lumen is a task and workflow management platform designed for product teams, developers, and creatives who want to move faster with clarity and control.',
},
{
id: 'technical-knowledge',
question: 'Can I use Lumen without technical knowledge?',
answer:
'Absolutely! Lumen is designed with simplicity in mind. You can start organizing tasks, creating workflows, and collaborating with your team without any technical background. The intuitive interface makes it easy for anyone to get started.',
},
{
id: 'integrations',
question: 'Does Lumen integrate with tools like Slack or Figma?',
answer:
"Yes, Lumen integrates seamlessly with popular tools including Slack, Figma, GitHub, Jira, and many more. You can connect your existing workflow tools to create a unified workspace that fits your team's needs.",
},
{
id: 'task-automation',
question: 'How does task automation work in Lumen?',
answer:
'Lumen offers intelligent task automation that helps streamline repetitive processes. You can set up custom rules, triggers, and workflows that automatically assign tasks, update statuses, send notifications, and move projects through different stages based on your defined criteria.',
},
{
id: 'security-compliance',
question: 'Is Lumen secure and compliant?',
answer:
'Security is our top priority. Lumen is built with enterprise-grade security features including end-to-end encryption, SOC 2 Type II compliance, GDPR compliance, and regular security audits. Your data is protected with industry-standard security protocols.',
},
];
export default function FAQSection() {
return (
<section className="section-padding relative">
<Noise />
<div className="container">
{/* Section Header */}
<h2 className="text-4xl leading-tight tracking-tight lg:text-5xl">
Frequently <br className="hidden md:block" />
asked questions:
</h2>
{/* FAQ Content */}
<div className="mt-8 grid gap-6 lg:mt-12 lg:grid-cols-3">
{/* FAQ Accordion - Left Side */}
<div className="lg:col-span-2">
<Accordion type="single" collapsible className="space-y-4">
{faqData.map((faq) => (
<AccordionItem
key={faq.id}
value={faq.id}
className="border-input hover:shadow-primary/5 rounded-lg !border px-6 py-2 transition-all duration-300 hover:shadow-md"
>
<AccordionTrigger className="cursor-pointer text-base font-medium hover:no-underline md:text-lg lg:text-xl">
{faq.question}
</AccordionTrigger>
<AccordionContent className="text-muted-foreground pb-6 text-base leading-relaxed">
{faq.answer}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
<Card className="hover:shadow-primary/5 h-full gap-6 transition-all duration-300 hover:shadow-lg">
<CardHeader className="gap-6 md:gap-8 lg:gap-11">
<MessageSquare className="text-secondary size-18 stroke-1 md:size-20" />
<h3 className="text-2xl">Still have questions?</h3>
</CardHeader>
<CardContent className="pt-0">
<p className="text-muted-foreground">
Let&apos;s talk. Our team is here to help you make the most of
Lumen. Whether it&apos;s onboarding, integration, or support.
</p>
</CardContent>
<CardFooter className="mt-auto">
<Button
size="lg"
variant="light"
className="group h-12 w-full gap-4"
asChild
>
<a href="/contact">
Contact With Us
<div className="bg-border border-input grid size-5.5 place-items-center rounded-full border">
<ChevronRightIcon className="size-4 transition-transform group-hover:translate-x-0.25" />
</div>
</a>
</Button>
</CardFooter>
</Card>
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,346 @@
'use client';
import { useEffect, useState } from 'react';
import { BarChart3, Clock, Filter, Link } from 'lucide-react';
import { motion } from 'motion/react';
import Noise from '@/components/elements/noise';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import {
Carousel,
type CarouselApi,
CarouselContent,
CarouselItem,
} from '@/components/ui/carousel';
import usePrefersReducedMotion from '@/hooks/usePrefersReducedMotion';
import { cn } from '@/lib/utils';
const features = [
{
id: 'navigation',
icon: Link,
title: 'Navigate your work with clarity',
description: 'A style board that adapts with your work.',
image: {
src: '/images/features-carousel/1.webp',
alt: 'Navigate your work with clarity',
width: 400,
height: 400,
className: 'ps-4 pt-4',
},
},
{
id: 'tracking',
icon: BarChart3,
title: 'Issue tracking with less noise',
description: 'Simple, powerful, and built for clarity.',
image: {
src: '/images/features-carousel/2.webp',
alt: 'Issue tracking with less noise',
width: 400,
height: 400,
className: 'pt-4',
},
},
{
id: 'filtering',
icon: Filter,
title: 'Filtering Tasks, no more distractions',
description: 'Smart filters that adapt to your needs.',
image: {
src: '/images/features-carousel/3.webp',
alt: 'Filtering Tasks',
width: 400,
height: 400,
className: 'p-4',
},
},
{
id: 'timeline',
icon: Clock,
title: 'Timeline Management, no more delays',
description: 'Keep track of project progress with ease.',
image: {
src: '/images/features-carousel/4.webp',
alt: 'Timeline Management',
width: 400,
height: 400,
className: 'pt-4',
},
},
];
export default function FeaturesCarousel() {
const prefersReducedMotion = usePrefersReducedMotion();
const [api, setApi] = useState<CarouselApi>();
const [activeIndex, setActiveIndex] = useState(0);
// Animation variants
const headerVariants = {
hidden: { opacity: 0, y: 30, filter: 'blur(2px)' },
visible: {
opacity: 1,
y: 0,
filter: 'blur(0px)',
transition: {
type: 'spring' as const,
stiffness: 100,
damping: 25,
duration: 0.6,
},
},
};
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2,
},
},
};
const buttonVariants = {
hidden: { opacity: 0, scale: 0.8, filter: 'blur(2px)' },
visible: {
opacity: 1,
scale: 1,
filter: 'blur(0px)',
transition: {
type: 'spring' as const,
stiffness: 120,
damping: 20,
},
},
};
const cardVariants = {
hidden: {
opacity: 0,
x: 60,
scale: 0.95,
filter: 'blur(3px)',
},
visible: {
opacity: 1,
x: 0,
scale: 1,
filter: 'blur(0px)',
transition: {
type: 'spring' as const,
stiffness: 80,
damping: 20,
duration: 0.8,
},
},
};
const handleFeatureClick = (index: number) => {
setActiveIndex(index);
api?.scrollTo(index);
};
// Listen to carousel changes to update active index
useEffect(() => {
if (!api) return;
const onSelect = () => {
setActiveIndex(api.selectedScrollSnap());
};
api.on('select', onSelect);
onSelect(); // Set initial state
return () => {
api.off('select', onSelect);
};
}, [api]);
return (
<section
id="features-carousel"
className="section-padding relative overflow-x-hidden"
>
<Noise />
<div className="container grid gap-8 lg:grid-cols-3 lg:gap-40">
{/* Left Content */}
<div className="flex flex-col gap-8 lg:col-span-1">
{/* Title and Description */}
<motion.div
className="space-y-4"
initial={prefersReducedMotion ? 'visible' : 'hidden'}
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
variants={headerVariants}
>
<h2 className="text-4xl tracking-tight text-balance lg:text-5xl">
Navigate your{' '}
<span className="text-muted-foreground/80">
work with clarity
</span>
</h2>
<p className="text-muted-foreground text-lg leading-snug">
A style board that adapts to how your team works.
</p>
</motion.div>
{/* Icon Buttons */}
<motion.div
className="mx-auto hidden max-w-[155px] grid-cols-2 justify-between gap-5 lg:grid"
initial={prefersReducedMotion ? 'visible' : 'hidden'}
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
variants={containerVariants}
>
{features.map((feature, index) => {
const IconComponent = feature.icon;
const isActive = index === activeIndex;
return (
<motion.button
key={feature.id}
onClick={() => handleFeatureClick(index)}
variants={buttonVariants}
className={cn(
`border-input hover:bg-border/50 flex h-16 w-16 cursor-pointer items-center justify-center rounded-sm border transition-all duration-300`,
isActive && 'bg-border',
)}
>
<IconComponent className="size-5" strokeWidth={2.1} />
</motion.button>
);
})}
</motion.div>
{/* Dots Indicator */}
<div className="mt-6 hidden flex-1 items-end justify-center gap-1 lg:flex">
{features.map((_, index) => (
<button
key={index}
onClick={() => handleFeatureClick(index)}
className={cn(
'size-1.5 cursor-pointer rounded-full transition-all duration-300',
index === activeIndex
? 'bg-foreground'
: 'bg-muted-foreground/30 hover:bg-muted-foreground/50',
)}
aria-label={`Go to slide ${index + 1}`}
/>
))}
</div>
</div>
{/* Right Content - Carousel Cards */}
<motion.div
className="select-none md:mask-r-from-60% md:mask-r-to-100% lg:col-span-2"
initial={prefersReducedMotion ? 'visible' : 'hidden'}
whileInView="visible"
viewport={{ once: true, amount: 0.2 }}
variants={cardVariants}
>
<Carousel
setApi={setApi}
opts={{
align: 'start',
skipSnaps: false,
}}
className="cursor-grab"
>
<CarouselContent className="h-full">
{features.map((feature) => (
<CarouselItem
key={feature.id}
className="h-full md:basis-[60%]"
>
<Card className="bg-border border-input aspect-[284/362] h-full !pb-0 transition-all duration-300 hover:shadow-lg lg:aspect-[384/562]">
<CardHeader>
<CardTitle className="text-lg leading-tight md:text-2xl lg:text-3xl">
{feature.title}
</CardTitle>
<CardDescription className="text-sm md:text-lg">
{feature.description}
</CardDescription>
</CardHeader>
<CardContent className="relative h-full">
<div className="bg-card dark:bg-card-foreground border-input relative h-full overflow-hidden rounded-lg border">
<img
src={feature.image.src}
alt={feature.image.alt}
className={cn(
'size-full object-contain transition-transform duration-300 hover:scale-105',
feature.image.className,
)}
/>
</div>
<div className="to-chart-4 absolute inset-0 bg-gradient-to-b from-transparent from-70%"></div>
</CardContent>
</Card>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
{/* Icon Buttons */}
<motion.div
className="mx-auto my-8 flex max-w-md justify-between gap-4 lg:hidden"
initial={prefersReducedMotion ? 'visible' : 'hidden'}
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
variants={containerVariants}
>
{features.map((feature, index) => {
const IconComponent = feature.icon;
const isActive = index === activeIndex;
return (
<motion.button
key={feature.id}
onClick={() => handleFeatureClick(index)}
variants={buttonVariants}
className={cn(
`border-input hover:bg-border/50 flex h-16 w-16 cursor-pointer items-center justify-center rounded-sm border transition-all duration-300`,
isActive && 'bg-border',
)}
>
<IconComponent className="size-5" strokeWidth={2.1} />
</motion.button>
);
})}
</motion.div>
{/* Dots Indicator */}
<motion.div
className="flex flex-1 items-end justify-center gap-1 lg:hidden"
initial={prefersReducedMotion ? 'visible' : 'hidden'}
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
variants={containerVariants}
>
{features.map((_, index) => (
<motion.button
key={index}
onClick={() => handleFeatureClick(index)}
variants={buttonVariants}
className={cn(
'size-1.5 cursor-pointer rounded-full transition-all duration-300',
index === activeIndex
? 'bg-foreground'
: 'bg-muted-foreground/30 hover:bg-muted-foreground/50',
)}
aria-label={`Go to slide ${index + 1}`}
/>
))}
</motion.div>
</motion.div>
</div>
</section>
);
}

View file

@ -0,0 +1,147 @@
import Noise from '@/components/elements/noise';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { cn } from '@/lib/utils';
const FEATURES_DATA = [
{
id: 1,
image: '/images/features-grid/1.webp',
imageAlt: 'Feature management interface',
title: 'Smart Task Management',
description:
'Organize and prioritize tasks with intelligent automation that adapts to your workflow patterns.',
className: 'lg:col-span-3',
width: 423,
height: 228,
},
{
id: 2,
image: '/images/features-grid/2.webp',
imageAlt: 'Team collaboration dashboard',
title: 'Team Collaboration',
description:
'Connect with your team seamlessly through integrated communication and shared workspaces.',
className: 'lg:col-span-3',
width: 435,
height: 228,
},
{
id: 3,
image: '/images/features-grid/3.webp',
imageAlt: 'Analytics and reporting',
title: 'Advanced Analytics',
description:
'Get comprehensive insights into your project performance with detailed analytics and customizable reports.',
className: 'lg:col-span-4',
width: 599,
height: 218,
},
{
id: 4,
image: '/images/features-grid/4.webp',
imageAlt: 'Project timeline view',
title: 'Project Timeline',
description:
'Visualize project progress and milestones with interactive timeline views and dependency tracking.',
className: 'lg:col-span-2',
width: 292,
height: 215,
},
{
id: 5,
image: '/images/features-grid/5.webp',
imageAlt: 'Integration capabilities',
title: 'Seamless Integrations',
description:
'Connect with your favorite tools and services to create a unified workflow ecosystem.',
className: 'lg:col-span-3',
width: 417,
height: 175,
},
{
id: 6,
image: '/images/features-grid/6.webp',
imageAlt: 'Mobile application',
title: 'Mobile Ready',
description:
'Access your projects anywhere with our fully responsive mobile application.',
className: 'lg:col-span-3',
width: 433,
height: 155,
},
];
export default function FeaturesGrid() {
return (
<section id="features-grid" className="section-padding relative">
<Noise />
<div className="container">
{/* Section Header */}
<div className="mx-auto max-w-5xl space-y-3 lg:space-y-4 lg:text-center">
<h2 className="text-4xl tracking-tight lg:text-5xl">
Feature management that fits your workflow
</h2>
<p className="text-muted-foreground text-lg leading-snug lg:text-balance">
Assign, prioritize, and monitor every feature with precision. Lumen
helps teams ship faster by bringing structure to your development
process, without slowing you down.
</p>
</div>
{/* Features Grid */}
<div className="mt-8 grid grid-cols-1 gap-2 lg:mt-12 lg:grid-cols-6">
{FEATURES_DATA.map((feature) => (
<FeatureCard key={feature.id} {...feature} />
))}
</div>
</div>
</section>
);
}
interface FeatureCardProps {
image: string;
imageAlt: string;
title: string;
description: string;
className?: string;
width: number;
height: number;
}
function FeatureCard({
image,
imageAlt,
title,
description,
className,
width,
height,
}: FeatureCardProps) {
return (
<Card className={cn('h-full', className)}>
{/* Image Section */}
<CardContent>
<div className="overflow-hidden rounded-lg">
<img
src={image}
alt={imageAlt}
width={width}
height={height}
className="w-full object-cover"
/>
</div>
</CardContent>
{/* Content Section */}
<CardHeader>
<CardTitle className="text-xl leading-tight font-semibold">
{title}
</CardTitle>
<p className="text-muted-foreground/70 leading-relaxed">
{description}
</p>
</CardHeader>
</Card>
);
}

View file

@ -0,0 +1,252 @@
'use client';
import { Shield, TrendingUp, Users, Zap } from 'lucide-react';
import { motion } from 'motion/react';
import Noise from '@/components/elements/noise';
import { Card, CardContent } from '@/components/ui/card';
import usePrefersReducedMotion from '@/hooks/usePrefersReducedMotion';
import { cn } from '@/lib/utils';
const features = [
{
id: 'security',
icon: Shield,
title: 'Manage features with clarity, not clutter',
description:
'Say goodbye to messy event logs. Lumen turns real usage data into clear, grouped feature insights, so you can track what matters, not just what happened.',
image: {
src: '/images/features-showcase/1.webp',
alt: 'Advanced Security',
width: 500,
height: 400,
},
},
{
id: 'performance',
icon: Zap,
title: 'Instant answers to product usage questions',
description:
'Lumens powerful filters make it easy to get actionable usage insights, no SQL needed.',
image: {
src: '/images/features-showcase/2.webp',
alt: 'Lightning Fast Performance',
width: 500,
height: 400,
},
},
{
id: 'collaboration',
icon: Users,
title: 'Segment users by feature behavior',
description:
'Slice your audience based on real feature interaction.Find champions, trial users, and at-risk accounts in seconds.',
image: {
src: '/images/features-showcase/3.webp',
alt: 'Team Collaboration',
width: 500,
height: 400,
},
},
{
id: 'analytics',
icon: TrendingUp,
title: 'Export insights, tie them to business impact',
description:
'Send enriched usage data to your warehouse.Blend Lumen metrics with revenue, churn, or NPS to connect product behavior to outcomes.',
image: {
src: '/images/features-showcase/4.webp',
alt: 'Smart Analytics',
width: 500,
height: 400,
},
},
];
export default function FeaturesShowcase() {
const prefersReducedMotion = usePrefersReducedMotion();
// Animation variants
const featureItem = {
hidden: {
opacity: 0,
y: 30,
filter: 'blur(2px)',
},
visible: {
opacity: 1,
y: 0,
filter: 'blur(0px)',
transition: {
type: 'spring' as const,
stiffness: 100,
damping: 25,
mass: 1,
duration: 0.6,
},
},
};
const contentVariants = {
hidden: { opacity: 0, x: 0 },
visible: {
opacity: 1,
x: 0,
transition: {
type: 'spring' as const,
stiffness: 120,
damping: 20,
delay: 0.1,
},
},
};
const imageVariants = {
hidden: {
opacity: 0,
scale: 0.95,
filter: 'blur(1px)',
},
visible: {
opacity: 1,
scale: 1,
filter: 'blur(0px)',
transition: {
type: 'spring' as const,
stiffness: 80,
damping: 20,
delay: 0.2,
},
},
};
return (
<section
id="features-showcase"
className="section-padding relative overflow-hidden"
>
<Noise />
<div className="container">
{/* Section Header */}
<motion.div
className="max-w-4xl space-y-6 md:space-y-8"
initial={prefersReducedMotion ? 'visible' : 'hidden'}
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
variants={{
hidden: { opacity: 0, y: 30, filter: 'blur(2px)' },
visible: {
opacity: 1,
y: 0,
filter: 'blur(0px)',
transition: {
type: 'spring' as const,
stiffness: 100,
damping: 25,
duration: 0.6,
},
},
}}
>
<h2 className="text-4xl tracking-tight lg:text-5xl">
Feature intelligence built for modern product teams
</h2>
<p className="text-muted-foreground text-lg leading-snug">
Stay ahead of user needs. Lumen turns your product features into
actionable insights, so you can prioritize what matters, streamline
delivery, and scale with confidence.
</p>
</motion.div>
{/* Features */}
<div className="mt-8 space-y-8 md:mt-14 md:space-y-14 lg:mt-24 lg:space-y-20">
{features.map((feature, index) => {
const IconComponent = feature.icon;
const isReverse = index >= 2;
return (
<motion.div
key={feature.id}
className={cn(
`grid items-center gap-4 lg:grid-cols-2 lg:gap-16`,
!isReverse && 'lg:grid-flow-col-dense',
)}
variants={featureItem}
initial={prefersReducedMotion ? 'visible' : 'hidden'}
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
>
{/* Content */}
<motion.div
className={cn(
`space-y-4 md:space-y-6 lg:space-y-8`,
!isReverse && 'lg:col-start-2',
)}
variants={contentVariants}
>
<div className="flex items-center gap-4">
<Card
className={cn(
`flex size-12 shrink-0 items-center justify-center rounded-sm !p-0 md:size-16`,
)}
>
<IconComponent className="size-6" strokeWidth={2.1} />
</Card>
<h3 className="text-2xl tracking-tight md:hidden lg:text-3xl">
{feature.title}
</h3>
</div>
<div className="space-y-2">
<h3 className="hidden text-2xl tracking-tight md:block lg:text-3xl">
{feature.title}
</h3>
<p className="text-muted-foreground/70 text-sm leading-relaxed md:text-base">
{feature.description}
</p>
</div>
</motion.div>
{/* Image */}
<motion.div
className={cn('relative', !isReverse && 'lg:col-start-1')}
variants={imageVariants}
>
{/* Background circles for first and third images */}
{(index === 0 || index === 2) && (
<>
<div
className={cn(
'bg-chart-2 absolute size-40 rounded-full opacity-30 blur-3xl will-change-transform md:opacity-80',
index === 0 && 'top-0 left-0 -translate-x-1/5',
index === 2 && 'top-0 right-0 translate-y-1/2',
)}
/>
<div
className={cn(
'bg-chart-3 absolute size-40 rounded-full opacity-50 blur-3xl will-change-transform md:opacity-100',
index === 0 && 'top-0 left-0 translate-y-1/2',
index === 2 && 'top-0 right-0 translate-x-1/5',
)}
/>
</>
)}
<Card className="bg-chart-4 relative overflow-hidden !pb-0">
<CardContent className="!pe-0">
<img
src={feature.image.src}
alt={feature.image.alt}
width={feature.image.width}
height={feature.image.height}
className="object-contain"
/>
</CardContent>
</Card>
</motion.div>
</motion.div>
);
})}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,230 @@
'use client';
import React, { useEffect, useState } from 'react';
import { AnimatePresence, motion } from 'motion/react';
import { ArrowRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
const slides = [
{
src: 'https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1528&h=700&fit=crop',
alt: 'Abstract digital network',
title: 'Explore our programs',
description: 'B.Tech, M.Tech, and Ph.D. programs in Computer Science & Engineering.',
},
{
src: 'https://images.unsplash.com/photo-1635070041078-e363dbe005cb?w=1528&h=700&fit=crop',
alt: 'Abstract geometric patterns',
title: 'Explore our research',
description: 'Cutting-edge research across AI, systems, security, theory, and more.',
},
{
src: 'https://images.unsplash.com/photo-1620712943543-bcc4688e7485?w=1528&h=700&fit=crop',
alt: 'Abstract AI visualization',
title: 'Join our community',
description: 'A vibrant student community with clubs, events, and hackathons.',
},
];
interface SvgPath {
path: string;
height: number;
width: number;
}
type MaskType = 'type-1' | 'type-2' | 'type-3' | 'type-4' | 'type-5';
const svgPaths: Record<MaskType, SvgPath> = {
'type-1': {
path: 'M0.928955 40.9769C0.928955 18.9149 18.7917 1.01844 40.8536 0.976903L289.97 0.507853C308.413 0.473128 323.521 15.1483 324.022 33.5845L324.886 65.4007C325.955 104.745 358.022 136.159 397.38 136.417L432.98 136.65C447.818 136.748 459.797 148.799 459.803 163.637L459.982 550.982C459.992 573.08 442.08 591 419.982 591H40.9289C18.8376 591 0.928955 573.091 0.928955 551V40.9769Z',
height: 591,
width: 460,
},
'type-2': {
path: 'M0.811768 77.2118C0.811768 60.4225 14.4222 46.8121 31.2115 46.8121H180.95C192.496 46.8121 201.855 37.4527 201.855 25.9073V25.9073C201.855 11.9565 213.164 0.647217 227.115 0.647217H529.273C548.014 0.647217 563.206 15.8395 563.206 34.5802V34.5802C563.206 50.0897 575.779 62.6626 591.289 62.6626H820.388C837.177 62.6626 850.787 76.273 850.787 93.0623V350.953C850.787 367.742 837.177 381.353 820.388 381.353H366.165C349.852 381.353 336.627 368.128 336.627 351.814V351.814C336.627 335.501 323.402 322.276 307.089 322.276H31.2114C14.4222 322.276 0.811768 308.666 0.811768 291.876V77.2118Z',
height: 381,
width: 850,
},
'type-3': {
path: 'M0.680664 112.659C0.680664 50.805 50.823 0.662672 112.677 0.662672H413.07C456.315 0.662672 497.495 19.1588 526.221 51.4846V51.4846C554.948 83.8104 596.128 102.307 639.373 102.307H711.793C752.427 102.307 790.787 83.5522 815.744 51.4846V51.4846C840.7 19.417 879.06 0.662644 919.695 0.662597L1225.01 0.66224C1286.86 0.662168 1337.01 50.8046 1337.01 112.658V652.815C1337.01 714.668 1286.86 764.811 1225.01 764.811L670 764.811H335.34H278.376C217.423 764.811 168.01 715.399 168.01 654.446V626.747C168.01 580.208 130.283 542.48 83.7437 542.48V542.48C37.8692 542.48 0.680664 505.292 0.680664 459.417V382.737V112.659Z',
height: 889,
width: 1340,
},
'type-4': {
path: 'M0.811768 34.5451C0.811768 15.7441 16.053 0.502808 34.8541 0.502808H816.745C835.546 0.502808 850.787 15.7441 850.787 34.5452V242.977C850.787 261.778 835.546 277.019 816.745 277.019H638.293H550.537C527.035 277.019 504.789 266.407 490.001 248.141L486.211 243.46C453.263 202.765 390.688 204.378 359.881 246.717V246.717C346.027 265.756 323.901 277.019 300.355 277.019H213.306H34.8541C16.0531 277.019 0.811768 261.778 0.811768 242.977V34.5451Z',
height: 278,
width: 851,
},
'type-5': {
path: 'M0.589399 112.279C0.589402 82.1213 25.037 57.6738 55.1946 57.6738H335.688C350.06 57.6738 361.712 46.0226 361.712 31.6502C361.712 14.2835 375.79 0.205078 393.157 0.205078H949.833C983.496 0.205078 1010.78 27.4941 1010.78 61.1568C1010.78 89.0156 1033.37 111.6 1061.23 111.6H1472.74C1502.9 111.6 1527.35 136.047 1527.35 166.205V629.438C1527.35 659.596 1502.9 684.044 1472.74 684.044H639.176C619.635 684.044 603.794 668.203 603.794 648.662C603.794 629.122 587.954 613.281 568.413 613.281H55.1945C25.0369 613.281 0.589358 588.833 0.58936 558.676L0.589399 112.279Z',
height: 700,
width: 1528,
},
};
interface MaskedDivProps {
children: React.ReactElement<HTMLImageElement | HTMLVideoElement>;
maskType?: MaskType;
className?: string;
backgroundColor?: string;
size?: number;
}
const MaskedDiv: React.FC<MaskedDivProps> = ({
children,
maskType = 'type-1',
className = '',
backgroundColor = 'transparent',
size = 1,
}) => {
const selectedMask = svgPaths[maskType];
const svgString = `data:image/svg+xml,%3Csvg width='${selectedMask.width}' height='${selectedMask.height}' viewBox='0 0 ${selectedMask.width} ${selectedMask.height}' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fillRule='evenodd' clipRule='evenodd' d='${selectedMask.path}' fill='%23D9D9D9'/%3E%3C/svg%3E%0A`;
const containerStyle: React.CSSProperties = {
aspectRatio: `${selectedMask.width}/${selectedMask.height}`,
backgroundColor,
maskImage: `url("${svgString}")`,
WebkitMaskImage: `url("${svgString}")`,
maskRepeat: 'no-repeat',
WebkitMaskRepeat: 'no-repeat',
maskSize: 'contain',
WebkitMaskSize: 'contain',
width: `${size * 100}%`,
maxWidth: '100%',
margin: '0 auto',
};
return (
<section
className={`pointer-events-none relative ${className}`}
style={containerStyle}
>
{React.cloneElement(children, {
className: `w-full h-full object-cover transition-all duration-300 ${
children.props.className || ''
}`,
})}
</section>
);
};
export default function Hero() {
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % slides.length);
}, 2500);
return () => clearTimeout(timer);
}, [currentIndex]);
return (
<section className="py-10 md:py-14 lg:py-16">
<div className="container">
{/* We're Growing banner */}
<div className="mb-6 flex justify-start">
<a
href="/about/careers"
className="inline-flex items-center justify-center gap-3 rounded-full bg-muted-foreground/5 p-1 pr-4 text-sm font-medium tracking-tight text-muted-foreground transition-colors hover:bg-muted-foreground/10"
>
<span className="flex items-center gap-3 rounded-full bg-muted-foreground/10 px-4 py-1.5">
<span className="inline-block size-2 rounded-full bg-blue-500" />
<span>We're Growing</span>
</span>
<span className="flex items-center gap-2">
Join Us <ArrowRight className="size-4" />
</span>
</a>
</div>
<div className="mb-5 max-w-3xl lg:mb-0">
<h1 className="text-left text-3xl font-semibold tracking-tighter">
Computer Science &amp; Engineering
<br />
<span className="text-4xl md:text-5xl">@IITGN</span>
</h1>
</div>
<div className="relative lg:-translate-y-4">
<MaskedDiv maskType="type-5">
<AnimatePresence mode="popLayout">
<motion.img
key={currentIndex}
className="h-full w-full object-cover"
src={slides[currentIndex].src}
alt={slides[currentIndex].alt}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 1.5 }}
/>
</AnimatePresence>
</MaskedDiv>
{/* Top-right box with dummy text */}
<div className="absolute -top-26 right-0 flex gap-6">
<motion.div
initial={{ opacity: 0, y: 30, scale: 0.9 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.3, ease: 'easeOut' }}
className="hidden size-44 -translate-y-4 overflow-hidden rounded-3xl border border-border bg-muted/50 p-4 lg:flex lg:flex-col lg:justify-center xl:-translate-y-0"
>
<AnimatePresence mode="wait">
<motion.div
key={currentIndex}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
<p className="text-sm font-medium text-foreground">
{slides[currentIndex].title}
</p>
<p className="mt-1 text-xs text-muted-foreground">
{slides[currentIndex].description}
</p>
</motion.div>
</AnimatePresence>
</motion.div>
</div>
{/* Mobile: box below the image */}
<div className="mt-5 flex w-full gap-6 lg:hidden">
<div className="flex h-44 w-full items-center justify-center rounded-3xl border border-border bg-muted/50 p-4">
<AnimatePresence mode="wait">
<motion.div
key={currentIndex}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
<p className="text-sm font-medium text-foreground">
{slides[currentIndex].title}
</p>
<p className="mt-1 text-xs text-muted-foreground">
{slides[currentIndex].description}
</p>
</motion.div>
</AnimatePresence>
</div>
</div>
<Button
className="group -bottom-5 left-0 mt-3 flex items-center justify-center rounded-full bg-foreground px-6 py-2 text-background tracking-tight hover:bg-blue-500 dark:hover:bg-blue-400 hover:gap-4 lg:absolute lg:bottom-0 lg:mt-0 xl:bottom-3"
asChild
>
<a href="/about/contact">
Contact Us{' '}
<ArrowRight className="size-4 -rotate-45 transition-all duration-300 ease-out group-hover:rotate-0" />
</a>
</Button>
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,152 @@
'use client';
import { useEffect, useState } from 'react';
import Noise from '@/components/elements/noise';
import { Marquee } from '@/components/magicui/marquee';
import { cn } from '@/lib/utils';
const companies = [
{
name: 'Booking.com',
logo: '/images/logos/booking.svg',
className: 'dark:hidden',
url: 'https://booking.com',
},
{
name: 'Fortinet',
logo: '/images/logos/fortinet.svg',
className: 'dark:hidden',
url: 'https://fortinet.com',
},
{
name: 'IBM',
logo: '/images/logos/ibm.svg',
className: '',
url: 'https://ibm.com',
},
{
name: 'Logitech',
logo: '/images/logos/logitech.svg',
className: 'dark:hidden',
url: 'https://logitech.com',
},
{
name: 'Netflix',
logo: '/images/logos/netflix.svg',
className: '',
url: 'https://netflix.com',
},
{
name: 'Spotify',
logo: '/images/logos/spotify.svg',
className: '',
url: 'https://spotify.com',
},
{
name: 'T-Mobile',
logo: '/images/logos/t-mobile.svg',
className: '',
url: 'https://t-mobile.com',
},
{
name: 'TIBCO',
logo: '/images/logos/tibc.svg',
className: '',
url: 'https://tibco.com',
},
];
export default function Logos() {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const [mounted, setMounted] = useState(false);
const storageKey = 'theme';
useEffect(() => {
setMounted(true);
// Get initial theme from localStorage, default to 'light' if none exists
const savedTheme = localStorage.getItem(storageKey) as
| 'light'
| 'dark'
| null;
const initialTheme = savedTheme || 'light';
setTheme(initialTheme);
// Listen for theme changes
const handleStorageChange = () => {
const newTheme = localStorage.getItem(storageKey) as
| 'light'
| 'dark'
| null;
if (newTheme) {
setTheme(newTheme);
}
};
window.addEventListener('storage', handleStorageChange);
// Listen for direct DOM class and data-theme changes (for immediate updates)
const observer = new MutationObserver(() => {
const isDark = document.documentElement.classList.contains('dark');
const dataTheme = document.documentElement.getAttribute('data-theme');
const currentTheme = dataTheme || (isDark ? 'dark' : 'light');
setTheme(currentTheme as 'light' | 'dark');
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class', 'data-theme'],
});
return () => {
window.removeEventListener('storage', handleStorageChange);
observer.disconnect();
};
}, []);
// Filter out companies with dark:hidden when theme is dark
// Only apply theme-based filtering after component is mounted to prevent hydration mismatch
const visibleCompanies = companies.filter((company) => {
if (
mounted &&
theme === 'dark' &&
company.className.includes('dark:hidden')
) {
return false;
}
return true;
});
return (
<section className="section-padding relative">
<Noise />
<p className="container text-center text-base">
Over 2+ million teams rely on Lumen to collaborate and get work done.
</p>
<div>
<Marquee
pauseOnHover
className="mt-8 mask-r-from-60% mask-r-to-100% mask-l-from-60% mask-l-to-100% [--duration:20s] [--gap:4rem]"
>
{visibleCompanies.map((company) => (
<a
key={company.name}
href={company.url}
target="_blank"
rel="noopener noreferrer"
className="relative h-8 w-24 transition-transform duration-200 hover:scale-105"
>
<img
src={company.logo}
alt={`${company.name} logo`}
className={cn('object-contai size-full', company.className)}
/>
</a>
))}
</Marquee>
</div>
</section>
);
}

View file

@ -0,0 +1,95 @@
---
import { allDepartmentNews, CATEGORY_LABELS } from '@/data/news';
const categoryOrder = ['media', 'research', 'award', 'event', 'infrastructure'];
---
<section class="from-primary/5 bg-gradient-to-b to-transparent py-16 md:py-20">
<div class="container">
<p class="text-secondary text-sm font-medium uppercase tracking-wider">
News and awards
</p>
<h1 class="mt-3 text-3xl md:text-4xl">CSE in the News</h1>
<p class="text-muted-foreground mt-4 max-w-2xl">
Recent media coverage, research highlights, awards, and department
achievements.
</p>
</div>
</section>
<section class="section-padding">
<div class="container">
<div class="mb-8 flex flex-wrap gap-2">
{categoryOrder.map((category) => (
<a
href={`#${category}`}
class="rounded-full border px-3 py-1 text-sm text-muted-foreground transition-colors hover:border-secondary hover:text-foreground"
>
{CATEGORY_LABELS[category]}
</a>
))}
</div>
<div class="space-y-12">
{categoryOrder.map((category) => {
const items = allDepartmentNews.filter((item) => item.category === category);
if (items.length === 0) return null;
return (
<section id={category} class="scroll-mt-24">
<div class="mb-4 flex items-end justify-between gap-4 border-b pb-3">
<div>
<h2 class="text-2xl">{CATEGORY_LABELS[category]}</h2>
<p class="text-muted-foreground mt-1 text-sm">
{items.length} verified item{items.length === 1 ? '' : 's'}
</p>
</div>
</div>
<div class="grid gap-4">
{items.map((item) => (
<article class="rounded-lg border bg-background p-5">
<div class="mb-3 flex flex-wrap items-center gap-2">
<span class="rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground">
{CATEGORY_LABELS[item.category]}
</span>
<span class="text-muted-foreground text-xs">
{item.displayDate}
</span>
</div>
<h3 class="text-xl font-semibold">
<a
href={item.sourceUrl}
target="_blank"
rel="noreferrer"
class="transition-colors hover:text-secondary"
>
{item.title}
</a>
</h3>
<p class="text-muted-foreground mt-2">{item.summary}</p>
<dl class="mt-4 grid gap-2 text-sm md:grid-cols-[8rem_1fr]">
<dt class="text-muted-foreground">People</dt>
<dd>{item.people}</dd>
<dt class="text-muted-foreground">Source</dt>
<dd>
<a
href={item.sourceUrl}
target="_blank"
rel="noreferrer"
class="underline underline-offset-2"
>
{item.sourceLabel}
</a>
</dd>
</dl>
</article>
))}
</div>
</section>
);
})}
</div>
</div>
</section>

View file

@ -0,0 +1,147 @@
'use client';
import { ChevronRight } from 'lucide-react';
import { motion } from 'motion/react';
import { Button } from '@/components/ui/button';
import usePrefersReducedMotion from '@/hooks/usePrefersReducedMotion';
export default function NotFound() {
const prefersReducedMotion = usePrefersReducedMotion();
// Animation variants
const backgroundTextVariants = {
hidden: {
opacity: 0,
scale: 0.8,
filter: 'blur(4px)',
rotate: -5,
},
visible: {
opacity: 0.8,
scale: 1,
filter: 'blur(0px)',
rotate: 0,
transition: {
type: 'spring' as const,
stiffness: 60,
damping: 15,
duration: 1.2,
delay: 0.2,
},
},
};
const contentVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2,
delayChildren: 0.4,
},
},
};
const titleVariants = {
hidden: { opacity: 0, y: 30, filter: 'blur(2px)' },
visible: {
opacity: 1,
y: 0,
filter: 'blur(0px)',
transition: {
type: 'spring' as const,
stiffness: 100,
damping: 20,
duration: 0.8,
},
},
};
const descriptionVariants = {
hidden: { opacity: 0, y: 20, filter: 'blur(2px)' },
visible: {
opacity: 1,
y: 0,
filter: 'blur(0px)',
transition: {
type: 'spring' as const,
stiffness: 120,
damping: 20,
duration: 0.6,
},
},
};
const buttonVariants = {
hidden: { opacity: 0, y: 20, scale: 0.95, filter: 'blur(2px)' },
visible: {
opacity: 1,
y: 0,
scale: 1,
filter: 'blur(0px)',
transition: {
type: 'spring' as const,
stiffness: 100,
damping: 15,
duration: 0.7,
},
},
};
return (
<section className="section-padding relative container flex min-h-screen items-center justify-center">
{/* Large 404 background text */}
<motion.div
className="absolute inset-0 flex items-center justify-center"
initial={prefersReducedMotion ? 'visible' : 'hidden'}
animate="visible"
variants={backgroundTextVariants}
>
<span className="text-muted/80 text-[12rem] font-bold select-none sm:text-[16rem] md:text-[25rem] lg:text-[32rem]">
404
</span>
</motion.div>
{/* Main content */}
<motion.div
className="relative z-10 text-center"
initial={prefersReducedMotion ? 'visible' : 'hidden'}
animate="visible"
variants={contentVariants}
>
<motion.h1
className="text-foreground mb-4 text-3xl font-bold md:text-4xl lg:text-5xl"
variants={titleVariants}
>
Page Not Found
</motion.h1>
<motion.p
className="text-muted-foreground mx-auto mb-6 max-w-md text-sm md:text-base lg:text-lg"
variants={descriptionVariants}
>
Duis dolor sit amet, consectetur adipiscing
<br />
elitvestibulum in pharetra.
</motion.p>
<motion.div variants={buttonVariants}>
<Button
asChild
size="lg"
variant="light"
className="group !pl-5.5 font-medium"
>
<a href="/">
Return Home
<div className="bg-border border-input grid size-5.5 place-items-center rounded-full border">
<ChevronRight className="size-4 transition-transform group-hover:translate-x-0.25" />
</div>
</a>
</Button>
</motion.div>
</motion.div>
</section>
);
}

View file

@ -0,0 +1,63 @@
import type { LucideIcon } from 'lucide-react';
import { Construction } from 'lucide-react';
interface PlaceholderPageProps {
title: string;
description: string;
icon?: LucideIcon;
sections?: string[];
}
export default function PlaceholderPage({
title,
description,
icon: Icon = Construction,
sections = [],
}: PlaceholderPageProps) {
return (
<div>
<section className="from-primary/5 bg-gradient-to-b to-transparent py-16 md:py-20">
<div className="container text-center">
<h1 className="text-3xl md:text-4xl">{title}</h1>
<p className="text-muted-foreground mx-auto mt-3 max-w-lg">
{description}
</p>
</div>
</section>
<section className="section-padding">
<div className="container">
<div className="border-border mx-auto flex max-w-md flex-col items-center rounded-xl border border-dashed p-12 text-center">
<div className="bg-muted mb-4 inline-flex rounded-lg p-3">
<Icon className="text-muted-foreground size-6" />
</div>
<h2 className="text-lg font-semibold">Coming Soon</h2>
<p className="text-muted-foreground mt-2 text-sm">
This page is under development. Check back soon for updates.
</p>
</div>
{sections.length > 0 && (
<div className="mx-auto mt-12 max-w-lg">
<h3 className="text-muted-foreground mb-4 text-sm font-medium uppercase tracking-wider">
Planned Content
</h3>
<ul className="space-y-2">
{sections.map((section, i) => (
<li
key={i}
className="text-muted-foreground flex items-center gap-2 text-sm"
>
<span className="bg-secondary/30 inline-block size-1.5 rounded-full" />
{section}
</li>
))}
</ul>
</div>
)}
</div>
</section>
</div>
);
}

View file

@ -0,0 +1,405 @@
'use client';
import { Check, ChevronsUpDown } from 'lucide-react';
import { useState } from 'react';
import Noise from '@/components/elements/noise';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
interface FeatureSection {
category: string;
isNew?: boolean;
features: {
name: string;
isNew?: boolean;
free: string | boolean | null;
pro: string | boolean | null;
business: string | boolean | null;
enterprise: string | boolean | null;
}[];
}
const pricingPlans = [
{
name: 'Free',
price: '$0/mo',
yearlyPrice: '$0/mo',
},
{
name: 'Pro',
price: '$250/mo',
yearlyPrice: '$188/mo',
},
{
name: 'Business',
price: '$499/mo',
yearlyPrice: '$374/mo',
},
{
name: 'Enterprise',
price: 'Custom',
yearlyPrice: 'Custom',
},
];
const comparisonFeatures: FeatureSection[] = [
{
category: 'Pricing',
features: [
{
name: 'Price/mo',
free: '$0/mo',
pro: '$250/mo',
business: '$499/mo',
enterprise: 'Custom',
},
{
name: 'Price/mo (annual billing)',
free: '$0/mo',
pro: '$188/mo',
business: '$374/mo',
enterprise: 'Custom',
},
],
},
{
category: 'Product',
features: [
{
name: 'Real-time user insights',
free: true,
pro: true,
business: true,
enterprise: true,
},
{
name: 'Goal tracking dashboard',
free: true,
pro: true,
business: true,
enterprise: true,
},
{
name: 'Releases and Goals',
isNew: true,
free: true,
pro: true,
business: true,
enterprise: true,
},
{
name: 'Segmentation & Slack notifications',
free: true,
pro: true,
business: true,
enterprise: true,
},
{
name: 'EU data hosting',
free: true,
pro: true,
business: true,
enterprise: true,
},
],
},
{
category: 'Usage',
features: [
{
name: 'Monthly Tracked Users (MTU)',
free: 'Up to 25K',
pro: 'Up to 50K',
business: 'Custom',
enterprise: 'Custom',
},
{
name: 'Team members',
free: 'Unlimited',
pro: 'Unlimited',
business: 'Unlimited',
enterprise: 'Unlimited',
},
{
name: 'Releases (concurrent)',
free: '1',
pro: 'Unlimited',
business: 'Unlimited',
enterprise: 'Unlimited',
},
{
name: 'Live Satisfaction surveys (concurrent)',
free: '1',
pro: 'Unlimited',
business: 'Unlimited',
enterprise: 'Unlimited',
},
{
name: 'Live Satisfaction feedback',
free: '10/mo',
pro: 'Unlimited',
business: 'Unlimited',
enterprise: 'Unlimited',
},
{
name: 'Live Satisfaction branding',
free: 'Yes',
pro: 'Customizable',
business: 'Custom',
enterprise: 'Custom',
},
{
name: 'Features',
free: '3',
pro: 'Unlimited',
business: 'Unlimited',
enterprise: 'Unlimited',
},
],
},
{
category: 'Data',
isNew: true,
features: [
{
name: 'Manual export',
free: null,
pro: null,
business: true,
enterprise: true,
},
{
name: 'Automatic export to S3',
free: null,
pro: null,
business: true,
enterprise: true,
},
{
name: 'Retention',
free: '3 months',
pro: '5 years',
business: '5 years',
enterprise: '5 years',
},
],
},
{
category: 'Privacy',
features: [
{
name: 'GDPR compliant',
free: true,
pro: true,
business: true,
enterprise: true,
},
{
name: 'EU-only storage option',
free: true,
pro: true,
business: true,
enterprise: true,
},
],
},
{
category: 'Support',
features: [
{
name: 'Email support',
free: true,
pro: true,
business: true,
enterprise: true,
},
{
name: 'Email support response time',
free: '<5 week days',
pro: '<5 week days',
business: '<1 week days',
enterprise: '<12 hours',
},
{
name: 'Private Slack Channel',
free: null,
pro: null,
business: null,
enterprise: '90 days',
},
{
name: 'Onboarding call',
free: null,
pro: null,
business: true,
enterprise: true,
},
],
},
];
const PricingTable = () => {
const [selectedPlan, setSelectedPlan] = useState(1);
return (
<section className="section-padding">
<div className="bigger-container space-y-8 lg:space-y-12">
<Noise />
<h2 className="text-4xl leading-tight font-medium tracking-tight lg:text-5xl">
Pricing Details
</h2>
<div>
<PlanHeaders
selectedPlan={selectedPlan}
onPlanChange={setSelectedPlan}
/>
<FeatureSections selectedPlan={selectedPlan} />
</div>
</div>
</section>
);
};
const PlanHeaders = ({
selectedPlan,
onPlanChange,
}: {
selectedPlan: number;
onPlanChange: (index: number) => void;
prefersReducedMotion?: boolean;
}) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="">
{/* Mobile View */}
<div className="md:hidden">
<Collapsible open={isOpen} onOpenChange={setIsOpen} className="">
<div className="flex items-center justify-between py-4">
<CollapsibleTrigger className="flex items-center gap-2">
<h3 className="text-2xl">{pricingPlans[selectedPlan].name}</h3>
<ChevronsUpDown
className={`size-5 transition-transform ${isOpen ? 'rotate-180' : ''}`}
/>
</CollapsibleTrigger>
</div>
<CollapsibleContent className="flex flex-col space-y-2 p-2">
{pricingPlans.map(
(plan, index) =>
index !== selectedPlan && (
<Button
variant="outline"
key={index}
onClick={() => {
onPlanChange(index);
setIsOpen(false);
}}
>
{plan.name}
</Button>
),
)}
</CollapsibleContent>
</Collapsible>
</div>
{/* Desktop View */}
<div className="grid grid-cols-6 gap-4 max-md:hidden">
<div className="col-span-1 max-md:hidden md:col-span-2"></div>
{pricingPlans.map((plan, index) => (
<h3 key={index} className="mb-3 text-center text-2xl">
{plan.name}
</h3>
))}
</div>
</div>
);
};
const FeatureSections = ({ selectedPlan }: { selectedPlan: number }) => {
return (
<>
{comparisonFeatures.map((section, sectionIndex) => (
<div key={sectionIndex} className="flex flex-col md:mt-4 md:gap-1">
<div className="py-4">
<h3 className="flex items-center gap-8 text-lg">
{section.category}
{section.isNew && <Badge variant="outline">NEW</Badge>}
</h3>
</div>
{section.features.map((feature, featureIndex) => (
<div
key={featureIndex}
className="text-primary grid grid-cols-2 items-center font-medium md:grid-cols-6"
>
<span className="me-8 inline-flex items-center gap-4 py-4 md:col-span-2">
{feature.name}
{feature.isNew && (
<Badge variant="default" className="rounded-sm">
NEW
</Badge>
)}
</span>
{/* Mobile View - Only Selected Plan */}
<div className="md:hidden">
<div className="bg-border border-input flex items-center justify-center gap-1 rounded-md border py-3 text-sm">
{(() => {
const value = [
feature.free,
feature.pro,
feature.business,
feature.enterprise,
][selectedPlan];
return renderFeatureValue(value);
})()}
</div>
</div>
{/* Desktop View - All Plans */}
<div className="hidden md:col-span-4 md:grid md:grid-cols-4 md:gap-1">
{[
feature.free,
feature.pro,
feature.business,
feature.enterprise,
].map((value, i) => (
<div
key={i}
className="bg-border border-input flex items-center justify-center gap-1 rounded-md border py-3 text-sm"
>
{renderFeatureValue(value)}
</div>
))}
</div>
</div>
))}
</div>
))}
</>
);
};
const renderFeatureValue = (value: string | boolean | null) => {
if (value === null) {
return <span className="text-gray-400">-</span>;
}
if (typeof value === 'boolean') {
return value ? (
<Check className="size-5" />
) : (
<span className="text-gray-400">-</span>
);
}
return <span>{value}</span>;
};
export default PricingTable;

View file

@ -0,0 +1,237 @@
'use client';
import { useState } from 'react';
import { Check, ChevronRight, X } from 'lucide-react';
import { motion } from 'motion/react';
import Noise from '@/components/elements/noise';
import Logo from '@/components/layout/logo';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { Switch } from '@/components/ui/switch';
import { cn } from '@/lib/utils';
const pricingPlans = {
individual: {
title: 'Individual Plan',
subtitle: 'Best option for solo',
description: 'Designers or Freelancers',
monthlyPrice: 25,
annualPrice: 19,
popular: true,
features: [
{ name: 'Real-time task syncing', included: true },
{ name: 'Basic project analytics', included: true },
{ name: 'Custom workflows & automation', included: true },
{ name: 'Cross-platform integrations', included: true },
{ name: 'Unlimited boards & views', included: false },
{ name: 'Priority support for teams', included: false },
{ name: 'API access (Limited)', included: false },
{ name: 'Community support', included: false },
],
cta: {
text: 'Contact us for Custom CRM Integration',
button: 'Contact With Us',
},
},
team: {
title: 'Power Users & Teams',
subtitle: 'Best option for team',
description: 'Agencies or Corporates',
monthlyPrice: 59,
annualPrice: 44,
popular: false,
features: [
{ name: 'Advanced task syncing with dependencies', included: true },
{ name: 'Smart automations & conditional triggers', included: true },
{ name: 'In-depth usage insights & analytics', included: true },
{ name: 'Priority team collaboration tools', included: true },
{ name: 'CRM integrations', included: true },
{ name: 'Developer toolkit', included: true },
{ name: 'API access (Full)', included: true },
{ name: 'Premium support & onboarding', included: true },
],
cta: {
text: 'Connect us for Custom CRM Integration',
button: 'Contact With Us',
},
},
};
export default function Pricing() {
const [isAnnual, setIsAnnual] = useState(false);
return (
<section className="section-padding relative overflow-hidden">
{/* Background Image with Mask */}
<div className="absolute size-full mask-t-from-50% mask-t-to-100% mask-b-from-50% mask-b-to-90%">
<div
className={cn(
'bg-chart-2 absolute size-full rounded-full blur-3xl will-change-transform',
'top-0 left-0 -translate-y-1/3 md:-translate-x-1/3 md:translate-y-0',
)}
/>
<div
className={cn(
'bg-chart-3 absolute size-full rounded-full blur-3xl will-change-transform',
'right-0 bottom-0 translate-y-1/3 md:top-0 md:translate-x-1/3 md:-translate-y-0',
)}
/>
</div>
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ duration: 0.5 }}
className="from-background/30 to-background/30 pointer-events-none absolute inset-0 z-[1] bg-gradient-to-b"
/>
<div className="relative z-[2]">
<Noise />
</div>
<div className="bigger-container relative z-10">
{/* Section Header */}
<div className="flex flex-col items-center justify-between gap-8 md:flex-row md:items-end">
{/* Left side - Title and subtitle */}
<div className="">
<h2 className="text-center text-4xl font-medium tracking-tighter md:text-start md:text-6xl md:leading-none lg:text-7xl">
Power your progress with <br className="hidden md:block" />
Pro Access
</h2>
<p className="text-muted-foreground/70 mt-3 hidden text-lg leading-relaxed md:block lg:mt-4">
Increase feature adoption and customer satisfaction with the right
Lumen plan.
</p>
</div>
{/* Right side - Billing Switch */}
<div className="flex flex-col items-center gap-4">
<div className="flex items-center gap-3">
<span
className={cn(
'text-lg font-semibold transition-colors',
!isAnnual ? 'text-foreground' : 'text-muted-foreground/70',
)}
>
Monthly
</span>
<Switch checked={isAnnual} onCheckedChange={setIsAnnual} />
<span
className={cn(
'text-lg font-semibold transition-colors',
isAnnual ? 'text-foreground' : 'text-muted-foreground/70',
)}
>
Annual
</span>
</div>
<p className="text-center text-sm font-medium">
Save 25% on annual plan
</p>
</div>
</div>
{/* Pricing Cards */}
<div className="mx-auto mt-8 grid gap-4 lg:mt-12 lg:grid-cols-2">
{Object.entries(pricingPlans).map(([key, plan]) => (
<Card
key={key}
className="bg-border hover:shadow-primary/5 h-full gap-4 p-3 transition-all duration-300 hover:shadow-lg md:p-6"
>
<CardHeader className="bg-card rounded-md p-4 md:p-6">
{/* Header with title and badge */}
<div className="flex items-start justify-between">
<h3 className="text-xl">{plan.title}</h3>
{plan.popular && (
<Badge className="rounded-none bg-[#FFE6D0] px-4 py-1 text-[#FB6D21] dark:bg-[#6b3200] dark:text-[#fcaa7d]">
Popular Plan
</Badge>
)}
</div>
{/* Subtitle and description */}
<div className="mt-6 text-2xl md:mt-8 md:space-y-2 md:text-4xl">
<div className="text-muted-foreground/70">
{plan.subtitle}
</div>
<div className="font-medium">{plan.description}</div>
</div>
{/* Price and contact section */}
<div className="mt-8 flex flex-col justify-between gap-8 md:mt-10 md:flex-row">
{/* Left side - Price and main CTA */}
<div className="flex flex-1 flex-wrap justify-between gap-4 md:flex-col md:gap-8">
<div className="flex items-baseline gap-1">
<span className="text-3xl font-medium">$</span>
<span className="text-5xl md:text-6xl">
{isAnnual ? plan.annualPrice : plan.monthlyPrice}
</span>
<span className="text-2xl">/mo</span>
</div>
<Button className="h-10 !pl-5.5">
Upgrade to Pro
<div className="bg-background/15 border-background/10 grid size-5.5 place-items-center rounded-full border">
<ChevronRight className="size-4" />
</div>
</Button>
</div>
{/* Right side - Contact info */}
<div className="bg-border flex-1 space-y-4 p-6">
<div className="flex justify-between gap-6">
<p className="text-card-foreground text-xs leading-none font-medium">
{plan.cta.text}
</p>
<Logo className="h-4 w-14" />
</div>
<Button
variant="light"
className="group h-10 w-full"
asChild
>
<a href="/contact">
Contact With Us
<div className="bg-border border-input grid size-5.5 place-items-center rounded-full border">
<ChevronRight className="size-4 transition-transform group-hover:translate-x-0.25" />
</div>
</a>
</Button>
</div>
</div>
</CardHeader>
<CardContent className="grid gap-4 p-4 md:grid-cols-2 md:p-6">
{plan.features.map((feature, index) => (
<div key={index} className="flex items-center gap-3">
<div className="">
{feature.included ? (
<div className="border-muted-foreground flex size-4 items-center justify-center rounded-full border-[0.5px]">
<Check className="text-muted-foreground size-2" />
</div>
) : (
<div className="border-muted-foreground flex size-4 items-center justify-center rounded-full border-[0.5px]">
<X className="text-muted-foreground/70 size-2" />
</div>
)}
</div>
<span
className={cn(
'text-sm',
feature.included
? 'text-muted-foreground'
: 'text-muted-foreground/70',
)}
>
{feature.name}
</span>
</div>
))}
</CardContent>
</Card>
))}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,786 @@
"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import {
ArrowUpRight,
BookOpen,
CalendarDays,
Check,
ChevronDown,
Database,
FileText,
Search,
UserRound,
X,
} from "lucide-react";
import publicationsData from "@/data/publications/dblp-career-faculty.json";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const ALL = "all";
const PAGE_SIZE = 40;
const AREA_LABELS: Record<string, string> = {
theory: "Theory",
systems: "Systems",
ai: "AI",
"data-science": "Data Science",
hci: "HCI",
security: "Security",
};
const TYPE_LABELS: Record<string, string> = {
conference: "Conference",
journal: "Journal",
preprint: "Preprint",
proceedings: "Proceedings",
"book-chapter": "Book Chapter",
book: "Book",
"phd-thesis": "PhD Thesis",
"masters-thesis": "Masters Thesis",
other: "Other",
};
type FacultyProfile = (typeof publicationsData.faculty)[number];
type Publication = (typeof publicationsData.publications)[number];
function areaLabel(area: string) {
return AREA_LABELS[area] ?? area;
}
function typeLabel(type: string) {
return TYPE_LABELS[type] ?? type;
}
function profileLabel(url: string) {
try {
const host = new URL(url).hostname.replace(/^www\./, "");
if (host === "scholar.google.com") return "Google Scholar";
if (host === "orcid.org") return "ORCID";
if (host === "dl.acm.org") return "ACM DL";
if (host === "openreview.net") return "OpenReview";
if (host === "mathgenealogy.org") return "Math Genealogy";
if (host === "wikidata.org") return "Wikidata";
if (host.includes("iitgn.ac.in")) return "IITGN";
return host;
} catch {
return "External Profile";
}
}
function getPublicationSearchText(
publication: Publication,
facultyById: Map<string, FacultyProfile>,
) {
return [
publication.title,
publication.venue,
publication.year,
publication.type,
publication.areaKeywords.join(" "),
publication.authors.map((author) => author.name).join(" "),
publication.facultyIds
.map((facultyId) => facultyById.get(facultyId)?.name)
.filter(Boolean)
.join(" "),
publication.links.doi,
publication.dblpKey,
]
.filter(Boolean)
.join(" ")
.toLowerCase();
}
function getExternalLinks(publication: Publication) {
const links = [
{
label: "DBLP",
href: publication.links.dblp,
},
];
if (publication.links.doi) {
links.push({
label: "DOI",
href: publication.links.ee.find((link) => link.includes("doi.org")) ?? "",
});
}
if (publication.links.arxiv) {
links.push({
label: "arXiv",
href: publication.links.arxiv,
});
}
const fallbackLink = publication.links.ee.find(
(link) =>
link &&
!link.includes("doi.org") &&
link !== publication.links.arxiv &&
link !== publication.links.dblp,
);
if (fallbackLink) {
links.push({
label: "Full Text",
href: fallbackLink,
});
}
return links.filter((link) => link.href);
}
function SearchableFilter({
ariaLabel,
options,
searchPlaceholder,
value,
onChange,
}: {
ariaLabel: string;
options: { value: string; label: string }[];
searchPlaceholder: string;
value: string;
onChange: (value: string) => void;
}) {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const rootRef = useRef<HTMLDivElement>(null);
const selectedOption = options.find((option) => option.value === value);
useEffect(() => {
if (!open) return;
function onPointerDown(event: PointerEvent) {
if (!rootRef.current?.contains(event.target as Node)) {
setOpen(false);
}
}
function onKeyDown(event: KeyboardEvent) {
if (event.key === "Escape") {
setOpen(false);
}
}
document.addEventListener("pointerdown", onPointerDown);
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("pointerdown", onPointerDown);
document.removeEventListener("keydown", onKeyDown);
};
}, [open]);
const filteredOptions = options.filter((option) =>
option.label.toLowerCase().includes(search.trim().toLowerCase()),
);
return (
<div ref={rootRef} className="relative">
<button
type="button"
aria-label={ariaLabel}
aria-expanded={open}
className="border-input flex h-9 w-full items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-left text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 dark:bg-input/30"
onClick={() => {
setOpen((current) => !current);
setSearch("");
}}
>
<span className="min-w-0 truncate">
{selectedOption?.label ?? options[0]?.label}
</span>
<ChevronDown className="size-4 shrink-0 opacity-50" />
</button>
{open && (
<div className="absolute left-0 top-full z-50 mt-1 w-full min-w-52 rounded-md border bg-popover text-popover-foreground shadow-md">
<div className="border-b p-2">
<div className="relative">
<Search className="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
<Input
value={search}
onChange={(event) => setSearch(event.target.value)}
placeholder={searchPlaceholder}
className="h-8 pl-8 text-sm"
autoFocus
/>
</div>
</div>
<div className="max-h-56 overflow-y-auto p-1">
{filteredOptions.length === 0 && (
<div className="px-2 py-2 text-sm text-muted-foreground">
No matches
</div>
)}
{filteredOptions.map((option) => (
<button
key={option.value}
type="button"
className="flex w-full items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
onClick={() => {
onChange(option.value);
setOpen(false);
setSearch("");
}}
>
<span className="min-w-0 truncate">{option.label}</span>
{option.value === value && (
<Check className="size-4 shrink-0" />
)}
</button>
))}
</div>
</div>
)}
</div>
);
}
function PublicationCard({
publication,
facultyById,
onSelectArea,
onSelectFaculty,
}: {
publication: Publication;
facultyById: Map<string, FacultyProfile>;
onSelectArea: (area: string) => void;
onSelectFaculty: (facultyId: string) => void;
}) {
const faculty = publication.facultyIds
.map((facultyId) => facultyById.get(facultyId))
.filter(Boolean);
const links = getExternalLinks(publication);
return (
<Card className="gap-0 rounded-lg p-5">
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div className="min-w-0 space-y-3">
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
{publication.year && (
<span className="inline-flex items-center gap-1">
<CalendarDays className="size-3.5" />
{publication.year}
</span>
)}
<span className="inline-flex items-center gap-1">
<BookOpen className="size-3.5" />
{typeLabel(publication.type)}
</span>
{publication.venue && (
<span className="min-w-0 truncate">{publication.venue}</span>
)}
</div>
<a
href={publication.links.dblp}
target="_blank"
rel="noopener noreferrer"
className="group/title inline-flex items-start gap-2 text-base font-semibold leading-snug hover:text-primary"
>
{publication.title}
<ArrowUpRight className="mt-0.5 size-4 shrink-0 opacity-0 transition-opacity group-hover/title:opacity-100" />
</a>
<p className="text-sm leading-relaxed text-muted-foreground">
{publication.authors.map((author) => author.name).join(", ")}
</p>
<div className="flex flex-wrap gap-2">
{faculty.map((member) => (
<Badge
key={member.id}
asChild
variant="outline"
className="cursor-pointer"
>
<button
type="button"
onClick={() => onSelectFaculty(member.id)}
>
<UserRound className="size-3" />
{member.name}
</button>
</Badge>
))}
{publication.areaKeywords.map((area) => (
<Badge
key={area}
asChild
variant="secondary"
className="cursor-pointer"
>
<button type="button" onClick={() => onSelectArea(area)}>
{areaLabel(area)}
</button>
</Badge>
))}
</div>
</div>
<div className="flex shrink-0 flex-row flex-wrap gap-2 md:max-w-28 md:flex-col">
{links.map((link) => (
<Button
key={`${publication.id}-${link.label}`}
asChild
variant="outline"
size="sm"
className="justify-start"
>
<a href={link.href} target="_blank" rel="noopener noreferrer">
<FileText className="size-3.5" />
{link.label}
</a>
</Button>
))}
</div>
</div>
</Card>
);
}
export default function PublicationsDirectory() {
const [query, setQuery] = useState("");
const [selectedFaculty, setSelectedFaculty] = useState(ALL);
const [selectedArea, setSelectedArea] = useState(ALL);
const [selectedType, setSelectedType] = useState(ALL);
const [selectedYear, setSelectedYear] = useState(ALL);
const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
const [isUrlReady, setIsUrlReady] = useState(false);
const faculty = publicationsData.faculty;
const publications = publicationsData.publications;
const facultyById = useMemo(
() => new Map(faculty.map((member) => [member.id, member])),
[faculty],
);
const publicationSearchText = useMemo(
() =>
new Map(
publications.map((publication) => [
publication.id,
getPublicationSearchText(publication, facultyById),
]),
),
[facultyById, publications],
);
const years = useMemo(
() =>
[
...new Set(
publications.map((publication) => publication.year).filter(Boolean),
),
]
.sort((a, b) => Number(b) - Number(a))
.map(String),
[publications],
);
const types = useMemo(
() =>
[...new Set(publications.map((publication) => publication.type))].sort(),
[publications],
);
const areaCounts = useMemo(() => {
const counts = new Map<string, number>();
for (const publication of publications) {
for (const area of publication.areaKeywords) {
counts.set(area, (counts.get(area) ?? 0) + 1);
}
}
return counts;
}, [publications]);
const facultyCounts = useMemo(() => {
const counts = new Map<string, number>();
for (const publication of publications) {
for (const facultyId of publication.facultyIds) {
counts.set(facultyId, (counts.get(facultyId) ?? 0) + 1);
}
}
return counts;
}, [publications]);
const facultyOptions = useMemo(
() => [
{ value: ALL, label: "All Faculty" },
...faculty.map((member) => ({
value: member.id,
label: `${member.name} (${facultyCounts.get(member.id) ?? 0})`,
})),
],
[faculty, facultyCounts],
);
const yearOptions = useMemo(
() => [
{ value: ALL, label: "All Years" },
...years.map((year) => ({ value: year, label: year })),
],
[years],
);
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const facultyParam = params.get("faculty");
const areaParam = params.get("area");
const typeParam = params.get("type");
const yearParam = params.get("year");
setQuery(params.get("q") ?? "");
setSelectedFaculty(
facultyParam && facultyById.has(facultyParam) ? facultyParam : ALL,
);
setSelectedArea(
areaParam && publicationsData.areaKeywordVocabulary.includes(areaParam)
? areaParam
: ALL,
);
setSelectedType(typeParam && types.includes(typeParam) ? typeParam : ALL);
setSelectedYear(yearParam && years.includes(yearParam) ? yearParam : ALL);
setVisibleCount(PAGE_SIZE);
setIsUrlReady(true);
}, [facultyById, types, years]);
useEffect(() => {
if (!isUrlReady) return;
const params = new URLSearchParams();
if (query.trim()) params.set("q", query.trim());
if (selectedFaculty !== ALL) params.set("faculty", selectedFaculty);
if (selectedArea !== ALL) params.set("area", selectedArea);
if (selectedType !== ALL) params.set("type", selectedType);
if (selectedYear !== ALL) params.set("year", selectedYear);
const search = params.toString();
const nextUrl = `${window.location.pathname}${search ? `?${search}` : ""}`;
window.history.replaceState(null, "", nextUrl);
}, [
isUrlReady,
query,
selectedArea,
selectedFaculty,
selectedType,
selectedYear,
]);
const selectedFacultyProfile =
selectedFaculty === ALL ? undefined : facultyById.get(selectedFaculty);
const filteredPublications = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();
return publications.filter((publication) => {
if (
normalizedQuery &&
!publicationSearchText.get(publication.id)?.includes(normalizedQuery)
) {
return false;
}
if (
selectedFaculty !== ALL &&
!publication.facultyIds.includes(selectedFaculty)
) {
return false;
}
if (
selectedArea !== ALL &&
!publication.areaKeywords.includes(selectedArea)
) {
return false;
}
if (selectedType !== ALL && publication.type !== selectedType) {
return false;
}
if (
selectedYear !== ALL &&
String(publication.year ?? "") !== selectedYear
) {
return false;
}
return true;
});
}, [
publications,
publicationSearchText,
query,
selectedArea,
selectedFaculty,
selectedType,
selectedYear,
]);
const visiblePublications = filteredPublications.slice(0, visibleCount);
const filtersActive =
query ||
selectedFaculty !== ALL ||
selectedArea !== ALL ||
selectedType !== ALL ||
selectedYear !== ALL;
function resetVisible() {
setVisibleCount(PAGE_SIZE);
}
function selectFaculty(facultyId: string) {
setSelectedFaculty(facultyId);
resetVisible();
}
function selectArea(area: string) {
setSelectedArea(area);
resetVisible();
}
function clearFilters() {
setQuery("");
setSelectedFaculty(ALL);
setSelectedArea(ALL);
setSelectedType(ALL);
setSelectedYear(ALL);
resetVisible();
}
return (
<div>
<section className="from-primary/5 bg-gradient-to-b to-transparent py-14 md:py-18">
<div className="container">
<div className="max-w-3xl">
<div className="mb-4 inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs text-muted-foreground">
<Database className="size-3.5" />
Last updated{" "}
{new Date(publicationsData.generatedAt).toLocaleDateString(
"en-IN",
{
day: "numeric",
month: "long",
year: "numeric",
},
)}
</div>
<h1 className="text-3xl md:text-4xl">Publications</h1>
</div>
<div className="mt-8 rounded-lg border bg-background/80 p-4 text-sm leading-relaxed text-muted-foreground">
Most of the data on this page is fetched directly from DBLP. It
includes publications in all formats, including preprints.
Publications that are not indexed by DBLP may not show up here. DBLP
exports are author-based, not affiliation-at-publication-time
records, so some entries may include work from before or outside IIT
Gandhinagar. If you notice any errors or anything that is amiss,
please{" "}
<a
className="text-primary underline-offset-4 hover:underline"
href="/about/contact"
>
let us know
</a>
.
</div>
</div>
</section>
<section className="border-y bg-muted/20">
<div className="container py-5">
<div className="grid gap-3 lg:grid-cols-[minmax(16rem,1fr)_12rem_11rem_11rem_8rem_auto]">
<div className="relative">
<Search className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
<Input
value={query}
onChange={(event) => {
setQuery(event.target.value);
resetVisible();
}}
placeholder="Search title, author, venue, DOI"
className="pl-9"
/>
</div>
<SearchableFilter
ariaLabel="Filter by faculty"
options={facultyOptions}
searchPlaceholder="Search faculty"
value={selectedFaculty}
onChange={(value) => {
setSelectedFaculty(value);
resetVisible();
}}
/>
<Select
value={selectedArea}
onValueChange={(value) => {
setSelectedArea(value);
resetVisible();
}}
>
<SelectTrigger className="w-full" aria-label="Filter by area">
<SelectValue placeholder="Area" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL}>All Areas</SelectItem>
{publicationsData.areaKeywordVocabulary.map((area) => (
<SelectItem key={area} value={area}>
{areaLabel(area)} ({areaCounts.get(area) ?? 0})
</SelectItem>
))}
</SelectContent>
</Select>
<Select
value={selectedType}
onValueChange={(value) => {
setSelectedType(value);
resetVisible();
}}
>
<SelectTrigger className="w-full" aria-label="Filter by type">
<SelectValue placeholder="Type" />
</SelectTrigger>
<SelectContent>
<SelectItem value={ALL}>All Types</SelectItem>
{types.map((type) => (
<SelectItem key={type} value={type}>
{typeLabel(type)}
</SelectItem>
))}
</SelectContent>
</Select>
<SearchableFilter
ariaLabel="Filter by year"
options={yearOptions}
searchPlaceholder="Search years"
value={selectedYear}
onChange={(value) => {
setSelectedYear(value);
resetVisible();
}}
/>
<Button
variant="outline"
className="w-full lg:w-auto"
onClick={clearFilters}
disabled={!filtersActive}
>
<X className="size-4" />
Clear
</Button>
</div>
<div className="mt-4 flex flex-wrap gap-2">
<Button
variant={selectedArea === ALL ? "default" : "outline"}
size="sm"
onClick={() => selectArea(ALL)}
>
All
</Button>
{publicationsData.areaKeywordVocabulary.map((area) => (
<Button
key={area}
variant={selectedArea === area ? "default" : "outline"}
size="sm"
onClick={() => selectArea(area)}
>
{areaLabel(area)}
</Button>
))}
</div>
</div>
</section>
<section className="section-padding">
<div className="container">
{selectedFacultyProfile && (
<div className="mb-6 flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
<span>{selectedFacultyProfile.dblpName}</span>
<Button asChild variant="link" size="sm" className="h-auto p-0">
<a
href={selectedFacultyProfile.dblpPage}
target="_blank"
rel="noopener noreferrer"
>
DBLP Profile
<ArrowUpRight className="size-3.5" />
</a>
</Button>
{selectedFacultyProfile.profileUrls.slice(0, 3).map((url) => (
<Button
key={url}
asChild
variant="link"
size="sm"
className="h-auto p-0"
>
<a href={url} target="_blank" rel="noopener noreferrer">
{profileLabel(url)}
<ArrowUpRight className="size-3.5" />
</a>
</Button>
))}
</div>
)}
<div className="space-y-3">
{visiblePublications.map((publication) => (
<PublicationCard
key={publication.id}
publication={publication}
facultyById={facultyById}
onSelectArea={selectArea}
onSelectFaculty={selectFaculty}
/>
))}
</div>
{filteredPublications.length === 0 && (
<div className="border-border rounded-lg border border-dashed py-16 text-center">
<h3 className="font-semibold">No publications found</h3>
<p className="mt-2 text-sm text-muted-foreground">
Adjust the filters and search terms.
</p>
</div>
)}
{visibleCount < filteredPublications.length && (
<div className="mt-8 flex justify-center">
<Button
variant="outline"
onClick={() => setVisibleCount((count) => count + PAGE_SIZE)}
>
Load More
</Button>
</div>
)}
</div>
</section>
</div>
);
}

View file

@ -0,0 +1,61 @@
import { Trophy } from 'lucide-react';
import { homepageAwardItems } from '@/data/news';
const colors = ['bg-amber-400', 'bg-blue-500', 'bg-emerald-500'];
export default function RecentWins() {
return (
<section className="section-padding">
<div className="container space-y-8">
<h2 className="flex items-center gap-2 text-2xl">
<Trophy className="text-secondary size-6" />
Recognitions &amp; Awards
</h2>
<table className="w-full table-fixed border-collapse">
<colgroup>
<col className="w-[35%]" />
<col className="hidden md:table-column w-[55%]" />
<col className="w-[10%]" />
</colgroup>
<thead>
<tr className="h-12 border-b text-left text-foreground/40">
<th className="pr-4 font-normal">Name</th>
<th className="hidden pr-4 font-normal md:table-cell">Description</th>
<th className="text-right font-normal">Year</th>
</tr>
</thead>
<tbody>
{homepageAwardItems.map((award, index) => (
<tr
key={award.id}
className="border-b text-left text-foreground/40"
>
<td className="py-4 pr-4 text-base font-medium tracking-tight text-foreground">
<div className="flex items-center gap-3">
<span className={`size-3 shrink-0 rounded-full ${colors[index % colors.length]}`} />
<a
href={award.sourceUrl}
target="_blank"
rel="noreferrer"
className="hover:text-secondary transition-colors"
>
{award.title}
</a>
</div>
</td>
<td className="hidden py-4 pr-4 text-sm md:table-cell">
{award.summary}
</td>
<td className="py-4 text-right text-sm text-foreground">
{award.displayDate}
</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
);
}

View file

@ -0,0 +1,84 @@
'use client';
import {
Brain,
Code2,
Database,
Lock,
Monitor,
Network,
} from 'lucide-react';
import { Card } from '@/components/ui/card';
const AREAS = [
{
icon: Brain,
title: 'Artificial Intelligence & ML',
description:
'Deep learning, NLP, computer vision, reinforcement learning, and AI for social good.',
},
{
icon: Code2,
title: 'Theoretical Computer Science',
description:
'Algorithms, computational complexity, graph theory, and combinatorial optimization.',
},
{
icon: Lock,
title: 'Security & Privacy',
description:
'Program analysis, web security, information flow, and privacy-preserving computation.',
},
{
icon: Database,
title: 'Data Science & Mining',
description:
'Large-scale data analytics, streaming algorithms, and knowledge discovery.',
},
{
icon: Monitor,
title: 'Systems & Architecture',
description:
'Computer architecture, embedded systems, VLSI design, and hardware-software co-design.',
},
{
icon: Network,
title: 'HCI & Cognitive Science',
description:
'Brain-computer interfaces, accessibility, human-AI interaction, and cognitive modeling.',
},
];
export default function ResearchAreas() {
return (
<section className="section-padding">
<div className="container">
<div className="mb-10 text-center">
<h2 className="text-2xl md:text-3xl">Research Areas</h2>
<p className="text-muted-foreground mt-2">
Our faculty pursue diverse and impactful research across computing
</p>
</div>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{AREAS.map((area, i) => (
<Card
key={i}
className="group p-6"
>
<div className="mb-4 flex items-center justify-between">
<h3 className="font-semibold">{area.title}</h3>
<div className="bg-primary/10 text-primary inline-flex rounded-lg p-2">
<area.icon className="size-4" />
</div>
</div>
<p className="text-muted-foreground text-sm leading-relaxed">
{area.description}
</p>
</Card>
))}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,104 @@
import { FlaskConical } from 'lucide-react';
import { cn } from '@/lib/utils';
interface ResearchArea {
title: string;
desc: string;
img: string;
href: string;
gridClass: string;
horizontal?: boolean;
}
const researchAreas: ResearchArea[] = [
{
title: 'AI & Machine Learning',
desc: 'Deep learning, NLP, computer vision, and AI for social good.',
img: 'https://images.unsplash.com/photo-1677442136019-21780ecad995?w=800&h=600&fit=crop',
href: '/research/ai',
gridClass: 'md:col-span-1',
},
{
title: 'HCI & Cognitive Science',
desc: 'Brain-computer interfaces, accessibility, and cognitive modeling.',
img: 'https://images.unsplash.com/photo-1559757175-5700dde675bc?w=800&h=600&fit=crop',
href: '/research/hci',
gridClass: 'lg:col-span-2',
},
{
title: 'Security & Privacy',
desc: 'Program analysis, web security, and privacy-preserving computation.',
img: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=800&h=600&fit=crop',
href: '/research/security',
gridClass: 'md:col-span-1 lg:row-span-2',
},
{
title: 'Data Science',
desc: 'Large-scale analytics, streaming algorithms, and knowledge discovery.',
img: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800&h=600&fit=crop',
href: '/research/data-science',
gridClass: 'lg:col-span-2',
},
{
title: 'Systems & Architecture',
desc: 'Computer architecture, embedded systems, and hardware-software co-design.',
img: 'https://images.unsplash.com/photo-1518770660439-4636190af475?w=800&h=600&fit=crop',
href: '/research/systems',
gridClass: 'md:col-span-1',
},
{
title: 'Theoretical CS',
desc: 'Algorithms, complexity, graph theory, and combinatorial optimization.',
img: 'https://images.unsplash.com/photo-1635070041078-e363dbe005cb?w=1200&h=300&fit=crop',
href: '/research/theory',
gridClass: 'md:col-span-2 lg:col-span-4',
horizontal: true,
},
];
export default function ResearchBento() {
return (
<section className="section-padding">
<div className="container">
<h2 className="mb-6 flex items-center gap-2 text-2xl">
<FlaskConical className="text-secondary size-6" />
Research Highlights
</h2>
<div className="grid w-full grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-4">
{researchAreas.map((area, index) => (
<a
key={index}
href={area.href}
className={cn(
'group relative flex gap-2 rounded-2xl border p-3 transition-colors hover:border-blue-500/50 hover:bg-blue-500/5',
area.horizontal ? 'flex-row items-center' : 'flex-col',
area.gridClass,
)}
>
<div className={cn(
'overflow-hidden rounded-xl bg-muted',
area.horizontal ? 'h-36 w-1/3 shrink-0' : 'w-full flex-1',
)}>
<img
src={area.img}
alt={area.title}
className={cn(
'pointer-events-none h-full w-full object-cover transition-transform duration-300 group-hover:scale-105',
!area.horizontal && 'min-h-32',
)}
/>
</div>
<div className={area.horizontal ? 'flex-1' : ''}>
<h3 className={cn('text-lg font-semibold tracking-tight', !area.horizontal && 'mt-2')}>
{area.title}
</h3>
<p className="text-sm text-muted-foreground">{area.desc}</p>
</div>
</a>
))}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,257 @@
'use client';
import { useMemo, useState } from 'react';
import { Grid2X2, List, Mail, Search } from 'lucide-react';
import { Card } from '@/components/ui/card';
import {
RESEARCH_SCHOLARS,
type ResearchScholar,
} from '@/data/research-scholars';
import { cn } from '@/lib/utils';
const ALL = 'all';
type SortKey = 'name-asc' | 'name-desc' | 'joined-desc' | 'joined-asc';
type ViewMode = 'gallery' | 'list';
const PROGRAM_LABELS: Record<ResearchScholar['program'], string> = {
'Computer Science and Engineering': 'CSE',
'Artificial Intelligence': 'AI',
};
const joinedYears = Array.from(
new Set(RESEARCH_SCHOLARS.map((scholar) => scholar.joined)),
).sort((a, b) => Number(b) - Number(a));
function initials(name: string) {
return name
.split(' ')
.map((part) => part[0])
.join('')
.slice(0, 2)
.toUpperCase();
}
function ScholarImage({ scholar }: { scholar: ResearchScholar }) {
const [failed, setFailed] = useState(false);
if (failed) {
return (
<div className="bg-primary/10 text-primary grid h-full w-full place-items-center text-2xl font-semibold">
{initials(scholar.name)}
</div>
);
}
return (
<img
src={scholar.image}
alt={scholar.name}
className="h-full w-full object-cover"
loading="lazy"
referrerPolicy="no-referrer"
onError={() => setFailed(true)}
/>
);
}
function GalleryCard({ scholar }: { scholar: ResearchScholar }) {
return (
<Card className="overflow-hidden">
<div className="aspect-[4/3] bg-muted">
<ScholarImage scholar={scholar} />
</div>
<div className="p-4">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<h3 className="truncate text-base font-semibold">
{scholar.name}
</h3>
<p className="text-muted-foreground mt-1 text-sm">
{PROGRAM_LABELS[scholar.program]} · Joined {scholar.joined}
</p>
</div>
<a
href={`mailto:${scholar.email}`}
className="text-muted-foreground hover:text-secondary shrink-0 transition-colors"
aria-label={`Email ${scholar.name}`}
>
<Mail className="size-4" />
</a>
</div>
</div>
</Card>
);
}
function ListRow({ scholar }: { scholar: ResearchScholar }) {
return (
<article className="grid gap-3 border-b py-4 last:border-b-0 md:grid-cols-[1.5fr_1fr_8rem_2rem] md:items-center">
<div className="flex items-center gap-3">
<div className="size-12 shrink-0 overflow-hidden rounded-lg bg-muted">
<ScholarImage scholar={scholar} />
</div>
<h3 className="font-medium">{scholar.name}</h3>
</div>
<p className="text-muted-foreground text-sm">{scholar.program}</p>
<p className="text-muted-foreground text-sm">Joined {scholar.joined}</p>
<a
href={`mailto:${scholar.email}`}
className="text-muted-foreground hover:text-secondary transition-colors"
aria-label={`Email ${scholar.name}`}
>
<Mail className="size-4" />
</a>
</article>
);
}
export default function ResearchScholarsDirectory() {
const [query, setQuery] = useState('');
const [program, setProgram] = useState(ALL);
const [year, setYear] = useState(ALL);
const [sort, setSort] = useState<SortKey>('joined-desc');
const [view, setView] = useState<ViewMode>('gallery');
const filtered = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();
return [...RESEARCH_SCHOLARS]
.filter((scholar) => {
const matchesQuery =
normalizedQuery.length === 0 ||
scholar.name.toLowerCase().includes(normalizedQuery) ||
scholar.email.toLowerCase().includes(normalizedQuery);
const matchesProgram = program === ALL || scholar.program === program;
const matchesYear = year === ALL || scholar.joined === year;
return matchesQuery && matchesProgram && matchesYear;
})
.sort((a, b) => {
if (sort === 'name-asc') return a.name.localeCompare(b.name);
if (sort === 'name-desc') return b.name.localeCompare(a.name);
if (sort === 'joined-asc') {
return Number(a.joined) - Number(b.joined) || a.name.localeCompare(b.name);
}
return Number(b.joined) - Number(a.joined) || a.name.localeCompare(b.name);
});
}, [program, query, sort, year]);
return (
<div>
<section className="from-primary/5 bg-gradient-to-b to-transparent py-16 md:py-20">
<div className="container">
<p className="text-secondary text-sm font-medium uppercase tracking-wider">
People
</p>
<h1 className="mt-3 text-3xl md:text-4xl">Research Scholars</h1>
<p className="text-muted-foreground mt-4 max-w-2xl">
Doctoral researchers in Computer Science and Engineering and
Artificial Intelligence.
</p>
</div>
</section>
<section className="border-b">
<div className="container grid gap-3 py-4 lg:grid-cols-[1fr_14rem_10rem_12rem_auto]">
<label className="relative">
<Search className="text-muted-foreground absolute left-3 top-1/2 size-4 -translate-y-1/2" />
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search by name or email"
className="h-10 w-full rounded-md border bg-background pl-9 pr-3 text-sm outline-none transition focus:border-secondary"
/>
</label>
<select
value={program}
onChange={(event) => setProgram(event.target.value)}
className="h-10 rounded-md border bg-background px-3 text-sm outline-none transition focus:border-secondary"
>
<option value={ALL}>All programs</option>
<option value="Computer Science and Engineering">CSE</option>
<option value="Artificial Intelligence">AI</option>
</select>
<select
value={year}
onChange={(event) => setYear(event.target.value)}
className="h-10 rounded-md border bg-background px-3 text-sm outline-none transition focus:border-secondary"
>
<option value={ALL}>All years</option>
{joinedYears.map((joinedYear) => (
<option key={joinedYear} value={joinedYear}>
{joinedYear}
</option>
))}
</select>
<select
value={sort}
onChange={(event) => setSort(event.target.value as SortKey)}
className="h-10 rounded-md border bg-background px-3 text-sm outline-none transition focus:border-secondary"
>
<option value="joined-desc">Newest first</option>
<option value="joined-asc">Oldest first</option>
<option value="name-asc">Name A-Z</option>
<option value="name-desc">Name Z-A</option>
</select>
<div className="grid h-10 grid-cols-2 rounded-md border p-1">
{[
{ id: 'gallery' as const, icon: Grid2X2, label: 'Gallery' },
{ id: 'list' as const, icon: List, label: 'List' },
].map((item) => {
const Icon = item.icon;
return (
<button
key={item.id}
type="button"
onClick={() => setView(item.id)}
className={cn(
'grid size-8 place-items-center rounded-sm transition',
view === item.id
? 'bg-secondary text-secondary-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
aria-label={`${item.label} view`}
>
<Icon className="size-4" />
</button>
);
})}
</div>
</div>
</section>
<section className="section-padding">
<div className="container">
<div className="mb-5 flex items-center justify-between gap-4">
<p className="text-muted-foreground text-sm">
{filtered.length} scholar{filtered.length === 1 ? '' : 's'}
</p>
</div>
{view === 'gallery' ? (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{filtered.map((scholar) => (
<GalleryCard key={scholar.email} scholar={scholar} />
))}
</div>
) : (
<Card className="p-4">
{filtered.map((scholar) => (
<ListRow key={scholar.email} scholar={scholar} />
))}
</Card>
)}
</div>
</section>
</div>
);
}

View file

@ -0,0 +1,67 @@
'use client';
import Noise from '@/components/elements/noise';
import { cn } from '@/lib/utils';
const TECH_STACK = [
{
name: 'Netflix',
logo: '/images/logos/netflix.svg',
url: 'https://netflix.com',
className: '',
},
{
name: 'T-Mobile',
logo: '/images/logos/t-mobile.svg',
className: '',
url: 'https://t-mobile.com',
},
{
name: 'Spotify',
logo: '/images/logos/spotify.svg',
url: 'https://spotify.com',
className: '',
},
{
name: 'TIBCO',
logo: '/images/logos/tibc.svg',
className: '',
url: 'https://tibco.com',
},
];
export default function TeamShowcase() {
return (
<section className="section-padding relative">
<Noise />
<div className="bigger-container">
<div className="flex flex-col items-center gap-6 lg:flex-row lg:justify-between lg:gap-12">
<p className="max-w-sm text-center text-2xl leading-tight lg:text-start">
We are a team of passionate developers, designers, and
entrepreneurs.
</p>
{/* Right Side - Tech Stack Logos */}
<div className="grid grid-cols-2 gap-6 md:grid-cols-4">
{TECH_STACK.map((tech) => (
<a
key={tech.name}
href={tech.url}
target="_blank"
rel="noopener noreferrer"
className="group relative flex h-8 w-32 items-center justify-center transition-opacity duration-200 hover:opacity-80"
>
<img
src={tech.logo}
alt={`${tech.name} logo`}
className={cn('size-full object-contain', tech.className)}
/>
</a>
))}
</div>
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,276 @@
import { Quote, Star } from 'lucide-react';
import Noise from '@/components/elements/noise';
import { Marquee } from '@/components/magicui/marquee';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { cn } from '@/lib/utils';
const platformLogos = [
{
name: 'G2',
src: 'https://logo.clearbit.com/g2.com',
alt: 'G2',
},
{
name: 'Airtable',
src: 'https://logo.clearbit.com/airtable.com',
alt: 'Airtable',
},
{
name: 'Trustpilot',
src: 'https://logo.clearbit.com/trustpilot.com',
alt: 'Trustpilot',
},
{
name: 'GetApp',
src: 'https://logo.clearbit.com/getapp.com',
alt: 'GetApp',
},
{
name: 'Software Advice',
src: 'https://logo.clearbit.com/softwareadvice.com',
alt: 'Software Advice',
},
];
const testimonials = [
{
id: '1',
name: 'Sarah Mitchell',
title: 'Head of Product',
company: 'Nike',
image:
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400&h=400&fit=crop&crop=face',
companyLogo: {
src: '/images/logos/nike.png',
width: 67.5,
height: 24,
},
testimonial:
'Lumen has completely changed the way we present our project workflows. We can create visual boards, share tasks instantly, and demo progress live.',
},
{
id: '2',
name: 'Alex Chen',
title: 'Senior Designer',
company: 'Spotify',
image:
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=400&fit=crop&crop=face',
testimonial:
"Lumen was the missing layer between our product and engineering teams. We've never had this much clarity in how tasks move through the pipeline and deliver results.",
},
{
id: '3',
name: 'Marcus Johnson',
title: 'VP Product',
company: 'T-Mobile',
image:
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400&h=400&fit=crop&crop=face',
testimonial:
'We used to lose track of deliverables every week. With Lumen, task ownership is crystal clear and timelines are actually realistic for our team.',
},
{
id: '4',
name: 'Emily Davis',
title: 'Product Manager',
company: 'Booking',
image:
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&h=400&fit=crop&crop=face',
testimonial:
'Lumen blended perfectly into our design-to-dev process. We organize prototypes, handoffs, and sprints without switching tools constantly.',
},
{
id: '5',
name: 'Ben Parker',
title: 'Engineering Lead',
company: 'IBM',
image:
'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=400&h=400&fit=crop&crop=face',
testimonial:
"Since adopting Lumen, our feedback cycles became shorter and much more effective. It's a must-have for any growing product team today.",
},
{
id: '6',
name: 'Samantha Lee',
title: 'Design Director',
company: 'Logitech',
image:
'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400&h=400&fit=crop&crop=face',
testimonial:
"Lumen makes it incredibly easy to manage cross-functional work. We've cut coordination time in half and deliver with better insights.",
},
{
id: '7',
name: 'David Kim',
title: 'CTO',
company: 'Fortinet',
image:
'https://images.unsplash.com/photo-1507591064344-4c6ce005b128?w=400&h=400&fit=crop&crop=face',
testimonial:
'We use Lumen across all departments — from tech to support. Creating shared workflows has drastically improved internal communication.',
},
{
id: '8',
name: 'Rachel Green',
title: 'Product Designer',
company: 'Zapier',
image:
'https://images.unsplash.com/photo-1489424731084-a5d8b219a5bb?w=400&h=400&fit=crop&crop=face',
companyLogo: {
src: '/images/logos/zapiar.png',
width: 105,
height: 28,
},
testimonial:
'Lumen has completely transformed how we approach daily project planning and execution. Before switching, we constantly missed deadlines due to misalignment. Our productivity skyrocketed.',
},
{
id: '9',
name: 'Mike Johnson',
title: 'Startup Founder',
company: 'Tailwind CSS',
image:
'https://images.unsplash.com/photo-1560250097-0b93528c311a?w=400&h=400&fit=crop&crop=face',
companyLogo: {
src: '/images/logos/tailwindcss.png',
width: 130,
height: 20,
},
testimonial:
"I created a workspace, invited my co-founder, and started assigning tasks in 45 seconds. That's how fast Lumen works for our team.",
},
];
const TestimonialsMarquee = () => {
return (
<section className="section-padding relative">
<Noise />
<div className="container">
{/* Reviews Section */}
<div className="mb-3 flex flex-wrap items-center gap-2">
{/* 5 Star Rating */}
<div className="flex items-center gap-1">
{[...Array(5)].map((_, i) => (
<Star
key={i}
className="size-3 fill-yellow-400 text-yellow-400"
/>
))}
</div>
<span className="text-muted-foreground text-xs font-medium">
25,000+ reviews from
</span>
{/* Platform Logos */}
{platformLogos.map((logo) => (
<img
key={logo.name}
src={logo.src}
alt={logo.alt}
width={12}
height={12}
/>
))}
</div>
<div className="max-w-4xl space-y-3 lg:space-y-4">
<h2 className="text-4xl tracking-tight lg:text-5xl">
Why teams are leaving Monday for Lumen
</h2>
<p className="text-muted-foreground text-lg leading-snug">
Hear how teams are moving faster, collaborating better, and finally
loving their workflow with Lumen.
</p>
</div>
</div>
<div className="mt-8 space-y-4 mask-r-from-90% mask-r-to-100% mask-l-from-90% mask-l-to-100% lg:mt-12">
<Marquee pauseOnHover className="py-0">
{firstRow.map((review) => (
<ReviewCard
key={review.id}
name={review.name}
title={review.title}
company={review.company}
image={review.image}
testimonial={review.testimonial}
/>
))}
</Marquee>
<Marquee reverse pauseOnHover className="py-0">
{secondRow.map((review) => (
<ReviewCard
key={review.id}
name={review.name}
title={review.title}
company={review.company}
image={review.image}
testimonial={review.testimonial}
/>
))}
</Marquee>
</div>
</section>
);
};
export default TestimonialsMarquee;
// Split testimonials into two rows for marquee
const firstRow = testimonials.slice(0, 5);
const secondRow = testimonials.slice(5);
interface ReviewCardProps {
name: string;
title: string;
company: string;
image: string;
testimonial: string;
}
function ReviewCard({
name,
title,
company,
image,
testimonial,
}: ReviewCardProps) {
return (
<Card
className={cn(
'hover:bg-accent/10 max-w-xs gap-3 bg-transparent md:max-w-md',
'transition-colors duration-200',
)}
>
<CardHeader className="flex items-center justify-between">
<div className="flex items-center gap-3">
<img
className="border-input rounded-full border"
width={48}
height={48}
src={image}
alt={name}
/>
<div className="flex flex-col">
<CardTitle className="text-sm font-medium">
<cite className="not-italic">{name}</cite>
</CardTitle>
<CardDescription className="text-muted-foreground text-xs">
{title} at {company}
</CardDescription>
</div>
</div>
<Quote className="fill-foreground size-5" />
</CardHeader>
<CardContent className="">
<blockquote className="leading-snug">{testimonial}</blockquote>
</CardContent>
</Card>
);
}

View file

@ -0,0 +1,327 @@
'use client';
import { useEffect, useState } from 'react';
import Noise from '@/components/elements/noise';
import {
Card,
CardContent,
CardFooter,
CardHeader,
} from '@/components/ui/card';
import {
Carousel,
type CarouselApi,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel';
import { cn } from '@/lib/utils';
const COMMON_CARDS_CLASSNAMES = {
big: 'col-span-4 lg:[&_blockquote]:text-base lg:[&_blockquote]:leading-loose lg:[&_blockquote]:text-foreground',
};
const testimonials = [
{
id: '1',
name: 'Sarah Mitchell',
title: 'Head of Product',
company: 'Nike',
image:
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400&h=400&fit=crop&crop=face',
companyLogo: {
src: '/images/logos/nike.png',
width: 67.5,
height: 24,
},
testimonial:
'Lumen has completely changed the way we present our project workflows. We can create visual boards, share tasks instantly, and demo progress live. Its business-focused collaboration without the overhead.',
className: COMMON_CARDS_CLASSNAMES.big,
},
{
id: '2',
name: 'Alex Chen',
title: 'Senior Designer',
company: 'Spotify',
image:
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=400&fit=crop&crop=face',
testimonial:
'Lumen was the missing layer between our product and engineering teams. Weve never had this much clarity in how tasks move through the pipeline.',
className: 'col-span-2 ',
},
{
id: '3',
name: 'Marcus Johnson',
title: 'VP Product',
company: 'T-Mobile',
image:
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400&h=400&fit=crop&crop=face',
testimonial:
'We used to lose track of deliverables every week. With Lumen, task ownership is crystal clear and timelines are actually realistic.',
className: 'col-span-2 ',
},
{
id: '4',
name: 'Emily Davis',
title: 'Product Manager',
company: 'Booking',
image:
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&h=400&fit=crop&crop=face',
testimonial:
'Lumen blended perfectly into our design-to-dev process. We organize prototypes, handoffs, and sprints without switching tools.',
className: 'col-span-2 ',
},
{
id: '5',
name: 'Ben Parker',
title: 'Engineering Lead',
company: 'IBM',
image:
'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=400&h=400&fit=crop&crop=face',
testimonial:
"Since adopting Lumen, our feedback cycles became shorter and much more effective. It's a must-have for any growing product team.",
className: 'col-span-2 ',
},
{
id: '6',
name: 'Samantha Lee',
title: 'Design Director',
company: 'Logitech',
image:
'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400&h=400&fit=crop&crop=face',
testimonial:
'Lumen makes it incredibly easy to manage cross-functional work. Weve cut coordination time in half and deliver with better insights.',
className: 'col-span-2',
},
{
id: '7',
name: 'David Kim',
title: 'CTO',
company: 'Fortinet',
image:
'https://images.unsplash.com/photo-1507591064344-4c6ce005b128?w=400&h=400&fit=crop&crop=face',
testimonial:
'We use Lumen across all departments — from tech to support. Creating shared workflows has drastically improved internal communication.”',
className: 'col-span-2',
},
{
id: '8',
name: 'Rachel Green',
title: 'Product Designer',
company: 'Zapier',
image:
'https://images.unsplash.com/photo-1489424731084-a5d8b219a5bb?w=400&h=400&fit=crop&crop=face',
companyLogo: {
src: '/images/logos/zapiar.png',
width: 105,
height: 28,
},
testimonial:
'Lumen has completely transformed how we approach daily project planning and execution. Before switching, we constantly missed deadlines due to misalignment. Now, everyone knows whats happening, whos responsible, and when things are due. Our productivity skyrocketed, and team communication has never been clearer.',
className: cn(COMMON_CARDS_CLASSNAMES.big, ''),
},
{
id: '9',
name: 'Mike Johnson',
title: 'Startup Founder',
company: 'Tailwind CSS',
image:
'https://images.unsplash.com/photo-1560250097-0b93528c311a?w=400&h=400&fit=crop&crop=face',
companyLogo: {
src: '/images/logos/tailwindcss.png',
width: 130,
height: 20,
},
testimonial:
'I created a workspace, invited my co-founder, and started assigning tasks in 45 seconds. Thats how fast Lumen works.',
className: cn(
COMMON_CARDS_CLASSNAMES.big,
'lg:[&_blockquote]:text-4xl lg:[&_blockquote]:leading-tight lg:shadow-lg',
),
},
];
export default function Testimonials() {
const [api, setApi] = useState<CarouselApi>();
const [current, setCurrent] = useState(0);
const [count, setCount] = useState(0);
useEffect(() => {
if (!api) {
return;
}
setCount(api.scrollSnapList().length);
setCurrent(api.selectedScrollSnap());
api.on('select', () => {
setCurrent(api.selectedScrollSnap());
});
}, [api]);
return (
<section className="section-padding relative overflow-x-hidden">
<Noise />
<div className="container">
{/* Section Header */}
<div className="mx-auto max-w-4xl space-y-3 lg:space-y-4 lg:text-center">
<h2 className="text-4xl tracking-tight lg:text-5xl">
Trusted by modern teams
</h2>
<p className="text-muted-foreground text-lg leading-snug lg:text-balance">
Join thousands of product managers, designers, and developers who
rely on Lumen to plan, track, and deliver value without the chaos.
</p>
</div>
{/* Desktop Grid */}
<div className="mx-auto mt-8 hidden max-w-6xl grid-cols-8 gap-2 lg:mt-12 lg:grid">
{testimonials.map((testimonial) => (
<TestimonialCard key={testimonial.id} testimonial={testimonial} />
))}
</div>
{/* Mobile Carousel */}
<div className="mt-8 -mr-[max(2rem,calc((100vw-80rem)/2+5rem))] lg:hidden">
<Carousel
opts={{
align: 'start',
loop: true,
}}
className="w-full"
setApi={setApi}
>
<CarouselContent className="-ml-2 lg:-ml-4">
{testimonials.map((testimonial) => (
<CarouselItem
key={testimonial.id}
className="basis-9/10 pl-2 sm:basis-1/2 lg:pl-4"
>
<TestimonialCard testimonial={testimonial} />
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious className="hidden" />
<CarouselNext className="hidden" />
</Carousel>
{/* Carousel Dots */}
<div className="mt-6 flex justify-center gap-2">
{Array.from({ length: count }, (_, index) => (
<button
key={index}
onClick={() => api?.scrollTo(index)}
className={cn(
'size-2 rounded-full transition-all duration-200',
index === current
? 'bg-foreground scale-110'
: 'bg-muted-foreground/30 hover:bg-muted-foreground/50',
)}
/>
))}
</div>
</div>
</div>
</section>
);
}
interface TestimonialCardProps {
testimonial: {
id: string;
name: string;
title: string;
company: string;
image: string;
companyLogo?: {
src: string;
width: number;
height: number;
};
testimonial: string;
featured?: boolean;
className?: string;
};
}
function TestimonialCard({ testimonial }: TestimonialCardProps) {
const isBigCard = testimonial.className?.includes('col-span-4');
const withGradientBorder = testimonial.id === '1' || testimonial.id === '9';
return (
<Card
className={cn(
'hover:shadow-primary/5 relative h-full transition-all duration-300 hover:shadow-lg',
withGradientBorder &&
'lg:before:from-chart-1 lg:before:via-chart-2 lg:before:to-chart-3 lg:border-0 lg:before:absolute lg:before:inset-[-1px] lg:before:z-[-1] lg:before:rounded-xl lg:before:bg-gradient-to-tr lg:before:content-[""]',
testimonial.className,
)}
>
<CardHeader>
{/* Author Info at Top for Small Cards */}
{!isBigCard && (
<div className="flex items-center gap-3">
<img
src={testimonial.image}
alt={`${testimonial.name} profile`}
width={40}
height={40}
className="rounded-full object-cover"
/>
<div className="min-w-0 flex-1">
<div className="text-card-foreground truncate text-sm font-medium">
{testimonial.name}
</div>
<div className="text-muted-foreground truncate text-xs">
{testimonial.title} at {testimonial.company}
</div>
</div>
</div>
)}
{/* Company Logo for Big Cards Only */}
{testimonial?.companyLogo?.src && (
<img
src={testimonial.companyLogo.src}
alt={`${testimonial.company} logo`}
width={testimonial.companyLogo.width}
height={testimonial.companyLogo.height}
className="object-contain dark:invert"
/>
)}
</CardHeader>
<CardContent className="">
{/* Testimonial Text */}
<blockquote
className={cn('lg:text-muted-foreground leading-relaxed lg:text-sm')}
>
{testimonial.testimonial}
</blockquote>
</CardContent>
{/* Author Info at Bottom for Big Cards */}
{isBigCard && (
<CardFooter className="flex items-center gap-3">
<div className="relative">
<img
src={testimonial.image}
alt={`${testimonial.name} profile`}
width={40}
height={40}
className="size-10 rounded-full object-cover"
/>
</div>
<div className="min-w-0 flex-1">
<div className="text-card-foreground truncate text-sm font-medium">
{testimonial.name}
</div>
<div className="text-muted-foreground truncate text-xs">
{testimonial.title} at {testimonial.company}
</div>
</div>
</CardFooter>
)}
</Card>
);
}

View file

@ -0,0 +1,89 @@
'use client';
import { useState } from 'react';
import { Play } from 'lucide-react';
import Noise from '@/components/elements/noise';
export default function VideoShowcase() {
const [isPlaying, setIsPlaying] = useState(false);
const handlePlay = () => {
setIsPlaying(true);
// You can add video play logic here
};
return (
<section className="section-padding relative">
<Noise />
<div className="bigger-container">
<span className="text-muted-foreground mb-4 text-sm font-semibold tracking-wide uppercase">
ABOUT US
</span>
<div className="flex flex-col justify-between gap-4 md:flex-row xl:gap-8">
<h2 className="max-w-2xl text-4xl leading-none font-medium tracking-tight lg:text-5xl">
Simplifying Complex Workflows with Developer-Focused Solutions
</h2>
<p className="max-w-lg">
Lumen removes the clutter from task management. We designed it for
modern product teams, helping them stay in sync across features,
deadlines, and discussions.
</p>
</div>
{/* Video Section */}
<div className="mt-8 lg:mt-12">
<div className="bg-muted relative aspect-video w-full overflow-hidden rounded-xl border">
{!isPlaying ? (
<>
{/* Video Thumbnail/Background */}
<div
className="absolute inset-0 bg-cover bg-center"
style={{
backgroundImage:
'url(https://images.unsplash.com/photo-1521737604893-d14cc237f11d?w=1600&h=900&fit=crop&crop=center)',
}}
>
{/* Dark overlay */}
<div className="absolute inset-0 bg-black/30" />
</div>
{/* Custom Play Button */}
<div className="absolute inset-0 flex flex-col items-center justify-center gap-4">
<button
onClick={handlePlay}
className="group flex h-20 w-20 cursor-pointer items-center justify-center rounded-full bg-white shadow-lg transition-all duration-300 hover:scale-103 hover:shadow-xl lg:h-24 lg:w-24"
aria-label="Play Video"
>
<Play className="ml-1 size-6 fill-black text-black lg:size-7" />
</button>
<span className="font-medium text-white">Play Video</span>
</div>
</>
) : (
/* Video Player - Replace with your actual video */
<video
className="h-full w-full object-cover"
controls
autoPlay
poster="https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=1600&h=900&fit=crop&crop=center"
>
<source
src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
type="video/mp4"
/>
Your browser does not support the video tag.
</video>
)}
</div>
{/* Description */}
<p className="text-muted-foreground mx-auto mt-4 text-center md:mt-8 md:text-lg">
It&apos;s built to support transparency, accountability, and
progress...no matter the team size.
</p>
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,356 @@
import type { LucideIcon } from 'lucide-react';
import {
BrainCircuit,
FileCheck2,
GraduationCap,
HeartPulse,
Landmark,
Microscope,
Network,
Orbit,
Palette,
Scale,
Cpu,
UsersRound,
} from 'lucide-react';
import { cn } from '@/lib/utils';
type IconItem = {
icon: LucideIcon;
title: string;
description: string;
tone: string;
};
const principles: IconItem[] = [
{
icon: Scale,
title: 'Responsible scale',
description:
'Build computing systems that are reliable, secure, verifiable, and ready for deployment in high-stakes settings.',
tone: 'bg-blue-500/10 text-blue-700 dark:text-blue-300',
},
{
icon: HeartPulse,
title: 'Societal priorities',
description:
'Advance work that addresses inclusive education, sustainable living, digital health, accessibility, and public infrastructure.',
tone: 'bg-emerald-500/10 text-emerald-700 dark:text-emerald-300',
},
{
icon: Network,
title: 'Open collaboration',
description:
'Create open datasets, reproducible tools, and durable partnerships with clinical, industrial, policy, and academic collaborators.',
tone: 'bg-amber-500/10 text-amber-700 dark:text-amber-300',
},
];
const demonstrations = [
{
icon: FileCheck2,
title: 'IITGN Exam-App',
eyebrow: 'Assessment infrastructure',
description:
'A secure, cross-platform examination application for BYOD settings, designed to lower operational overhead while preserving academic integrity.',
details: [
'Electron-based desktop app across Windows, macOS, and Linux',
'Controlled exam environment with PDF rendering and process monitoring',
'Roadmap: proctoring, admin dashboards, sandboxing, offline sync, and LLM-assisted question generation and evaluation',
],
tone: 'bg-blue-500/10 text-blue-700 dark:text-blue-300',
},
{
icon: BrainCircuit,
title: 'Code, math, and reasoning models',
eyebrow: 'Sovereign AI systems',
description:
'In collaboration with Soket AI, the department is developing multilingual, code-centric models for reasoning, software engineering, and secure deployment.',
details: [
'120B-parameter multilingual LLM under training and evaluation',
'Agent-first architecture for repository-level code work, tool use, and enterprise workflows',
'Focus areas include Indic language support, cybersecurity, AI safety, and on-premise or air-gapped deployments',
],
tone: 'bg-violet-500/10 text-violet-700 dark:text-violet-300',
},
{
icon: HeartPulse,
title: 'ApneaEye',
eyebrow: 'Deployable clinical AI',
description:
'A contact-free sleep apnea screening system that uses low-cost thermal sensing to move clinical AI beyond specialized sleep labs.',
details: [
'Single thermal camera and Raspberry Pi hardware below Rs 25,000',
'535 hours of synchronized thermal and PSG data from 70 participants with AIIMS Delhi',
'AHI estimation at R2 = 0.95 and MAE 2.22 events/hour against PSG',
],
tone: 'bg-emerald-500/10 text-emerald-700 dark:text-emerald-300',
},
];
const researchDirections: IconItem[] = [
{
icon: Orbit,
title: 'Quantum algorithms',
description:
'Identify problems where quantum systems offer provable advantages, including kernels, QNNs, QAOA/VQE, and group-theoretic complexity.',
tone: 'bg-sky-500/10 text-sky-700 dark:text-sky-300',
},
{
icon: Cpu,
title: 'Secure systems and Rust OS',
description:
'Develop an indigenous Rust-based ARMv8 operating system, network stack, testing pipeline, formal verification methods, and sandboxing support for mobile, edge, and IoT devices.',
tone: 'bg-red-500/10 text-red-700 dark:text-red-300',
},
{
icon: Palette,
title: 'AI for visual arts and design',
description:
'Use differentiable rendering, inverse design, and learning-based methods for shadow art, knots, scribble art, and anamorphic 3D forms.',
tone: 'bg-fuchsia-500/10 text-fuchsia-700 dark:text-fuchsia-300',
},
{
icon: UsersRound,
title: 'Accessibility and assistive technology',
description:
'Create culturally and linguistically inclusive AI for communication, adaptive learning, eye-gaze interfaces, and assistive interaction.',
tone: 'bg-orange-500/10 text-orange-700 dark:text-orange-300',
},
];
const educationTracks = [
{
icon: GraduationCap,
title: 'Foundational CS training',
description:
'A program for students in Gujarat engineering colleges, combining two five-day IITGN bootcamps each semester with online modules.',
points: [
'Programming, data structures and algorithms, and discrete mathematics',
'Interactive lectures, problem-solving labs, coding contests, and peer learning',
'Teaching assistantships for IITGN PhD and master\'s students',
],
},
{
icon: Landmark,
title: 'Academic growth',
description:
'Faculty expansion is planned to deepen coverage in systems, security, quantum technologies, AI, theory, and interdisciplinary computing.',
points: [
'Near-term goal: add four to five faculty members in priority areas',
'Decade goal: 35 to 40 core CSE faculty',
'Target faculty-student ratio: 1:10, aligned with MoE recommendations',
],
},
];
export default function VisionPage() {
return (
<div>
<section className="border-b bg-muted/40">
<div className="container py-14 md:py-18">
<p className="text-sm font-medium text-secondary">
Department of Computer Science &amp; Engineering, IIT Gandhinagar
</p>
<div className="mt-5 grid gap-8 lg:grid-cols-[minmax(0,1fr)_22rem] lg:items-start">
<div className="max-w-3xl">
<h1 className="text-3xl font-semibold leading-tight md:text-5xl">
Responsible, scalable computing for India and the world
</h1>
<p className="mt-5 text-lg leading-relaxed text-muted-foreground">
The department&apos;s decade-long vision is to pair fundamental
advances in AI, theory, and systems with deployable technologies
for inclusive education, sustainable living, digital health, and
secure public infrastructure.
</p>
</div>
<aside className="rounded-lg border border-border bg-card p-5 shadow-sm">
<div className="flex items-center gap-3">
<div className="rounded-md bg-primary/10 p-2 text-primary">
<Microscope className="size-5" />
</div>
<p className="font-semibold">Long term outcome</p>
</div>
<p className="mt-4 text-sm leading-relaxed text-muted-foreground">
Become a global hub for socially impactful, scientifically
rigorous computing research through open datasets, reproducible
tools, and strong links with industry, policy, clinics, and
peer institutions.
</p>
</aside>
</div>
</div>
</section>
<section className="section-padding">
<div className="container">
<SectionHeader
eyebrow="Research culture"
title="What will guide the department"
description="The vision emphasizes research that is deep enough to advance computer science and practical enough to be validated in the environments where it will be used."
/>
<div className="mt-8 grid gap-4 md:grid-cols-3">
{principles.map((principle) => (
<IconCard key={principle.title} item={principle} />
))}
</div>
</div>
</section>
<section className="border-y bg-muted/30 section-padding">
<div className="container">
<SectionHeader
eyebrow="High-TRL demonstrations"
title="From research prototypes to deployable systems"
description="Three initiatives anchor the near-term translation agenda: assessment infrastructure, sovereign AI systems, and clinical AI that can leave the lab."
/>
<div className="mt-8 grid gap-5 lg:grid-cols-3">
{demonstrations.map((item) => {
const Icon = item.icon;
return (
<article
key={item.title}
className="flex h-full flex-col rounded-lg border border-border bg-card p-5 shadow-sm"
>
<div className="flex items-start justify-between gap-4">
<div>
<p className="text-xs font-medium text-muted-foreground">
{item.eyebrow}
</p>
<h3 className="mt-2 text-xl font-semibold">
{item.title}
</h3>
</div>
<div className={cn('rounded-md p-2', item.tone)}>
<Icon className="size-5" />
</div>
</div>
<p className="mt-4 text-sm leading-relaxed text-muted-foreground">
{item.description}
</p>
<ul className="mt-5 space-y-3 text-sm">
{item.details.map((detail) => (
<li key={detail} className="flex gap-3">
<span className="mt-2 size-1.5 shrink-0 rounded-full bg-secondary" />
<span className="leading-relaxed">{detail}</span>
</li>
))}
</ul>
</article>
);
})}
</div>
</div>
</section>
<section className="section-padding">
<div className="container">
<SectionHeader
eyebrow="Research directions"
title="Significant areas of advancement"
description="The document identifies long-horizon research efforts where the department can build distinctive depth across theory, systems, AI, and human-centered computing."
/>
<div className="mt-8 grid gap-4 sm:grid-cols-2">
{researchDirections.map((direction) => (
<IconCard key={direction.title} item={direction} horizontal />
))}
</div>
</div>
</section>
<section className="border-y bg-muted/30 section-padding">
<div className="container">
<SectionHeader
eyebrow="Education and capacity"
title="Training students while growing the department"
description="The academic plan combines regional capacity building with deliberate faculty growth, improved ratios, and stronger research output."
/>
<div className="mt-8 grid gap-5 lg:grid-cols-2">
{educationTracks.map((track) => {
const Icon = track.icon;
return (
<article
key={track.title}
className="rounded-lg border border-border bg-card p-5 shadow-sm"
>
<div className="flex items-center gap-3">
<div className="rounded-md bg-primary/10 p-2 text-primary">
<Icon className="size-5" />
</div>
<h3 className="text-xl font-semibold">{track.title}</h3>
</div>
<p className="mt-4 text-sm leading-relaxed text-muted-foreground">
{track.description}
</p>
<ul className="mt-5 space-y-3 text-sm">
{track.points.map((point) => (
<li key={point} className="flex gap-3">
<span className="mt-2 size-1.5 shrink-0 rounded-full bg-secondary" />
<span className="leading-relaxed">{point}</span>
</li>
))}
</ul>
</article>
);
})}
</div>
</div>
</section>
</div>
);
}
function SectionHeader({
eyebrow,
title,
description,
}: {
eyebrow: string;
title: string;
description: string;
}) {
return (
<div className="max-w-3xl">
<p className="text-sm font-medium text-secondary">{eyebrow}</p>
<h2 className="mt-3 text-2xl font-semibold leading-tight md:text-3xl">
{title}
</h2>
<p className="mt-3 leading-relaxed text-muted-foreground">
{description}
</p>
</div>
);
}
function IconCard({
item,
horizontal = false,
}: {
item: IconItem;
horizontal?: boolean;
}) {
const Icon = item.icon;
return (
<article
className={cn(
'rounded-lg border border-border bg-card p-5 shadow-sm',
horizontal && 'sm:flex sm:gap-4',
)}
>
<div className={cn('mb-4 rounded-md p-2', item.tone, horizontal && 'sm:mb-0')}>
<Icon className="size-5" />
</div>
<div>
<h3 className="font-semibold">{item.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
{item.description}
</p>
</div>
</article>
);
}

View file

@ -0,0 +1,204 @@
'use client';
import { MailIcon } from 'lucide-react';
import Noise from '@/components/elements/noise';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
const stats = [
{
value: '2024',
label: 'Launched',
},
{
value: '$2.2M',
label: 'Pre-Seed Round',
},
];
const performanceStats = [
{
value: '42%',
description:
'Teams using Lumen report a 42% increase in overall project efficiency and communication clarity within the first month.',
},
{
value: '3200+',
description:
'Projects successfully managed through Lumen across product, marketing, operations, and creative teams worldwide.',
},
{
value: '97%',
description:
'Our customer satisfaction score stands at 97%, reflecting the trust teams place in Lumen for critical workflows.',
},
];
export default function WhyWeBegan() {
return (
<section className="section-padding relative">
<Noise />
<div className="bigger-container">
<div className="flex flex-col-reverse items-center gap-8 md:flex-row lg:gap-12">
<div className="relative h-full w-full md:w-[453px]">
{/* Background gradient circles */}
<div className="bg-chart-2 absolute top-0 left-0 size-60 -translate-x-1/6 rounded-full opacity-30 blur-[80px] will-change-transform md:opacity-70" />
<div className="bg-chart-3 absolute right-0 bottom-0 size-60 -translate-x-1/4 translate-y-1/6 rounded-full opacity-50 blur-[80px] will-change-transform md:opacity-70" />
<div className="relative aspect-square size-full overflow-hidden rounded-xl">
<img
src="https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=800&h=1066&fit=crop"
alt="Team collaboration"
className="size-full rounded-xl object-cover"
/>
</div>
</div>
{/* Content Section */}
<div className="flex-1 space-y-6 lg:space-y-8">
<div className="space-y-8 lg:space-y-12">
<h2 className="text-3xl leading-none font-medium tracking-tight lg:text-4xl">
Why We Began
</h2>
<div>
<p>
We built Lumen after experiencing the headaches of managing
multiple tools and scattered communication. Instead of
switching tabs and losing focus, we imagined one space where
everything connects.
</p>
<br />
<p>
Today, Lumen is used by thousands of teams who value
structure, speed, and a more intuitive way to manage their
projects.
</p>
</div>
</div>
{/* Stats Cards */}
<div className="flex flex-1 flex-wrap gap-4">
{stats.map((stat, index) => (
<Card
key={index}
className="min-w-[200px] flex-1 gap-0 text-center"
>
<CardHeader>
<CardTitle className="text-4xl font-medium">
{stat.value}
</CardTitle>
</CardHeader>
<CardContent className="text-base">{stat.label}</CardContent>
</Card>
))}
</div>
</div>
</div>
{/* Pro Access Section */}
<div className="mt-16 grid items-center gap-8 lg:mt-24 lg:grid-cols-2 lg:gap-12">
{/* Content Section */}
<div className="flex-1 space-y-6 lg:space-y-8">
<div className="space-y-8 lg:space-y-12">
<h2 className="text-3xl leading-none font-medium tracking-tight lg:text-4xl">
Power your progress with Pro Access
</h2>
<div className="">
<p>
At Lumen, our mission is to help modern teams eliminate chaos
and regain clarity by offering beautifully simple task and
project management tools. We believe great work doesn&apos;t
need to be complicated it needs to be intentional.
</p>
<br />
<p>
With years of experience building tools for creatives,
developers, and teams of all sizes, we&apos;ve shaped Lumen to
be the silent partner for you.
</p>
</div>
</div>
{/* CTA Buttons */}
<div className="flex flex-col gap-3 sm:flex-row sm:gap-4">
<Button className="!text-sm shadow-none" size="lg" asChild>
<a href="#">Explore Lumen</a>
</Button>
<Button
variant="outline"
className="border-input !text-sm shadow-none"
size="lg"
asChild
>
<a href="/contact">
Contact Us
<MailIcon className="size-4" />
</a>
</Button>
</div>
</div>
{/* Images Grid */}
<div className="grid gap-4">
{/* First row - 2 images */}
<div className="grid grid-cols-2 gap-4">
<div className="relative h-48 overflow-hidden rounded-lg">
<img
src="https://images.unsplash.com/photo-1521737604893-d14cc237f11d?w=800&h=800&fit=crop"
alt="Team collaboration workspace"
width={300}
height={200}
className="h-full w-full object-cover"
/>
</div>
<div className="relative h-48 overflow-hidden rounded-lg">
<img
src="https://images.unsplash.com/photo-1552664730-d307ca884978?w=800&h=800&fit=crop"
alt="Developer workspace"
width={300}
height={200}
className="h-full w-full object-cover"
/>
</div>
</div>
<div className="relative h-72 overflow-hidden rounded-lg">
{/* Second row - 1 full width image */}
<img
src="https://images.unsplash.com/photo-1587825140708-dfaf72ae4b04?w=1600&h=800&fit=crop"
alt="Modern office workspace"
width={600}
height={256}
className="h-full w-full rounded-lg object-cover"
/>
</div>
</div>
</div>
{/* Performance Statistics Cards */}
<div className="section-padding grid gap-4 !pb-0 md:grid-cols-3">
{performanceStats.map((stat, index) => (
<Card key={index} className="bg-border md:gap-10">
<CardHeader>
<CardTitle className="text-3xl font-semibold">
{stat.value}
</CardTitle>
</CardHeader>
<CardContent>
<CardDescription className="">
{stat.description}
</CardDescription>
</CardContent>
</Card>
))}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,64 @@
import * as React from "react"
import { ChevronDownIcon } from "lucide-react"
import { Accordion as AccordionPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Accordion({
...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
}
function AccordionItem({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
return (
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn("border-b last:border-b-0", className)}
{...props}
/>
)
}
function AccordionTrigger({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
return (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
"flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDownIcon className="pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
)
}
function AccordionContent({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return (
<AccordionPrimitive.Content
data-slot="accordion-content"
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pt-0 pb-4", className)}>{children}</div>
</AccordionPrimitive.Content>
)
}
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View file

@ -0,0 +1,11 @@
"use client"
import { AspectRatio as AspectRatioPrimitive } from "radix-ui"
function AspectRatio({
...props
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
}
export { AspectRatio }

View file

@ -0,0 +1,48 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary:
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
outline:
"border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
link: "text-primary underline-offset-4 [a&]:hover:underline",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Badge({
className,
variant = "default",
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "span"
return (
<Comp
data-slot="badge"
data-variant={variant}
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}
export { Badge, badgeVariants }

View file

@ -0,0 +1,64 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }

View file

@ -0,0 +1,92 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
function Card({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card"
className={cn(
'bg-card text-card-foreground border-input flex flex-col gap-6 rounded-xl border py-6',
className,
)}
{...props}
/>
);
}
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-header"
className={cn(
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
className,
)}
{...props}
/>
);
}
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-title"
className={cn('leading-none font-semibold', className)}
{...props}
/>
);
}
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-description"
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);
}
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-action"
className={cn(
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
className,
)}
{...props}
/>
);
}
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-content"
className={cn('px-6', className)}
{...props}
/>
);
}
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-footer"
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
{...props}
/>
);
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
};

View file

@ -0,0 +1,241 @@
"use client"
import * as React from "react"
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react"
import { ArrowLeft, ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselProps = {
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: "horizontal" | "vertical"
setApi?: (api: CarouselApi) => void
}
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
function useCarousel() {
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />")
}
return context
}
function Carousel({
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
}: React.ComponentProps<"div"> & CarouselProps) {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) return
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext]
)
React.useEffect(() => {
if (!api || !setApi) return
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) return
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api?.off("select", onSelect)
}
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
data-slot="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
)
}
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
const { carouselRef, orientation } = useCarousel()
return (
<div
ref={carouselRef}
className="overflow-hidden"
data-slot="carousel-content"
>
<div
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
)}
{...props}
/>
</div>
)
}
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
const { orientation } = useCarousel()
return (
<div
role="group"
aria-roledescription="slide"
data-slot="carousel-item"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
)}
{...props}
/>
)
}
function CarouselPrevious({
className,
variant = "outline",
size = "icon",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
data-slot="carousel-previous"
variant={variant}
size={size}
className={cn(
"absolute size-8 rounded-full",
orientation === "horizontal"
? "top-1/2 -left-12 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeft />
<span className="sr-only">Previous slide</span>
</Button>
)
}
function CarouselNext({
className,
variant = "outline",
size = "icon",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
data-slot="carousel-next"
variant={variant}
size={size}
className={cn(
"absolute size-8 rounded-full",
orientation === "horizontal"
? "top-1/2 -right-12 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ArrowRight />
<span className="sr-only">Next slide</span>
</Button>
)
}
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
}

View file

@ -0,0 +1,32 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { CheckIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="flex items-center justify-center text-current transition-none"
>
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox }

View file

@ -0,0 +1,33 @@
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
function Collapsible({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
}
function CollapsibleTrigger({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
return (
<CollapsiblePrimitive.CollapsibleTrigger
data-slot="collapsible-trigger"
{...props}
/>
)
}
function CollapsibleContent({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
return (
<CollapsiblePrimitive.CollapsibleContent
data-slot="collapsible-content"
{...props}
/>
)
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent }

248
src/components/ui/field.tsx Normal file
View file

@ -0,0 +1,248 @@
"use client"
import { useMemo } from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
return (
<fieldset
data-slot="field-set"
className={cn(
"flex flex-col gap-6",
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
className
)}
{...props}
/>
)
}
function FieldLegend({
className,
variant = "legend",
...props
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
return (
<legend
data-slot="field-legend"
data-variant={variant}
className={cn(
"mb-3 font-medium",
"data-[variant=legend]:text-base",
"data-[variant=label]:text-sm",
className
)}
{...props}
/>
)
}
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-group"
className={cn(
"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
className
)}
{...props}
/>
)
}
const fieldVariants = cva(
"group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
{
variants: {
orientation: {
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
horizontal: [
"flex-row items-center",
"[&>[data-slot=field-label]]:flex-auto",
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
],
responsive: [
"flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
],
},
},
defaultVariants: {
orientation: "vertical",
},
}
)
function Field({
className,
orientation = "vertical",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
return (
<div
role="group"
data-slot="field"
data-orientation={orientation}
className={cn(fieldVariants({ orientation }), className)}
{...props}
/>
)
}
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-content"
className={cn(
"group/field-content flex flex-1 flex-col gap-1.5 leading-snug",
className
)}
{...props}
/>
)
}
function FieldLabel({
className,
...props
}: React.ComponentProps<typeof Label>) {
return (
<Label
data-slot="field-label"
className={cn(
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
"has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10",
className
)}
{...props}
/>
)
}
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-label"
className={cn(
"flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
className
)}
{...props}
/>
)
}
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="field-description"
className={cn(
"text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className
)}
{...props}
/>
)
}
function FieldSeparator({
children,
className,
...props
}: React.ComponentProps<"div"> & {
children?: React.ReactNode
}) {
return (
<div
data-slot="field-separator"
data-content={!!children}
className={cn(
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
className
)}
{...props}
>
<Separator className="absolute inset-0 top-1/2" />
{children && (
<span
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
data-slot="field-separator-content"
>
{children}
</span>
)}
</div>
)
}
function FieldError({
className,
children,
errors,
...props
}: React.ComponentProps<"div"> & {
errors?: Array<{ message?: string } | undefined>
}) {
const content = useMemo(() => {
if (children) {
return children
}
if (!errors?.length) {
return null
}
const uniqueErrors = [
...new Map(errors.map((error) => [error?.message, error])).values(),
]
if (uniqueErrors?.length == 1) {
return uniqueErrors[0]?.message
}
return (
<ul className="ml-4 flex list-disc flex-col gap-1">
{uniqueErrors.map(
(error, index) =>
error?.message && <li key={index}>{error.message}</li>
)}
</ul>
)
}, [children, errors])
if (!content) {
return null
}
return (
<div
role="alert"
data-slot="field-error"
className={cn("text-sm font-normal text-destructive", className)}
{...props}
>
{content}
</div>
)
}
export {
Field,
FieldLabel,
FieldDescription,
FieldError,
FieldGroup,
FieldLegend,
FieldSeparator,
FieldSet,
FieldContent,
FieldTitle,
}

View file

@ -0,0 +1,168 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="input-group"
role="group"
className={cn(
"group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30",
"h-9 min-w-0 has-[>textarea]:h-auto",
// Variants based on alignment.
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
"has-[>[data-align=inline-end]]:[&>input]:pr-2",
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
// Focus state.
"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
// Error state.
"has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
className
)}
{...props}
/>
)
}
const inputGroupAddonVariants = cva(
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
{
variants: {
align: {
"inline-start":
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
"inline-end":
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
"block-start":
"order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3",
"block-end":
"order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3",
},
},
defaultVariants: {
align: "inline-start",
},
}
)
function InputGroupAddon({
className,
align = "inline-start",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
return (
<div
role="group"
data-slot="input-group-addon"
data-align={align}
className={cn(inputGroupAddonVariants({ align }), className)}
onClick={(e) => {
if ((e.target as HTMLElement).closest("button")) {
return
}
e.currentTarget.parentElement?.querySelector("input")?.focus()
}}
{...props}
/>
)
}
const inputGroupButtonVariants = cva(
"flex items-center gap-2 text-sm shadow-none",
{
variants: {
size: {
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
"icon-xs":
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
},
},
defaultVariants: {
size: "xs",
},
}
)
function InputGroupButton({
className,
type = "button",
variant = "ghost",
size = "xs",
...props
}: Omit<React.ComponentProps<typeof Button>, "size"> &
VariantProps<typeof inputGroupButtonVariants>) {
return (
<Button
type={type}
data-size={size}
variant={variant}
className={cn(inputGroupButtonVariants({ size }), className)}
{...props}
/>
)
}
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
className={cn(
"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function InputGroupInput({
className,
...props
}: React.ComponentProps<"input">) {
return (
<Input
data-slot="input-group-control"
className={cn(
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
className
)}
{...props}
/>
)
}
function InputGroupTextarea({
className,
...props
}: React.ComponentProps<"textarea">) {
return (
<Textarea
data-slot="input-group-control"
className={cn(
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
className
)}
{...props}
/>
)
}
export {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupInput,
InputGroupTextarea,
}

View file

@ -0,0 +1,21 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
<input
type={type}
data-slot="input"
className={cn(
"h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
className
)}
{...props}
/>
)
}
export { Input }

View file

@ -0,0 +1,24 @@
"use client"
import * as React from "react"
import { Label as LabelPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Label({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className
)}
{...props}
/>
)
}
export { Label }

View file

@ -0,0 +1,168 @@
import * as React from "react"
import { cva } from "class-variance-authority"
import { ChevronDownIcon } from "lucide-react"
import { NavigationMenu as NavigationMenuPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function NavigationMenu({
className,
children,
viewport = true,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
viewport?: boolean
}) {
return (
<NavigationMenuPrimitive.Root
data-slot="navigation-menu"
data-viewport={viewport}
className={cn(
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
{viewport && <NavigationMenuViewport />}
</NavigationMenuPrimitive.Root>
)
}
function NavigationMenuList({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return (
<NavigationMenuPrimitive.List
data-slot="navigation-menu-list"
className={cn(
"group flex flex-1 list-none items-center justify-center gap-1",
className
)}
{...props}
/>
)
}
function NavigationMenuItem({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
return (
<NavigationMenuPrimitive.Item
data-slot="navigation-menu-item"
className={cn("relative", className)}
{...props}
/>
)
}
const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-[color,box-shadow] outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=open]:bg-accent/50 data-[state=open]:text-accent-foreground data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent"
)
function NavigationMenuTrigger({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger"
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDownIcon
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
)
}
function NavigationMenuContent({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
return (
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
"top-0 left-0 w-full p-2 pr-2.5 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out md:absolute md:w-auto",
"group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95",
className
)}
{...props}
/>
)
}
function NavigationMenuViewport({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
return (
<div
className={cn(
"absolute top-full left-0 isolate z-50 flex justify-center"
)}
>
<NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport"
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
{...props}
/>
</div>
)
}
function NavigationMenuLink({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
return (
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground data-[active=true]:hover:bg-accent data-[active=true]:focus:bg-accent [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
className
)}
{...props}
/>
)
}
function NavigationMenuIndicator({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
return (
<NavigationMenuPrimitive.Indicator
data-slot="navigation-menu-indicator"
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:animate-in data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
)
}
export {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
navigationMenuTriggerStyle,
}

View file

@ -0,0 +1,130 @@
import * as React from 'react';
import {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button, buttonVariants } from '@/components/ui/button';
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
return (
<nav
role="navigation"
aria-label="pagination"
data-slot="pagination"
className={cn('mx-auto flex w-full justify-center', className)}
{...props}
/>
);
}
function PaginationContent({
className,
...props
}: React.ComponentProps<'ul'>) {
return (
<ul
data-slot="pagination-content"
className={cn('flex flex-row items-center gap-1', className)}
{...props}
/>
);
}
function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
return <li data-slot="pagination-item" {...props} />;
}
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<React.ComponentProps<typeof Button>, 'size'> &
React.ComponentProps<'a'>;
function PaginationLink({
className,
isActive,
size = 'icon',
...props
}: PaginationLinkProps) {
return (
<a
aria-current={isActive ? 'page' : undefined}
data-slot="pagination-link"
data-active={isActive}
className={cn(
buttonVariants({
variant: isActive ? 'outline' : 'ghost',
size,
}),
isActive && 'shadow-none',
isActive &&
"before:from-chart-1 before:via-chart-2 before:to-chart-3 relative border-0 before:absolute before:inset-[-1px] before:z-[-1] before:rounded-md before:bg-gradient-to-tr before:content-['']",
className,
)}
{...props}
/>
);
}
function PaginationPrevious({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) {
return (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
{...props}
>
<ChevronLeftIcon />
<span className="hidden sm:block">Previous</span>
</PaginationLink>
);
}
function PaginationNext({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) {
return (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
{...props}
>
<span className="hidden sm:block">Next</span>
<ChevronRightIcon />
</PaginationLink>
);
}
function PaginationEllipsis({
className,
...props
}: React.ComponentProps<'span'>) {
return (
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn('flex size-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontalIcon className="size-4" />
<span className="sr-only">More pages</span>
</span>
);
}
export {
Pagination,
PaginationContent,
PaginationLink,
PaginationItem,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
};

View file

@ -0,0 +1,61 @@
'use client';
import * as React from 'react';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from '@/lib/utils';
function ScrollArea({
className,
children,
orientation = 'vertical',
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root> & {
orientation?: 'vertical' | 'horizontal';
}) {
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn('relative', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar orientation={orientation} />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
);
}
function ScrollBar({
className,
orientation = 'vertical',
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
'flex touch-none p-px transition-colors select-none',
orientation === 'vertical' &&
'h-full w-2.5 border-l border-l-transparent',
orientation === 'horizontal' &&
'h-2.5 flex-col border-t border-t-transparent',
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
);
}
export { ScrollArea, ScrollBar };

View file

@ -0,0 +1,185 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "popper",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}

View file

@ -0,0 +1,26 @@
import * as React from "react"
import { Separator as SeparatorPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className
)}
{...props}
/>
)
}
export { Separator }

143
src/components/ui/sheet.tsx Normal file
View file

@ -0,0 +1,143 @@
"use client"
import * as React from "react"
import { XIcon } from "lucide-react"
import { Dialog as SheetPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />
}
function SheetTrigger({
...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
}
function SheetClose({
...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
}
function SheetPortal({
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
}
function SheetOverlay({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
return (
<SheetPrimitive.Overlay
data-slot="sheet-overlay"
className={cn(
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
className
)}
{...props}
/>
)
}
function SheetContent({
className,
children,
side = "right",
showCloseButton = true,
...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: "top" | "right" | "bottom" | "left"
showCloseButton?: boolean
}) {
return (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
data-slot="sheet-content"
className={cn(
"fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
side === "right" &&
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
side === "left" &&
"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
side === "top" &&
"inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
side === "bottom" &&
"inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
className
)}
{...props}
>
{children}
{showCloseButton && (
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
)}
</SheetPrimitive.Content>
</SheetPortal>
)
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-header"
className={cn("flex flex-col gap-1.5 p-4", className)}
{...props}
/>
)
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
}
function SheetTitle({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
return (
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn("font-semibold text-foreground", className)}
{...props}
/>
)
}
function SheetDescription({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
return (
<SheetPrimitive.Description
data-slot="sheet-description"
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
}
export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}

View file

@ -0,0 +1,31 @@
'use client';
import * as React from 'react';
import * as SwitchPrimitive from '@radix-ui/react-switch';
import { cn } from '@/lib/utils';
function Switch({
className,
...props
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
return (
<SwitchPrimitive.Root
data-slot="switch"
className={cn(
'peer data-[state=checked]:bg-secondary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.7rem] w-12 shrink-0 cursor-pointer items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className={cn(
'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-secondary-foreground pointer-events-none block size-5 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[115%] data-[state=unchecked]:translate-x-[15%]',
)}
/>
</SwitchPrimitive.Root>
);
}
export { Switch };

119
src/components/ui/tabs.tsx Normal file
View file

@ -0,0 +1,119 @@
'use client';
import * as React from 'react';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import { cn } from '@/lib/utils';
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn('flex flex-col gap-2', className)}
{...props}
/>
);
}
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
const [activeRect, setActiveRect] = React.useState<{
width: number;
left: number;
} | null>(null);
const listRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
const updateActiveRect = () => {
if (!listRef.current) return;
const activeTab = listRef.current.querySelector('[data-state="active"]');
if (activeTab) {
const listRect = listRef.current.getBoundingClientRect();
const activeTabRect = activeTab.getBoundingClientRect();
setActiveRect({
width: activeTabRect.width,
left: activeTabRect.left - listRect.left,
});
}
};
// Update on mount and when tabs change
updateActiveRect();
// Create a MutationObserver to watch for state changes
const observer = new MutationObserver(updateActiveRect);
if (listRef.current) {
observer.observe(listRef.current, {
attributes: true,
subtree: true,
attributeFilter: ['data-state'],
});
}
return () => observer.disconnect();
}, []);
return (
<TabsPrimitive.List
ref={listRef}
data-slot="tabs-list"
className={cn(
'bg-muted/50 text-muted-foreground relative inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]',
className,
)}
{...props}
>
{/* Sliding gradient background */}
{activeRect && (
<div
className="from-chart-1 via-chart-2 to-chart-3 absolute z-0 h-[calc(100%-6px)] rounded-sm bg-gradient-to-tr p-[1px] transition-all duration-200 ease-out"
style={{
width: activeRect.width,
left: activeRect.left,
}}
>
<div className="bg-background h-full w-full rounded-sm" />
</div>
)}
{props.children}
</TabsPrimitive.List>
);
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-muted-foreground relative z-10 inline-flex h-[calc(100%-6px)] flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-md border-0 px-4 py-1 text-sm font-medium whitespace-nowrap transition-[color] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-transparent [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn('flex-1 outline-none', className)}
{...props}
/>
);
}
export { Tabs, TabsList, TabsTrigger, TabsContent };

View file

@ -0,0 +1,18 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
<textarea
data-slot="textarea"
className={cn(
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
className
)}
{...props}
/>
)
}
export { Textarea }

View file

@ -0,0 +1,47 @@
"use client"
import * as React from "react"
import * as TogglePrimitive from "@radix-ui/react-toggle"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const toggleVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
{
variants: {
variant: {
default: "bg-transparent",
outline:
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-9 px-2 min-w-9",
sm: "h-8 px-1.5 min-w-8",
lg: "h-10 px-2.5 min-w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Toggle({
className,
variant,
size,
...props
}: React.ComponentProps<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>) {
return (
<TogglePrimitive.Root
data-slot="toggle"
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Toggle, toggleVariants }

56
src/consts.ts Normal file
View file

@ -0,0 +1,56 @@
export const SITE_TITLE = 'CSE @ IIT Gandhinagar';
export const SITE_DESCRIPTION =
'Department of Computer Science and Engineering at the Indian Institute of Technology Gandhinagar';
export const SITE_METADATA = {
title: {
default: SITE_TITLE,
template: '%s | CSE @ IITGN',
},
description: SITE_DESCRIPTION,
keywords: [
'IIT Gandhinagar',
'IITGN',
'Computer Science',
'CSE',
'Engineering',
'Research',
'Education',
],
authors: [{ name: 'IIT Gandhinagar' }],
creator: 'IIT Gandhinagar',
publisher: 'IIT Gandhinagar',
robots: {
index: true,
follow: true,
},
icons: {
icon: [
{ url: '/favicon/favicon.ico', sizes: '48x48' },
{ url: '/favicon/favicon.svg', type: 'image/svg+xml' },
{ url: '/favicon/favicon-96x96.png', sizes: '96x96', type: 'image/png' },
],
apple: [{ url: '/favicon/apple-touch-icon.png', sizes: '180x180' }],
shortcut: [{ url: '/favicon/favicon.ico' }],
},
openGraph: {
title: SITE_TITLE,
description: SITE_DESCRIPTION,
siteName: 'CSE @ IITGN',
images: [
{
url: '/images/og-image.jpg',
width: 1200,
height: 630,
alt: 'CSE @ IIT Gandhinagar',
},
],
},
twitter: {
card: 'summary_large_image',
title: SITE_TITLE,
description: SITE_DESCRIPTION,
images: ['/images/og-image.jpg'],
creator: '@iaboratories',
},
};

29
src/content.config.ts Normal file
View file

@ -0,0 +1,29 @@
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
import { defineCollection } from 'astro:content';
const blog = defineCollection({
// Load Markdown and MDX files in the `src/content/blog/` directory.
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
date: z
.string()
.or(z.date())
.transform((val) => new Date(val)),
author: z.object({
name: z.string(),
image: z.string(),
facebookUrl: z.string().optional(),
twitterUrl: z.string().optional(),
linkedinUrl: z.string().optional(),
}),
tags: z.array(z.string()).optional(),
coverImage: z.string().optional(),
}),
});
export const collections = { blog };

View file

View file

View file

@ -0,0 +1,73 @@
---
title: 'Building a Rule-Aware Snake Bot for Hackrush 2026'
description: 'Aditya reflects on Ferrate, a Hackrush 2026 Snake agent built around fast pathfinding, safety checks, energy-aware decisions, and robust fallbacks.'
date: '2026-05-01'
author:
name: 'Aditya'
image: '/favicon/web-app-manifest-192x192.png'
tags: ['Hackrush 2026', 'hackrush2026', 'Game AI']
coverImage: '/images/blog/hackrush-2026/hackrush-2026-cover.webp'
---
Hackrush 2026 gave us a compact but demanding Game AI challenge: build a bot that could play Beat My Bot v2, read the game state from JSON, choose a move within a 500 ms turn limit, and survive longer than the opponent while maximizing length and score.
My entry, Ferrate, was a Snake agent shaped around three goals:
- Reach valuable apples quickly.
- Avoid moves that lead into dead ends, walls, or bad head-to-head trades.
- Stay fast enough to answer reliably within the tournament budget.
The main improvement over a naive pathfinder was that the bot combined search, safety checks, energy-aware behavior, and emergency fallbacks into one layered decision pipeline.
## Understanding the Game
The board is grid-based and fully observable. Apples restore energy and can also change length or apply status effects. Normal apples eventually decay into poison. Speed can make a turn cover two cells. Sleep can freeze the opponent. Shed can create permanent walls, which is sometimes useful but also expensive.
These rules make the game more than a shortest-path problem. A reachable apple may still be strategically bad if the snake gets trapped, arrives too late, or loses a head-to-head trade.
## Layered Decision-Making
Ferrate tracks apples by remembering when each apple position first appeared. That lets the bot estimate whether a normal apple will decay into poison before the snake arrives. A long path to a normal apple can turn a good-looking target into a trap.
To stay within the time limit, the bot does not run full BFS on every apple. It first ranks apples using a cheap heuristic: Manhattan distance, apple type value, and an age penalty. Only the top candidates are checked with BFS. This two-stage filter was the biggest runtime optimization, especially on larger boards.
In code, `get_apple_priority_score()` scores each apple using the apple type, distance, and age. God, sleep, speed, normal, and poison apples receive different base values. `collect_apple_evaluations()` keeps only the strongest heuristic candidates for BFS, and BFS itself is depth-limited. If the bot enters poison-search fallback, it expands the candidate list only after the first choices fail.
## Safety Before Greed
Before committing to a move, Ferrate checks for wall and obstacle collisions, two-step movement during speed turns, flood-fill escape space, dangerous head-to-head trades, and poison contact when energy allows avoidance.
This turns the bot from a food chaser into a survival system. `is_safe_move()` requires enough reachable flood-fill space relative to the snake length. `flood_fill_count()` stops early after a fixed cap, keeping the calculation cheap. Speed turns validate both steps of the move, and `check_head_on_collision()` rejects moves where an equal or larger opponent could contest the same destination.
Tail cells are handled carefully in obstacle generation, because the bot can often move through space that is expected to clear. This detail matters in Snake, where conservative obstacle handling can incorrectly rule out safe paths.
## Energy-Aware Behavior
The decision logic changes when energy gets low. At moderately low energy, the bot relaxes some penalties and becomes more willing to take risky food. At critical energy, it prioritizes the nearest reachable apple and may even accept poison as a last resort.
This was a direct response to the starvation rule. In a survival game, food urgency sometimes matters more than ideal path quality.
## Fallbacks and Shedding
If no apple path looks good, the bot switches to tail-chasing. This keeps it moving through open space and reduces the chance of self-trapping. If tail-chasing also fails, Ferrate chooses any legal move, preferring the one that preserves more open area and stays closer to the center of the board.
Shed is treated as a situational defensive tool rather than a default action. The bot allows shedding only when it has enough energy and length, speed is active with sufficient energy, and the opponent is alive and nearby.
## What Worked
The hardest part was balancing strength and speed. Full search on every apple would be too slow, while aggressive pruning could miss important targets. The two-stage apple filter gave a practical middle ground: a cheap first pass followed by exact BFS only where it mattered.
The final strategy was simple at the top level:
```python
if energy <= critical_threshold:
force_nearest_food()
elif apple_path_is_safe:
take_best_apple()
elif tail_path_exists:
chase_tail()
else:
choose_any_safe_move()
```
The most important outcome was that Ferrate learned the difference between a reachable apple and a survivable apple. That distinction, backed by explicit numeric heuristics, made the bot much more robust under Hackrush conditions.

View file

@ -0,0 +1,84 @@
---
title: 'Bazaar@IITGN: A Campus Marketplace for Hackrush 2026'
description: 'Arpan writes about building Bazaar@IITGN, a verified peer-to-peer marketplace with campus trust, negotiation, reputation, and real-time chat.'
date: '2026-05-02'
author:
name: 'Arpan'
image: '/favicon/web-app-manifest-192x192.png'
tags: ['Hackrush 2026', 'hackrush2026', 'Web Development']
coverImage: '/images/blog/hackrush-2026/hackrush-2026-cover.webp'
---
Bazaar@IITGN is a peer-to-peer campus marketplace built exclusively for the IIT Gandhinagar community. The motivation came from a familiar student problem: someone wants to sell an old cycle before the semester ends, or find an affordable textbook before exams, but there is no reliable, trusted platform designed for the campus.
Generic marketplaces and informal WhatsApp groups do not provide proximity, institutional trust, or community context. Bazaar@IITGN addresses this by creating a closed, verified marketplace where only `@iitgn.ac.in` accounts can participate. Students can list items for sale, rent, or trade; negotiate through real-time chat; and build a visible reputation over time.
## Design Thinking
The core question was: what makes a campus marketplace different from a general marketplace?
That led to three pillars:
- Trust is institutional. Access is gated through Google OAuth with a hard IITGN domain restriction.
- Reputation must be earned. A karma system rewards good traders and penalizes bad behavior.
- Negotiation is normal. Students rarely treat prices as fixed, so offers and counteroffers needed first-class support.
Once these ideas were clear, the technical architecture followed naturally.
## Architecture
The project is a client-server monorepo with two independently deployable units:
- A React 19 single-page frontend built with Vite, styled using Tailwind CSS, and state-managed through Zustand.
- A Node.js and Express 5 backend with Socket.IO for real-time features and MongoDB Atlas for storage.
The frontend communicates through Axios for REST operations and a singleton `socket.io-client` instance for live events. The backend exposes multiple Express routers and a WebSocket handler, all protected by JWT middleware.
The system integrates Google OAuth for authentication, Cloudinary for image upload and CDN delivery, GPT-4o-mini for report moderation and listing-description refinement, and a QR service for UPI payment requests.
## The Karma Engine
The trust backbone of the platform is a composite karma score:
```text
karma = (successfulTrades * 15)
+ ((averageRating - 3) * 10)
+ (accountAgeDays * 0.1)
+ (totalListings * 2)
- (flagsReceived * 20)
```
The 3-star baseline means average behavior contributes nothing; a user has to trade well to benefit. Flags carry a strong penalty, and account age grows slowly so long-term community participation is rewarded without giving newcomers an easy shortcut.
Karma maps to four visible tiers: New Member, Trusted Trader, Community Pillar, and Campus Legend. These tiers appear on profiles and listings, making reputation part of the browsing experience.
## Negotiation as a State Machine
Negotiation is modeled with a formal offer state machine:
```text
pending -> countered -> accepted -> completed
| |
v v
rejected cancelled
```
When an offer is accepted, the listing is marked reserved, competing offers are auto-rejected, and trade counts increment for both parties. Completion uses a dual-handshake protocol: both buyer and seller must independently confirm the physical handover before the listing moves to sold.
At that point, the listing description, images, and FAQs are scrubbed from the database. This was a deliberate privacy-first decision.
## Real-Time UX Details
One subtle feature is notification deduplication. If both parties are already active in the same chat, the server avoids creating extra notifications. It inspects the Socket.IO room adapter to determine whether the recipient is present in the relevant offer room. If the recipient is active, the message appears live in the chat instead of producing redundant notification noise.
The platform also works as a PWA. Cache strategies are split by data type: dynamic API data uses a network-first strategy, while images and static assets use cache-first behavior. Authentication distinguishes between invalid tokens and offline failures, so a user is not logged out just because the network is bad.
## Challenges and Result
React Strict Mode caused duplicate WebSocket connections during development. This was fixed with a connection guard. Another challenge was keeping listing scores consistent when a seller's karma changed after reviews or report resolution; a utility recalculates active seller listings so search rankings remain correct.
Offline behavior also needed careful scoping. Full message history was too large to persist, but the active chat and offer list could be stored through Zustand's persistence layer so users still see ongoing negotiations.
The final result is a deployable campus marketplace with IITGN-only authentication, a karma-based reputation system, real-time negotiation chat, privacy-aware trade completion, AI-assisted listing and moderation workflows, UPI payment requests, and PWA support.
Deployed link: [https://iitgn-bazzar-client.vercel.app](https://iitgn-bazzar-client.vercel.app)

View file

@ -0,0 +1,51 @@
---
title: 'Few-Shot Bone Marrow Cell Classification at Hackrush 2026'
description: 'Nihar reflects on two representation-learning approaches for classifying bone marrow cell types from limited labeled medical images.'
date: '2026-05-03'
author:
name: 'Nihar'
image: '/favicon/web-app-manifest-192x192.png'
tags: ['Hackrush 2026', 'hackrush2026', 'Machine Learning']
coverImage: '/images/blog/hackrush-2026/hackrush-2026-cover.webp'
---
For Hackrush 2026, I worked on few-shot bone marrow cell classification. The task was to classify bone marrow microscopy images under severely limited labeled data conditions.
This is difficult because bone marrow cell categories can differ in very subtle morphological ways. The dataset also brings the usual problems of real medical imaging: class imbalance, limited examples, high intra-class variation, visually similar classes, and staining or illumination differences.
Standard supervised CNN approaches tend to overfit in this setting. My focus was therefore on learning transferable and discriminative feature representations rather than relying only on a conventional classifier.
## Pipeline 1: Vision-Language Representation Learning
The first approach used pretrained vision-language encoders to extract rich semantic embeddings. Instead of training from scratch, the pipeline reused a pretrained vision encoder, extracted feature embeddings, adapted them for few-shot use, and performed prototype-based inference with cosine similarity classification.
The idea was that pretrained embeddings can capture useful structure and texture information even when labeled medical samples are limited.
The main challenge was the domain gap between natural-image pretraining and microscopy images. I addressed this with domain-specific augmentations, embedding normalization, and feature calibration. These steps helped bridge the shift in appearance.
Class imbalance required balanced episodic sampling, weighted objectives, and prototype regularization. Morphological similarity between classes was handled through similarity-margin objectives, hard negative sampling, and embedding consistency techniques.
The outcome was reduced overfitting, better feature separability, and more stable few-shot inference.
## Pipeline 2: Metric Learning and Adaptive Few-Shot Classification
The second approach framed the problem as metric learning. Instead of predicting labels directly, the model learned an embedding space where similar cell types cluster together and different classes are pushed apart.
This formulation is naturally suited to few-shot classification. The architecture used a deep feature encoder, a metric embedding head, prototype-based inference, distance-aware classification, and episodic training.
The main risk was feature collapse, where embeddings stop separating the classes meaningfully. Strong augmentations, margin-based losses, and embedding normalization helped prevent this. Training instability was handled with learning-rate scheduling, gradient clipping, and episode balancing. Overfitting was reduced with heavy augmentation, dropout regularization, and validation-driven checkpointing.
The result was better generalization to sparse classes, more consistent embeddings, and improved robustness to imbalance.
## What I Learned
The biggest takeaway was that in low-data medical imaging, robust representation learning often matters more than adding increasingly complex classification heads.
The challenge reinforced several ideas:
- Few-shot learning needs careful validation, not just a clever architecture.
- Balanced sampling can matter as much as the model choice.
- Domain-aware preprocessing is essential in medical imaging.
- Generalization should drive the design from the beginning.
Hackrush was a useful opportunity to apply modern machine-learning techniques to a clinically relevant problem and to experiment with scalable few-shot strategies where labeled data is scarce and expensive to obtain.

View file

@ -0,0 +1,56 @@
---
title: 'Hardware-Friendly Activation Functions at Hackrush 2026'
description: 'Srihith explains how four ML activation functions were implemented in fixed-point hardware with one-cycle latency and zero DSP usage.'
date: '2026-05-04'
author:
name: 'Srihith'
image: '/favicon/web-app-manifest-192x192.png'
tags: ['Hackrush 2026', 'hackrush2026', 'Computer Architecture']
coverImage: '/images/blog/hackrush-2026/hackrush-2026-cover.webp'
---
For Hackrush 2026, I worked on a computer architecture problem that required implementing four machine-learning activation functions in hardware: ReLU, Leaky ReLU, sigmoid approximation, and tanh approximation.
The main constraints were low latency, fixed-point representation, and efficient resource usage. Synthesis was performed on Basys3 using Vivado, and Q16 fixed-point representation was used throughout.
## ReLU and Leaky ReLU
ReLU was the simplest function: output the input when it is positive and output 0 otherwise. This can be implemented directly with a conditional operator, giving a latency of one cycle.
Leaky ReLU is similar, except negative inputs are scaled by a constant `alpha`. The problem allowed contestants to choose `alpha`. To avoid multipliers, I used `alpha = 0.125`, which can be implemented as a right shift by 3 bits. This also achieved one-cycle latency.
## Sigmoid Approximation
For sigmoid, I used a five-segment piecewise linear approximation:
```text
x < -3 -> 0
-3 <= x < -1 -> 0.125x + 0.375
-1 <= x < 1 -> 0.25x + 0.5
1 <= x < 3 -> 0.125x + 0.625
x >= 3 -> 1
```
All multiplications were implemented using shifts. This eliminated multiplier usage while keeping the approximation simple enough for one-cycle latency.
## Tanh Approximation
The tanh function needed a finer approximation, so I used seven piecewise segments:
```text
x < -2 -> -1
-2 <= x < -1 -> 0.25x - 0.5
-1 <= x < -0.5 -> 0.5x - 0.25
-0.5 <= x < 0.5 -> x
0.5 <= x < 1 -> 0.5x + 0.25
1 <= x < 2 -> 0.25x + 0.5
x >= 2 -> 1
```
Again, all scaling was shift-based. The goal was to improve accuracy over sigmoid while keeping the hardware implementation compact.
## Result
All four activation functions were implemented with one-cycle latency. The bit-shift-based scaling removed the need for multipliers, reducing DSP usage to 0.
The main lesson was that hardware implementation changes how we think about familiar ML functions. In software, multiplying by a small constant is ordinary. In hardware, choosing constants that map cleanly to shifts can make the design simpler, faster, and more resource-efficient.

View file

@ -0,0 +1,76 @@
---
title: 'Reconstructing Shuffled Mystery Pages at Hackrush 2026'
description: 'Surriya describes a Hackrush 2026 solution that models page-order reconstruction as a TSP-like problem over semantic and lexical continuity signals.'
date: '2026-05-05'
author:
name: 'Surriya'
image: '/favicon/web-app-manifest-192x192.png'
tags: ['Hackrush 2026', 'hackrush2026', 'Machine Learning']
coverImage: '/images/blog/hackrush-2026/hackrush-2026-cover.webp'
---
One of the Hackrush 2026 problems was a narrative reconstruction challenge inspired by *Cain's Jawbone*. We were given two mystery books as CSV files of shuffled text fragments. The goal was to reconstruct the original page order for each book.
The submission format required, for each book, a CSV mapping reconstructed `original_page` positions to the `shuffled_page` identifiers from the input. The evaluation used a normalized Kendall-Tau score: a random permutation scores near 0.5, while a perfect reconstruction scores 1.0.
## High-Level Strategy
I framed page ordering as a minimum-cost Hamiltonian path problem, similar to a directed TSP. Each page is a node. A directed edge from page `i` to page `j` receives a weight that estimates how naturally page `i` precedes page `j`.
Solving the full book as one massive TSP instance would be too expensive, so the pipeline first divides pages into chapter buckets using structural cues and embedding similarity. Each bucket is solved independently and the results are concatenated.
The pipeline looked like this:
```text
Load pages
-> normalize text and extract head/tail windows
-> compute dense embeddings
-> detect chapter-heading anchors
-> assign pages to chapter buckets
-> build directed edge weights inside each bucket
-> solve a Hamiltonian path
-> concatenate bucket solutions
-> write submission CSV
```
## Text, Embeddings, and Anchors
Each page was normalized with Unicode fixes, whitespace cleanup, and punctuation cleanup. I extracted head and tail windows of about 120 words. The tail of one page and the head of another are the most important regions for estimating adjacency.
For dense embeddings, I used `BAAI/bge-large-en-v1.5` through `sentence-transformers`. The pipeline computed embeddings for full pages, heads, and tails. Full-page embeddings were used for chapter bucketing, while tail and head embeddings were used for directed transition scoring.
Chapter headings such as `Chapter IV` or `CHAPTER XII` were detected using a regex that supported both Roman numerals and digits. Detected anchors were treated as fixed starting pages for their chapter buckets.
## Bucket Assignment
The default assignment method was nearest-anchor matching: each page was assigned to the chapter whose anchor embedding was most similar. A balance constraint prevented any single chapter from becoming too large.
I also supported spectral methods. Spectral seriation builds a graph from embedding similarities and uses a one-dimensional ordering signal to divide pages into buckets. A dynamic-programming variant added penalties for implausible chapter transitions.
After assignment, oversized buckets were trimmed by moving outlying pages to adjacent chapters. This helped keep bucket sizes practical for the route solver.
## Edge Scoring
Within each bucket, I built an `N x N` directed edge-weight matrix. Several signals contributed to each edge:
- Tail-to-head embedding cosine similarity.
- Exact boundary word overlap between the suffix of page `i` and prefix of page `j`.
- Character or named-entity flow, using Jaccard similarity over recurring proper nouns.
- Optional causal language-model boundary scores.
- Optional cross-encoder reranker scores.
The expensive LM and reranker features were computed only for the top candidates from cheaper signals, usually the top 30 per page. Scores were cached in SQLite so repeated experiments did not recompute the same pairs.
## Solving and Ensembling
The main solver used OR-Tools routing to find a minimum-cost directed Hamiltonian path for each bucket, with a configurable time limit. Beam search and greedy search were available as faster fallbacks.
After route solving, an optional sliding-window refinement pass checked consecutive windows and applied local reorderings when they improved the score.
Multiple independent runs could then be ensembled. I supported Borda averaging and an approximate Kemeny median using pairwise preferences, greedy insertion, and adjacent-swap refinement.
## Takeaway
The key insight was that page ordering is not purely local. Chapter boundaries provide global scaffolding, while semantic similarity, exact boundary overlap, and character flow handle fine-grained ordering inside each chapter.
The OR-Tools solver helped avoid many greedy local mistakes. Hackrush made this problem especially interesting because it sat between NLP, optimization, and software engineering: the final score depended not just on a good model, but on a reliable end-to-end pipeline.

View file

@ -0,0 +1,98 @@
---
title: 'Building an AI Image Editor at Hackrush 2026'
description: 'Yuvraj writes about a modular AI image editor that combines natural-language editing, masks, sketches, voice input, and multiple generative AI workflows.'
date: '2026-05-06'
author:
name: 'Yuvraj'
image: '/favicon/web-app-manifest-192x192.png'
tags: ['Hackrush 2026', 'hackrush2026', 'Deep Learning']
coverImage: '/images/blog/hackrush-2026/hackrush-2026-cover.webp'
---
For Hackrush 2026, I built an AI Image Editor: a web application that lets users edit images using natural-language instructions, masks, sketches, and voice input.
The motivation was to make advanced image editing accessible to ordinary users without requiring professional editing skills or complex software. A user should be able to upload an image, describe the desired modification, and receive an edited result automatically.
The platform was designed to support tasks such as object editing, object removal, object insertion, and sketch-based generation.
## Problem Statement
Traditional image-editing software often requires technical editing knowledge, manual masking, layer management, complex UI interactions, and significant time even for small edits.
The goal was to support simple instructions such as:
- Remove the tree.
- Add a dog near the chair.
- Change the shirt color to black.
- Turn this sketch into a real object.
The challenge was to make these operations accurate, automated, and visually realistic while keeping the interface simple.
## Approach
The design centered on three ideas: simplicity for the user, modular AI workflow design, and automatic workflow selection.
Instead of building separate tools for each task, I designed a unified system where different AI pipelines work behind the scenes. The user only interacts with image upload, prompt input, and optional sketch or mask input. The backend decides which workflow and models should be used.
For example:
| User prompt | Selected workflow |
| --- | --- |
| Remove the chair | Remove Object |
| Add a cat on the sofa | Add Object |
| Make the shirt red | Edit Object |
| Sketch input provided | Sketch Insert |
This automatic intent detection reduces user complexity and makes the editing flow easier to understand.
## Architecture and Features
The system follows a simple flow:
```text
User
-> Frontend web app
-> API route
-> Intent detection
-> ComfyUI workflow
-> AI models
-> Generated result
-> Frontend result viewer
```
The Edit Object workflow detects the object region, generates a segmentation mask, applies SDXL inpainting, and returns the edited image. This supports changes such as color, texture, and style modifications.
The Remove Object workflow uses GroundingDINO to detect the object, SAM2 to segment it, LaMa to reconstruct the background, and blending to produce a natural result.
The Add Object workflow creates an insertion mask, applies prompt-guided inpainting, matches lighting and composition, and blends the object into the scene.
The Sketch Insert workflow lets users draw a rough sketch. ControlNet Scribble processes the sketch, and the prompt guides generation of a realistic object.
Voice input makes interaction faster and more accessible, while automatic intent detection selects the correct workflow from the prompt.
## Models Used
The system combines several specialized models:
- SDXL Inpainting for region-specific object editing and insertion.
- GroundingDINO and SAM2 for object localization and mask generation.
- LaMa for background reconstruction during object removal.
- ControlNet Scribble for sketch-to-image generation.
- Florence-2 for scene understanding and prompt enhancement.
- KSampler to control iterative diffusion generation.
The main software stack used React or Next.js on the frontend, Node.js on the backend, ComfyUI as the workflow engine, and the Web Speech API for voice input.
## Challenges
Accurate object segmentation was one of the main difficulties. Small or complex objects were hard to isolate from text prompts, so I combined GroundingDINO for localization with SAM2 for fine segmentation.
Maintaining visual consistency was another challenge. Generated objects sometimes had mismatched lighting, texture, perspective, or style. Florence-2 prompt enhancement and tuned SDXL inpainting settings helped preserve scene consistency.
Workflow selection also required care because users often give vague prompts. The intent detection layer analyzes keywords and context before choosing the workflow.
## Result
The final system is a modular AI-powered image-editing platform capable of editing objects, removing objects, adding objects, generating objects from sketches, understanding voice instructions, and automatically selecting workflows.
Hackrush was a chance to integrate multiple generative AI and computer-vision models into one practical tool. The most important lesson was that a good AI application is not just a model demo; it needs workflow design, careful defaults, and a user interface that hides complexity without removing capability.

View file

@ -0,0 +1,81 @@
---
title: 'Teaching Python Through Play: KVS Computer Science PGT Training at IITGN'
description: 'A 12-day IITGN and CCL in-service program helped Kendriya Vidyalaya Computer Science teachers transition from C++ to Python through hands-on, activity-led learning.'
date: '2018-06-19'
author:
name: 'Center for Creative Learning, IIT Gandhinagar'
image: '/favicon/web-app-manifest-192x192.png'
tags: ['Outreach', 'Teacher Training', 'School Education', 'Python', 'CCL']
coverImage: '/images/blog/kvs-cs-pgt-2018/kvs-cs-pgt-2018-01.png'
---
![KVS Computer Science PGT teachers and facilitators at IIT Gandhinagar](/images/blog/kvs-cs-pgt-2018/kvs-cs-pgt-2018-01.png)
From June 8 to June 19, 2018, the Center for Creative Learning (CCL) at IIT Gandhinagar conducted the first spell of an in-service course for Kendriya Vidyalaya Sangathan Post Graduate Teachers of Computer Science. Fifty-two teachers from across India came to the IITGN campus for a 12-day program on the basics of computer science and Python.
The timing mattered. In 2018, Python was being introduced into the CBSE curriculum in place of C++, and many teachers arrived with understandable apprehension about teaching a new programming language. The workshop was designed to make that transition concrete, confident, and engaging.
Faculty members from Computer Science and Engineering at IITGN and members of CCL facilitated the program, including Anirban Dasgupta, Neeldhara Misra, Manish Jain, and Ravi Sinha.
## Learning by Doing
The workshop had lectures, but its center of gravity was experiential learning. Teachers solved problems, built models, played games, and used stories and classroom activities to understand computing ideas.
The program connected Python with broader computer science. Participants worked through programming basics, regular expressions, file handling, object-oriented programming, Python libraries, Django, SQLite, and web development. They also explored sorting, searching, graph ideas, computer architecture, Arduino, microcontrollers, and robotics through activities that could travel back to classrooms.
![A classroom session during the KVS Computer Science PGT training program](/images/blog/kvs-cs-pgt-2018/kvs-cs-pgt-2018-16.png)
One highlight was the use of games to explain computing concepts. Sorting, searching, and even computer architecture were explored through group activities rather than only board work. Teachers also built and programmed a mechanical arm, making hardware and software feel connected.
![Hands-on robotics and classroom activity during the training](/images/blog/kvs-cs-pgt-2018/kvs-cs-pgt-2018-23.png)
The workshop also introduced classroom-management and teaching tools such as Repl.it and Canva, with an emphasis on reducing administrative overhead and increasing time available for conceptual discussion.
## Outcomes
The largest outcome was confidence. After the 12-day immersion, more than 75% of survey respondents reported feeling confident about teaching Python in the classroom. This was a significant shift from the baseline, where participants were worried about the curriculum change and the move to a new language.
![Survey response showing increased confidence after the program](/images/blog/kvs-cs-pgt-2018/kvs-cs-pgt-2018-08.png)
The workshop also broadened how teachers saw computer science. By connecting programming with STEM activities, bioinformatics, robotics, microcontrollers, and classroom games, the course positioned computer science as an interdisciplinary way of thinking rather than a stand-alone subject.
![Survey response on interdisciplinary learning and curriculum design](/images/blog/kvs-cs-pgt-2018/kvs-cs-pgt-2018-09.png)
More than 80% of participants approached instructors for a special session on innovative pedagogy and technology-enhanced teaching. More than 90% wanted a follow-up spell at IIT Gandhinagar, and many emphasized that such pedagogy should reach computer science teachers across India.
Navneet Sadh, a teacher from KV Kokrajhar, Assam, described the program this way:
> This workshop was on a whole different level. The target was to provide better education opportunities to children through us. My classes are going to be full of such activities.
Manish Jain, Head of CCL, summarized the idea behind the design:
> The curriculum of KVS has been revamped. So now, the teachers have to teach Computer Science in Python language instead of C++. We showed them how Computer Science can be taught in a lucid manner using Python. For instance, teachers learnt about graphs, sorting, parallel computing and computer architecture by playing many games.
## A 12-Day Arc
The program moved from orientation and problem solving into Python, computer architecture, Arduino, web development, projects, robotics, peer demonstrations, and open-house presentations.
Early sessions introduced computer science as both engineering and science. Python sessions covered variables, expressions, control flow, functions, lists, dictionaries, regular expressions, file input/output, exceptions, libraries, object-oriented programming, and Django. Practical sessions gave teachers time to work through assignments and projects.
![Participants during a hands-on session](/images/blog/kvs-cs-pgt-2018/kvs-cs-pgt-2018-18.png)
Later sessions focused on CS Unplugged-style activities, Arduino, robotic-arm construction, project work, group presentations, and reflection. The course concluded with project presentations, an open house, feedback, and certificates.
![Teachers receiving certificates at the end of the program](/images/blog/kvs-cs-pgt-2018/kvs-cs-pgt-2018-28.png)
## CCL and the Larger Outreach Story
The program reflected CCL's central belief: learners understand deeply when they do, make, explore, pull things apart, and put them back together. CCL began as the Creative Learning Initiative at IIT Gandhinagar and grew into a lab full of STEM models, toys, exhibits, and classroom-ready activities.
By the time of this training, CCL had worked with thousands of teachers and college professors across India and had designed hundreds of activities for hands-on STEM learning.
![Pictorial glimpse from the KVS training program](/images/blog/kvs-cs-pgt-2018/kvs-cs-pgt-2018-36.png)
The KVS Computer Science PGT program showed how university faculty, CCL facilitators, and school teachers can work together on a curriculum transition. The goal was not just to teach Python syntax. It was to help teachers carry a more joyful, conceptual, and activity-rich version of computer science back to their students.
## Links
- [Program blog](https://iscpgtcs18.wordpress.com/)
- [Feedback video](https://www.youtube.com/watch?v=wIAFZRcPD1s)
- [Feedback forms and material](https://drive.google.com/drive/folders/1cDw5mibr3yjZCJqmwXEllXDIeeJyiFTJ?usp=sharing)
- [Original Google Doc source](https://docs.google.com/document/d/1BWce28URA8LvWdDQek6lLT_PMSitgp2FDLDV2JPFQlc/edit?usp=sharing)

View file

@ -0,0 +1,425 @@
---
title: 'Python Is Not an Acceptable ML'
description: 'Balagopal Komarath argues that Python is a poor fit for introductory programming when compared with languages that catch more mistakes early.'
date: '2022-11-28'
author:
name: 'Balagopal Komarath'
image: '/favicon/web-app-manifest-192x192.png'
tags: ['Programming Languages', 'Teaching', 'OCaml', 'Python']
coverImage: '/images/course-resources/theory-of-computing.png'
---
Python is a popular fixture in introductory programming courses. Its adoption is mainly driven by two reasons: its popularity in scientific computing and industry, and its perceived readability.
The first point is irrefutable. The second is more complicated. Python's surface-level readability does not compensate for its violations of several fundamental principles of programming-language and user-interface design. Many of the problems below are faced most sharply by beginning programmers. A language with better design, such as OCaml, avoids or even prevents several of them.
When programming, errors can be detected at different stages. Some are caught by an editor or IDE while typing; some by a compiler; some always appear when the program runs; and some remain hidden until particular runtime conditions occur. A well-designed programming language should detect as many errors as possible as early as possible.
Can we create a perfect language where all errors are compile-time errors? Such languages exist, but they usually require advanced knowledge to use well. Every language is designed around a tradeoff between correctness and usability. But the tradeoff is not linear. If we quantified usability and correctness on a scale of 1 to 100, some languages might be 80 in usability and 80 in correctness, while others might be 90 in usability and 50 in correctness.
My thesis is that OCaml is an 80-usability, 80-correctness language for introductory programming, while Python is, at best, a 90-usability, 50-correctness language. The examples below explain why.
## Errors of Commission and Omission
The following Python program has an error. Can you find it?
```python
def sound(animal):
if animal == 'dog':
return 'bow'
elif animal == 'cat':
return 'meow'
elif animal == 'cow':
return 'moo'
elif anima1 == 'pig':
return 'oink'
elif animal == 'human':
return 'huh'
print(sound('cat'))
```
It prints:
```text
meow
```
The error is that one occurrence of `animal` is misspelled as `anima1`. Python accepts the program and the bug remains hidden unless that branch is reached. This is a conditional runtime error that should have been a compilation error.
In OCaml, the same function can be written without even spelling out the parameter:
```ocaml
let sound = function
| "dog" -> "bow"
| "cat" -> "meow"
| "cow" -> "moo"
| "pig" -> "oink"
| "human" -> "huh"
| _ -> assert false
let () = print_endline (sound "cat")
```
The parameter name conveys no useful information here, so the language lets us avoid it. If we write the function in a more Python-like style and make the same spelling mistake, OCaml reports it immediately:
```ocaml
let sound animal =
if animal = "dog" then "bow"
else if animal = "cat" then "meow"
else if animal = "cow" then "moo"
else if anima1 = "pig" then "oink"
else if animal = "human" then "huh"
else assert false
```
```text
Line 5, characters 10-16:
5 | else if anima1 = "pig" then "oink"
^^^^^^
Error: Unbound value anima1
Hint: Did you mean animal?
```
The same issue appears when programs evolve. Suppose we write two Python functions:
```python
def sound(animal):
if animal == 'dog':
return 'bow'
elif animal == 'cat':
return 'meow'
elif animal == 'caterpillar':
return '...'
def legs(animal):
if animal == 'dog' or animal == 'cat':
return 4
elif animal == 'caterpillar':
return 1000
```
Later, we decide to handle humans:
```python
def sound(animal):
if animal == 'dog':
return 'bow'
elif animal == 'cat':
return 'meow'
elif animal == 'caterpillar':
return '...'
elif animal == 'human':
return 'huh'
```
If we forget to update `legs`, then `legs('human')` returns `None`. Python gives no warning. The error remains conditional, because it appears only if that function is called with that input.
In OCaml, the natural representation uses a sum type:
```ocaml
type animal = Cat | Dog | Caterpillar
let sound = function
| Cat -> "meow"
| Dog -> "bow"
| Caterpillar -> "..."
let legs = function
| Cat | Dog -> 4
| Caterpillar -> 1000
```
If we add `Human` to the type but forget to update `legs`, the compiler points out the omission:
```ocaml
type animal = Cat | Dog | Caterpillar | Human
let legs = function
| Cat | Dog -> 4
| Caterpillar -> 1000
```
```text
Warning 8 [partial-match]: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
Human
```
## What Is in a Name?
The scope of a name defines the context in which it is valid. Python's rules for scope are often unintuitive:
```python
day = 'Monday'
def setday(newday):
day = newday
setday('Tuesday')
print(day)
```
This prints:
```text
Monday
```
The `day` inside `setday` refers to a newly created local variable, not the global `day`. Python implicitly creates variables on first assignment in functions, avoiding an explicit keyword like `let` or `var`. This violates Python's own principle that explicit is better than implicit.
The problem is not limited to global variables:
```python
def end(s):
last = "x"
def a(): last = "a"
def b(): last = "b"
for c in s:
if c == "a": a()
elif c == "b": b()
return last
print(end("abracadabra"))
```
This prints:
```text
x
```
The assignments inside `a()` and `b()` do not affect the `last` in the scope of `end()`. Python's fix is to use `global` and `nonlocal` declarations. But it is easy for a beginner to forget them, producing conditional runtime errors.
## Python UI Lies
A fundamental rule of user-interface design, including programming-language design, is that things that look the same should behave the same. Consider:
```python
x = 5
y = x
x = x + 1
print(x, y)
x = []
y = x
x.append(0)
print(x, y)
```
The output is:
```text
6 5
[0] [0]
```
Changing `x` also changes `y` in the second case but not in the first. Python provides a consistent-looking interface to value types and reference types, even though they behave fundamentally differently.
The list replication operator makes the same issue worse:
```python
xs = [[0] * 3] * 3
xs[0][0] = 1
print(xs)
```
The result is:
```text
[[1, 0, 0], [1, 0, 0], [1, 0, 0]]
```
The rows are not independent lists. The expression creates repeated references to the same inner list.
Implicit duplicate references can also appear during iteration:
```python
xs = [1, 2, 3, 4]
for x in xs:
if x % 2 == 1:
xs.remove(x)
print(xs)
```
This seems to work:
```text
[2, 4]
```
But a small change exposes the problem:
```python
xs = [1, 2, 3, 4]
for x in xs:
xs.remove(x)
print(xs)
```
The result is still:
```text
[2, 4]
```
The iterator keeps an implicit reference to the list, while the loop body mutates it through `xs`. The language allows the conflict.
## Python Non-Functionality
Higher-order programming, the ability to manipulate functions as values, is important because it enables code reuse beyond first-order abstractions. Python has adopted many higher-order features from the ML family, but its inability to distinguish value and reference types weakens them.
Consider:
```python
def dup(x):
return (x, x)
def applyfst(f, pair):
(x, y) = pair
return (f(x), y)
```
With integers, this behaves as expected:
```python
print(applyfst(lambda x: x + 1, dup(0)))
```
```text
(1, 0)
```
With lists, it does not:
```python
def append0(xs):
xs.append(0)
return xs
print(applyfst(append0, dup([])))
```
```text
([0], [0])
```
The definitions of `dup` and `applyfst` are logical, but their behavior changes depending on whether they are used with values or references. In a real program, such functions may come from a library. A user should not need to know the implementation details of a library to use it safely.
The OCaml equivalent has no such surprise:
```ocaml
let dup x = (x, x)
let applyfst f (x, y) = (f x, y)
let inc x = x + 1
let () = assert (
applyfst inc (dup 0) = (1, 0)
)
let append0 xs = xs @ [0]
let () = assert (
applyfst append0 (dup []) = ([0], [])
)
```
Even Python's built-in higher-order functions must be used with care:
```python
def listmap(f, xs): return list(map(f, xs))
print(
listmap(
lambda f: f(0),
[lambda x: x + 1, lambda x: x + 2]
)
)
print(
listmap(
lambda f: f(0),
[lambda x: x + i for i in range(1, 10)]
)
)
```
The output is:
```text
[1, 2]
[9, 9, 9, 9, 9, 9, 9, 9, 9]
```
It is possible to teach students to avoid such errors by explaining Python's abstract machine. But the point of a high-level language is to raise the machine's level of conversation toward the human, not to lower the human's level of conversation toward the machine.
In OCaml, the corresponding code works as expected:
```ocaml
let apply fs x = List.map (fun f -> f x) fs
let rec range n m =
if n = m then [n] else n :: range (n + 1) m
let fs = List.map (fun i -> (+) i) (range 1 9)
let () = assert (apply fs 0 = range 1 9)
```
## OCaml Imperativity
There is at least one place where Python is usually considered more usable: mutation and iteration. The following computes a factorial using a mutable variable:
```python
def factorial(n):
p = 1
for i in range(2, n + 1):
p = p * i
return p
```
The classic recursive OCaml version mirrors the mathematical definition:
```ocaml
let rec factorial n =
if n = 0 then 1
else n * factorial (n - 1)
```
Functional-programming experts would usually write a tail-recursive version for performance:
```ocaml
let factorial n =
let rec loop acc = function
| 0 -> acc
| n -> loop (acc * n) (n - 1)
in loop 1 n
```
But OCaml is not as strict about functional style as some functional languages. We can mirror the Python implementation:
```ocaml
let factorial n =
let p = ref 1 in
for i = 2 to n do
p := !p * i
done;
!p
```
The difference is that we must explicitly state that `p` is mutable by making it a `ref`. The `!` operator retrieves the current contents of the reference. This may look less pretty than Python, but it satisfies the principle that explicit is better than implicit.
## Fixing Python?
Realistically, it would be difficult to convince people to switch from Python to OCaml. If Python is used with learners, I suggest these guidelines:
- For misspellings and type changes, introduce automated tests early. Teach function syntax before control structures. Mandate linters such as `flake8`, unit testing with `pytest`, and type hints from the beginning.
- Avoid global variables and local variables accessed by nested functions as much as possible.
- Treat lists and dictionaries as either immutable, or ensure there is exactly one active reference to them at all times.
- Introduce higher-order programming anyway, because it is important. It may be better to let learners encounter Python-specific problems as they arise rather than turning an introductory course into a course on Python arcana.
## Epilogue
There are many scenarios where Python may be a better choice than OCaml. This article considers only the suitability of a language for introductory programming courses. The switch from C to Python helped a larger number of students get into programming. A switch from Python to a better-designed language could have a similar effect in the future.

View file

View file

View file

2708
src/data/courses.json Normal file

File diff suppressed because it is too large Load diff

411
src/data/events.ts Normal file
View file

@ -0,0 +1,411 @@
export type EventCategory =
| "conference"
| "school"
| "workshop"
| "symposium"
| "course";
export interface DepartmentEvent {
id: string;
title: string;
category: EventCategory;
year: number;
displayDate: string;
startDate?: string;
endDate?: string;
organizer?: string;
collaborators?: string;
description: string;
source: "CSE events.xlsx" | "CSE archive";
featured?: boolean;
}
export const EVENT_CATEGORY_LABELS: Record<EventCategory, string> = {
conference: "Conference",
school: "School",
workshop: "Workshop",
symposium: "Symposium",
course: "Course",
};
export const EVENT_CATEGORY_STYLES: Record<EventCategory, string> = {
conference: "border-sky-200 bg-sky-50 text-sky-700",
school: "border-emerald-200 bg-emerald-50 text-emerald-700",
workshop: "border-amber-200 bg-amber-50 text-amber-800",
symposium: "border-violet-200 bg-violet-50 text-violet-700",
course: "border-rose-200 bg-rose-50 text-rose-700",
};
export const departmentEvents: DepartmentEvent[] = [
{
id: "ai-day-2026",
title: "AI Day",
category: "symposium",
year: 2026,
displayDate: "12 Feb 2026",
startDate: "2026-02-12",
organizer: "Mayank Singh",
collaborators: "Yogesh Meena",
description:
"A department symposium around AI research, applications, and institute-wide engagement.",
source: "CSE events.xlsx",
featured: true,
},
{
id: "cybersecurity-workshop-2026",
title:
"Cybersecurity Workshop on Strengthening IT/OT and Financial Sector Security through Collaboration",
category: "workshop",
year: 2026,
displayDate: "9 Apr 2026",
startDate: "2026-04-09",
organizer: "Abhishek Bichhawat",
collaborators: "Sameer G. Kulkarni",
description:
"A collaborative workshop focused on IT/OT security and financial-sector cybersecurity practice.",
source: "CSE events.xlsx",
featured: true,
},
{
id: "modern-80211-wlans-2025",
title: "Certificate Course on Modern 802.11 WLANs",
category: "course",
year: 2025,
displayDate: "17-23 Mar 2025",
startDate: "2025-03-17",
endDate: "2025-03-23",
organizer: "Sameer G. Kulkarni",
collaborators: "Ravi Hegde and Uttama Lahiri",
description:
"A certificate course on modern wireless LAN systems and practice.",
source: "CSE events.xlsx",
featured: true,
},
{
id: "beyond-5g-workshop-2025",
title: "Workshop on Paradigms for Beyond 5G Communication",
category: "workshop",
year: 2025,
displayDate: "24 Mar 2025",
startDate: "2025-03-24",
organizer: "Sameer G. Kulkarni",
collaborators: "Ravi Hegde and Uttama Lahiri",
description:
"A focused workshop on emerging communication paradigms beyond 5G.",
source: "CSE events.xlsx",
},
{
id: "bci-stroke-rehab-2025",
title:
"Advances in Neuro Rehabilitation and Assistive Technologies: Workshop on BCI-Driven Stroke Rehabilitation",
category: "workshop",
year: 2025,
displayDate: "10-11 Mar 2025",
startDate: "2025-03-10",
endDate: "2025-03-11",
organizer: "Yogesh Meena",
description:
"A workshop connecting brain-computer interfaces, assistive technologies, and stroke rehabilitation.",
source: "CSE events.xlsx",
},
{
id: "fsttcs-2024",
title:
"44th IARCS Annual Conference on Foundations of Software Technology and Theoretical Computer Science",
category: "conference",
year: 2024,
displayDate: "14-20 Dec 2024",
startDate: "2024-12-14",
endDate: "2024-12-20",
organizer: "Neeldhara Misra",
description:
"IIT Gandhinagar hosted FSTTCS, one of Indias flagship theoretical computer science conferences.",
source: "CSE events.xlsx",
featured: true,
},
{
id: "compute-t4e-2024",
title: "COMPUTE and T4E",
category: "conference",
year: 2024,
displayDate: "5-8 Dec 2024",
startDate: "2024-12-05",
endDate: "2024-12-08",
organizer: "Neeldhara Misra",
collaborators: "Sameer Sahasrabudhe and Aditi Kothiyal",
description:
"A joint computing-education gathering around teaching, learning, and research in CS education.",
source: "CSE events.xlsx",
featured: true,
},
{
id: "python-ai-workshop-2024",
title: "One-Day Workshop on Python Programming and AI Applications",
category: "workshop",
year: 2024,
displayDate: "4 May 2024",
startDate: "2024-05-04",
organizer: "Anirban Dasgupta and Raviraj Joshi",
collaborators: "Nirav Bhatt",
description:
"A hands-on programming and AI applications workshop for learners building practical computing fluency.",
source: "CSE events.xlsx",
},
{
id: "cricket-computing-education-week-2024",
title:
"Computing Education Week / CRiCKET / Building Imagination with Knowledge Exchange",
category: "workshop",
year: 2024,
displayDate: "3-9 Feb 2024",
startDate: "2024-02-03",
endDate: "2024-02-09",
organizer: "Neeldhara Misra",
description:
"A computing education program focused on imagination, knowledge exchange, and learner-centered CS activities.",
source: "CSE events.xlsx",
featured: true,
},
{
id: "5g-use-case-labs-2024",
title: "5G Use Case Labs Awareness and Pre-Commissioning Readiness",
category: "workshop",
year: 2024,
displayDate: "18 Mar 2024",
startDate: "2024-03-18",
organizer: "Sameer G. Kulkarni",
description:
"An awareness and readiness workshop around the institutes 5G use-case lab activity.",
source: "CSE events.xlsx",
},
{
id: "ants-2022",
title:
"2022 IEEE International Conference on Advanced Networks and Telecommunications Systems",
category: "conference",
year: 2022,
displayDate: "18-21 Dec 2022",
startDate: "2022-12-18",
endDate: "2022-12-21",
organizer: "Sameer G. Kulkarni",
collaborators: "Shanmuganathan Raman",
description:
"The IEEE ANTS conference brought researchers together around advanced networking and telecommunications systems.",
source: "CSE events.xlsx",
},
{
id: "acm-india-csed-2022",
title: "ACM-India CSEd Workshop",
category: "workshop",
year: 2022,
displayDate: "23 Dec 2022",
startDate: "2022-12-23",
organizer: "Neeldhara Misra",
description:
"A computing education workshop run with ACM-India participation.",
source: "CSE events.xlsx",
featured: true,
},
{
id: "gian-randomized-methods-2022",
title:
"GIAN Course on Randomized Methods in Parameterized Algorithms",
category: "course",
year: 2022,
displayDate: "5-9 Dec 2022",
startDate: "2022-12-05",
endDate: "2022-12-09",
organizer: "Neeldhara Misra",
description:
"A GIAN short course on randomized methods for approximation and parameterized algorithms, taught by Daniel Lokshtanov.",
source: "CSE events.xlsx",
featured: true,
},
{
id: "indoml-2022",
title: "Indian Symposium on Machine Learning",
category: "symposium",
year: 2022,
displayDate: "15-17 Dec 2022",
startDate: "2022-12-15",
endDate: "2022-12-17",
organizer: "Anirban Dasgupta",
collaborators: "Mayank Singh and Udit Bhatia",
description:
"The Indian Symposium on Machine Learning brought together research communities working on data, learning, and AI.",
source: "CSE events.xlsx",
},
{
id: "acm-ikdd-summer-school-2022",
title: "ACM-IKDD Summer School on Data Sciences",
category: "school",
year: 2022,
displayDate: "4-16 Jul 2022",
startDate: "2022-07-04",
endDate: "2022-07-16",
organizer: "Anirban Dasgupta",
collaborators: "Mayank Singh",
description:
"A short-term summer school on data sciences, organized with ACM-IKDD.",
source: "CSE events.xlsx",
featured: true,
},
{
id: "ai-emerging-economies-2022",
title: "AI Reflections and Applications in Emerging Economies",
category: "workshop",
year: 2022,
displayDate: "1-2 Oct 2022",
startDate: "2022-10-01",
endDate: "2022-10-02",
organizer: "Sameer G. Kulkarni",
collaborators: "Deepak Singhania",
description:
"A workshop on AI applications and reflection in emerging-economy contexts.",
source: "CSE events.xlsx",
},
{
id: "indoml-2021",
title: "Indian Symposium on Machine Learning",
category: "symposium",
year: 2021,
displayDate: "16-18 Dec 2021",
startDate: "2021-12-16",
endDate: "2021-12-18",
organizer: "Anirban Dasgupta",
collaborators: "Mayank Singh and Udit Bhatia",
description:
"A symposium gathering Indian machine-learning researchers and practitioners.",
source: "CSE events.xlsx",
},
{
id: "acm-iriiss-2020",
title: "ACM-India Inter-Research Institute Student Seminar in CS",
category: "symposium",
year: 2020,
displayDate: "2020",
description:
"A student seminar connecting CS researchers across Indian institutes.",
source: "CSE archive",
},
{
id: "acm-india-annual-event-2020",
title: "ACM-India Annual Event",
category: "conference",
year: 2020,
displayDate: "2020",
description:
"A department-hosted ACM-India annual gathering.",
source: "CSE archive",
},
{
id: "acmw-women-cs-research-2020",
title: "ACM-W India Workshop for Women in CS Research",
category: "workshop",
year: 2020,
displayDate: "2020",
description:
"A workshop supporting women researchers in computer science.",
source: "CSE archive",
featured: true,
},
{
id: "acmw-algorithmic-game-theory-2019",
title: "ACM-W Summer School on Algorithmic Game Theory",
category: "school",
year: 2019,
displayDate: "2019",
description:
"A summer school introducing algorithmic game theory through ACM-W.",
source: "CSE archive",
featured: true,
},
{
id: "gian-social-choice-2017",
title: "GIAN Course on Computational Social Choice",
category: "course",
year: 2017,
displayDate: "2017",
description:
"A GIAN course on computational social choice taught by Edith Elkind.",
source: "CSE archive",
featured: true,
},
{
id: "gian-pattern-matching-2017",
title: "GIAN Course on Pattern Matching Algorithms",
category: "course",
year: 2017,
displayDate: "2017",
description:
"A GIAN course on pattern matching algorithms taught by Amihood Amir.",
source: "CSE archive",
},
{
id: "acm-graph-theory-summer-school-2017",
title: "ACM Summer School on Graph Theory and Graph Algorithms",
category: "school",
year: 2017,
displayDate: "2017",
description:
"A summer school on graph theory and graph algorithms.",
source: "CSE archive",
featured: true,
},
{
id: "nmi-complexity-theory-2016",
title: "NMI Workshop on Complexity Theory",
category: "workshop",
year: 2016,
displayDate: "2016",
description:
"A workshop on complexity theory from the department archive.",
source: "CSE archive",
},
{
id: "teqip-design-analysis-algorithms-2016",
title: "TEQIP Summer School on Design and Analysis of Algorithms",
category: "school",
year: 2016,
displayDate: "2016",
description:
"A TEQIP summer school on algorithm design and analysis.",
source: "CSE archive",
featured: true,
},
{
id: "gian-3d-digitization-cultural-heritage-2015",
title: "GIAN Course on 3D Digitization for Cultural Heritage",
category: "course",
year: 2015,
displayDate: "30 Nov-11 Dec 2015",
startDate: "2015-11-30",
endDate: "2015-12-11",
organizer: "Shanmuganathan Raman",
collaborators: "Marco Callieri, Visual Computing Lab, ISTI-CNR, Italy",
description:
"A GIAN course on 3D digitization methods for cultural heritage.",
source: "CSE archive",
},
];
export const currentEventDate = "2026-06-01";
export const upcomingEvents = departmentEvents
.filter((event) => event.startDate && event.startDate >= currentEventDate)
.sort((a, b) => (a.startDate ?? "").localeCompare(b.startDate ?? ""));
export const pastEvents = departmentEvents
.filter((event) => !event.startDate || event.startDate < currentEventDate)
.sort((a, b) => {
const dateCompare = (b.startDate ?? `${b.year}`).localeCompare(
a.startDate ?? `${a.year}`,
);
return dateCompare || a.title.localeCompare(b.title);
});
export const featuredLearningEvents = departmentEvents.filter(
(event) =>
event.featured &&
["school", "course", "workshop", "conference"].includes(event.category),
);

286
src/data/faculty.ts Normal file
View file

@ -0,0 +1,286 @@
export type FacultyCategory =
| 'core'
| 'affiliated'
| 'visiting'
| 'practice'
| 'teaching'
| 'guest';
export interface FacultyMember {
name: string;
designation: string;
category: FacultyCategory;
primaryDepartment: string;
secondaryDepartment?: string;
affiliations?: string[];
researchAreas: string[];
dateOfJoining: string;
homepage?: string;
}
export const FACULTY: FacultyMember[] = [
// --- Core Faculty ---
{
name: 'Rajat Moona',
designation: 'Professor & Director',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [],
dateOfJoining: '2022-10-03',
},
{
name: 'Anirban Dasgupta',
designation: 'Professor',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: ['Computer Science and Engineering', 'Artificial Intelligence'],
dateOfJoining: '2013-12-30',
},
{
name: 'Bireswar Das',
designation: 'Associate Professor',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: ['Computer Science and Engineering'],
dateOfJoining: '2010-06-28',
},
{
name: 'Neeldhara Misra',
designation: 'Associate Professor',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: ['Computer Science and Engineering', 'Artificial Intelligence'],
dateOfJoining: '2015-09-23',
},
{
name: 'Nipun Batra',
designation: 'Associate Professor',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
affiliations: ['Sustainability Lab'],
researchAreas: ['Computer Science and Engineering', 'Artificial Intelligence'],
dateOfJoining: '2018-07-09',
},
{
name: 'Manoj D Gupta',
designation: 'Associate Professor',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: ['Computer Science and Engineering'],
dateOfJoining: '2016-01-18',
},
{
name: 'Mayank Singh',
designation: 'Associate Professor',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: ['Computer Science and Engineering', 'Artificial Intelligence'],
dateOfJoining: '2019-02-06',
},
{
name: 'Sameer G Kulkarni',
designation: 'Assistant Professor',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
secondaryDepartment: 'Electrical Engineering',
researchAreas: ['Computer Science and Engineering', 'Electrical Engineering'],
dateOfJoining: '2020-04-03',
},
{
name: 'Balagopal Komarath',
designation: 'Assistant Professor',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: ['Computer Science and Engineering'],
dateOfJoining: '2020-12-21',
},
{
name: 'Abhishek Bichhawat',
designation: 'Assistant Professor',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: ['Computer Science and Engineering'],
dateOfJoining: '2021-03-30',
},
{
name: 'Yogesh Kumar Meena',
designation: 'Assistant Professor',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [
'Computer Science and Engineering',
'Artificial Intelligence',
'Cognitive Science',
],
dateOfJoining: '2023-02-14',
},
{
name: 'Shouvick Mondal',
designation: 'Assistant Professor',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: ['Computer Science and Engineering'],
dateOfJoining: '2023-02-01',
},
{
name: 'Manisha Padala',
designation: 'Assistant Professor (Contract)',
category: 'core',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [],
dateOfJoining: '2024-03-26',
},
// --- Affiliated Faculty ---
{
name: 'Shanmuganathan Raman',
designation: 'Professor',
category: 'affiliated',
primaryDepartment: 'Electrical Engineering',
secondaryDepartment: 'Computer Science and Engineering',
researchAreas: [
'Electrical Engineering',
'Computer Science and Engineering',
'Artificial Intelligence',
],
dateOfJoining: '2013-05-20',
},
{
name: 'Udit Bhatia',
designation: 'Associate Professor',
category: 'affiliated',
primaryDepartment: 'Civil Engineering',
secondaryDepartment: 'Computer Science and Engineering',
researchAreas: ['Civil Engineering', 'Artificial Intelligence', 'Earth Sciences'],
dateOfJoining: '2019-01-17',
},
{
name: 'Krishna Prasad Miyapuram',
designation: 'Associate Professor',
category: 'affiliated',
primaryDepartment: 'Cognitive and Brain Sciences',
researchAreas: ['Cognitive Science', 'Artificial Intelligence'],
dateOfJoining: '2012-10-03',
},
// --- Teaching Faculty ---
{
name: 'Jyoti Krishnan',
designation: 'Assistant Teaching Professor',
category: 'teaching',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: ['Computer Science and Engineering'],
dateOfJoining: '2024-03-11',
},
// --- Practice Faculty ---
{
name: 'Manu Awasthi',
designation: 'Associate Professor of Practice',
category: 'practice',
primaryDepartment: 'Computer Science and Engineering',
affiliations: ['Center for Creative Learning'],
researchAreas: [],
dateOfJoining: '2025-12-12',
},
{
name: 'Anup Kalbalia',
designation: 'Associate Professor of Practice',
category: 'practice',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [],
dateOfJoining: '2026-02-02',
},
// --- Visiting / Guest Faculty ---
{
name: 'Nirmal Kumar Sancheti',
designation: 'Visiting Professor',
category: 'visiting',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [],
dateOfJoining: '2025-08-04',
},
{
name: 'Samit Bhattacharya',
designation: 'Visiting Associate Professor',
category: 'visiting',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [],
dateOfJoining: '2025-03-01',
},
{
name: 'Venkatesh Raman',
designation: 'Guest Faculty',
category: 'guest',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [],
dateOfJoining: '2025-10-17',
},
{
name: 'Viraj Shah',
designation: 'Guest Faculty',
category: 'guest',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [],
dateOfJoining: '2026-01-05',
},
{
name: 'Madhavan Unnikrishnan Nair',
designation: 'Guest Professor',
category: 'guest',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [],
dateOfJoining: '2025-11-11',
},
{
name: 'Yuvraj Patel',
designation: 'Guest Assistant Professor',
category: 'guest',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [],
dateOfJoining: '2025-01-01',
},
{
name: 'Subir Verma',
designation: 'Guest Professor',
category: 'guest',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [],
dateOfJoining: '2025-10-17',
},
{
name: 'Ambarish Ojha',
designation: 'Guest Professor',
category: 'guest',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [],
dateOfJoining: '2026-02-17',
},
{
name: 'K. Gopinath',
designation: 'Guest Professor',
category: 'guest',
primaryDepartment: 'Computer Science and Engineering',
researchAreas: [],
dateOfJoining: '2025-12-17',
},
];
export const CATEGORY_LABELS: Record<FacultyCategory, string> = {
core: 'Core Faculty',
affiliated: 'Affiliated Faculty',
visiting: 'Visiting Faculty',
practice: 'Faculty of Practice',
teaching: 'Teaching Faculty',
guest: 'Guest Faculty',
};
export const CATEGORY_ORDER: FacultyCategory[] = [
'core',
'affiliated',
'teaching',
'practice',
'visiting',
'guest',
];

403
src/data/news.ts Normal file
View file

@ -0,0 +1,403 @@
export type NewsCategory =
| "media"
| "research"
| "award"
| "event"
| "infrastructure";
export type NewsStatus = "confirmed" | "achievement";
export interface DepartmentNewsItem {
id: string;
title: string;
summary: string;
people: string;
displayDate: string;
date: string;
category: NewsCategory;
status: NewsStatus;
sourceLabel: string;
sourceUrl: string;
homepage?: boolean;
}
export const CATEGORY_LABELS: Record<NewsCategory, string> = {
media: "Media",
research: "Research",
award: "Award",
event: "Event",
infrastructure: "Infrastructure",
};
export const STATUS_LABELS: Record<NewsStatus, string> = {
confirmed: "Confirmed",
achievement: "Achievement",
};
export const departmentNews: DepartmentNewsItem[] = [
{
id: "thermeval-kdd-2026",
title: "ThermEval accepted at KDD 2026",
summary:
"A Sustainability Lab benchmark evaluates vision-language models on thermal imagery, extending CSE work on robust AI for sensing and climate applications.",
people:
"Ayush Shrivastava, Kirtan Gangani, Laksh Jain, Prof. Nipun Batra, and Prof. Mayank Goel",
displayDate: "May 2026",
date: "2026-05-20",
category: "research",
status: "confirmed",
sourceLabel: "Sustainability Lab news",
sourceUrl: "https://sustainability-lab.github.io/news.html",
homepage: true,
},
{
id: "nilmb-2026-buildsys",
title: "Undergraduate-led NILMBench2026 accepted at BuildSys 2026",
summary:
"Second-year undergraduates led a reproducible benchmark for energy disaggregation, continuing the department focus on deployable AI systems.",
people: "Aayush Kuloor, Anurag Singh, Harsh Dhru, and Prof. Nipun Batra",
displayDate: "May 2026",
date: "2026-05-15",
category: "research",
status: "confirmed",
sourceLabel: "Sustainability Lab news",
sourceUrl: "https://sustainability-lab.github.io/news.html",
homepage: true,
},
{
id: "project-madhava-india-today",
title: "Project Madhava covered by India Today",
summary:
"The hands-on computer-systems pedagogy effort uses RISC-V microcontrollers to connect classroom learning with self-reliant hardware practice.",
people: "Prof. Manu Awasthi, Manish Jain, Vicharak, C2S, ChipIN, and CCL",
displayDate: "2026",
date: "2026-03-01",
category: "media",
status: "confirmed",
sourceLabel: "India Today",
sourceUrl:
"https://www.indiatoday.in/education-today/featurephilia/story/iit-gandhinagars-self-reliant-india-story-begins-in-classrooms-2869886-2026-02-18",
homepage: true,
},
{
id: "cvig-pollen-ai-2026",
title:
"CVIG Lab work on AI-assisted pollen classification receives coverage",
summary:
"The project combines scanning electron microscopy and vision transformers to classify pollen grains, with coverage in science and education outlets.",
people: "Prof. Shanmuganathan Raman and S. Sankaranarayanan",
displayDate: "Feb 2026",
date: "2026-02-20",
category: "research",
status: "confirmed",
sourceLabel: "Phys.org",
sourceUrl:
"https://phys.org/news/2026-02-exploring-electron-microscopy-ai-key.html",
homepage: true,
},
{
id: "udit-bhatia-drought-synchrony",
title:
"Nature Portfolio paper links ocean variability and global drought synchrony",
summary:
"The MIR Lab and collaborators showed how ocean cycles constrain planet-wide drought synchrony, with broad external science coverage.",
people:
"Dr. Udit Bhatia, Hemant Poonia, D. Mansoor Tantary, Vimal Mishra, and Rohini Kumar",
displayDate: "Jan 2026",
date: "2026-01-06",
category: "research",
status: "confirmed",
sourceLabel: "Communications Earth & Environment",
sourceUrl: "https://www.nature.com/articles/s43247-025-03111-5",
},
{
id: "soket-ai-pm-roundtable",
title: "Soket AI presented at PM-chaired AI startup roundtable",
summary:
"Soket AI, co-founded by Prof. Mayank Singh, was among 12 AI startups that presented ahead of the India AI Impact Summit.",
people: "Soket AI and Prof. Mayank Singh",
displayDate: "8 Jan 2026",
date: "2026-01-08",
category: "media",
status: "confirmed",
sourceLabel: "PIB / PM India",
sourceUrl:
"https://www.pib.gov.in/PressReleasePage.aspx?PRID=2212390&reg=3&lang=1",
},
{
id: "arc-centre-launch",
title: "IIT Gandhinagar launches AI Resilience and Command Centre",
summary:
"The ARC Centre advances data-driven climate-risk management, including AI-based urban flooding modules and decision support tools.",
people: "Dr. Udit Bhatia and IITGN ARC Centre",
displayDate: "Feb 2026",
date: "2026-02-05",
category: "infrastructure",
status: "confirmed",
sourceLabel: "IITGN News",
sourceUrl:
"https://news.iitgn.ac.in/iit-gandhinagar-launches-ai-resilience-and-command-centre-for-data-driven-climate-risk-management/",
},
{
id: "mayank-ai-impact-summit",
title: "Prof. Mayank Singh listed as AI Impact Summit panelist",
summary:
"The India AI Impact Summit agenda listed Prof. Singh on a panel about India frontier labs and Global South impact.",
people: "Prof. Mayank Singh",
displayDate: "Feb 2026",
date: "2026-02-01",
category: "event",
status: "confirmed",
sourceLabel: "Indian Express agenda",
sourceUrl:
"https://indianexpress.com/article/explained/explained-sci-tech/ai-impact-summit-begins-delhi-agenda-10533773/",
},
{
id: "ai-day-iitgn-2026",
title: "AI Day at IITGN builds momentum for India AI Impact Pre-Summit",
summary:
"The institute event highlighted CSE and Center for AI-driven Innovations work before the India AI Impact Pre-Summit.",
people:
"CSE Department, Center for AI-driven Innovations, Prof. Mayank Singh, Prof. Anirban Dasgupta, and Prof. Yogesh Kumar Meena",
displayDate: "12 Feb 2026",
date: "2026-02-12",
category: "event",
status: "confirmed",
sourceLabel: "IITGN News",
sourceUrl:
"https://news.iitgn.ac.in/ai-day-iitgn-builds-momentum-for-india-ai-impact-pre-summit-2026/",
},
{
id: "inter-iit-2025-algorithmic-optimisation",
title: "IITGN wins Inter-IIT gold in Algorithmic Optimisation",
summary:
"The team won gold at Inter-IIT Tech Meet 14.0 for solving computational problems under strict time and memory constraints.",
people: "Anurag Singh and Nishchay Bhutoria",
displayDate: "Dec 2025",
date: "2025-12-14",
category: "award",
status: "achievement",
sourceLabel: "IITGN Technical Council",
sourceUrl: "https://technical-council.iitgn.tech/achievements",
},
{
id: "inter-iit-2025-game-development",
title: "IITGN wins Inter-IIT silver in Game Development Challenge",
summary:
"The team won silver at Inter-IIT Tech Meet 14.0 for a polished playable game with strong mechanics, level design, and coding architecture.",
people: "Divyansh Sharma, Hem Tilva, Nilay, and Shubham",
displayDate: "Dec 2025",
date: "2025-12-14",
category: "award",
status: "achievement",
sourceLabel: "IITGN Technical Council",
sourceUrl: "https://technical-council.iitgn.tech/achievements",
},
{
id: "inter-iit-2025-isro-geospatial",
title: "IITGN wins Inter-IIT silver in ISRO Geospatial Challenge",
summary:
"The team won silver at Inter-IIT Tech Meet 14.0 for applying machine learning and remote-sensing methods to satellite imagery and geospatial data.",
people: "Umang, Karan, Laksh, Romit, Shreyans, and Viraj",
displayDate: "Dec 2025",
date: "2025-12-14",
category: "award",
status: "achievement",
sourceLabel: "IITGN Technical Council",
sourceUrl: "https://technical-council.iitgn.tech/achievements",
},
{
id: "chitrabhasha-anrf",
title: "Chitrabhasha receives ANRF Advanced Research Grant support",
summary:
"The Lingo Research Group project focuses on large-scale datasets and foundational multilingual, multimodal AI models.",
people: "Lingo Research Group and Prof. Mayank Singh",
displayDate: "2026",
date: "2026-01-15",
category: "research",
status: "confirmed",
sourceLabel: "IITGN official post",
sourceUrl: "https://x.com/iitgn/status/2040285734062473311",
},
{
id: "comsnets-2026-mcp-diag",
title: "MCP-Diag recognized at COMSNETS 2026 Graduate Forum",
summary:
"The paper on deterministic, protocol-driven AI-native network diagnostics was named Graduate Forum Best Paper Runner-up.",
people: "Devansh Lodha, Mohit Panchal, and Prof. Sameer G. Kulkarni",
displayDate: "2026",
date: "2026-01-20",
category: "award",
status: "confirmed",
sourceLabel: "COMSNETS awards",
sourceUrl: "https://www.comsnets.org/awards.html",
},
{
id: "himanshu-msr-fulbright",
title:
"Himanshu Beniwal receives Microsoft Research India PhD Award and Fulbright-Nehru Fellowship",
summary:
"The CSE/NLP doctoral student is recognized for work with the Lingo Research Group under Prof. Mayank Singh.",
people: "Himanshu Beniwal and Prof. Mayank Singh",
displayDate: "2025-26",
date: "2025-12-15",
category: "award",
status: "confirmed",
sourceLabel: "IITGN Connections v18i1",
sourceUrl: "https://iitgn.ac.in/assets/pdfs/connections/v18i1.pdf",
homepage: true,
},
{
id: "gayatri-google-phd-fellowship",
title: "Gayatri Priyadarsini Kancherla receives Google PhD Fellowship",
summary:
"The fellowship recognizes doctoral work in privacy, safety, and security.",
people: "Gayatri Priyadarsini Kancherla",
displayDate: "2025",
date: "2025-10-01",
category: "award",
status: "confirmed",
sourceLabel: "Google Research recipients",
sourceUrl:
"https://research.google/programs-and-events/phd-fellowship/recipients/",
homepage: true,
},
{
id: "dharaben-acm-india-dda",
title: "Dharaben R. Thakkar receives ACM India Doctoral Dissertation Award",
summary:
"The theoretical CSE dissertation award recognizes work advised by Prof. Bireswar Das.",
people: "Dr. Dharaben R. Thakkar and Prof. Bireswar Das",
displayDate: "2025",
date: "2025-09-01",
category: "award",
status: "confirmed",
sourceLabel: "ACM India",
sourceUrl: "https://india.acm.org/acm-india-doctoral-dissertation-award",
homepage: true,
},
{
id: "nipun-sigenergy-awards",
title: "Prof. Nipun Batra receives ACM SIGEnergy recognitions",
summary:
"Prof. Batra was recognized with ACM SIGEnergy Rising Star and e-Energy Test-of-Time awards.",
people: "Prof. Nipun Batra",
displayDate: "2025",
date: "2025-07-01",
category: "award",
status: "confirmed",
sourceLabel: "ACM SIGEnergy",
sourceUrl: "https://energy.acm.org/",
},
{
id: "inter-iit-2024-adobe-research",
title: "IITGN wins Inter-IIT silver in Adobe Research challenge",
summary:
"The team won silver at Inter-IIT Tech Meet 13.0 for a system to distinguish AI-generated images from real images.",
people:
"Shreyans Jain, Birudugadda Srivibhav, Chandrabhan Patel, Viraj Vekaria, Karan Gandhi, and Rugved Patil",
displayDate: "Dec 2024",
date: "2024-12-14",
category: "award",
status: "achievement",
sourceLabel: "IITGN Technical Council",
sourceUrl: "https://technical-council.iitgn.tech/achievements",
},
{
id: "inter-iit-2024-insolation-energy",
title: "IITGN wins Inter-IIT bronze in Insolation Energy challenge",
summary:
"The team won bronze at Inter-IIT Tech Meet 13.0 for predictive modeling of solar waste and scalable recycling solutions.",
people:
"Parth Deshpande, Siddharth Shah, Nakul S Raj, and Manasi Kulkarni",
displayDate: "Dec 2024",
date: "2024-12-14",
category: "award",
status: "achievement",
sourceLabel: "IITGN Technical Council",
sourceUrl: "https://technical-council.iitgn.tech/achievements",
},
{
id: "comi-lingua-emnlp-2025",
title: "COMI-LINGUA appears in EMNLP 2025 Findings",
summary:
"The Lingo Research Group dataset and paper support code-mixed Indian-language NLP.",
people: "Rajvee Sheth, Himanshu Beniwal, and Prof. Mayank Singh",
displayDate: "2025",
date: "2025-11-01",
category: "research",
status: "confirmed",
sourceLabel: "ACL Anthology",
sourceUrl: "https://aclanthology.org/events/findings-2025/",
},
{
id: "sentinelkildb-neurips-2025",
title: "SentinelKilnDB accepted at NeurIPS 2025 Datasets and Benchmarks",
summary:
"The hand-validated benchmark covers 62,671 brick kilns across South Asia for AI-supported air-pollution monitoring.",
people:
"Rishabh Mondal, Zeel B. Patel, collaborators, and Prof. Nipun Batra",
displayDate: "2025",
date: "2025-11-15",
category: "research",
status: "confirmed",
sourceLabel: "NeurIPS 2025",
sourceUrl: "https://neurips.cc/virtual/2025/loc/san-diego/poster/121530",
},
{
id: "shouvick-set-lab-2026",
title: "SET Lab expands GenAI-for-software-engineering research",
summary:
"The lab reports top-venue 2025-26 wins across LLM-code-agent faults, mutation evaluation, and academic research support.",
people: "Prof. Shouvick Mondal and SET Lab",
displayDate: "2025-26",
date: "2025-12-01",
category: "research",
status: "confirmed",
sourceLabel: "SET Lab site",
sourceUrl: "https://sites.google.com/view/shouvick/shouvick-mondal",
},
{
id: "vayubuddy-2024",
title: "VayuBuddy air-quality chatbot receives press coverage",
summary:
"The LLM-powered air-quality chatbot demonstrates CSE work on AI for social impact.",
people: "Prof. Nipun Batra and collaborators",
displayDate: "Dec 2024",
date: "2024-12-16",
category: "media",
status: "confirmed",
sourceLabel: "Indian Express e-paper",
sourceUrl:
"https://epaper.indianexpress.com/3953344/Ahmedabad/December-17-2024#page/3/2",
},
{
id: "ganga-1b-2024",
title: "Lingo Research Group releases Ganga-1B Hindi language model",
summary:
"The first pre-trained Hindi language model from the group was covered by AI and technology media.",
people: "Lingo Research Group and Prof. Mayank Singh",
displayDate: "Jul 2024",
date: "2024-07-08",
category: "media",
status: "confirmed",
sourceLabel: "TechChilli",
sourceUrl:
"https://techchilli.com/ai-india/iit-gandhinagar-unveils-ganga-1b-a-powerful-pre-trained-hindi-language-model/",
},
];
const sortByDateDesc = (items: DepartmentNewsItem[]) =>
[...items].sort(
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
);
export const allDepartmentNews = sortByDateDesc(departmentNews);
export const homepageNewsItems = sortByDateDesc(
departmentNews.filter((item) => item.homepage && item.category !== "award"),
).slice(0, 4);
export const homepageAwardItems = sortByDateDesc(
departmentNews.filter((item) => item.homepage && item.category === "award"),
).slice(0, 3);

24
src/data/postdocs.ts Normal file
View file

@ -0,0 +1,24 @@
export interface Postdoc {
name: string;
period: string;
phd: string;
phdInstitute: string;
workingWith: string;
}
export const POSTDOCS: Postdoc[] = [
{
name: 'Dr. Gourav Siddhad',
period: 'January 2026 - Present',
phd: 'PhD (CS 2020-2025)',
phdInstitute: 'IIT Roorkee',
workingWith: 'Yogesh Meena',
},
{
name: 'Dr. Gowtham Reddy',
period: 'September 2025 - Present',
phd: 'PhD (BE 2020-2025)',
phdInstitute: 'IIT Kharagpur',
workingWith: 'Yogesh Meena',
},
];

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
{
"generatedAt": null,
"items": []
}

View file

@ -0,0 +1,30 @@
[
{ "facultyId": "rajat-moona", "name": "Rajat Moona", "aliases": [], "areaSlugs": [], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "anirban-dasgupta", "name": "Anirban Dasgupta", "aliases": [], "areaSlugs": ["ai", "theory"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "bireswar-das", "name": "Bireswar Das", "aliases": [], "areaSlugs": ["theory"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "neeldhara-misra", "name": "Neeldhara Misra", "aliases": [], "areaSlugs": ["theory"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "nipun-batra", "name": "Nipun Batra", "aliases": [], "areaSlugs": ["ai", "systems"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "manoj-d-gupta", "name": "Manoj D Gupta", "aliases": ["Manoj D. Gupta"], "areaSlugs": ["theory"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "mayank-singh", "name": "Mayank Singh", "aliases": [], "areaSlugs": ["ai"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "sameer-g-kulkarni", "name": "Sameer G Kulkarni", "aliases": ["Sameer G. Kulkarni"], "areaSlugs": ["systems"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "balagopal-komarath", "name": "Balagopal Komarath", "aliases": [], "areaSlugs": ["theory"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "abhishek-bichhawat", "name": "Abhishek Bichhawat", "aliases": [], "areaSlugs": ["security"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "yogesh-kumar-meena", "name": "Yogesh Kumar Meena", "aliases": [], "areaSlugs": ["hci", "ai"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "shouvick-mondal", "name": "Shouvick Mondal", "aliases": [], "areaSlugs": ["systems"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "manisha-padala", "name": "Manisha Padala", "aliases": [], "areaSlugs": ["ai", "security"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "shanmuganathan-raman", "name": "Shanmuganathan Raman", "aliases": [], "areaSlugs": ["ai"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "udit-bhatia", "name": "Udit Bhatia", "aliases": [], "areaSlugs": ["ai", "data-science"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "krishna-prasad-miyapuram", "name": "Krishna Prasad Miyapuram", "aliases": [], "areaSlugs": ["hci", "ai"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "jyoti-krishnan", "name": "Jyoti Krishnan", "aliases": [], "areaSlugs": [], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "manu-awasthi", "name": "Manu Awasthi", "aliases": [], "areaSlugs": ["systems"], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "anup-kalbalia", "name": "Anup Kalbalia", "aliases": [], "areaSlugs": [], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "nirmal-kumar-sancheti", "name": "Nirmal Kumar Sancheti", "aliases": [], "areaSlugs": [], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "samit-bhattacharya", "name": "Samit Bhattacharya", "aliases": [], "areaSlugs": [], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "venkatesh-raman", "name": "Venkatesh Raman", "aliases": [], "areaSlugs": [], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "viraj-shah", "name": "Viraj Shah", "aliases": [], "areaSlugs": [], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "madhavan-unnikrishnan-nair", "name": "Madhavan Unnikrishnan Nair", "aliases": [], "areaSlugs": [], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "yuvraj-patel", "name": "Yuvraj Patel", "aliases": [], "areaSlugs": [], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "subir-verma", "name": "Subir Verma", "aliases": [], "areaSlugs": [], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "ambarish-ojha", "name": "Ambarish Ojha", "aliases": [], "areaSlugs": [], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} },
{ "facultyId": "k-gopinath", "name": "K. Gopinath", "aliases": ["K Gopinath"], "areaSlugs": [], "semanticScholarAuthorId": null, "dblpPid": null, "profiles": {} }
]

View file

@ -0,0 +1,59 @@
export interface ResearchScholar {
name: string;
program: 'Computer Science and Engineering' | 'Artificial Intelligence';
joined: string;
email: string;
image: string;
}
export const RESEARCH_SCHOLARS: ResearchScholar[] = [
{ name: 'Anant Kumar', program: 'Computer Science and Engineering', joined: '2019', email: 'kumar_anant@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1laer0zSBbSPZp-NBHC4wqJGTMDvX0dgW' },
{ name: 'Malaviya Jayesh Vallabhbhai', program: 'Computer Science and Engineering', joined: '2019', email: 'malaviya_jayesh@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1x-demSAE9lNmeyKLLiSER5JdgQ6dH9JK' },
{ name: 'Patel Zeel Bharatkumar', program: 'Computer Science and Engineering', joined: '2019', email: 'patel_zeel@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1go_KZ2h_2n1CzMyqCqidNqanu99ykfFQ' },
{ name: 'Jinia Ghosh', program: 'Computer Science and Engineering', joined: '2020', email: 'jiniag@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1RlcWvFk7bRVbsMbjeUUYFniWnTOjBNGe' },
{ name: 'Binita Maity', program: 'Computer Science and Engineering', joined: '2021', email: 'binitamaity@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1d3xCnQ67qjmSNuQLW57lop_DlHaSUZSq' },
{ name: 'Himanshu Beniwal', program: 'Computer Science and Engineering', joined: '2021', email: 'himanshubeniwal@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1kpUlhOmROR2NTi7WQse_Y1HHgm3NRLPF' },
{ name: 'K.K. Gayatri Priyadarsini', program: 'Computer Science and Engineering', joined: '2021', email: 'gayatripriyadarsini@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1xlT7lJP3ntP-gKmLtTUDlhBb0g6X164V' },
{ name: 'Kadasi Pritam Shankaraiah', program: 'Computer Science and Engineering', joined: '2021', email: 'pritam.k@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1WLEOVtX_FBKW5y7Opbeu5GGP1iwOpqZP' },
{ name: 'Shrutimoy Das', program: 'Computer Science and Engineering', joined: '2021', email: 'shrutimoydas@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1np7qydj2HoRGxz2UV-BmUFOF9lV_CK_l' },
{ name: 'Prajwal Kumar Singh', program: 'Computer Science and Engineering', joined: '2021', email: 'singh_prajwal@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1qYIxJ0leoNBNOgISDItOQB5etuAol2Py' },
{ name: 'Sarth Dubey', program: 'Computer Science and Engineering', joined: '2021', email: 'dubey_sarth@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1T-TVWfa8PYZdSaVTIkcud-bEhYItpi08' },
{ name: 'Shubhajit Roy', program: 'Computer Science and Engineering', joined: '2022', email: 'royshubhajit@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=19HUCjhZhOZbezTcr_z4ToIaGk434tQWQ' },
{ name: 'Akbar Ali', program: 'Computer Science and Engineering', joined: '2022', email: 'akbar.ali@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1jluVMJ2OacregX3LT2W6uwxnxIYQHXb1' },
{ name: 'Rishabh Mondal', program: 'Computer Science and Engineering', joined: '2023', email: 'rishabh.mondal@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1oDT5Lf7a6WgM5zbxRgrE48j3w7qHs4Y5' },
{ name: 'Muskan Priyadarshani', program: 'Computer Science and Engineering', joined: '2023', email: 'muskan.priyadarshani@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1yh--IrUqkgsAevp4Q2XLcYkqSUC_sd6r' },
{ name: 'Ayush Shrivastava', program: 'Computer Science and Engineering', joined: '2024', email: 'shrivastavaayush@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1LMgOCEzVVZbUaU-R3g6tsFCymkjtKepa' },
{ name: 'Rohit Narayanan', program: 'Computer Science and Engineering', joined: '2024', email: 'narayananrohit@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1OdUhS45c-F6PdTupa-mMNOvvk2Qj85RB' },
{ name: 'Udit Kumar', program: 'Computer Science and Engineering', joined: '2024', email: 'kumarudit@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1Z7unsB8HvhAxL83EYPxJIStXMZ4fyAZY' },
{ name: 'Priyanshi Agrawal', program: 'Computer Science and Engineering', joined: '2024', email: 'agrawalpriyanshi@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1OHyPrp54epe_RfymDaUkEXjXi8elSEgE' },
{ name: 'Arjun Badola', program: 'Artificial Intelligence', joined: '2024', email: 'arjun.badola@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1pN_HyrvrN6nYg0XF5Ruybas5CWdnSleS' },
{ name: 'Ayushman Singh', program: 'Computer Science and Engineering', joined: '2024', email: 'ayushman.singh@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=147rqDQqiLa50sFd5S0eA6T76kw1Fjxrj' },
{ name: 'Ekta Joshi', program: 'Computer Science and Engineering', joined: '2024', email: 'ekta.joshi@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1K89QHatiV88B3O_bEvc0sgZQzkJAx5iG' },
{ name: 'Isha Jain', program: 'Artificial Intelligence', joined: '2024', email: 'isha.jain@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1-VjFfA8_n_5ALJHCpY3XQ7jO_5z1lCCp' },
{ name: 'Koustav Das', program: 'Computer Science and Engineering', joined: '2024', email: 'koustav.das@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1SZ87T6usibPN7ScqWzRvIzziha0Dd2Oa' },
{ name: 'Ramanand', program: 'Artificial Intelligence', joined: '2024', email: 'ramanand.k@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1NsZmEkiA-F30g_F9bB_z_UNAisQFujUR' },
{ name: 'Sreyashi Karmakar', program: 'Computer Science and Engineering', joined: '2024', email: 'sreyashi.karmakar@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1bKw_glJO19Kwo1eca5qzlKIblk04uA_x' },
{ name: 'Vaishnav Koka', program: 'Computer Science and Engineering', joined: '2024', email: 'vaishnav.koka@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1v8owdqi3zNnM4xkJOKyeiXjJI6phDvfs' },
{ name: 'Yash Sahu', program: 'Computer Science and Engineering', joined: '2024', email: 'yash.sahu@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1Sl6uTURyizizm8IlD2P_-YclRrqVJrMg' },
{ name: 'Anupam Sharma', program: 'Computer Science and Engineering', joined: '2024', email: 'sharmaanupam@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1PS1qRBnGkRrrQXusfbu0fQG_tb8osIGf' },
{ name: 'Ashutosh Gupta', program: 'Artificial Intelligence', joined: '2024', email: 'ashutosh.gupta@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1T51DG6lI15xz1vu1uPrVNG7t_cW2XRAW' },
{ name: 'Ujjwal Kumar Gupta', program: 'Artificial Intelligence', joined: '2024', email: '24330048@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1X5kSwMyany6R9H_cRS0ED1cFkNHxUUEa' },
{ name: 'Saurabh Tripathi', program: 'Artificial Intelligence', joined: '2024', email: 'saurabh.tripathi@iitgn.ac.in', image: 'https://iitgn.ac.in/assets/img/rs/Grey.jpg' },
{ name: 'Vivek Kumar Yadav', program: 'Computer Science and Engineering', joined: '2024', email: 'yadav.vivek@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=1q_Je8Wu-gq5cyAnZ7t2v6DPQ-CNI2jHc' },
{ name: 'Subhrajit Das', program: 'Computer Science and Engineering', joined: '2024', email: 'subhrajit.das@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=184O9F1KCYc_flClJKtK-xhekr02bljpm' },
{ name: 'Hari Prapan', program: 'Computer Science and Engineering', joined: '2025', email: 'hari.prapan@iitgn.ac.in', image: 'https://iitgn.ac.in/assets/img/rs/Grey.jpg' },
{ name: 'Shubham Jaswal', program: 'Computer Science and Engineering', joined: '2025', email: 'shubham.jaswal@iitgn.ac.in', image: 'https://iitgn.ac.in/assets/img/rs/Grey.jpg' },
{ name: 'Manvendra Singh', program: 'Artificial Intelligence', joined: '2025', email: 'manvendra.singh@iitgn.ac.in', image: 'https://iitgn.ac.in/assets/img/rs/Grey.jpg' },
{ name: 'Devansh Yadav', program: 'Computer Science and Engineering', joined: '2025', email: 'devansh.yadav@iitgn.ac.in', image: 'https://drive.google.com/thumbnail?id=18KVy7nSMszf26izQw9EnruBvpvE9oKF0' },
{ name: 'Naren Kumar S', program: 'Computer Science and Engineering', joined: '2025', email: 'naren.kumar@iitgn.ac.in', image: 'https://iitgn.ac.in/assets/img/rs/Grey.jpg' },
{ name: 'Porika Rakesh', program: 'Computer Science and Engineering', joined: '2025', email: 'porika.rakesh@iitgn.ac.in', image: 'https://iitgn.ac.in/assets/img/rs/Grey.jpg' },
{ name: 'Dikshit Hegde', program: 'Computer Science and Engineering', joined: '2025', email: 'dikshit.hegde@iitgn.ac.in', image: 'https://iitgn.ac.in/assets/img/rs/Grey.jpg' },
{ name: 'Prasad Shivanshu Kant Shiv Kant', program: 'Computer Science and Engineering', joined: '2025', email: 'shivanshu.kant@iitgn.ac.in', image: 'https://iitgn.ac.in/assets/img/rs/Grey.jpg' },
{ name: 'Shruti Saxena', program: 'Computer Science and Engineering', joined: '2025', email: 'shruti.saxena@iitgn.ac.in', image: 'https://iitgn.ac.in/assets/img/rs/Grey.jpg' },
{ name: 'Thakker Riddhi Rajesh', program: 'Computer Science and Engineering', joined: '2025', email: 'riddhi.thakker@iitgn.ac.in', image: 'https://iitgn.ac.in/assets/img/rs/Grey.jpg' },
{ name: 'Gulshan Gupta', program: 'Computer Science and Engineering', joined: '2025', email: 'gulshan.gupta@iitgn.ac.in', image: 'https://iitgn.ac.in/assets/img/rs/Grey.jpg' },
{ name: 'Pravin Kumar Choudhary', program: 'Computer Science and Engineering', joined: '2025', email: 'pravin.choudhary@iitgn.ac.in', image: 'https://iitgn.ac.in/assets/img/rs/Grey.jpg' },
];
export const RESEARCH_SCHOLAR_SOURCE =
'https://iitgn.ac.in/people/students/cse';

829
src/data/seminars.ts Normal file
View file

@ -0,0 +1,829 @@
export type SeminarType =
| 'CSE seminar'
| 'CS theory seminar'
| 'Faculty candidate seminar'
| 'Virtual research seminar'
| 'Research proposal session'
| 'Teaching session';
export interface SeminarEntry {
id: string;
type: SeminarType;
title: string;
speaker: string;
affiliation?: string;
date: string;
displayDate: string;
time?: string;
venue?: string;
summary?: string;
abstract?: string;
bio?: string;
}
export const seminarTypeLabels: Record<SeminarType, string> = {
'CSE seminar': 'CSE Seminar',
'CS theory seminar': 'CS Theory Seminar',
'Faculty candidate seminar': 'Faculty Candidate Seminar',
'Virtual research seminar': 'Virtual Research Seminar',
'Research proposal session': 'Research Proposal Session',
'Teaching session': 'Teaching Session',
};
const aninditaMaitiBio =
'Dr. Anindita Maiti is a Postdoctoral Fellow at the Perimeter Institute for Theoretical Physics, cross-affiliated with Prof. Roger Melkos group Perimeter Institute Quantum Intelligence Lab (PIQuIL) since September 2023. Previously, Anindita held a short postdoctoral appointment in physics for ML foundations, supervised by Prof. Cengiz Pehlevan, at Harvard Applied Math (May-August 2023). She earned her PhD in theoretical high-energy physics (string theory and particle theory division) at Northeastern University and the NSF AI Institute for Artificial Intelligence and Fundamental Interactions (IAIFI) in May 2023, under the supervision of Prof. James Halverson. Anindita received her Integrated Bachelors and Masters in Engineering Physics at IIT Bombay in Aug 2017, supervised by Prof. Urjit Yajnik. Aninditas research lies at the intersection of AI/ML, quantum, and statistical physics. In short, she works on Physics of Learning and ML for Quantum. Broadly, Anindita uses theoretical physics concepts: such as Feynman path integrals, renormalization group flow, computational statistics, and random matrix theory, to develop a physics-informed theoretical foundation for ML and to guide the principled design of AI systems. Anindita applies this framework to construct interpretable and trustworthy AI- and ML-based simulation strategies for quantum field theory and quantum many-body physics.';
const rawSeminarEntries = [
{
id: 'anindita-maiti-research-proposal-session-2026',
type: 'Research proposal session',
title: 'Research Proposal Session of Dr. Anindita Maiti',
speaker: 'Dr. Anindita Maiti',
affiliation: 'Perimeter Institute for Theoretical Physics',
date: '2026-06-01',
displayDate: '1 Jun 2026',
time: '10:45 am IST',
venue: 'AB 13/124',
bio: aninditaMaitiBio,
},
{
id: 'anindita-maiti-teaching-session-2026',
type: 'Teaching session',
title: 'Markov and Chebyshev Inequalities with Applications',
speaker: 'Dr. Anindita Maiti',
affiliation: 'Perimeter Institute for Theoretical Physics',
date: '2026-06-01',
displayDate: '1 Jun 2026',
time: '11:30 am IST',
venue: 'AB 13/124',
bio: aninditaMaitiBio,
},
{
id: 'prateek-verma-llm-improvements-2026',
type: 'CSE seminar',
title:
'Full Stack LLM Improvements - From Input Representations to Internal Circuits to Test-Time Scaling',
speaker: 'Prateek Verma',
affiliation: 'Stanford',
date: '2026-05-25',
displayDate: '25 May 2026',
time: '11:30 am IST',
venue: 'AB 13/404',
summary:
'The talk examines improvements across modern LLMs: adaptive text and audio representations, internal circuits in large text models, adaptive LLMs that rewire based on input complexity, and test-time scaling for modalities beyond text.',
bio:
'Prateek Verma is a researcher affiliated with Stanford whose work spans AI, LLMs, NLP, signal processing, speech, and audio.',
},
{
id: 'rijoy-mukherjee-pipelined-processors-2026',
type: 'Teaching session',
title: 'Pipelined Processors',
speaker: 'Dr. Rijoy Mukherjee',
affiliation: 'Synopsys',
date: '2026-05-14',
displayDate: '14 May 2026',
time: '10:00 am IST',
venue: 'AB 13/125',
bio:
'Dr. Rijoy Mukherjee is a Staff R&D Engineer in the VC Formal R&D group at Synopsys. He earned his PhD in Computer Science and Engineering from IIT Kharagpur in 2025.',
},
{
id: 'rijoy-mukherjee-eda-hardware-security-2026',
type: 'Faculty candidate seminar',
title: 'Intelligent Electronic Design Automation for Hardware Security',
speaker: 'Dr. Rijoy Mukherjee',
affiliation: 'Synopsys',
date: '2026-05-13',
displayDate: '13 May 2026',
time: '10:30 am IST',
venue: 'AB 13/126',
summary:
'The seminar presents a unified approach to hardware security for domain-specific accelerators, integrating learning-driven analysis, intermediate-representation-level security, and language-model-assisted verification across the electronic design automation pipeline.',
bio:
'Dr. Rijoy Mukherjee is a Staff R&D Engineer in the VC Formal R&D group at Synopsys. He earned his PhD in Computer Science and Engineering from IIT Kharagpur in 2025.',
},
{
id: 'rijoy-mukherjee-research-proposal-session-2026',
type: 'Research proposal session',
title: 'Research Proposal Session of Dr. Rijoy Mukherjee',
speaker: 'Dr. Rijoy Mukherjee',
affiliation: 'Synopsys',
date: '2026-05-13',
displayDate: '13 May 2026',
time: '11:45 am IST',
venue: 'AB 13/126',
bio:
'Dr. Rijoy Mukherjee is a Staff R&D Engineer in the VC Formal R&D group at Synopsys. He earned his PhD in Computer Science and Engineering from IIT Kharagpur in 2025.',
},
{
id: 'ishaan-batta-spectral-clustering-2026',
type: 'Teaching session',
title: 'Spectral Clustering and Applications',
speaker: 'Dr. Ishaan Batta',
affiliation: 'Georgia State University TReNDS',
date: '2026-05-12',
displayDate: '12 May 2026',
time: '11:00 am IST',
venue: 'AB 13/126',
bio:
'Dr. Ishaan Batta is a postdoctoral research associate at the Center for Translational Research in Neuroimaging and Data Science, Georgia State University.',
},
{
id: 'ishaan-batta-neuroimaging-2026',
type: 'Faculty candidate seminar',
title:
'Learning Assessment-Aware Brain Representations from Multimodal Neuroimaging Data',
speaker: 'Dr. Ishaan Batta',
affiliation: 'Georgia State University TReNDS',
date: '2026-05-11',
displayDate: '11 May 2026',
time: '10:30 am IST',
venue: 'AB 13/126',
summary:
'The seminar presents interpretable representation learning frameworks for multimodal neuroimaging, including multimodal active subspace analysis, constrained source-based salience, and conditional graph variational autoencoders for clinical-assessment-aware brain representations.',
bio:
'Dr. Ishaan Batta is a postdoctoral research associate at the Center for Translational Research in Neuroimaging and Data Science, Georgia State University.',
},
{
id: 'ishaan-batta-research-proposal-session-2026',
type: 'Research proposal session',
title: 'Research Proposal Session of Dr. Ishaan Batta',
speaker: 'Dr. Ishaan Batta',
affiliation: 'Georgia State University TReNDS',
date: '2026-05-11',
displayDate: '11 May 2026',
time: '11:45 am IST',
venue: 'AB 13/126',
bio:
'Dr. Ishaan Batta is a postdoctoral research associate at the Center for Translational Research in Neuroimaging and Data Science, Georgia State University.',
},
{
id: 'nishad-kothari-solitude-graphs-2026',
type: 'CSE seminar',
title:
"Investigating Solitude in 2-Connected 3-Regular Graphs, and in R-Graphs, Through the Lens of CLM's Dependence Relation",
speaker: 'Prof. Nishad Kothari',
affiliation: 'IIT Madras',
date: '2026-04-24',
displayDate: '24 Apr 2026',
time: '11:00 am IST',
venue: 'AB 7/207',
summary:
'The talk studies solitary edges in 2-connected 3-regular graphs and r-graphs, using the dependence relation introduced by Carvalho, Lucchesi, and Murty to generalize characterizations and bounds for perfect matchings.',
},
{
id: 'anindita-maiti-ai-physics-2026',
type: 'Virtual research seminar',
title:
'Physics for AI/ML Robustness, Interpretability, and Uncertainty Quantification',
speaker: 'Dr. Anindita Maiti',
affiliation: 'Perimeter Institute for Theoretical Physics',
date: '2026-04-14',
displayDate: '14 Apr 2026',
time: '5:30 pm IST',
venue: 'Online',
summary:
'The talk connects theoretical physics with AI and ML, covering in-context learning theory, neural-network field-theory correspondence, and renormalization-group-inspired approaches for interpretable and uncertainty-aware learning.',
bio:
'Dr. Anindita Maiti is a Postdoctoral Fellow at the Perimeter Institute for Theoretical Physics, cross-affiliated with the Perimeter Institute Quantum Intelligence Lab.',
},
{
id: 'aditya-subramanian-online-connectivity-augmentation-2026',
type: 'CS theory seminar',
title: 'Online Connectivity Augmentation',
speaker: 'Aditya Subramanian',
affiliation: 'IISc Bangalore',
date: '2026-04-07',
displayDate: '7 Apr 2026',
time: '5:00 pm IST',
venue: 'Online',
summary:
'The talk presents new results for online connectivity augmentation, including an optimal deterministic O(log n)-competitive algorithm for general online CAP and weighted and random-order variants.',
bio:
'Aditya Subramanian is a PhD student in the Department of Computer Science and Automation at IISc Bangalore, advised by Prof. Arindam Khan.',
},
{
id: 'venkatakeerthy-ml4code-2026',
type: 'CSE seminar',
title: 'ML4Code: Learning to Represent, Optimize, and Understand Programs',
speaker: 'Dr. S. VenkataKeerthy',
affiliation: 'Microsoft Research India / IIT Hyderabad',
date: '2026-03-27',
displayDate: '27 Mar 2026',
time: '11:30 am IST',
venue: 'AB 13/126',
summary:
'The talk presents machine-learning techniques for software systems, including semantic code embeddings, reinforcement-learning-based compiler optimization, and scalable program understanding for similarity, security, and comprehension tasks.',
bio:
'S. VenkataKeerthy is a postdoctoral researcher at Microsoft Research India and a PhD candidate in Computer Science and Engineering at IIT Hyderabad.',
},
{
id: 'anik-paul-mirror-descent-2026',
type: 'CSE seminar',
title:
'Almost Sure Convergence Analysis of Stochastic First and Zeroth-Order Mirror Descent Algorithm: A Projected Dynamical Systems Viewpoint',
speaker: 'Dr. Anik Paul',
affiliation: 'IISc Bangalore',
date: '2026-03-25',
displayDate: '25 Mar 2026',
time: '10:00 am IST',
venue: 'AB 13/126',
summary:
'The talk connects mirror descent and continuous-time projected dynamical systems, establishing almost-sure convergence results for stochastic mirror descent and stochastic zeroth-order mirror descent under uncertainty.',
bio:
'Anik Kumar Paul is a Walmart Postdoctoral Fellow in the Department of Computer Science and Automation at IISc Bangalore.',
},
{
id: 'aditya-anand-managed-runtimes-2026',
type: 'CSE seminar',
title:
'Precision without Regret: Sound and Efficient Memory Optimization in Managed Runtimes',
speaker: 'Aditya Anand',
affiliation: 'IIT Bombay',
date: '2026-03-24',
displayDate: '24 Mar 2026',
time: '11:30 am IST',
venue: 'AB 13/126',
summary:
'The talk presents static and dynamic techniques for precise escape analysis and stack allocation in managed runtimes, using dynamic heapification and speculative optimization to reduce garbage collection while preserving correctness.',
bio:
'Aditya Anand is a PhD scholar in Computer Science and Engineering at IIT Bombay, working on compiler optimizations and program analysis.',
},
{
id: 'utsav-singh-hierarchical-control-2026',
type: 'CSE seminar',
title: 'Learning Hierarchical Control via Feasible Subgoal Prediction',
speaker: 'Dr. Utsav Singh',
affiliation: 'IIT Kanpur / University of Central Florida',
date: '2026-03-23',
displayDate: '23 Mar 2026',
time: '11:30 am IST',
venue: 'AB 13/126',
summary:
'The talk discusses hierarchical reinforcement learning methods that train high-level policies to propose feasible subgoals, improving stability and performance for long-horizon robotic navigation and manipulation tasks.',
bio:
'Utsav Singh received his PhD from IIT Kanpur and recently served as a Visiting Research Scholar at the University of Central Florida.',
},
{
id: 'lakshay-saggi-disjoint-shortest-paths-2026',
type: 'CS theory seminar',
title:
'Efficient Algorithms for the Disjoint Shortest Paths Problem and Its Extensions',
speaker: 'Lakshay Saggi',
affiliation: 'IIT Delhi',
date: '2026-03-19',
displayDate: '19 Mar 2026',
time: '5:00 pm IST',
venue: 'Online',
summary:
'The seminar discusses faster algorithms for the 2-Disjoint Shortest Paths problem and extensions that minimize vertex intersections between shortest paths in directed graphs, DAGs, and undirected graphs.',
bio:
'Lakshay Saggi is a PhD student in Computer Science and Engineering at IIT Delhi, advised by Prof. Naveen Garg and Prof. Keerti Choudhary.',
},
{
id: 'janki-bhimani-system-design-2026',
type: 'CSE seminar',
title: 'Advancing System Design: Classical and Quantum',
speaker: 'Dr. Janki Bhimani',
affiliation: 'Florida International University',
date: '2026-03-13',
displayDate: '13 Mar 2026',
time: '3:30 pm IST',
venue: 'AB 10/202',
summary:
'The talk presents a system-design view spanning classical and quantum computing, with research thrusts in emerging memory and storage systems, storage reliability under environmental stress, and intelligent design methodologies.',
bio:
'Dr. Janki Bhimani is on the faculty at Florida International University. Her research spans system design, storage systems, machine learning, quantum computing, and electronic design automation.',
},
{
id: 'venkatesh-raman-nseth-2026',
type: 'CSE seminar',
title: '(N)SETH and Fine-Grained Lower Bounds',
speaker: 'Prof. Venkatesh Raman',
affiliation: 'Institute of Mathematical Sciences, Chennai',
date: '2026-03-13',
displayDate: '13 Mar 2026',
time: '11:30 am IST',
venue: 'AB 13/126',
summary:
'The seminar introduces SETH, a nondeterministic version called NSETH, and consequences for fine-grained lower bounds, including improved algorithms for Max2SAT and the complement of 3-SUM.',
},
{
id: 'rajeev-shorey-edge-computing-2026',
type: 'CSE seminar',
title: 'Recent Investigations in the Intersection of ML and Edge Computing',
speaker: 'Prof. Rajeev Shorey',
affiliation: 'IIIT Surat / IIT Delhi / IIT Gandhinagar',
date: '2026-03-12',
displayDate: '12 Mar 2026',
time: '3:45 pm IST',
venue: 'AB 13/126',
bio:
'Prof. Rajeev Shorey is Director of IIIT Surat and Adjunct Faculty in Computer Science and Engineering at IIT Delhi and IIT Gandhinagar.',
},
{
id: 'ashwin-verma-optimizers-for-learning-2026',
type: 'Teaching session',
title: 'Optimizers for Learning: SGD, Adam, RMSProp',
speaker: 'Dr. Ashwin Verma',
affiliation: 'Purdue University',
date: '2026-03-03',
displayDate: '3 Mar 2026',
time: '10:30 am IST',
venue: 'AB 13/125',
bio:
'Dr. Ashwin Verma is a Postdoctoral Researcher in the School of Electrical and Computer Engineering at Purdue University.',
},
{
id: 'ashwin-verma-distributed-learning-2026',
type: 'Faculty candidate seminar',
title: 'Distributed Learning under Adaptive Information Constraints',
speaker: 'Dr. Ashwin Verma',
affiliation: 'Purdue University',
date: '2026-03-02',
displayDate: '2 Mar 2026',
time: '10:30 am IST',
venue: 'AB 13/125',
summary:
'The seminar studies distributed learning under limited communication, evolving network topology, and unreliable sources, with results for adaptive communication in distributed convex optimization and unsupervised recovery from noisy crowd labels.',
bio:
'Dr. Ashwin Verma is a Postdoctoral Researcher in the School of Electrical and Computer Engineering at Purdue University.',
},
{
id: 'ashwin-verma-research-proposal-session-2026',
type: 'Research proposal session',
title: 'Research Proposal Session of Dr. Ashwin Verma',
speaker: 'Dr. Ashwin Verma',
affiliation: 'Purdue University',
date: '2026-03-02',
displayDate: '2 Mar 2026',
time: '11:45 am IST',
venue: 'AB 13/125',
bio:
'Dr. Ashwin Verma is a Postdoctoral Researcher in the School of Electrical and Computer Engineering at Purdue University.',
},
{
id: 'parth-paritosh-networked-inference-2026',
type: 'Faculty candidate seminar',
title: 'Scalable & Trustworthy Inference in Networked Mission Critical Systems',
speaker: 'Dr. Parth Paritosh',
affiliation: 'DEVCOM Army Research Laboratory',
date: '2026-02-23',
displayDate: '23 Feb 2026',
time: '10:30 am IST',
venue: 'AB 6/201',
summary:
'The seminar presents scalable, privacy-preserving, and resilient probabilistic inference algorithms for networked mission-critical systems, with applications in robotics, autonomous transit, infrastructure management, and defense.',
bio:
'Dr. Parth Paritosh is a postdoctoral fellow in the Military Information Sciences division at DEVCOM Army Research Laboratory.',
},
{
id: 'parth-paritosh-research-proposal-session-2026',
type: 'Research proposal session',
title: 'Research Proposal Session of Dr. Parth Paritosh',
speaker: 'Dr. Parth Paritosh',
affiliation: 'DEVCOM Army Research Laboratory',
date: '2026-02-23',
displayDate: '23 Feb 2026',
time: '11:45 am IST',
venue: 'AB 6/201',
bio:
'Dr. Parth Paritosh is a postdoctoral fellow in the Military Information Sciences division at DEVCOM Army Research Laboratory.',
},
{
id: 'parth-paritosh-3d-rotation-representations-2026',
type: 'Teaching session',
title: '3D Rotation Representations',
speaker: 'Dr. Parth Paritosh',
affiliation: 'DEVCOM Army Research Laboratory',
date: '2026-02-23',
displayDate: '23 Feb 2026',
time: '3:30 pm IST',
venue: 'AB 6/201',
bio:
'Dr. Parth Paritosh is a postdoctoral fellow in the Military Information Sciences division at DEVCOM Army Research Laboratory.',
},
{
id: 'tameesh-suri-ai-codesign-2026',
type: 'CSE seminar',
title: 'Enabling the Next 1000x in AI: Co-Design from Materials to Systems',
speaker: 'Dr. Tameesh Suri',
affiliation: 'NVIDIA',
date: '2026-02-20',
displayDate: '20 Feb 2026',
time: '5:00 pm IST',
venue: 'AB 13/124',
summary:
'The talk argues that the next 1000x improvement in AI compute will require co-design across materials, devices, architecture, packaging, and systems rather than traditional scaling alone.',
bio:
'Dr. Tameesh Suri is a Principal Architect in the HPC + AI group at NVIDIA, Santa Clara.',
},
{
id: 'biswabandan-panda-architecture-security-2026',
type: 'CSE seminar',
title: 'Architecture Security: The Good, the Bad, and the Ugly',
speaker: 'Dr. Biswabandan Panda',
affiliation: 'IIT Bombay',
date: '2026-02-19',
displayDate: '19 Feb 2026',
time: '3:30 pm IST',
venue: 'AB 13/126',
},
{
id: 'vaibhav-krishan-boolean-circuits-2026',
type: 'CSE seminar',
title: 'Frontiers in Boolean Circuit Lower Bounds',
speaker: 'Dr. Vaibhav Krishan',
affiliation: 'Institute of Mathematical Sciences, Chennai',
date: '2026-02-19',
displayDate: '19 Feb 2026',
time: '11:30 am IST',
venue: 'AB 13/126',
summary:
'The talk discusses Boolean circuit lower bounds, foundational results for restricted circuit classes, and recent advances along major directions in circuit complexity.',
bio:
'Vaibhav Krishan is a postdoctoral researcher at the Institute of Mathematical Sciences, Chennai. He completed his PhD at IIT Bombay.',
},
{
id: 'sneha-mohanty-pinball-wizards-2026',
type: 'CS theory seminar',
title: 'How Pinball Wizards Simulate a Turing Machine',
speaker: 'Sneha Mohanty',
affiliation: 'University of Freiburg',
date: '2026-02-19',
displayDate: '19 Feb 2026',
time: '5:15 pm IST',
venue: 'Online',
summary:
'The seminar introduces the Pinball Wizard problem and shows how an idealized pinball system with gates, walls, and bumpers can simulate a two-stack pushdown automaton, making the problem Turing-complete.',
bio:
'Sneha Mohanty is a PhD student in Computer Science at the University of Freiburg, working on computational complexity of illumination and ray-tracing-based models.',
},
{
id: 'biswabandan-panda-microarchitecture-2026',
type: 'CSE seminar',
title: 'Microarchitecture Mysteries in Datacenter Processors',
speaker: 'Dr. Biswabandan Panda',
affiliation: 'IIT Bombay',
date: '2026-02-18',
displayDate: '18 Feb 2026',
time: '11:30 am IST',
venue: 'AB 1/101',
},
{
id: 'samarth-brahmabhatt-homography-matrix-2026',
type: 'Teaching session',
title: 'Homography Matrix - Theory and Applications',
speaker: 'Dr. Samarth Brahmabhatt',
affiliation: 'Overland AI',
date: '2026-02-13',
displayDate: '13 Feb 2026',
time: '11:30 am IST',
venue: 'AB 6/202',
bio:
'Dr. Samarth Brahmabhatt is Technical Lead for Perception at Overland AI. He previously worked at Intel Labs after completing his PhD at Georgia Tech.',
},
{
id: 'samarth-brahmabhatt-robotic-manipulation-2026',
type: 'Faculty candidate seminar',
title: 'Robotic Manipulation for Advanced Manufacturing',
speaker: 'Dr. Samarth Brahmabhatt',
affiliation: 'Overland AI',
date: '2026-02-12',
displayDate: '12 Feb 2026',
time: '11:30 am IST',
venue: 'AB 10/102',
summary:
'The seminar surveys challenges in reliable robotic manipulation for advanced manufacturing, then presents work on perception, imitation of human manipulation behaviors, and policy learning for contact-rich manipulation skills.',
bio:
'Dr. Samarth Brahmabhatt is Technical Lead for Perception at Overland AI. He previously worked at Intel Labs after completing his PhD at Georgia Tech.',
},
{
id: 'samarth-brahmabhatt-research-proposal-session-2026',
type: 'Research proposal session',
title: 'Research Proposal Session of Dr. Samarth Brahmabhatt',
speaker: 'Dr. Samarth Brahmabhatt',
affiliation: 'Overland AI',
date: '2026-02-12',
displayDate: '12 Feb 2026',
time: '12:40 pm IST',
venue: 'AB 10/102',
bio:
'Dr. Samarth Brahmabhatt is Technical Lead for Perception at Overland AI. He previously worked at Intel Labs after completing his PhD at Georgia Tech.',
},
{
id: 'akhil-s-algorithmic-information-theory-2026',
type: 'CS theory seminar',
title: 'Algorithmic Information Theory: Some History and Some Recent Developments',
speaker: 'Akhil S.',
affiliation: 'IIT Kanpur',
date: '2026-02-11',
displayDate: '11 Feb 2026',
time: '5:00 pm IST',
venue: 'Online',
summary:
'The talk introduces Kolmogorov complexity, randomness for infinite sequences, constructive dimension, and polynomial-time dimension, including links between robustness questions and one-way functions.',
bio:
'Akhil S. is a doctoral student at IIT Kanpur working with Prof. Satyadev Nandakumar.',
},
{
id: 'ajay-singh-memory-reclamation-2026',
type: 'Faculty candidate seminar',
title: 'High Performance Memory Reclamation in Concurrent Data Structures',
speaker: 'Dr. Ajay Singh',
affiliation: 'ICS FORTH / University of Waterloo',
date: '2026-02-09',
displayDate: '9 Feb 2026',
time: '11:30 am IST',
venue: 'AB 13/126',
summary:
'The seminar presents three safe memory reclamation methods for optimistic and lock-free concurrent data structures, with a focus on speed, scalability, usability, bounded memory footprint, and production readiness.',
bio:
'Dr. Ajay Singh received his PhD in Computer Science from the University of Waterloo in 2024 and is a postdoctoral researcher at ICS FORTH in Greece.',
},
{
id: 'ajay-singh-research-proposal-session-2026',
type: 'Research proposal session',
title: 'Research Proposal Session of Dr. Ajay Singh',
speaker: 'Dr. Ajay Singh',
affiliation: 'ICS FORTH / University of Waterloo',
date: '2026-02-09',
displayDate: '9 Feb 2026',
time: '12:30 pm IST',
venue: 'AB 13/126',
bio:
'Dr. Ajay Singh received his PhD in Computer Science from the University of Waterloo in 2024 and is a postdoctoral researcher at ICS FORTH in Greece.',
},
{
id: 'ajay-singh-process-synchronization-2026',
type: 'Teaching session',
title: 'Process Synchronization in Operating Systems',
speaker: 'Dr. Ajay Singh',
affiliation: 'ICS FORTH / University of Waterloo',
date: '2026-02-09',
displayDate: '9 Feb 2026',
time: '2:00 pm IST',
venue: 'AB 13/126',
bio:
'Dr. Ajay Singh received his PhD in Computer Science from the University of Waterloo in 2024 and is a postdoctoral researcher at ICS FORTH in Greece.',
},
{
id: 'utkarsh-mall-visual-discovery-2026',
type: 'CSE seminar',
title: 'Visual Discovery for Science',
speaker: 'Dr. Utkarsh Mall',
affiliation: 'MBZUAI',
date: '2026-02-06',
displayDate: '6 Feb 2026',
time: '11:30 am IST',
venue: 'AB 13/126',
summary:
'The talk covers label-efficient vision and multimodal foundation models for scientific domains, including remote sensing and interpretable vision-language models for scientific discovery.',
bio:
'Utkarsh Mall is an Assistant Professor of Computer Vision at Mohamed bin Zayed University of Artificial Intelligence.',
},
{
id: 'adithya-kumar-dataflow-analysis-2026',
type: 'Teaching session',
title: 'Dataflow Analysis',
speaker: 'Dr. Adithya Kumar',
affiliation: 'Meta FAIR',
date: '2026-02-04',
displayDate: '4 Feb 2026',
time: '10:00 am IST',
venue: 'Online',
bio:
'Dr. Adithya Kumar is a software engineer at Meta FAIR Labs. He received his PhD in Computer Science and Engineering from Pennsylvania State University.',
},
{
id: 'lakshmi-mandal-reinforcement-learning-2026',
type: 'Faculty candidate seminar',
title: 'Multi-Timescale and Multi-Agent Reinforcement Learning Algorithms',
speaker: 'Dr. Lakshmi Mandal',
affiliation: 'IBM Research',
date: '2026-02-02',
displayDate: '2 Feb 2026',
time: '11:30 am IST',
venue: 'AB 13/126',
summary:
'The seminar presents multi-timescale and multi-agent reinforcement learning algorithms, with theoretical convergence guarantees and experimental results across reinforcement learning tasks.',
bio:
'Dr. Lakshmi Mandal is a Research Scientist at IBM Research, Bangalore. She completed her PhD in Computer Science and Engineering at IISc Bangalore in 2025.',
},
{
id: 'lakshmi-mandal-research-proposal-session-2026',
type: 'Research proposal session',
title: 'Research Proposal Session of Dr. Lakshmi Mandal',
speaker: 'Dr. Lakshmi Mandal',
affiliation: 'IBM Research',
date: '2026-02-02',
displayDate: '2 Feb 2026',
time: '12:30 pm IST',
venue: 'AB 13/126',
bio:
'Dr. Lakshmi Mandal is a Research Scientist at IBM Research, Bangalore. She completed her PhD in Computer Science and Engineering at IISc Bangalore in 2025.',
},
{
id: 'lakshmi-mandal-multi-agent-rl-teaching-2026',
type: 'Teaching session',
title: 'Introduction to Multi Agent Reinforcement Learning',
speaker: 'Dr. Lakshmi Mandal',
affiliation: 'IBM Research',
date: '2026-02-02',
displayDate: '2 Feb 2026',
time: '3:30 pm IST',
venue: 'AB 7/208',
bio:
'Dr. Lakshmi Mandal is a Research Scientist at IBM Research, Bangalore. She completed her PhD in Computer Science and Engineering at IISc Bangalore in 2025.',
},
{
id: 'nisarg-shah-fair-ai-2026',
type: 'CSE seminar',
title: 'Democratic Foundations of Fair AI via Social Choice',
speaker: 'Dr. Nisarg Shah',
affiliation: 'University of Toronto',
date: '2026-01-29',
displayDate: '29 Jan 2026',
time: '2:00 pm IST',
venue: 'AB 13/125',
summary:
'The talk presents an algorithmic fairness approach inspired by democratic principles, connecting computational social choice to AI applications such as classification, clustering, reinforcement learning, and AI alignment.',
bio:
'Nisarg Shah is an Associate Professor of Computer Science at the University of Toronto and a Research Lead for Ethics of AI at the Schwartz Reisman Institute.',
},
{
id: 'adithya-kumar-systems-heterogeneity-2026',
type: 'Virtual research seminar',
title: 'Designing System Software in the Era of Heterogeneity',
speaker: 'Dr. Adithya Kumar',
affiliation: 'Meta FAIR',
date: '2026-01-19',
displayDate: '19 Jan 2026',
time: '11:30 am IST',
venue: 'Online',
summary:
'The seminar discusses system-software design for heterogeneous datacenter platforms, including SplitRPC, an RPC framework that reduces ML inference overheads by using SmartNIC capabilities.',
bio:
'Dr. Adithya Kumar is a software engineer at Meta FAIR Labs. He received his PhD in Computer Science and Engineering from Pennsylvania State University.',
},
{
id: 'ambarish-ojha-ai-regulated-worlds-2026',
type: 'CSE seminar',
title: 'AI in Regulated Worlds: How to Build Systems That Banks and Boards Can Trust',
speaker: 'Ambarish Ojha',
date: '2026-01-09',
displayDate: '9 Jan 2026',
time: '11:45 am IST',
venue: 'AB 13/404',
bio:
'Ambarish Ojha is a technology and customer-success leader with experience across enterprise software, platforms, services, robotic process automation, embedded finance, and IT advisory for banking.',
},
{
id: 'harsh-parikh-causal-inference-2026',
type: 'CSE seminar',
title: 'Regularizing Extrapolation in Causal Inference',
speaker: 'Prof. Harsh Parikh',
affiliation: 'Yale University',
date: '2026-01-08',
displayDate: '8 Jan 2026',
time: '11:30 am IST',
venue: 'AB 13/126',
summary:
'The talk presents a framework that penalizes extrapolation in linear smoother estimators, introducing a bias-bias-variance tradeoff and optimization procedure for causal inference under poor positivity and model misspecification.',
bio:
'Harsh Parikh is an Assistant Professor in the Department of Biostatistics at Yale University.',
},
{
id: 'soumyajit-chatterjee-transport-layer-protocols-2026',
type: 'Teaching session',
title: 'Transport Layer Protocols',
speaker: 'Dr. Soumyajit Chatterjee',
affiliation: 'Brave Software Research / University of Cambridge',
date: '2026-01-06',
displayDate: '6 Jan 2026',
time: '11:30 am IST',
venue: 'AB 13/126',
bio:
'Dr. Soumyajit Chatterjee is a Systems and Performance Researcher at Brave Software Research and a Visiting Scholar at the University of Cambridge.',
},
{
id: 'soumyajit-chatterjee-edge-intelligence-2026',
type: 'Faculty candidate seminar',
title:
'Bringing Intelligence to the Edge: Resolving Challenges of Labeling and Collecting Information at the Edge',
speaker: 'Dr. Soumyajit Chatterjee',
affiliation: 'Brave Software Research / University of Cambridge',
date: '2026-01-05',
displayDate: '5 Jan 2026',
time: '11:30 am IST',
venue: 'AB 13/126',
summary:
'The seminar examines efficient machine learning at the edge, including automated annotation, federated learning in multi-device ecosystems, on-device training, and test-time adaptation for robust deployment.',
bio:
'Dr. Soumyajit Chatterjee is a Systems and Performance Researcher at Brave Software Research and a Visiting Scholar at the University of Cambridge.',
},
{
id: 'soumyajit-chatterjee-research-proposal-session-2026',
type: 'Research proposal session',
title: 'Research Proposal Session of Dr. Soumyajit Chatterjee',
speaker: 'Dr. Soumyajit Chatterjee',
affiliation: 'Brave Software Research / University of Cambridge',
date: '2026-01-05',
displayDate: '5 Jan 2026',
time: '12:30 pm IST',
venue: 'AB 13/126',
bio:
'Dr. Soumyajit Chatterjee is a Systems and Performance Researcher at Brave Software Research and a Visiting Scholar at the University of Cambridge.',
},
] satisfies SeminarEntry[];
const seminarAbstracts: Partial<Record<string, string>> = {
'prateek-verma-llm-improvements-2026':
'This talk will open up and improve components of modern LLMs. First, we explore how crucial the representations we feed into generative models for text and audio are. Can we make tokenization adaptive? Can we improve audioLLM performance by leveraging both discrete and continuous representations? We show that by just incorporating better inputs, audioLLMs can match the performance of models 10x larger on LibriSpeech.\n\nSecondly, we harness and manipulate the internal circuits of billion+ parameter text-LLMs. Trained solely on next-text token prediction, it implicitly learns a rich set of latent functions and circuits that, when activated, can be used for other tasks. GPT weights, for instance, can enable models to see and hear, improving with scale. We further introduce the concept of adaptive LLMs. Depending on input complexity, the GPT model can dynamically rewire itself, thereby not wasting massive decoder layers that end up learning trivial functions.\n\nFinally, we extend the ideas of test-time scaling and thinking to modalities beyond text. By keeping the model weights and the input fixed, we explore how we can enable them to think during inference, significantly boosting baseline performance.',
'rijoy-mukherjee-eda-hardware-security-2026':
'Hardware security for domain-specific accelerators has gained significant attention, with a growing number of techniques aiming to protect intellectual property and detect malicious modifications during the design flow. Some of these approaches focus on post-silicon validation or isolated protection mechanisms, but fail to provide security guarantees throughout the design automation pipeline. Conversely, recent advances in electronic design automation (EDA) leverage machine learning and compiler-level analysis; yet such systems often overlook security as a first-class objective.\n\nMy research explores the vulnerabilities and presents a unified approach to hardware security for domain-specific accelerators, seamlessly integrating intelligent techniques across multiple stages of the EDA flow, enabling automated protection and verification within a single framework. Our methodology incorporates learning-driven analysis, intermediate-representation-level security, and language-model-assisted verification to address security vulnerabilities in modern hardware design.\n\nWe first analyze the limitations of existing DNN weight protection schemes by demonstrating effective known-plaintext attacks, exposing their susceptibility to model extraction. To address this, we propose a selective encryption strategy that protects critical weights while maintaining design efficiency. We then introduce a novel class of hardware Trojans embedded in the compilers intermediate representation, showing how subtle transformations during high-level synthesis can compromise security, and develop automated detection mechanisms within the synthesis flow. Finally, we present a language-model-driven verification framework that generates test scenarios for synthesized RTL, enabling scalable and automated detection of security violations. Experimental results across representative hardware benchmarks demonstrate that the proposed techniques significantly improve IP protection and hardware Trojan detection capabilities, while incurring minimal overhead. The unified, intelligent EDA methodology provides a scalable and automation-friendly pathway toward secure hardware design.',
'ishaan-batta-neuroimaging-2026':
'Standard supervised learning on neuroimaging data optimizes for diagnostic prediction while yielding feature-level importance scores that lack network-level, assessment-specific interpretability required for biomarker discovery; while unsupervised methods reduce data dimensions leading to loss of assessment-specific information. This talk presents frameworks developed towards addressing these gaps via biologically interpretable methodologies for neuroimaging data analysis.\n\nFirst, a multimodal active subspace analysis framework to compute multiple salient directions that define the gradient space of a prediction function learned on brain features, followed by repeated analysis to extract consistent and robust assessment-oriented subspace centers: compact multimodal representations of co-varying brain regions and functional connections maximally associated with a target clinical assessment. Second, an interpretable deep learning framework, constrained source-based salience, that embeds active subspace learning and spatially constrained ICA directly into the saliency space of trained deep learning architectures, producing network-level full-brain visualizations anchored around spatial brain templates. Lastly, it will include some of the ongoing work on a conditional graph variational autoencoder that encodes static functional network connectivity in the brain into a structured latent space conditioned on demographic and cognitive variables.\n\nCollectively, these frameworks establish a principled methodology for learning brain representations that are simultaneously predictive, network-interpretable, and account for clinical observations.',
'nishad-kothari-solitude-graphs-2026':
'The study of perfect matchings, also known as 1-factors, in 2-connected 3-regular graphs goes back to the Four Color Problem through Taits equivalence established in 1880. Schonberger proved that, in any such graph, each edge belongs to at least one perfect matching; this fact is easily deduced using Tuttes 1-factor Theorem. We say that an edge is solitary if it belongs to precisely one perfect matching. Schonbergers result naturally leads to two problems in the context of 2-connected 3-regular graphs: characterize solitary edges, and prove bounds on the number of solitary edges.\n\nIn a recent work, Goedgebeur, Mattiolo, Mazzuoccolo, Renders, and Wolf proved that, in any 3-connected 3-regular graph, there are at most six solitary edges, and they also provided a complete characterization of those that have three or more solitary edges.\n\nIn joint work with Narayana, Mattiolo, and Gohokar, we generalize their results, both characterizations and bounds, to 3-edge-connected r-graphs. An r-graph, where r is at least 3, is any r-regular graph with the additional property that each odd cut has at least r edges; when r is 3, this is precisely the class of 2-connected 3-regular graphs. We make extensive use of the dependence relation introduced by Carvalho, Lucchesi, and Murty in 1999, and this is what allows us to obtain stronger results. In this talk, I shall discuss all of this, including the historical context. Basic familiarity with graph theory is expected; nothing beyond that.',
'anindita-maiti-ai-physics-2026':
'Next-generation discoveries in quantum computing and particle physics require large-scale data generation and classification strategies, with theoretical support for its precision and accuracy. While such needs are empirically addressed by state-of-the-art Artificial Intelligence (AI) and Machine Learning (ML), theoretical support for robust data generation via reliable algorithms, that meet scientific uncertainty quantification benchmarks, are still majorly lacking.\n\nIn this talk I will discuss a few Physics-for-AI directions that advance AI-for-Physics in a first-principles manner: improving robustness, mechanistic interpretability, and uncertainty quantification of complex learning and sample generation abilities. First, I will present an asymptotically exact theory of in-context learning, an ability speculated to underpin LLM success in learning quantum attributes. The emergence of in-context learning ability is studied in a simplified Transformer, for the simplest class of tasks: linear regression; results are supported by experiments on full architectures. Next, I will introduce Neural Network Field Theory Correspondence, a paradigm to generate field theory samples without any training algorithms, while guaranteeing low uncertainty bounds at scale. This explainable and interpretable alternative to Monte Carlo sampling maps field theory actions to dual Neural Network architectures. Lastly, I will introduce a framework hinged on Renormalization Group (RG), that systematically coarsegrains data features irrelevant to learning, while capturing nontrivial perturbations to model predictions, elusive to traditional spectral bias method, within scientific uncertainty bounds.',
'aditya-subramanian-online-connectivity-augmentation-2026':
'The Connectivity Augmentation Problem (CAP) is a classic problem in network design: how can we add the minimum number of edges to a graph to make it more resilient to failures? In the online version of this problem, requests for increased connectivity between pairs of vertices arrive one by one. For each request, the algorithm must immediately and irrevocably decide which edges, or links, to add to ensure the network remains robust, all without knowing what future requests will look like.\n\nIn this talk, we present several new results that improve our understanding of this problem. For the general online CAP, we introduce a deterministic algorithm that achieves an O(log n) competitive ratio. This result is optimal up to constant factors and improves significantly over previous randomized bounds, which were sensitive to the graphs initial connectivity. Additionally, we consider the weighted version of the problem and provide an O(log^2 n)-competitive algorithm. We also explore the random-order model, in which requests arrive in a uniform random order rather than in worst-case order. We show a lower bound of Omega(log n) for this setting, proving that random arrival order does not actually make the problem easier to solve, even for the simplest case of trees.\n\nThis talk is based on joint work with Mohit Garg and appeared at SODA 2026.',
'venkatakeerthy-ml4code-2026':
'Modern software systems are increasingly complex and performance-sensitive, yet traditional compilers and analysis tools rely on rigid, handcrafted heuristics. ML4Code leverages machine learning to improve software performance, maintainability, and security. In this talk, I will present techniques for building self-learning systems, focusing on program embeddings: dense semantic representations of code across various levels of abstraction, from the intermediate representations derived from the source code to binaries. On the compiler side, embeddings combined with reinforcement learning enable end-to-end optimizations for performance enhancement. On the program understanding side, embeddings enable scalable analyses for similarity, security, and comprehension tasks at source and binary levels. Together, these approaches point toward a unified foundation for machine learning in software systems, where embeddings become the common language across abstraction layers, enabling tools that can learn, reason, and adapt to programs in a principled manner.',
'anik-paul-mirror-descent-2026':
'The mirror descent algorithm generalizes gradient descent to non-Euclidean spaces, establishing a framework with a profound geometric interpretation for optimization. This talk focuses on three key aspects of this framework and its continuous-time interpretations.\n\nFirst, we discuss the continuous-time projected dynamical system (PDS) in a Riemannian manifold, demonstrating how the Euler discretization of this PDS translates directly into the iterative updates of the mirror descent algorithm. Second, we analyze the stochastic mirror descent algorithm in settings characterized by uncertainty, where direct access to the exact gradient is infeasible. We demonstrate that the almost sure convergence of the stochastic algorithm is mathematically equivalent to the asymptotic stability of the underlying deterministic PDS.\n\nThird, we extend this analysis to the zeroth-order optimization setting, assuming access only to an oracle that provides noisy function evaluations. By approximating the gradient from these noisy values, we establish that the convergence analysis of the stochastic zeroth-order mirror descent algorithm mirrors the stability analysis of a PDS in a non-Euclidean domain subjected to a disturbance input. Employing robust stability analysis tools, we establish the almost sure convergence of the algorithm. This work bridges the gap between projected dynamical systems and mirror descent, providing a rigorous theoretical foundation for optimization in non-Euclidean domains under uncertainty.',
'aditya-anand-managed-runtimes-2026':
'The runtimes of managed object-oriented languages such as Java allocate objects on the heap, and rely on automatic garbage collection techniques for freeing up unused objects. Most such runtimes also consist of just-in-time compilers that optimize memory access and garbage collection times by employing escape analysis: an object that does not escape, or outlive, its allocating method can be allocated on and freed up with the stack frame of the corresponding method. However, in order to minimize the time spent in JIT compilation, the scope of such useful analyses is quite limited, thereby restricting their precision significantly. On the contrary, even though it is feasible to perform precise program analyses statically, it is not possible to use their results in a managed runtime without a closed-world assumption.\n\nIn this talk, I will present a novel static+dynamic scheme that allows one to harness the results of a precise static escape analysis for allocating objects on the stack, while taking care of both soundness and efficiency concerns in the runtime. Our scheme succeeds in allocating a much larger number of objects on the stack, resulting in a significant reduction in garbage collection cycles and noticeable improvement in performance. Further to this, I will introduce a first-of-its-kind approach to enrich static analysis with the possibility of speculative optimization during JIT compilation. Combining this idea with the correctness derived from dynamic heapification leads to a best-of-both-worlds approach, and results in aggressive stack allocation on a production Java Virtual Machine, without sacrificing efficiency.',
'utsav-singh-hierarchical-control-2026':
'Solving long-horizon tasks remains a central challenge in robotics because agents must explore efficiently, assign credit over long time scales, and act under sparse supervision. To address this, hierarchical reinforcement learning offers a promising alternative to flat reinforcement learning by enabling a high-level policy to propose subgoals and a low-level policy to execute them. However, in practice, hierarchical reinforcement learning suffers from a fundamental issue: the higher level can propose subgoals that are infeasible for the lower level to achieve, leading to training instability and sub-optimal performance.\n\nIn this talk, I will present my research around a central idea: hierarchy is effective only when its high-level decisions are grounded in the capabilities of the lower-level policies. I will discuss methods for training high-level policies to predict feasible subgoals by leveraging expert demonstrations, preference-based feedback, and visually grounded reward synthesis. Across challenging navigation and manipulation tasks, these approaches improve training stability, mitigate non-stationarity, and enable agents to solve complex sparse-reward tasks in both simulation and real-world robotic settings. More broadly, this work argues that building intelligent robotic agents that can solve long-horizon tasks is not just a matter of better planning, but of ensuring that high-level decisions remain aligned with what lower-level policies can actually achieve.',
'lakshay-saggi-disjoint-shortest-paths-2026':
'We consider the 2-Disjoint Shortest Paths problem: given a directed weighted graph and two terminal pairs, decide whether there exist vertex-disjoint shortest paths between each pair.\n\nBuilding on recent advances in disjoint shortest paths for DAGs and undirected graphs, we present an O(mn log n)-time algorithm for this problem in weighted directed graphs that do not contain negative or zero weight cycles. This algorithm presents a significant improvement over the previously known O(m^5n) time bound. Our approach exploits the algebraic structure of polynomials that enumerate shortest paths between terminal pairs. A key insight is that these polynomials admit a recursive decomposition, enabling efficient evaluation via dynamic programming over fields of characteristic two.\n\nIn addition, we extend our techniques to a more general setting: given two terminal pairs in a directed graph, find the minimum possible number of vertex intersections between any shortest path from the first pair and any shortest path from the second pair. We call this the Minimum 2-Disjoint Shortest Paths problem. We provide the first efficient algorithm for this problem, including an O(m^2 n^3)-time algorithm for directed graphs with positive edge weights and an O(m+n)-time algorithm for DAGs and undirected graphs. This is joint work with Keerti Choudhary and Amit Kumar, presented at ITCS 2026.',
'janki-bhimani-system-design-2026':
'This talk presents a unified vision for advancing system design across classical and quantum computing by rethinking how memory, storage, and intelligence are integrated into modern computing architectures. As systems evolve across cloud, HPC, AI, autonomous platforms, and emerging quantum environments, long-standing assumptions about storage abstractions, memory hierarchies, and reliability no longer hold. Heterogeneous devices, dynamic workloads, and operational stressors expose fundamental limitations in isolated, layer-specific optimizations, motivating the need for principled, end-to-end system design.\n\nThe talk is organized around three interconnected research thrusts. First, it examines emerging memory and storage systems, tracing the evolution of SSD abstractions from legacy block interfaces to programmable, computational, and byte-addressable storage, including key-value SSDs, zoned namespaces, CXL-enabled devices, and hybrid memory systems. This work challenges conventional host-device boundaries and introduces new approaches to caching, indexing, tiering, concurrency control, and endurance management for modern workloads.\n\nSecond, the talk highlights work that establishes a new perspective on storage reliability under environmental stress, demonstrating that environmental factors within vendor-specified limits, such as temperature, humidity, and vibration, affect SSDs in fundamentally different and persistent ways compared to HDDs. Through controlled experimentation, my team was among the first to show that these stressors induce lasting performance degradation, increased variability, and tail-latency amplification due to flash-specific behaviors. Third, the talk extends system design into quantum computing, where my team has been among the trailblazers in identifying and formalizing the need for quantum checkpointing. Together, these efforts advocate for a new generation of intelligent, adaptive, and resilient system architectures, where storage, memory, learning, and computation are co-designed to support the next wave of classical and quantum computing systems.',
'venkatesh-raman-nseth-2026':
'SETH, the Strong Exponential Time Hypothesis, has been used to show fine-grained lower bounds, such as that the diameter of a graph cannot be determined in O(n^{2-epsilon}) time. We will see a non-deterministic version of it, NSETH, and some of its consequences.\n\nEn route, we will see a better-than-brute-force algorithm for Max2SAT and a better-than-brute-force non-deterministic algorithm for the complement of the 3-SUM problem.',
'ashwin-verma-distributed-learning-2026':
'Modern distributed systems, from peer-to-peer networks to collaborative learning platforms, operate under fundamental information constraints: communication is limited, network topology evolves, and data sources may be unreliable. In such systems, the information structure is not merely an implementation detail; it fundamentally shapes algorithm design and convergence behavior. This talk presents algorithmic and theoretical contributions organized around two core challenges.\n\nFirst, I analyze communication-efficient distributed convex optimization over graphs. Standard gossip and consensus-based methods rely on fixed or random communication schedules. I instead study adaptive protocols in which nodes prioritize communication with maximally disagreeing neighbors, inducing state-dependent network evolution. I establish almost-sure convergence over dynamically evolving graphs without relying on spectral gap assumptions, and characterize how adaptive communication affects convergence behavior.\n\nSecond, I study learning from unreliable sources without ground truth. In a crowdsourced binary classification setting, workers provide noisy labels with unknown error rates and no labeled validation data. I establish that three workers are necessary and sufficient for parameter recovery, while two workers admit infinitely many indistinguishable models. I then develop an online stochastic-approximation algorithm that provably converges without supervised signals. Across these problems, a common theme emerges: convergence guarantees depend jointly on objective geometry and the evolution of information constraints. Treating information structure as an algorithmic design variable enables principled approaches to distributed learning under uncertainty.',
'parth-paritosh-networked-inference-2026':
'Networked mission-critical systems in domains such as robotics, autonomous transit, infrastructure management, and defense need to make autonomous decisions to operate independently in evolving environments. For autonomous operations, such networks of diverse agents must interpret noisy measurements in real-time to deliver reliable decisions. These agents collect measurements with underlying dependencies, communicate intermittently, and need to mitigate coordinated attacks in the information space. Building upon the networked architecture that scales computation and communication and avoids single point of failure, we leverage causal dependencies in distributed settings to drive further efficiencies, accommodate heterogeneous noise, and handle eavesdropping and data poisoning attacks. Our principled approach quantifies trade-offs between statistical convergence, communication costs and privacy as a result of probabilistic representations, causal dependencies, privacy and resilience.\n\nThis talk will focus on three contributions to networked statistical inference. First, we recast the online distributed inference problem in terms of mirror descent optimization over general probability densities. This framework leads to provably correct inference algorithms that accommodate discrete and continuous variables, and represent both joint and marginal probability densities over time-varying networks. To incorporate high-dimensional, non-linear and heterogeneous sensing likelihoods, we derive a distributed version of evidence lower bound to apply variational inference technique. The resulting algorithm is validated over a distributed mapping task using LiDAR sensor data from a team of TurtleBot robots.\n\nSecondly, we design a provably correct estimation algorithm that scales by leveraging local relevance and causal dependencies between the landmarks and agent states. Finally, we explain how state decompositions can be leveraged to perform estimation while preserving the privacy of both local data and its generating statistics. After discussing resilience in this context, the talk ends with a brief overview of future research directions.',
'tameesh-suri-ai-codesign-2026':
'AI workloads have driven explosive growth in compute and memory demand, now accelerating even faster with large-scale inference and interactive systems. The performance gains required far exceed what traditional Moores Law scaling can deliver. The next 1000x will not come from the same innovations that powered the last one. It will require holistic co-design across materials, devices, architecture, packaging, and systems, bringing both profound challenges and unprecedented opportunities.',
'vaibhav-krishan-boolean-circuits-2026':
'Boolean circuits provide a combinatorial representation of computation, where the number of gates, or the size, represents running time, and the number of layers, or the depth, capture parallel running time. They form a framework for answering fundamental questions such as P vs NP: proving that some NP problem requires circuits of superpolynomial size would separate P from NP.\n\nWith limited progress on this question for general circuits, early breakthroughs focused on restricted circuit classes. Hastad proved that constant-depth circuits with AND, OR, and NOT gates require exponential size to compute the parity function, which determines whether the sum of inputs is even or odd. Razborov and, independently, Smolensky extended this to circuits augmented with parity or modular gates for prime moduli, showing that such circuits require exponential size to compute the majority function, which determines whether the sum of inputs is at least half their number.\n\nFollowing these foundational results, research has advanced along two principal directions, though further progress has become increasingly challenging with only a handful of breakthroughs. In this talk, Vaibhav will present some of his work contributing new advances at the frontier of both directions.',
'sneha-mohanty-pinball-wizards-2026':
'We introduce and investigate the computational complexity of a novel physical problem known as the Pinball Wizard problem. It involves an idealized pinball moving through a maze composed of one-way gates, plane walls, parabolic walls, moving plane walls, and bumpers that cause acceleration or deceleration. Given the initial position and velocity of the pinball, the task is to decide whether it will hit a specified target point.\n\nBy simulating a two-stack pushdown automaton, we show that the problem is Turing-complete, even in two-dimensional space. In our construction, each step of the automaton corresponds to a constant number of reflections. Thus, deciding the Pinball Wizard problem is at least as hard as the Halting problem. Furthermore, our construction allows bumpers to be replaced with moving walls. In this case, even a ball moving at constant speed, a so-called ray particle, can be used, demonstrating that the Ray Particle Tracing problem is also Turing-complete.',
'samarth-brahmabhatt-robotic-manipulation-2026':
'As local manufacturing becomes a priority across countries, there is an increased interest in the use of innovative technology like robotics and machine learning to boost reliability and cost-effectiveness. Manipulation is the robotics sub-field most relevant to this. In this talk, I will first present some technical challenges in building reliable robotic systems for advanced manufacturing and survey the state of the art. Next, I will dive into the details of my research, which covers perception and imitation of human manipulation behaviours as well as policy learning for contact-rich robotic manipulation skills. Finally, I will briefly mention some lessons learnt from the robotics startup industry that can benefit academic labs.',
'akhil-s-algorithmic-information-theory-2026':
'In this talk, we will look at some of the basic ideas from algorithmic information theory and discuss a few recent developments. Well begin with Kolmogorov complexity, which measures the amount of algorithmic information in a finite string. This leads to a clean notion of randomness for infinite sequences: a sequence is random if all but finitely many of its prefixes are incompressible, meaning they contain essentially full information. Well then move to the constructive dimension, which refines this idea by measuring the rate of randomness. Informally, this can be thought of as taking the Kolmogorov complexity of longer and longer prefixes, normalizing by their length, and looking at the limiting behavior. Finally, well look at the polynomial-time analogue of constructive dimension and an old problem of robustness in this setting. Roughly speaking, the question is whether polynomial-time dimension can also be characterized by rates of polynomial-time bounded Kolmogorov complexity. Somewhat surprisingly, this turns out to be closely connected to the existence of one-way functions.',
'ajay-singh-memory-reclamation-2026':
'Safe memory reclamation is crucial to ensure memory safety in optimistic and lock-free concurrent data structures in non-garbage collected programming languages. Yet, designing an ideal safe memory reclamation algorithm remains challenging. The main difficulties include achieving high speed and scalability, keeping the interface easy to use for programmers, ensuring applicability to a broad class of data structures, bounding the memory footprint, and avoiding asymmetric overhead across data structure operations. Addressing these issues is critical for enabling the deployment of concurrent data structures in production systems.\n\nIn this talk, I will present three new methods for safe memory reclamation that are each designed to address a distinct shortcoming of state-of-the-art safe memory reclamation algorithms. Together, these methods demonstrate how codesigning safe memory reclamation algorithms with neighboring layers of the programming environment, including operating system and hardware cache-coherence, can overcome long-standing challenges efficiently.',
'utkarsh-mall-visual-discovery-2026':
'From social media all the way to satellite images, we are capturing visual data at an unprecedented scale. These images tell a story about our planet. With advances in automatic recognition, we can build a collective understanding of world-scale events as recorded through visual media. To achieve these goals in different domains, we need label-efficient vision and multimodal foundation models that are interpretable and trustworthy for those domains.\n\nIn this talk, we will first look at an annotation-efficient method for building a multimodal vision-language model in the scientific domain of remote sensing, where language annotations are sparse. Then, I will present my recent research on building interpretable models for scientific discovery using such black-box vision-language models. Finally, we will look at some open challenges in this area through some of the datasets that my work proposed in order to evaluate these challenges and enable scientific discovery.',
'lakshmi-mandal-reinforcement-learning-2026':
'This research seminar focuses on my recent research experience in multiple novel works involving several research domains, such as reinforcement learning, both with or without function approximators including deep neural networks and multi-agent reinforcement learning; stochastic optimization; and natural language processing. All these works fall under either multi-timescale or multi-agent reinforcement learning algorithms. Reinforcement learning deals with the problem of dynamic decision-making under uncertainty. In reinforcement learning, we often need multi-layer decision-making. These problems can be handled by incorporating many time scales proportionate to the number of layers of decision-making.\n\nFurther, we come across various applications in which multiple agents are involved rather than a single agent. These agents can work together to accomplish a common goal or can compete against each other for a certain resource. It is hard to immediately extend single-timescale and single-agent learning algorithms to multi-timescale and multi-agent settings, respectively, because of scalability, stability, and coordination issues. Thus, we considered multi-timescale and multi-agent settings in our work and proposed novel algorithms. Further, we provided theoretical convergence guarantees for all proposed algorithms using either asymptotic or finite-time analyses, or both. Moreover, our experimental results on different standard reinforcement learning tasks demonstrate that our proposed algorithms are better than the corresponding state-of-the-art algorithms. The detailed problem statements, methodology, and convergence analysis will be discussed in the research seminar.',
'nisarg-shah-fair-ai-2026':
'Over the millennia, human society has invented numerous systems for decision-making, from early councils and monarchies to modern democratic systems, ensuring that these systems treat individuals and groups fairly. With AI now emerging as the latest decision-making tool, there is naturally a growing interest in ensuring the fairness of AI-driven decisions. In this talk, I will present an approach to algorithmic fairness inspired by democratic principles.\n\nFirst, I will review mathematical treatments of this approach in computational social choice, with applications to elections and resource allocation. Then, I will talk about how this framework can be extended to AI applications, such as classification, clustering, and reinforcement learning, providing broadly applicable criteria that are less sensitive to group definitions. Finally, I will discuss how this approach can be applied to AI alignment more broadly, and how robust mechanisms designed for democratic governance systems can be adapted to the AI ecosystem.',
'adithya-kumar-systems-heterogeneity-2026':
'As the landscape of modern computer applications enters the realms of pervasive AI, their colossal computational needs are increasingly met by datacenter platforms that expose a diverse mix of CPUs, GPUs, accelerators, and smart peripherals such as SmartNICs and SSDs. While this plurality has unlocked numerous opportunities and enabled tremendous progress for AI applications, it has necessitated revisiting fundamental questions of what, when, and how software systems should leverage device-specific capabilities to navigate challenges not only in performance, efficiency, and reliability but also the long-term viability of these large-scale computing environments.\n\nIn this talk, I will outline a systems design approach for heterogeneous platforms that first characterizes the resource capabilities, designs components to explore the trade-offs, and finally demonstrates system solutions that exploit the heterogeneity. As a concrete instantiation of these ideas, I will detail SplitRPC, an RPC framework that tackles the overheads of serving ML inference on GPUs, or RPC tax, by leveraging the P2P data-path and the compute capabilities of a SmartNIC under distributed settings. Finally, I will present a case for future system designers to build heterogeneity-first system components that implicitly discover and adapt to hardware and workload diversity in order to improve performance, efficiency, and reliability while reducing long-term system and energy costs.',
'harsh-parikh-causal-inference-2026':
'Many common estimators in machine learning and causal inference are linear smoothers, where the prediction is a weighted average of the training outcomes. Some estimators, such as ordinary least squares and kernel ridge regression, allow for arbitrarily negative weights, which improve feature imbalance but often at the cost of increased dependence on parametric modeling assumptions and higher variance. By contrast, estimators like importance weighting and random forests, sometimes implicitly, restrict weights to be non-negative, reducing dependence on parametric modeling and variance at the cost of worse imbalance.\n\nIn this work, we propose a unified framework that directly penalizes the level of extrapolation, replacing the current practice of a hard non-negativity constraint with a soft constraint and corresponding hyperparameter. We derive a worst-case extrapolation error bound and introduce a novel bias-bias-variance tradeoff, encompassing biases due to feature imbalance, model misspecification, and estimator variance; this tradeoff is especially pronounced in high dimensions, particularly when positivity is poor. We then develop an optimization procedure that regularizes this bound while minimizing imbalance and outline how to use this approach as a sensitivity analysis for dependence on parametric modeling assumptions. We demonstrate the effectiveness of our approach through synthetic experiments and a real-world application, involving the generalization of randomized controlled trial estimates to a target population of interest.',
'soumyajit-chatterjee-edge-intelligence-2026':
'Efficiency is a central design principle for enabling machine learning at the edge, where resource constraints and scalability challenges dominate. This talk examines efficiency from three perspectives: reducing annotation costs, minimizing data collection, and enhancing robustness at runtime. I will begin by briefly summarizing my PhD work on automated annotations using multimodal sensing, along with collaborative efforts that demonstrate the potential of multimodal approaches. I will then discuss some of my recent works at Bell Labs, including federated learning in multi-device ecosystems and on-device training, before moving on to the primary focus of the talk: the efficiency of on-device retraining and test-time adaptation. I will conclude by highlighting recent collaborative projects, briefly touching on adaptation methods, and outlining my future research directions in efficient, adaptive, and human-centered edge intelligence.',
};
export const seminarEntries = rawSeminarEntries.map((entry) => ({
...entry,
abstract: seminarAbstracts[entry.id] ?? entry.abstract,
})) satisfies SeminarEntry[];
export const seminarEntriesChronological = [...seminarEntries].sort((a, b) =>
a.date.localeCompare(b.date),
);
const formatNavDate = (date: string) => {
const [year = 2026, month = 1, day = 1] = date.split('-').map(Number);
return new Intl.DateTimeFormat('en-IN', {
day: 'numeric',
month: 'short',
}).format(new Date(year, month - 1, day));
};
const toNavItem = (entry: SeminarEntry) => ({
title: entry.title,
speaker: entry.speaker,
date: formatNavDate(entry.date),
time: entry.time ?? '',
href: `/updates/seminars#${entry.id}`,
});
export const seminarNavHighlights = {
latest: seminarEntries.slice(0, 2).map(toNavItem),
theory: seminarEntries
.filter((entry) => entry.type === 'CS theory seminar')
.slice(0, 2)
.map(toNavItem),
};

27
src/data/staff.ts Normal file
View file

@ -0,0 +1,27 @@
export interface StaffMember {
name: string;
designation: string;
emailUser: string;
office?: string;
image: string;
}
export const CSE_STAFF: StaffMember[] = [
{
name: 'Dr. Supin Gopi',
designation: 'Assistant Manager (Technical)',
emailUser: 'supin.gopi',
image:
'https://iitgn.ac.in/media/pages/staff/cse/1-supin/3517437127-1767089308/Supin.png',
},
{
name: 'Mr. Dinesh B. Desai',
designation: 'Junior Laboratory Assistant',
emailUser: 'desaidinesh',
office: '4/307',
image:
'https://iitgn.ac.in/media/pages/staff/cse/2-dinesh/7eedf1491e-1767089308/dinesh.jpg',
},
];
export const CSE_STAFF_SOURCE = 'https://iitgn.ac.in/staff';

View file

@ -0,0 +1,89 @@
import { useCallback, useEffect, useState } from 'react';
// Tailwind default breakpoints in pixels
const SCREEN_SIZES = {
xs: 480,
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
'2xl': 1536,
} as const;
type ScreenSize = keyof typeof SCREEN_SIZES;
// Debounce utility function
const debounce = <T extends (...args: unknown[]) => void>(
func: T,
wait: number,
) => {
let timeout: NodeJS.Timeout;
return (...args: Parameters<T>) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
};
export const useMediaQuery = (debounceMs = 100) => {
const [screenSize, setScreenSize] = useState<ScreenSize>('xs');
const updateScreenSize = useCallback(() => {
// Use document.documentElement.clientWidth for more reliable responsive breakpoints
// This matches what CSS media queries use and excludes scrollbars
const effectiveWidth =
document.documentElement.clientWidth || window.innerWidth;
let newSize: ScreenSize;
if (effectiveWidth >= SCREEN_SIZES['2xl']) {
newSize = '2xl';
} else if (effectiveWidth >= SCREEN_SIZES.xl) {
newSize = 'xl';
} else if (effectiveWidth >= SCREEN_SIZES.lg) {
newSize = 'lg';
} else if (effectiveWidth >= SCREEN_SIZES.md) {
newSize = 'md';
} else {
newSize = 'sm';
}
setScreenSize(newSize);
}, []);
useEffect(() => {
// Create debounced handler
const debouncedHandler = debounce(updateScreenSize, debounceMs);
// Initial check without debounce
updateScreenSize();
// Add event listener with debounced handler
window.addEventListener('resize', debouncedHandler);
// Cleanup
return () => window.removeEventListener('resize', debouncedHandler);
}, [debounceMs, updateScreenSize]);
return {
screenSize,
isXs: screenSize === 'xs',
isSm: screenSize === 'sm',
isMd: screenSize === 'md',
isLg: screenSize === 'lg',
isXl: screenSize === 'xl',
is2Xl: screenSize === '2xl',
// Helper methods for comparisons
isAtLeast: (size: ScreenSize) => {
const breakpoints: ScreenSize[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
const currentIndex = breakpoints.indexOf(screenSize);
const targetIndex = breakpoints.indexOf(size);
return currentIndex >= targetIndex;
},
isAtMost: (size: ScreenSize) => {
const breakpoints: ScreenSize[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
const currentIndex = breakpoints.indexOf(screenSize);
const targetIndex = breakpoints.indexOf(size);
return currentIndex <= targetIndex;
},
};
};

View file

@ -0,0 +1,16 @@
import { useEffect, useState } from "react";
export const useMediaQuery = (query: string): boolean => {
const [matches, setMatches] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia(query);
setMatches(mediaQuery.matches);
const handleChange = () => setMatches(mediaQuery.matches);
mediaQuery.addEventListener("change", handleChange);
return () => mediaQuery.removeEventListener("change", handleChange);
}, [query]);
return matches;
};

View file

@ -0,0 +1,25 @@
import { useEffect, useState } from 'react';
/**
* Hook to detect if user prefers reduced motion for accessibility
*/
export default function usePrefersReducedMotion(): boolean {
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
setPrefersReducedMotion(mediaQuery.matches);
const handleChange = (event: MediaQueryListEvent) => {
setPrefersReducedMotion(event.matches);
};
mediaQuery.addEventListener('change', handleChange);
return () => {
mediaQuery.removeEventListener('change', handleChange);
};
}, []);
return prefersReducedMotion;
}

View file

@ -0,0 +1,47 @@
---
import { Font } from 'astro:assets';
import BaseHead from '@/components/BaseHead.astro';
import Banner from '@/components/layout/banner';
import Footer from '@/components/layout/footer';
import Navbar from '@/components/layout/navbar';
const { title, description, frontmatter } = Astro.props;
const currentPage = Astro.url.pathname;
const pageTitle = frontmatter?.title || title;
---
<html lang="en">
<head>
<BaseHead title={title} description={description} />
<Font cssVariable="--font-imprima" preload />
<Font cssVariable="--font-rubik" preload />
</head>
<body
class={`h-screen antialiased [--header-height:calc(var(--spacing)*14)] lg:[--header-height:calc(var(--spacing)*23)]`}
>
<Banner client:only="react" />
<Navbar currentPage={currentPage} client:only="react" />
<main>
<section class="section-padding bg-chart-4">
<div class="container">
<article class="space-y-6 md:space-y-8">
<h1
class="mb-10 text-4xl font-medium tracking-tighter md:mb-12 md:text-5xl md:leading-none lg:text-6xl"
>
{pageTitle}
</h1>
<div
class="prose prose-headings:text-2xl dark:prose-invert max-w-none leading-8"
>
<slot />
</div>
</article>
</div>
</section>
</main>
<Footer client:only="react" />
</body>
</html>

View file

@ -0,0 +1,111 @@
---
import { Font } from 'astro:assets';
import { ClientRouter } from 'astro:transitions';
import BaseHead from '@/components/BaseHead.astro';
import Banner from '@/components/layout/banner';
import Footer from '@/components/layout/footer';
import Navbar from '@/components/layout/navbar';
const { title, description } = Astro.props;
const currentPath = Astro.url.pathname;
---
<html lang="en">
<head>
<ClientRouter />
<BaseHead title={title} description={description} />
<Font cssVariable="--font-imprima" preload />
<Font cssVariable="--font-rubik" preload />
<Font cssVariable="--font-eb-garamond" preload />
<meta name="view-transition" content="same-origin" />
</head>
<body class="h-screen antialiased flex flex-col">
<div
id="route-preloader"
class="pointer-events-none fixed inset-0 z-[1000] place-items-center bg-background/75 backdrop-blur-sm"
style="display: none; opacity: 0; transition: opacity 160ms ease;"
role="status"
aria-hidden="true"
aria-live="polite"
>
<div class="flex items-center gap-3 rounded-lg border bg-background px-4 py-3 shadow-lg">
<img
src="/images/publications-preloader.gif"
alt=""
width="48"
height="48"
class="size-8"
decoding="async"
/>
<span class="text-sm text-muted-foreground">Loading page...</span>
</div>
</div>
<Banner client:only="react" />
<Navbar currentPage={currentPath} client:only="react" />
<main class="flex-1" style="view-transition-name: main;">
<slot />
</main>
<Footer client:only="react" />
<script is:inline>
(() => {
if (window.__routePreloaderInitialized) return;
window.__routePreloaderInitialized = true;
const reducedMotionQuery = window.matchMedia(
"(prefers-reduced-motion: reduce)",
);
let showTimer;
let hideTimer;
let fallbackTimer;
const getRoutePreloader = () =>
document.getElementById("route-preloader");
const show = () => {
const routePreloader = getRoutePreloader();
if (!routePreloader) return;
window.clearTimeout(hideTimer);
routePreloader.style.display = "grid";
routePreloader.setAttribute("aria-hidden", "false");
requestAnimationFrame(() => {
routePreloader.style.opacity = "1";
});
};
const hide = () => {
const routePreloader = getRoutePreloader();
window.clearTimeout(showTimer);
window.clearTimeout(fallbackTimer);
if (!routePreloader) return;
routePreloader.style.opacity = "0";
routePreloader.setAttribute("aria-hidden", "true");
hideTimer = window.setTimeout(
() => {
getRoutePreloader()?.style.setProperty("display", "none");
},
reducedMotionQuery.matches ? 0 : 160,
);
};
const queue = () => {
window.clearTimeout(showTimer);
window.clearTimeout(fallbackTimer);
showTimer = window.setTimeout(show, 450);
fallbackTimer = window.setTimeout(hide, 8000);
};
document.addEventListener("astro:before-preparation", queue);
document.addEventListener("astro:after-swap", hide);
document.addEventListener("astro:page-load", hide);
window.addEventListener("load", hide);
window.addEventListener("pageshow", hide);
})();
</script>
</body>
</html>

6
src/lib/utils.ts Normal file
View file

@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

11
src/pages/404.astro Normal file
View file

@ -0,0 +1,11 @@
---
import NotFound from '@/components/sections/not-found';
import DefaultLayout from '@/layouts/DefaultLayout.astro';
---
<DefaultLayout
title="404: Not Found"
description="Sorry, the page you're looking for doesn't exist."
>
<NotFound client:only="react" />
</DefaultLayout>

Some files were not shown because too many files have changed in this diff Show more