diff --git a/blocks/feature199.tsx b/blocks/feature199.tsx index 6d47f10..8ed0c95 100644 --- a/blocks/feature199.tsx +++ b/blocks/feature199.tsx @@ -220,56 +220,61 @@ const Feature199 = ({ className }: Feature199Props) => { const handleScroll = () => { const container = containerRef.current; if (!container) return; - const items = container.getElementsByClassName("feature-item"); - const containerMiddle = window.innerHeight * 0.6; - let closestItem: number | null = null; - let closestDistance = Infinity; + const rect = container.getBoundingClientRect(); + const listHeight = container.offsetHeight; + const viewportHeight = window.innerHeight; + const scrollRange = Math.max(listHeight - viewportHeight * 0.45, 1); + const passed = Math.min(Math.max(-rect.top + viewportHeight * 0.2, 0), scrollRange); + const progress = passed / scrollRange; + const steppedIndex = Math.round(progress * (FEATURES.length - 1)); + const nextIndex = Math.min(Math.max(steppedIndex, 0), FEATURES.length - 1); - Array.from(items).forEach((item, index) => { - const rect = item.getBoundingClientRect(); - const distance = Math.abs(rect.top + rect.height / 2 - containerMiddle); - - if (distance < closestDistance) { - closestDistance = distance; - closestItem = index; - } - }); - - if (closestItem !== null) { - setActiveFeature(closestItem); - } + setActiveFeature((prev) => (prev === nextIndex ? prev : nextIndex)); }; - window.addEventListener("scroll", handleScroll); + handleScroll(); + window.addEventListener("scroll", handleScroll, { passive: true }); return () => window.removeEventListener("scroll", handleScroll); }, [isMobile]); return ( -
-
-

+
+
+

هر سرنخ را تا قرارداد دنبال کنید

-
-
+
+
{FEATURES.map((feature, index) => (
!isMobile && setActiveFeature(index)} > -
+

@@ -281,11 +286,11 @@ const Feature199 = ({ className }: Feature199Props) => { ))}

-
+
{FEATURES[activeFeature].imageAlt}
diff --git a/blocks/feature216.tsx b/blocks/feature216.tsx index 8075149..4399553 100644 --- a/blocks/feature216.tsx +++ b/blocks/feature216.tsx @@ -153,7 +153,6 @@ const Feature216 = ({ className }: Feature216Props) => { const [buttonPosition, setButtonPosition] = useState(0); // Rightmost tab in RTL (`justify-start`) is the first item. const [tabValue, setTabValue] = useState(TAB_ITEMS[0].value); - const triggerRefs = useRef([]); const imageRef = useRef(null); const updateBottom = () => { @@ -178,10 +177,6 @@ const Feature216 = ({ className }: Feature216Props) => { }; const handleTabNavigation = (direction: "next" | "prev") => { - triggerRefs.current.forEach((ref) => { - ref.classList.add("grayscale-[1]", "opacity-[0.2]"); - }); - const currentIndex = getCurrentTabIndex(); if (currentIndex === -1) return; @@ -207,21 +202,19 @@ const Feature216 = ({ className }: Feature216Props) => {

شاخص‌های کلیدی را یکجا ببینید

- + {TAB_ITEMS.map((trigger, index) => { const Icon = trigger.icon; return ( { - if (el) { - triggerRefs.current[index] = el; - } else { - delete triggerRefs.current[index]; - } - }} - className="flex !h-auto flex-col items-center gap-5 !border-none !bg-transparent p-0 opacity-[0.2] !shadow-none grayscale-[1] hover:!bg-transparent hover:opacity-[0.5] hover:grayscale-[0] data-[state=active]:!bg-transparent data-[state=active]:opacity-100 data-[state=active]:!shadow-none data-[state=active]:grayscale-0 dark:!bg-transparent dark:data-[state=active]:!bg-transparent" + className="flex !h-auto !flex-none shrink-0 flex-col items-center gap-5 !border-none !bg-transparent p-0 opacity-[0.2] !shadow-none grayscale hover:!bg-transparent hover:opacity-50 hover:grayscale-0 data-[state=active]:!bg-transparent data-[state=active]:opacity-100 data-[state=active]:!shadow-none data-[state=active]:grayscale-0 dark:!bg-transparent dark:data-[state=active]:!bg-transparent" key={trigger.id} value={trigger.value} data-index={index} diff --git a/blocks/feature220.tsx b/blocks/feature220.tsx index 50fa3c8..c8bc501 100644 --- a/blocks/feature220.tsx +++ b/blocks/feature220.tsx @@ -1,6 +1,5 @@ import { ArrowRight } from "lucide-react"; -import { AspectRatio } from "@/components/ui/aspect-ratio"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; @@ -32,13 +31,13 @@ const Feature220 = ({ className }: Feature220Props) => { dir="rtl" lang="fa" className={cn( - "bg-muted bg-[url('/block-media/shadcnblocks/block/patterns/dot-pattern-2.svg')] bg-[length:3.125rem_3.125rem] bg-repeat py-20", + "flex min-h-full w-full min-w-0 flex-col justify-center bg-muted bg-[url('/block-media/shadcnblocks/block/patterns/dot-pattern-2.svg')] bg-[length:3.125rem_3.125rem] bg-repeat py-12 sm:py-16 lg:py-20", className, )} > -
-
-
+
+
+

از خرید تا فروش در یک سیستم

@@ -53,7 +52,7 @@ const Feature220 = ({ className }: Feature220Props) => {
-
+
{STATS.map((item) => (
@@ -67,21 +66,20 @@ const Feature220 = ({ className }: Feature220Props) => {
-
- +
+
diff --git a/blocks/feature220a.tsx b/blocks/feature220a.tsx index ecc3b9a..450d6f3 100644 --- a/blocks/feature220a.tsx +++ b/blocks/feature220a.tsx @@ -33,44 +33,52 @@ const Feature220a = ({ className }: Feature220aProps) => { dir="rtl" lang="fa" className={cn( - "bg-muted bg-[url('/block-media/shadcnblocks/block/patterns/dot-pattern-2.svg')] bg-[length:3.125rem_3.125rem] bg-repeat py-24", + "flex min-h-full w-full min-w-0 flex-col justify-center overflow-x-hidden bg-muted bg-[url('/block-media/shadcnblocks/block/patterns/dot-pattern-2.svg')] bg-[length:3.125rem_3.125rem] bg-repeat py-12 sm:py-16 lg:py-24", className, )} > -
-
+
+
هر تیم داشبورد خودش را داشته باشد -

+

روندها را زودتر از رقبا ببینید - گزارش‌هایی که مدیران واقعاً استفاده می‌کنند + + گزارش‌هایی که مدیران واقعاً استفاده می‌کنند +

-

- داشبوردهای تعاملی به تیم‌ها اجازه می‌دهد سؤال جدید بپرسند بدون اینکه هر بار به تیم داده وابسته باشند. +

+ داشبوردهای تعاملی به تیم‌ها اجازه می‌دهد سؤال جدید بپرسند بدون اینکه + هر بار به تیم داده وابسته باشند.

-
-
- +
+
-
-
+
+
{STATS.map((item) => (
diff --git a/blocks/feature220b.tsx b/blocks/feature220b.tsx index 4b25f3f..1c70a82 100644 --- a/blocks/feature220b.tsx +++ b/blocks/feature220b.tsx @@ -32,11 +32,11 @@ const Feature220b = ({ className }: Feature220bProps) => { dir="rtl" lang="fa" className={cn( - "bg-muted bg-[url('/block-media/shadcnblocks/block/patterns/dot-pattern-2.svg')] bg-[length:3.125rem_3.125rem] bg-repeat py-20", + "flex min-h-full w-full min-w-0 flex-col justify-center overflow-x-hidden bg-muted bg-[url('/block-media/shadcnblocks/block/patterns/dot-pattern-2.svg')] bg-[length:3.125rem_3.125rem] bg-repeat py-12 sm:py-16 lg:py-20", className, )} > -
+
{STATS.map((item) => (
@@ -57,11 +57,13 @@ const Feature220b = ({ className }: Feature220bProps) => { className="overflow-hidden rounded-2xl" >

- تمام تعاملات، سرنخ‌ها و قراردادها در یک فضای کاری متمرکز جمع می‌شود. تیم فروش بدون جابجایی بین ابزارها، سریع‌تر پیش‌بینی می‌کند و معاملات بیشتری می‌بندد. + تمام تعاملات، سرنخ‌ها و قراردادها در یک فضای کاری متمرکز جمع + می‌شود. تیم فروش بدون جابجایی بین ابزارها، سریع‌تر پیش‌بینی می‌کند + و معاملات بیشتری می‌بندد.

-
+
{FEATURES.map((feature) => ( - - + +

{feature.title}

- -

الگوهای موفق فروش را در کل سازمان تکرار کنید. داده لحظه‌ای نشان می‌دهد کدام مرحله بیشترین افت تبدیل را دارد.

+ + ویژگی
@@ -106,7 +107,7 @@ const Feature221 = ({ className }: Feature221Props) => { {feature.description}

- +
@@ -108,20 +115,20 @@ const CardsContainer = () => { const [hoveredIndex, setHoveredIndex] = useState(null); return ( -
+
{CARDS.map((card, index) => { const yOffset = Math.pow(Math.abs(index - 3), 1.2) * 12; const isHovered = hoveredIndex === index; const visibilityClasses = (index === 0 || index === 1 || index === 5 || index === 6 - ? " hidden sm:hidden " + ? "hidden sm:hidden " : "") + (index === 0 || index === 6 ? "md:hidden " : "md:block ") + "lg:block"; return ( setHoveredIndex(index)} onMouseLeave={() => setHoveredIndex(null)} @@ -134,14 +141,14 @@ const CardsContainer = () => { >
{ return ( -
-
+
+
+ -
-
+
+
-

از اولین تماس تا تمدید قرارداد، هر مرحله فروش قابل ردیابی است. گزارش‌های لحظه‌ای به مدیران کمک می‌کند گلوگاه‌ها را قبل از از دست دادن درآمد شناسایی کنند.

+

+ از اولین تماس تا تمدید قرارداد، هر مرحله فروش قابل ردیابی است. + گزارش‌های لحظه‌ای به مدیران کمک می‌کند گلوگاه‌ها را قبل از از دست + دادن درآمد شناسایی کنند. +

-

+

هر سرنخ را تا قرارداد دنبال کنید

-

+ +

فروش تیم‌تان را یکجا مدیریت کنید

-

- پروفایل یکپارچه مشتری، تاریخچه تماس و وضعیت معامله را همزمان نشان می‌دهد. تیم‌ها با دید مشترک، هماهنگ‌تر پیش می‌روند و نرخ تبدیل بالاتر می‌رود. +

+ پروفایل یکپارچه مشتری، تاریخچه تماس و وضعیت معامله را همزمان نشان + می‌دهد. تیم‌ها با دید مشترک، هماهنگ‌تر پیش می‌روند و نرخ تبدیل بالاتر + می‌رود.

-
-
@@ -61,12 +80,12 @@ const Feature236 = ({ className }: Feature236Props) => { const BracketedBorder = ({ children }: { children: ReactNode }) => ( (
-
+
{children}
diff --git a/blocks/feature239.tsx b/blocks/feature239.tsx index 0c0daa5..d4271e5 100644 --- a/blocks/feature239.tsx +++ b/blocks/feature239.tsx @@ -7,45 +7,53 @@ import { cn } from "@/lib/utils"; const Feature239 = () => { return ( -
-
- -
-
+
+
+ +
+
- - قوانین ساده تعریف کنید تا وظایف تکراری بدون دخالت دست انجام شود. تیم روی تصمیم‌های مهم تمرکز می‌کند و سرعت اجرا بالاتر می‌رود. + + قوانین ساده تعریف کنید تا وظایف تکراری بدون دخالت دست انجام + شود. تیم روی تصمیم‌های مهم تمرکز می‌کند و سرعت اجرا بالاتر + می‌رود. - + -

+

فرایندها را استاندارد و سریع کنید
اتوماسیون را در کل سازمان یکپارچه کنید
خطای انسانی را در کارهای روزمره کم کنید

-

- فرایندهای بین‌تیمی را در یک بوم بصری طراحی کنید. هر مرحله قابل ردیابی است و گلوگاه‌ها قبل از تأخیر جدی مشخص می‌شوند. +

+ فرایندهای بین‌تیمی را در یک بوم بصری طراحی کنید. هر مرحله قابل + ردیابی است و گلوگاه‌ها قبل از تأخیر جدی مشخص می‌شوند.

-
-
- -
-
+ + +
+
سازنده گردش‌کار {
-
-

- با اتصال ابزارهای موجود، داده بین سیستم‌ها خودکار جریان پیدا می‌کند و نیاز به کپی دستی از بین می‌رود. - - قوانین ساده تعریف کنید تا وظایف تکراری بدون دخالت دست انجام شود. تیم روی تصمیم‌های مهم تمرکز می‌کند و سرعت اجرا بالاتر می‌رود. +

+

+ + با اتصال ابزارهای موجود، داده بین سیستم‌ها خودکار جریان + پیدا می‌کند و نیاز به کپی دستی از بین می‌رود. + + + + قوانین ساده تعریف کنید تا وظایف تکراری بدون دخالت دست + انجام شود. +

-
-

+
+

کارهای تکراری را خودکار کنید
گردش‌کار تیم را بدون کد بسازید

-

- فرایندهای بین‌تیمی را در یک بوم بصری طراحی کنید. هر مرحله قابل ردیابی است و گلوگاه‌ها قبل از تأخیر جدی مشخص می‌شوند. +

+ فرایندهای بین‌تیمی را در یک بوم بصری طراحی کنید. هر مرحله + قابل ردیابی است و گلوگاه‌ها قبل از تأخیر جدی مشخص + می‌شوند.

-

+

مشاهده همه

@@ -102,11 +118,11 @@ const DottedDiv = ({ children: React.ReactNode; className?: string; }) => ( -
-
-
-
-
+
+
+
+
+
diff --git a/blocks/feature240.tsx b/blocks/feature240.tsx index e5ee7f7..7dc918b 100644 --- a/blocks/feature240.tsx +++ b/blocks/feature240.tsx @@ -133,9 +133,9 @@ const Feature240 = () => { ))} -
- - +
+ +
diff --git a/blocks/feature242.tsx b/blocks/feature242.tsx index 1835418..cb0bbba 100644 --- a/blocks/feature242.tsx +++ b/blocks/feature242.tsx @@ -1,6 +1,6 @@ "use client"; -import { ChevronLeft, ChevronRight, Plus } from "lucide-react"; +import { ChevronRight, Plus } from "lucide-react"; import React from "react"; import { Button } from "@/components/ui/button"; @@ -9,6 +9,8 @@ import { Carousel, CarouselContent, CarouselItem, + CarouselNext, + CarouselPrevious, } from "@/components/ui/carousel"; import { cn } from "@/lib/utils"; @@ -149,32 +151,9 @@ const Feature242 = ({ className }: Feature242Props) => {
- {/* - Embla RTL: scrollNext/scrollPrev are index-based, so default - ChevronLeft→prev / ChevronRight→next feels reversed visually. - Match arrow direction to movement by swapping handlers. - */}
- - + +
diff --git a/blocks/feature244.tsx b/blocks/feature244.tsx index c1c443e..32c5609 100644 --- a/blocks/feature244.tsx +++ b/blocks/feature244.tsx @@ -93,11 +93,11 @@ const Feature244 = () => { ))} -
- +
+
diff --git a/blocks/feature245.tsx b/blocks/feature245.tsx index 76ae9d0..9c03367 100644 --- a/blocks/feature245.tsx +++ b/blocks/feature245.tsx @@ -7,7 +7,7 @@ import { Card, CardContent } from "@/components/ui/card"; import { cn } from "@/lib/utils"; const handwrittenTextClasses = - "flex gap-3 font-handwriting text-xs tracking-tight md:text-xl"; + "flex min-w-0 items-center gap-2 font-handwriting text-xs tracking-tight xl:gap-3 xl:text-xl"; const FEATURES = [ { id: "dark-mode", label: "ترافیک ارگانیک" }, @@ -97,9 +97,13 @@ interface Feature245Props { const Feature245 = ({ className }: Feature245Props) => { return ( -
-
-
+
+
+

رتبه کلمات کلیدی را پیگیری کنید

@@ -107,8 +111,8 @@ const Feature245 = ({ className }: Feature245Props) => { تحلیل فنی، محتوا و رقبا در یک ابزار جمع می‌شود. تیم محتوا می‌داند روی کدام صفحه و کلمه باید تمرکز کند.

-
-
+
+
{[0, 1, 2, 3].map((i) => ( { ))}
-
+
{[4, 5, 6, 7].map((i) => ( {
- + { const div6Ref = useRef(null); const div7Ref = useRef(null); return ( -
-
-
- +
+
+
+

تریگر رویداد @@ -36,7 +40,7 @@ const Feature251 = ({ className }: Feature251Props) => { شرط هوشمند

- + } src="/block-media/shadcnblocks/block/logos/google-icon.svg" @@ -47,7 +51,7 @@ const Feature251 = ({ className }: Feature251Props) => { ref={div2Ref as React.RefObject} src="/block-media/shadcnblocks/block/logos/figma-icon.svg" imageAlt="نمای اتوماسیون فرایند" - className="translate-x-32 rtl:-translate-x-32" + className="translate-x-12 rtl:-translate-x-12 sm:translate-x-20 sm:rtl:-translate-x-20 md:translate-x-32 md:rtl:-translate-x-32" /> } @@ -59,7 +63,7 @@ const Feature251 = ({ className }: Feature251Props) => { ref={div5Ref as React.RefObject} src="/block-media/shadcnblocks/block/block-1.svg" imageAlt="سازنده گردش‌کار" - className="absolute end-12 top-1/2 -translate-y-1/2" + className="absolute end-4 top-1/2 -translate-y-1/2 sm:end-8 md:end-12" />
} @@ -94,7 +98,7 @@ const Feature251 = ({ className }: Feature251Props) => { - +

اعلان خودکار @@ -132,7 +136,7 @@ const Feature251 = ({ className }: Feature251Props) => { - + {

- +

اتصال ابزار diff --git a/blocks/feature313.tsx b/blocks/feature313.tsx index b6e16a8..608ff2d 100644 --- a/blocks/feature313.tsx +++ b/blocks/feature313.tsx @@ -72,12 +72,19 @@ const Feature313 = ({ const isMobile = useIsMobile(); return ( -
-
-
+
+
+
{cards.map((card, index) => ( - -
+ +
{cards.map((_, circleIndex) => (
-
+
-

+

{card.title}

@@ -111,7 +118,7 @@ const Feature313 = ({ @@ -126,10 +133,10 @@ const Feature313 = ({ @@ -148,7 +155,7 @@ const Feature313 = ({ initial: { x: 0, scale: 1 }, hover: { x: isMobile ? 0 : "-75%", - scale: 1.2, + scale: isMobile ? 1 : 1.2, }, }} transition={{ duration: 0.3, ease: "easeOut" }} @@ -163,18 +170,20 @@ const Feature313 = ({ -
-

+

+

{videoCaption}

-

+

{videoSecondaryCaption}

@@ -183,7 +192,7 @@ const Feature313 = ({
- + {videoCaption} {videoSecondaryCaption} {videoCaption} diff --git a/blocks/feature322.tsx b/blocks/feature322.tsx index e1e13c0..da85ec9 100644 --- a/blocks/feature322.tsx +++ b/blocks/feature322.tsx @@ -8,7 +8,7 @@ import { useScroll, useTransform, } from "framer-motion"; -import React, { useRef, useState } from "react"; +import React, { useLayoutEffect, useRef, useState } from "react"; import { cn } from "@/lib/utils"; @@ -228,11 +228,31 @@ const Feature322 = ({ testimonial = DEFAULT_TESTIMONIAL, }: Feature322Props) => { const scrollRef = useRef(null); + const [stageHeight, setStageHeight] = useState(0); const { scrollYProgress } = useScroll({ container: scrollRef, }); const [activeIndex, setActiveIndex] = useState(0); + useLayoutEffect(() => { + const scrollContainer = scrollRef.current; + if (!scrollContainer) return; + + const updateStageHeight = () => { + setStageHeight(scrollContainer.clientHeight); + }; + + updateStageHeight(); + const observer = new ResizeObserver(updateStageHeight); + observer.observe(scrollContainer); + + return () => observer.disconnect(); + }, []); + + const scrollSegmentCount = Math.max(features.length - 1, 1); + const scrollSpacerHeight = + stageHeight > 0 ? stageHeight * scrollSegmentCount : undefined; + useMotionValueEvent(scrollYProgress, "change", (latest) => { const nextIndex = getActiveFeatureIndex(latest, features.length); setActiveIndex((prev) => (prev === nextIndex ? prev : nextIndex)); @@ -246,14 +266,21 @@ const Feature322 = ({ dir="rtl" lang="fa" className={cn( - "relative h-svh w-full min-w-[20rem] max-w-full overflow-x-clip overflow-y-auto sm:min-w-[36rem] lg:min-w-[64rem]", + "relative h-svh max-h-full min-h-[28rem] w-full min-w-[20rem] max-w-full overflow-x-clip overflow-y-auto overscroll-contain sm:min-w-[36rem] lg:min-h-[32rem] lg:min-w-[64rem]", className, )} >
{/* Desktop: sticky stage + scroll spacer */}
-
+
0 + ? { height: stageHeight } + : { minHeight: "28rem" } + } + >
{activeFeature ? ( @@ -279,8 +306,12 @@ const Feature322 = ({
diff --git a/blocks/feature52.tsx b/blocks/feature52.tsx index 10a4635..698dec4 100644 --- a/blocks/feature52.tsx +++ b/blocks/feature52.tsx @@ -70,7 +70,7 @@ const Feature52 = ({ className }: Feature52Props) => { return (
- + {tabs.map((tab, index) => ( { نسخه جدید معرفی Collaboration 2.0 - +

diff --git a/blocks/hero40.tsx b/blocks/hero40.tsx index 44b2405..d28473a 100644 --- a/blocks/hero40.tsx +++ b/blocks/hero40.tsx @@ -8,21 +8,33 @@ import { Share2, Sparkles, Video, + type LucideIcon, } from "lucide-react"; -import { Marquee } from "@/components/ui/marquee"; +import { + Marquee, + MarqueeContent, + MarqueeFade, + MarqueeItem, +} from "@/components/kibo-ui/marquee"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; -const integrations = [ +const integrations: { + name: string; + description: string; + icon: LucideIcon; +}[] = [ { name: "ذخیره‌سازی", - description: "فایل‌های ویدیویی خود را به‌صورت امن در فضای ذخیره‌سازی دلخواه مدیریت کنید.", + description: + "فایل‌های ویدیویی خود را به‌صورت امن در فضای ذخیره‌سازی دلخواه مدیریت کنید.", icon: Cloud, }, { name: "هوش مصنوعی", - description: "با کمک هوش مصنوعی، تولید محتوای ویدیویی و تصویری را سرعت دهید.", + description: + "با کمک هوش مصنوعی، تولید محتوای ویدیویی و تصویری را سرعت دهید.", icon: Sparkles, }, { @@ -37,7 +49,8 @@ const integrations = [ }, { name: "شبکه‌های اجتماعی", - description: "ویدیوهای خود را تنها با چند کلیک در شبکه‌های اجتماعی منتشر کنید.", + description: + "ویدیوهای خود را تنها با چند کلیک در شبکه‌های اجتماعی منتشر کنید.", icon: Share2, }, { @@ -47,6 +60,27 @@ const integrations = [ }, ]; +function IntegrationCard({ + name, + description, + icon: Icon, +}: (typeof integrations)[number]) { + return ( +
+ +
+

{name}

+

+ {description} +

+
+
+ ); +} + interface Hero40Props { className?: string; } @@ -59,10 +93,11 @@ const Hero40 = ({ className }: Hero40Props) => {

همه ابزارهای موردنیاز خود را یکپارچه کنید -

+

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

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