This commit is contained in:
Hamed
2026-08-05 11:28:05 +04:00
parent fbacd2ca22
commit 121287e105
5 changed files with 189 additions and 23 deletions
+15 -4
View File
@@ -2,10 +2,11 @@
<project version="4">
<component name="ChangeListManager">
<list default="true" id="bb7b50a5-be9c-4cd7-a05b-979f0e91b173" name="Changes" comment="update">
<change afterPath="$PROJECT_DIR$/components/MobileShowcase.jsx" afterDir="false" />
<change afterPath="$PROJECT_DIR$/components/ShowcaseSection.jsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/page.jsx" beforeDir="false" afterPath="$PROJECT_DIR$/app/page.jsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/components/FloatingItems.jsx" beforeDir="false" afterPath="$PROJECT_DIR$/components/FloatingItems.jsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/components/HorizontalShowcase.jsx" beforeDir="false" afterPath="$PROJECT_DIR$/components/HorizontalShowcase.jsx" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -68,7 +69,7 @@
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-WS-261.23567.141" />
<option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-WS-261.26222.58" />
</set>
</attachedChunks>
</component>
@@ -89,6 +90,8 @@
<workItem from="1785645096071" duration="26000" />
<workItem from="1785646314677" duration="16621000" />
<workItem from="1785730130350" duration="12049000" />
<workItem from="1785757166936" duration="5000" />
<workItem from="1785901279535" duration="9314000" />
</task>
<task id="LOCAL-00001" summary="update">
<option name="closed" value="true" />
@@ -218,7 +221,15 @@
<option name="project" value="LOCAL" />
<updated>1785754201946</updated>
</task>
<option name="localTasksCounter" value="17" />
<task id="LOCAL-00017" summary="update">
<option name="closed" value="true" />
<created>1785755999255</created>
<option name="number" value="00017" />
<option name="presentableId" value="LOCAL-00017" />
<option name="project" value="LOCAL" />
<updated>1785755999255</updated>
</task>
<option name="localTasksCounter" value="18" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
+3 -3
View File
@@ -1,6 +1,5 @@
import Header from '@/components/Header';
import HeroSection from '@/components/HeroSection';
import HorizontalShowcase from '@/components/HorizontalShowcase';
import TrustedBySection from '@/components/TrustedBySection';
import FeaturesSection from '@/components/FeaturesSection';
import AboutSection from '@/components/AboutSection';
@@ -12,6 +11,7 @@ import TicketForm from '@/components/TicketForm';
import Footer from '@/components/Footer';
import CartDrawer from '@/components/CartDrawer';
import ChatWidget from '@/components/ChatWidget';
import ShowcaseSection from "@/components/ShowcaseSection";
export default function HomePage() {
@@ -25,7 +25,7 @@ export default function HomePage() {
<HeroSection/>
<HorizontalShowcase/>
<ShowcaseSection/>
<TrustedBySection/>
@@ -33,7 +33,7 @@ export default function HomePage() {
<AboutSection/>
{/*<CatalogSection/>*/}
<CatalogSection/>
<LocationsSection/>
+36 -16
View File
@@ -1,9 +1,5 @@
'use client';
// سکشن ویترین محصولات: با اسکرول عمودی به اسکرول افقی پین‌شده تبدیل می‌شود (GSAP ScrollTrigger)
// و یک ویدیوی پس‌زمینه دارد که با پیشرفت اسکرول جلو/عقب می‌رود. طبق درخواست کاربر، این سکشن
// و هیرو از چرخه‌ی رنگ‌بندی متناوب بقیه سکشن‌ها مستثنا هستند.
import {useEffect, useRef, useState} from 'react';
import {gsap} from 'gsap';
import {ScrollTrigger} from 'gsap/ScrollTrigger';
@@ -30,9 +26,28 @@ export default function HorizontalShowcase() {
const ctx = gsap.context(() => {
const track = trackRef.current;
const section = sectionRef.current;
if (!track || !section) return;
const scrollDistance = () => track.scrollWidth - window.innerWidth;
const scrollDistance = () => (
Math.max(track.scrollWidth - window.innerWidth, 0)
);
let animationFrameId = null;
let latestProgress = 0;
const syncVideo = (progress) => {
latestProgress = progress;
if (animationFrameId !== null) return;
animationFrameId = window.requestAnimationFrame(() => {
const video = videoRef.current;
if (video && Number.isFinite(video.duration) && video.duration > 0) {
video.currentTime = latestProgress * video.duration;
}
animationFrameId = null;
});
};
const horizontalTween = gsap.to(track, {
x: () => -scrollDistance(),
@@ -44,16 +59,12 @@ export default function HorizontalShowcase() {
scrub: 1,
pin: true,
invalidateOnRefresh: true,
onUpdate: (self) => {
const video = videoRef.current;
if (video && video.duration) {
video.currentTime = self.progress * video.duration;
}
},
onUpdate: (self) => syncVideo(self.progress),
},
});
return () => {
if (animationFrameId !== null) window.cancelAnimationFrame(animationFrameId);
horizontalTween.scrollTrigger?.kill();
horizontalTween.kill();
};
@@ -64,6 +75,7 @@ export default function HorizontalShowcase() {
const handleModalAddToCart = () => {
if (!quickViewMachine) return;
dispatch(
addToCart({
id: quickViewMachine.id,
@@ -73,6 +85,7 @@ export default function HorizontalShowcase() {
image: quickViewMachine.image,
})
);
message.success(t.cart.addToast(quickViewMachine.name));
setQuickViewMachine(null);
};
@@ -115,12 +128,19 @@ export default function HorizontalShowcase() {
>
{quickViewMachine && (
<div className="flex flex-col gap-4">
<img src={quickViewMachine.image} alt={quickViewMachine.name}
className="w-full h-64 object-contain"/>
<img
src={quickViewMachine.image}
alt={quickViewMachine.name}
className="w-full h-64 object-contain"
/>
<p className="text-secondary leading-7">{quickViewMachine.description}</p>
<p className="text-xl font-bold text-primary">{quickViewMachine.priceLabel}</p>
<Button type="primary" size="large" icon={<ShoppingCartOutlined/>}
onClick={handleModalAddToCart}>
<Button
type="primary"
size="large"
icon={<ShoppingCartOutlined/>}
onClick={handleModalAddToCart}
>
{t.machineActions.addToCart}
</Button>
</div>
@@ -128,4 +148,4 @@ export default function HorizontalShowcase() {
</Modal>
</section>
);
}
}
+107
View File
@@ -0,0 +1,107 @@
'use client';
import {useState} from 'react';
import {Modal, Button, message} from 'antd';
import {ShoppingCartOutlined, EyeOutlined} from '@ant-design/icons';
import {useDispatch} from 'react-redux';
import {useLanguage} from '@/app/context/LanguageContext';
import {addToCart} from '@/store/cartSlice';
export default function MobileShowcase() {
const dispatch = useDispatch();
const {t, dir} = useLanguage();
const [quickViewMachine, setQuickViewMachine] = useState(null);
const handleAddToCart = (machine) => {
dispatch(
addToCart({
id: machine.id,
name: machine.name,
priceNumeric: machine.priceNumeric,
priceLabel: machine.priceLabel,
image: machine.image,
})
);
message.success(t.cart.addToast(machine.name));
};
const handleModalAddToCart = () => {
if (!quickViewMachine) return;
handleAddToCart(quickViewMachine);
setQuickViewMachine(null);
};
return (
<section id="showcase" className="relative w-full py-8 px-4" dir={dir}>
<div className="flex flex-col gap-4">
{t.machines.map((machine) => (
<div
key={machine.id}
className="flex gap-4 items-center bg-surface border border-subtle rounded-2xl p-4"
>
<img
src={machine.image}
alt={machine.name}
className="w-24 h-24 object-contain shrink-0"
onClick={() => setQuickViewMachine(machine)}
/>
<div className="flex flex-col gap-1.5 min-w-0 flex-1">
<span className="text-brand-400 text-xs font-semibold tracking-wide">
{machine.shortLabel}
</span>
<h3 className="text-lg font-bold text-primary truncate">
{machine.name}
</h3>
<p className="text-sm text-secondary line-clamp-2">
{machine.description}
</p>
<p className="text-base font-bold text-primary mt-1">
{machine.priceLabel}
</p>
<div className="flex justify-start items-center mt-3">
<Button
type="primary"
size="middle"
icon={<ShoppingCartOutlined/>}
onClick={() => handleAddToCart(machine)}
>
{t.machineActions.addToCart}
</Button>
</div>
</div>
</div>
))}
</div>
<Modal
open={!!quickViewMachine}
onCancel={() => setQuickViewMachine(null)}
footer={null}
centered
title={quickViewMachine?.name}
>
{quickViewMachine && (
<div className="flex flex-col gap-4">
<img
src={quickViewMachine.image}
alt={quickViewMachine.name}
className="w-full h-64 object-contain"
/>
<p className="text-secondary leading-7">{quickViewMachine.description}</p>
<p className="text-xl font-bold text-primary">{quickViewMachine.priceLabel}</p>
<Button
type="primary"
size="large"
icon={<ShoppingCartOutlined/>}
onClick={handleModalAddToCart}
>
{t.machineActions.addToCart}
</Button>
</div>
)}
</Modal>
</section>
);
}
+28
View File
@@ -0,0 +1,28 @@
'use client';
import {useEffect, useState} from 'react';
import HorizontalShowcase from './HorizontalShowcase';
import MobileShowcase from './MobileShowcase';
const MOBILE_BREAKPOINT = 768;
export default function ShowcaseSection() {
const [isMobile, setIsMobile] = useState(null);
useEffect(() => {
const mediaQuery = window.matchMedia(
`(max-width: ${MOBILE_BREAKPOINT - 1}px)`
);
const handleChange = (event) => setIsMobile(event.matches);
setIsMobile(mediaQuery.matches);
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, []);
if (isMobile === null) return null;
return isMobile ? <MobileShowcase/> : <HorizontalShowcase/>;
}