mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-27 10:24:48 +10:00
127 lines
4.3 KiB
TypeScript
127 lines
4.3 KiB
TypeScript
import type { TemplateMetadata } from "@/dialogs/resume/template/data";
|
|
import { Trans } from "@lingui/react/macro";
|
|
import { m } from "motion/react";
|
|
import { useMemo } from "react";
|
|
import { templates } from "@/dialogs/resume/template/data";
|
|
|
|
type TemplateItemProps = {
|
|
metadata: TemplateMetadata;
|
|
};
|
|
|
|
type TemplateMarqueeItem = {
|
|
id: string;
|
|
metadata: TemplateMetadata;
|
|
};
|
|
|
|
function TemplateItem({ metadata }: TemplateItemProps) {
|
|
return (
|
|
<m.div
|
|
className="group relative shrink-0 will-change-transform"
|
|
initial={{ scale: 1, zIndex: 10 }}
|
|
whileHover={{ scale: 1.06, zIndex: 20 }}
|
|
whileTap={{ scale: 0.99 }}
|
|
transition={{ type: "spring", stiffness: 320, damping: 26 }}
|
|
>
|
|
<div className="relative aspect-page w-48 overflow-hidden rounded-md border bg-card shadow-lg transition-all duration-300 group-hover:shadow-2xl sm:w-56 md:w-64 lg:w-72">
|
|
<img src={metadata.imageUrl} alt={metadata.name} className="size-full object-cover" />
|
|
|
|
{/* Subtle overlay on hover */}
|
|
<div className="absolute inset-0 bg-linear-to-t from-black/70 via-black/20 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
|
|
|
{/* Template name on hover */}
|
|
<div className="absolute inset-x-0 bottom-0 translate-y-full p-4 transition-transform duration-300 group-hover:translate-y-0">
|
|
<p className="font-semibold text-white drop-shadow-lg">{metadata.name}</p>
|
|
</div>
|
|
|
|
{/* Shine effect on hover */}
|
|
<div className="pointer-events-none absolute inset-0 -translate-x-full rotate-12 bg-linear-to-r from-transparent via-white/10 to-transparent transition-transform duration-700 group-hover:translate-x-full" />
|
|
</div>
|
|
</m.div>
|
|
);
|
|
}
|
|
|
|
type MarqueeRowProps = {
|
|
templates: TemplateMarqueeItem[];
|
|
direction: "left" | "right";
|
|
duration?: number;
|
|
};
|
|
|
|
function MarqueeRow({ templates, direction, duration = 40 }: MarqueeRowProps) {
|
|
const animateX = direction === "left" ? ["0%", "-50%"] : ["-50%", "0%"];
|
|
|
|
return (
|
|
<m.div
|
|
className="flex gap-x-4 will-change-transform sm:gap-x-6"
|
|
animate={{ x: animateX }}
|
|
transition={{
|
|
x: {
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
repeatType: "loop",
|
|
duration,
|
|
ease: "linear",
|
|
},
|
|
}}
|
|
>
|
|
{templates.map(({ id, metadata }) => (
|
|
<TemplateItem key={id} metadata={metadata} />
|
|
))}
|
|
</m.div>
|
|
);
|
|
}
|
|
|
|
const createMarqueeItems = (entries: Array<[string, TemplateMetadata]>, rowId: string): TemplateMarqueeItem[] =>
|
|
entries.flatMap(([template, metadata]) => [
|
|
{ id: `${rowId}-${template}-primary`, metadata },
|
|
{ id: `${rowId}-${template}-repeat`, metadata },
|
|
]);
|
|
|
|
export function Templates() {
|
|
// Split templates into two rows and duplicate for seamless infinite scroll
|
|
const { row1, row2 } = useMemo(() => {
|
|
const entries = Object.entries(templates);
|
|
const half = Math.ceil(entries.length / 2);
|
|
const firstHalf = entries.slice(0, half);
|
|
const secondHalf = entries.slice(half);
|
|
|
|
// Duplicate each row for seamless scrolling
|
|
return {
|
|
row1: createMarqueeItems(firstHalf, "row1"),
|
|
row2: createMarqueeItems(secondHalf, "row2"),
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<section id="templates" className="overflow-hidden border-t-0! p-4 md:p-8 xl:py-16">
|
|
<m.div
|
|
className="space-y-4 will-change-[transform,opacity]"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ duration: 0.35 }}
|
|
>
|
|
<h2 className="font-semibold text-2xl tracking-tight md:text-4xl xl:text-5xl">
|
|
<Trans>Templates</Trans>
|
|
</h2>
|
|
|
|
<p className="max-w-2xl text-muted-foreground leading-relaxed">
|
|
<Trans>
|
|
Explore our diverse selection of templates, each designed to fit different styles, professions, and
|
|
personalities. Reactive Resume currently offers 12 templates, with more on the way.
|
|
</Trans>
|
|
</p>
|
|
</m.div>
|
|
|
|
<div className="relative mt-8 -rotate-3 py-8 sm:-rotate-4 lg:mt-0 lg:-rotate-5">
|
|
{/* Marquee container with minimum height */}
|
|
<div className="flex min-h-[280px] flex-col gap-y-4 sm:min-h-[320px] sm:gap-y-6 md:min-h-[380px] lg:min-h-[420px]">
|
|
{/* First row - moves left to right */}
|
|
<MarqueeRow templates={row1} direction="left" duration={45} />
|
|
|
|
{/* Second row - moves right to left (opposite direction) */}
|
|
<MarqueeRow templates={row2} direction="right" duration={50} />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|