feat: add projects previews

This commit is contained in:
Kentai Radiquum 2025-08-06 01:43:15 +05:00
parent bd2388aaa5
commit d8bc89e1c6
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
10 changed files with 146 additions and 9 deletions

View file

@ -1,6 +1,5 @@
import Link from "next/link";
import { IconWithText } from "../components/IconWithText";
import { Section } from "../components/Section";
import { ProjectLink } from "../components/ProjectLink";
const links = [
{
@ -8,54 +7,81 @@ const links = [
text: "Anix",
desc: "Unofficial web client for anixart",
url: "https://github.com/radiquum/AniX",
preview: [
"/images/projects/anix/1.jpg",
"/images/projects/anix/2.png",
"/images/projects/anix/3.png",
],
},
{
icon: "/icons/mdi_github.svg",
text: "Furaffinity-dl",
desc: "Fork with additional functionality",
url: "https://github.com/radiquum/furaffinity-dl",
preview: [
"/images/projects/anix/3.png",
],
},
{
icon: "/icons/mdi_github.svg",
text: "TG-Photos",
desc: "Google Photo like bot for Telegram",
url: "https://github.com/radiquum/TG-Photos",
preview: [
"/images/projects/anix/3.png",
],
},
{
icon: "/icons/mdi_github.svg",
text: "TIG",
desc: "Generate images from text",
url: "https://github.com/radiquum/TIG",
preview: [
"/images/projects/anix/1.jpg",
"/images/projects/anix/2.png",
"/images/projects/anix/3.png",
],
},
{
icon: "/icons/mdi_github.svg",
text: "GitHub",
desc: "Other Projects",
url: "https://github.com/radiquum",
preview: [
"/images/projects/anix/3.png",
],
},
{
icon: "/icons/wahsu.svg",
text: "wah.su",
desc: "Self-Hosting project",
url: "https://wah.su",
preview: [
"/images/projects/anix/3.png",
],
},
{
icon: "/icons/ic_baseline-telegram.svg",
text: "Red Pandas Stickers",
desc: "Collection of Red Panda sticker packs",
url: "https://t.me/red_panda_stickers",
preview: [
"/images/projects/anix/1.jpg",
"/images/projects/anix/2.png",
"/images/projects/anix/3.png",
],
},
];
export const Projects = () => {
return (
<Section>
<h2 className="text-4xl md:text-5xl border-1 px-3 py-2 rounded-xl border-white/5 bg-[#101316]/5">Projects</h2>
<h2 className="text-4xl md:text-5xl border-1 px-3 py-2 rounded-xl border-white/5 bg-[#101316]/5">
Projects
</h2>
<div className="grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3">
{links.map((item) => (
<Link href={item.url} key={`projects.link.${item.text}`}>
<IconWithText icon={item.icon} text={item.text} desc={item.desc} backgroundColor={"#101316"} backgroundOpacity={"5%"} />
</Link>
<ProjectLink key={`projects.link.${item.text}`} {...item} />
))}
</div>
</Section>

View file

@ -4,6 +4,7 @@ type IconWithTextProps = {
desc: string;
backgroundColor?: string | null;
backgroundOpacity?: string | null;
isGroup?: boolean;
};
export const IconWithText = ({
@ -12,10 +13,15 @@ export const IconWithText = ({
desc,
backgroundColor,
backgroundOpacity,
isGroup,
}: IconWithTextProps) => {
return (
<div
className={`flex items-start gap-1 border-1 px-3 py-1.5 rounded-xl border-white/5 bg-[var(--bg-color)]/[var(--bg-opacity)] transition-[scale] hover:scale-105 duration-100 ease-in-out`}
className={`flex items-start gap-1 border-1 px-3 py-1.5 rounded-xl border-white/5 bg-[var(--bg-color)]/[var(--bg-opacity)] transition-[scale] ${
!isGroup
? "hover:scale-105"
: "group-hover:scale-105"
} duration-100 ease-in-out`}
style={
{
"--bg-color": backgroundColor || "#161213",

View file

@ -1,5 +1,3 @@
import Styles from "./Photos.Carousel.module.css";
import React, { useCallback, useEffect, useRef } from "react";
import {
EmblaCarouselType,

View file

@ -0,0 +1,89 @@
/* eslint-disable @next/next/no-img-element */
"use client";
import Link from "next/link";
import { IconWithText } from "../components/IconWithText";
import { useEffect, useRef, useState } from "react";
import useEmblaCarousel from "embla-carousel-react";
import Autoplay from "embla-carousel-autoplay";
import { useInView } from "motion/react";
type ProjectLinkProps = {
icon: string;
text: string;
desc: string;
url: string;
preview: string[];
};
export const ProjectLink = ({
icon,
text,
desc,
url,
preview,
}: ProjectLinkProps) => {
const [shouldUseCarousel] = useState(preview ? preview.length > 1 : false);
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true }, [
Autoplay({ delay: 5000, playOnInit: false }),
]);
const ref = useRef(null);
const isInView = useInView(ref);
useEffect(() => {
if (!shouldUseCarousel) return;
if (!emblaApi) return;
const autoplay = emblaApi?.plugins()?.autoplay;
if (!autoplay) return;
if (!isInView) {
autoplay.stop();
} else {
autoplay.play();
}
}, [shouldUseCarousel, emblaApi, isInView]);
return (
<Link href={url} key={`projects.link.${text}`} className="relative group">
<div className="px-2 overflow-hidden">
{shouldUseCarousel ? (
<div className="embla embla--projects" ref={ref}>
<div className="embla__viewport" ref={emblaRef}>
<div className="embla__container">
{preview.map((item, index) => (
<div
className="embla__slide"
key={`embla.project.${text}.slide.${index}`}
>
<img
className="embla__slide__img rounded-xl! border-white/5 border-1 group-hover:scale-105 duration-100 ease-in-out origin-bottom"
src={item}
alt=""
/>
</div>
))}
</div>
</div>
</div>
) : (
<img
src={preview?.[0] || "/images/projects/no-preview.png"}
alt={text}
className="w-full aspect-video object-cover rounded-xl border-white/5 border-1 group-hover:scale-105 duration-100 ease-in-out origin-bottom "
/>
)}
</div>
<div className="absolute bottom-0 left-0 right-0">
<IconWithText
icon={icon}
text={text}
desc={desc}
backgroundColor={"#101316"}
backgroundOpacity={"100%"}
isGroup={true}
/>
</div>
</Link>
);
};

View file

@ -95,3 +95,10 @@ body {
--slide-size: 512px;
}
}
.embla--projects {
--slide-size: 100%;
--slide-width: 100%;
--slide-height: 100%;
--slide-spacing: 16px;
}

10
package-lock.json generated
View file

@ -8,6 +8,7 @@
"name": "radiquum.github.io",
"version": "0.1.0",
"dependencies": {
"embla-carousel-autoplay": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"motion": "^12.23.9",
"next": "15.4.2",
@ -2556,6 +2557,15 @@
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
"license": "MIT"
},
"node_modules/embla-carousel-autoplay": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel-autoplay/-/embla-carousel-autoplay-8.6.0.tgz",
"integrity": "sha512-OBu5G3nwaSXkZCo1A6LTaFMZ8EpkYbwIaH+bPqdBnDGQ2fh4+NbzjXjs2SktoPNKCtflfVMc75njaDHOYXcrsA==",
"license": "MIT",
"peerDependencies": {
"embla-carousel": "8.6.0"
}
},
"node_modules/embla-carousel-react": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz",

View file

@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"embla-carousel-autoplay": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"motion": "^12.23.9",
"next": "15.4.2",

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB