"use client"; import { ArrowLeft, ArrowRight, ArrowUpLeft } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { Container } from "@/components/container"; import { MotionReveal } from "@/components/motion-reveal"; import { Button } from "@/components/ui/button"; import type { CarouselApi } from "@/components/ui/carousel"; import { Carousel, CarouselContent, CarouselItem, } from "@/components/ui/carousel"; import { layout } from "@/lib/layout"; import { type, typeMeasure, typeSpacing } from "@/lib/typography"; import { cn } from "@/lib/utils"; const AUTOPLAY_MS = 4000; const TWEEN_FACTOR_BASE = 0.84; const SCALE_MIN = 0.8; const SCALE_MAX = 1.05; const OPACITY_MIN = 0.45; const OPACITY_MAX = 1; const projects17Prop = [ { id: 1, title: "طراحی سایت سنگبری", profession: "سنگبری", image: "/assets/main-pic/100.jpg", }, { id: 2, title: "طراحی سایت فروشگاهی کفش", profession: "فروشگاه کفش", image: "/assets/main-pic/101.jpg", }, { id: 3, title: "طراحی سایت لوازم یدکی خودرو", profession: "لوازم یدکی خودرو", image: "/assets/main-pic/102.jpg", }, { id: 4, title: "طراحی سایت لوازم یدکی", profession: "لوازم یدکی", image: "/assets/main-pic/103.jpg", }, { id: 5, title: "طراحی سایت تجهیزات ساختمانی", profession: "تجهیزات ساختمانی", image: "/assets/main-pic/104.jpg", }, ]; function numberWithinRange(value: number, min: number, max: number) { return Math.min(Math.max(value, min), max); } interface Projects17Props { className?: string; } export function Projects17({ className }: Projects17Props) { const [carouselApi, setCarouselApi] = useState(); const [canScrollPrev, setCanScrollPrev] = useState(false); const [canScrollNext, setCanScrollNext] = useState(false); const [selectedIndex, setSelectedIndex] = useState(0); const [scrollSnaps, setScrollSnaps] = useState([]); const tweenNodesRef = useRef<(HTMLElement | null)[]>([]); const tweenFactorRef = useRef(0); useEffect(() => { if (!carouselApi) return; const updateSelection = () => { setCanScrollPrev(carouselApi.canScrollPrev()); setCanScrollNext(carouselApi.canScrollNext()); setSelectedIndex(carouselApi.selectedScrollSnap()); setScrollSnaps(carouselApi.scrollSnapList()); }; updateSelection(); carouselApi.on("select", updateSelection); carouselApi.on("reInit", updateSelection); return () => { carouselApi.off("select", updateSelection); carouselApi.off("reInit", updateSelection); }; }, [carouselApi]); // Continuous scale/opacity while scrolling (desktop only) useEffect(() => { if (!carouselApi) return; const desktop = window.matchMedia("(min-width: 768px)"); const setTweenFactor = () => { tweenFactorRef.current = TWEEN_FACTOR_BASE * carouselApi.scrollSnapList().length; }; const resetTween = () => { tweenNodesRef.current.forEach((node) => { if (!node) return; node.style.transform = ""; node.style.opacity = ""; node.style.zIndex = ""; }); }; const tweenScale = () => { if (!desktop.matches) { resetTween(); return; } const engine = carouselApi.internalEngine(); const scrollProgress = carouselApi.scrollProgress(); const slidesInView = carouselApi.slidesInView(); carouselApi.scrollSnapList().forEach((scrollSnap, snapIndex) => { let diffToTarget = scrollSnap - scrollProgress; const slidesInSnap = engine.slideRegistry[snapIndex]; slidesInSnap.forEach((slideIndex) => { const tweenNode = tweenNodesRef.current[slideIndex]; if (!tweenNode) return; if (engine.options.loop) { engine.slideLooper.loopPoints.forEach((loopItem) => { const target = loopItem.target(); if (slideIndex === loopItem.index && target !== 0) { const sign = Math.sign(target); if (sign === -1) { diffToTarget = scrollSnap - (1 + scrollProgress); } if (sign === 1) { diffToTarget = scrollSnap + (1 - scrollProgress); } } }); } const tweenValue = 1 - Math.abs(diffToTarget * tweenFactorRef.current); const progress = numberWithinRange(tweenValue, 0, 1); const scale = SCALE_MIN + progress * (SCALE_MAX - SCALE_MIN); const opacity = OPACITY_MIN + progress * (OPACITY_MAX - OPACITY_MIN); if (slidesInView.includes(slideIndex) || progress > 0.01) { tweenNode.style.transform = `scale(${scale})`; tweenNode.style.opacity = String(opacity); tweenNode.style.zIndex = progress > 0.85 ? "1" : "0"; } }); }); }; const onReInit = () => { setTweenFactor(); tweenScale(); }; const onDesktopChange = () => { setTweenFactor(); tweenScale(); }; setTweenFactor(); tweenScale(); carouselApi.on("reInit", onReInit); carouselApi.on("scroll", tweenScale); carouselApi.on("slideFocus", tweenScale); desktop.addEventListener("change", onDesktopChange); return () => { carouselApi.off("reInit", onReInit); carouselApi.off("scroll", tweenScale); carouselApi.off("slideFocus", tweenScale); desktop.removeEventListener("change", onDesktopChange); resetTween(); }; }, [carouselApi]); // Desktop autoplay — paused on hover/focus; skipped for reduced motion & mobile useEffect(() => { if (!carouselApi) return; const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); const desktop = window.matchMedia("(min-width: 768px)"); let timer: ReturnType | undefined; let paused = false; const clear = () => { if (timer) { clearInterval(timer); timer = undefined; } }; const tick = () => { clear(); if (paused || reducedMotion.matches || !desktop.matches) return; timer = setInterval(() => { carouselApi.scrollNext(); }, AUTOPLAY_MS); }; const pause = () => { paused = true; clear(); }; const resume = () => { paused = false; tick(); }; const onPointerDown = () => clear(); const onSettle = () => { if (!paused) tick(); }; const onChange = () => tick(); const root = carouselApi.rootNode(); root.addEventListener("mouseenter", pause); root.addEventListener("mouseleave", resume); root.addEventListener("focusin", pause); root.addEventListener("focusout", resume); reducedMotion.addEventListener("change", onChange); desktop.addEventListener("change", onChange); carouselApi.on("pointerDown", onPointerDown); carouselApi.on("settle", onSettle); tick(); return () => { clear(); root.removeEventListener("mouseenter", pause); root.removeEventListener("mouseleave", resume); root.removeEventListener("focusin", pause); root.removeEventListener("focusout", resume); reducedMotion.removeEventListener("change", onChange); desktop.removeEventListener("change", onChange); carouselApi.off("pointerDown", onPointerDown); carouselApi.off("settle", onSettle); }; }, [carouselApi]); return (

پورتفولیو

نمونه کارها

نگاهی به وب‌سایت‌هایی که برای کسب‌وکارهای مختلف طراحی و پیاده‌سازی کرده‌ایم.

{projects17Prop.map((project, index) => { const isActive = index === selectedIndex; return ( { tweenNodesRef.current[index] = node; }} href="#" aria-current={isActive ? "true" : undefined} className={cn( "media-frame group relative block aspect-[4/3] overflow-hidden rounded-xl", "md:origin-center md:will-change-transform", isActive && "md:shadow-glow-media" )} > {/* eslint-disable-next-line @next/next/no-img-element */} {project.title}
{scrollSnaps.length > 1 ? (
{scrollSnaps.map((_, index) => (
) : null}
); }