diff --git a/web/src/app/globals.css b/web/src/app/globals.css index 9ff21031..644b57e6 100644 --- a/web/src/app/globals.css +++ b/web/src/app/globals.css @@ -1,7 +1,7 @@ @import "tailwindcss"; @import "tw-animate-css"; - @plugin "tailwindcss-motion"; + @custom-variant dark (&:is(.dark *)); @theme inline { @@ -62,7 +62,20 @@ height: 0; } } -} + + --animate-marquee: marquee var(--duration) infinite linear; + --animate-marquee-vertical: marquee-vertical var(--duration) linear infinite; + + @keyframes marquee { + from { + transform: translateX(0);} + to { + transform: translateX(calc(-100% - var(--gap)));}} + @keyframes marquee-vertical { + from { + transform: translateY(0);} + to { + transform: translateY(calc(-100% - var(--gap)));}}} :root { --radius: 0.75rem; @@ -158,4 +171,4 @@ .glass-effect { @apply backdrop-blur-sm; } -} +} \ No newline at end of file diff --git a/web/src/components/magicui/marquee.tsx b/web/src/components/magicui/marquee.tsx new file mode 100644 index 00000000..fa9c129b --- /dev/null +++ b/web/src/components/magicui/marquee.tsx @@ -0,0 +1,73 @@ +import { cn } from "@/lib/utils"; +import { ComponentPropsWithoutRef } from "react"; + +interface MarqueeProps extends 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 ( +
+ {Array(repeat) + .fill(0) + .map((_, i) => ( +
+ {children} +
+ ))} +
+ ); +} diff --git a/web/src/components/recently-added-icons.tsx b/web/src/components/recently-added-icons.tsx index 35c1d9eb..959e5f36 100644 --- a/web/src/components/recently-added-icons.tsx +++ b/web/src/components/recently-added-icons.tsx @@ -1,13 +1,14 @@ "use client"; +import { Marquee } from "@/components/magicui/marquee"; import { BASE_URL } from "@/constants"; +import { cn } from "@/lib/utils"; import type { Icon, IconWithName } from "@/types/icons"; import { format, isToday, isYesterday } from "date-fns"; -import { motion, useInView } from "framer-motion"; +import { motion } from "framer-motion"; import { ArrowRight, Clock, ExternalLink } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; -import { useRef } from "react"; function formatIconDate(timestamp: string): string { const date = new Date(timestamp); @@ -21,21 +22,17 @@ function formatIconDate(timestamp: string): string { } export function RecentlyAddedIcons({ icons }: { icons: IconWithName[] }) { + // Split icons into two rows for the marquee + const firstRow = icons.slice(0, Math.ceil(icons.length / 2)); + const secondRow = icons.slice(Math.ceil(icons.length / 2)); + return (
{/* Background glow */}
-
- {icons.map(({ name, data }) => ( - - ))} +
+ + {firstRow.map(({ name, data }) => ( + + ))} + + + {secondRow.map(({ name, data }) => ( + + ))} + +
+
- -
+
-
- {`${name} -
- - {name.replace(/-/g, " ")} +
+ {`${name} +
+ + {name.replace(/-/g, " ")} + +
+ + + {formatIconDate(data.update.timestamp)} -
- - - {formatIconDate(data.update.timestamp)} - -
+
-
- -
- - +
+ +
+ ); }