+
- کاتالوگ جامع محصولات
+ فروشگاه تخصصی محصولات کنینا
- کاتالوگ آنلاین و دوز مصرفی
+ کاتالوگ دیجیتال و راهنمای بالینی
)}
diff --git a/frontend/application/components/Hero.tsx b/frontend/application/components/Hero.tsx
index d82e5c0..2daef11 100644
--- a/frontend/application/components/Hero.tsx
+++ b/frontend/application/components/Hero.tsx
@@ -70,7 +70,7 @@ export default function Hero({ banners = [], onShopNavigate }: { banners?: Banne
const subtitle = (mounted && isInitialized ? getText('hero_desc', '') : '') || defaultSubtitle;
const titleParts = title.split('\n');
- const activeImageUrl = activeBanner?.imageUrl || (mounted && isInitialized ? getText('hero_image_url', '') : '') || (banners.length > 0 && banners[0].imageUrl ? banners[0].imageUrl : "/assets/images/hero-section-image.png");
+ const activeImageUrl = activeBanner?.imageUrl || (mounted && isInitialized ? getText('hero_image_url', '') : '') || (banners.length > 0 && banners[0].imageUrl ? banners[0].imageUrl : "");
const handleNextSlide = () => {
if (totalSlides > 1) {
@@ -193,18 +193,20 @@ export default function Hero({ banners = [], onShopNavigate }: { banners?: Banne
onTouchEnd={onTouchEnd}
>
-
+ {activeImageUrl && (
+
+ )}
{/* Gradient overlay and slide caption */}
diff --git a/frontend/application/components/PodcastPlayerModal.tsx b/frontend/application/components/PodcastPlayerModal.tsx
index c119990..c424007 100644
--- a/frontend/application/components/PodcastPlayerModal.tsx
+++ b/frontend/application/components/PodcastPlayerModal.tsx
@@ -1,5 +1,5 @@
"use client";
-import React, { useState, useRef, useEffect, useCallback } from "react";
+import React, { useState, useRef, useEffect, useCallback, useMemo } from "react";
import { motion, AnimatePresence } from "motion/react";
import {
Play,
@@ -32,8 +32,13 @@ interface PodcastPlayerModalProps {
const PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5];
+// Generate dynamic waveform bars for Soundcloud-like UI
+const WAVEFORM_BAR_COUNT = 48;
+
export default function PodcastPlayerModal({ isOpen, onClose, podcast }: PodcastPlayerModalProps) {
const audioRef = useRef
(null);
+ const waveformRef = useRef(null);
+
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
@@ -44,6 +49,16 @@ export default function PodcastPlayerModal({ isOpen, onClose, podcast }: Podcast
const [isBuffering, setIsBuffering] = useState(false);
const [isCopied, setIsCopied] = useState(false);
+ // Deterministic heights for waveform visualization
+ const waveformHeights = useMemo(() => {
+ const seed = (podcast?.title || "canina").split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
+ return Array.from({ length: WAVEFORM_BAR_COUNT }).map((_, i) => {
+ const pseudo = Math.sin((i + 1) * 0.45 + seed) * 0.5 + 0.5;
+ const heightPercent = Math.max(20, Math.min(95, Math.floor(pseudo * 80 + 15)));
+ return heightPercent;
+ });
+ }, [podcast?.title]);
+
// Reset states on open/close
useEffect(() => {
if (isOpen) {
@@ -67,14 +82,22 @@ export default function PodcastPlayerModal({ isOpen, onClose, podcast }: Podcast
}
}, []);
- const handleSeek = (e: React.ChangeEvent) => {
- const time = Number(e.target.value);
- if (audioRef.current) {
- audioRef.current.currentTime = time;
- setCurrentTime(time);
+ const seekToPercent = (percent: number) => {
+ if (audioRef.current && duration > 0) {
+ const newTime = (percent / 100) * duration;
+ audioRef.current.currentTime = Math.max(0, Math.min(duration, newTime));
+ setCurrentTime(newTime);
}
};
+ const handleWaveformClick = (e: React.MouseEvent) => {
+ if (!waveformRef.current || duration <= 0) return;
+ const rect = waveformRef.current.getBoundingClientRect();
+ const clickX = e.clientX - rect.left;
+ const percent = Math.max(0, Math.min(100, (clickX / rect.width) * 100));
+ seekToPercent(percent);
+ };
+
const skipTime = (seconds: number) => {
if (audioRef.current) {
audioRef.current.currentTime = Math.max(0, Math.min(audioRef.current.duration || 0, audioRef.current.currentTime + seconds));
@@ -116,7 +139,7 @@ export default function PodcastPlayerModal({ isOpen, onClose, podcast }: Podcast
if (navigator.share) {
try {
await navigator.share({
- title: podcast?.title || 'پادکست و بررسی تخصصی کنینا',
+ title: podcast?.title || 'پادکست و بررسی بالینی مکمل کنینا',
text: podcast?.description || '',
url: shareUrl
});
@@ -156,6 +179,8 @@ export default function PodcastPlayerModal({ isOpen, onClose, podcast }: Podcast
if (!isOpen || !podcast) return null;
const displayCover = podcast.cover || podcast.fallbackCover || '/images/default-podcast.png';
+ const progressPercent = duration > 0 ? (currentTime / duration) * 100 : 0;
+ const volumePercent = isMuted ? 0 : volume * 100;
return (
@@ -178,10 +203,10 @@ export default function PodcastPlayerModal({ isOpen, onClose, podcast }: Podcast
className="relative bg-gradient-to-b from-neutral-900 via-neutral-900 to-black w-full max-w-lg rounded-[2.5rem] overflow-hidden shadow-2xl border border-white/15 p-6 sm:p-8 flex flex-col items-center select-none text-white z-10"
>
{/* Top Bar (Header + Close) */}
-
-
+
+
- پادکست و بررسی صوتی کنینا
+ پادکست و تحلیل صوتی بالینی
@@ -235,7 +260,7 @@ export default function PodcastPlayerModal({ isOpen, onClose, podcast }: Podcast
{/* Cover Art Artwork */}
-
+

{/* Title and Metadata */}
-
-
+
+
{podcast.title || "بررسی بالینی و نحوه مصرف مکمل"}
{podcast.productName && (
-
+
محصول: {podcast.productName}
)}
{podcast.description && (
-
+
{podcast.description}
)}
- {/* Seeker / Progress Bar */}
-
-
-
- {formatTime(currentTime)}
-
-
-
-
-
- {formatTime(duration)}
-
+ {/* Soundcloud-like Waveform Visualizer & Seeker */}
+
+
+ {waveformHeights.map((h, i) => {
+ const barPercent = (i / (WAVEFORM_BAR_COUNT - 1)) * 100;
+ const isPassed = barPercent <= progressPercent;
+ return (
+
+ );
+ })}
+
+
+ {/* Time Stamp Row */}
+
+ {formatTime(currentTime)}
+ {formatTime(duration)}
@@ -302,7 +344,7 @@ export default function PodcastPlayerModal({ isOpen, onClose, podcast }: Podcast
- {/* Central Controls: -15s, Play/Pause, +15s */}
-
- {/* -15s */}
-
-
+ {/* Central Controls: -15s on Left, Play/Pause in Middle, +15s on Right in RTL */}
+
{/* Play / Pause button */}
- {/* +15s */}
+ {/* -15s button (left) */}
+
+
+ {/* +15s button (right) */}
- {/* Volume Control */}
-
+ {/* Volume Control: Increases towards the speaker icon */}
+
diff --git a/frontend/application/components/ProductPage.tsx b/frontend/application/components/ProductPage.tsx
index 2e1bcc0..df43eba 100644
--- a/frontend/application/components/ProductPage.tsx
+++ b/frontend/application/components/ProductPage.tsx
@@ -692,140 +692,134 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
-
- {/* Video Player Card */}
+
+ {/* Video Player Row */}
{product.videoUrl && (
-
-
-
-
-
- ویدیو راهنما
-
- Video Guide
-
-
- {/* Thumbnail / Cover Box */}
-
setIsVideoModalOpen(true)}
- className="relative aspect-video rounded-2xl overflow-hidden cursor-pointer bg-neutral-800 border border-white/10 group-hover:border-canina-blue/50 transition-all flex items-center justify-center"
- >
-

{
- (e.target as HTMLImageElement).src = product.image;
- }}
- />
-
-
+
+ {/* Thumbnail / Cover Box */}
+
setIsVideoModalOpen(true)}
+ className="relative w-full md:w-80 aspect-video rounded-2xl overflow-hidden cursor-pointer bg-neutral-800 border border-white/10 group-hover:border-canina-blue/50 transition-all flex items-center justify-center shrink-0"
+ >
+

{
+ (e.target as HTMLImageElement).src = product.image;
+ }}
+ />
+
-
-
-
- {product.videoTitle || "ویدیو معرفی و راهنمای مصرف محصول"}
-
-
- {product.videoDescription || "مشاهده راهنمای کامل، دوز مصرفی و آموزش خوراندن مکمل توسط کادر تخصصی کنینا"}
-
-
-
+
+
+
+
+ ویدیو راهنما و معرفی
+
+
+
+ {product.videoTitle || "ویدیو راهنمای مصرف و معرفی مکمل"}
+
+
+ {product.videoDescription || "مشاهده ویدیوی آموزشی، دوزبندی دقیق و نحوه مصرف مکمل توسط کادر علمی کنینا"}
+
+
+
+
+
)}
- {/* Podcast Player Card */}
+ {/* Podcast Player Row */}
{product.podcastUrl && (
-
-
-
-
- 🎧 پادکست اختصاصی
-
- Audio Analysis
-
-
- {/* Podcast Cover / Artwork Box */}
-
setIsPodcastModalOpen(true)}
- className="relative aspect-video rounded-2xl overflow-hidden cursor-pointer bg-neutral-800 border border-white/10 group-hover:border-purple-500/50 transition-all flex items-center justify-center"
- >
-

{
- (e.target as HTMLImageElement).src = product.image;
- }}
- />
-
-
+
+ {/* Podcast Cover / Artwork Box */}
+
setIsPodcastModalOpen(true)}
+ className="relative w-full md:w-80 aspect-video rounded-2xl overflow-hidden cursor-pointer bg-neutral-800 border border-white/10 group-hover:border-purple-500/50 transition-all flex items-center justify-center shrink-0"
+ >
+

{
+ (e.target as HTMLImageElement).src = product.image;
+ }}
+ />
+
-
-
-
- {product.podcastTitle || "پادکست و بررسی صوتی بالینی مکمل"}
-
-
- {product.podcastDescription || "توضیحات صوتی دامپزشک و کادر علمی درباره اثربخشی، مکانیسم اثر و نحوه مصرف دقیق"}
-
-
-
+ )}
+
+ {/* PDF Catalog Download Row */}
+ {product.pdfUrl && (
+
)}
-
- {/* PDF Catalog Download Box */}
- {product.pdfUrl && (
-
-
-
- PDF
-
-
-
- {product.pdfTitle || "بروشور و کاتالوگ علمی Canina آلمان"}
-
-
- {product.pdfDescription || "شامل مقالات رفرنس، جدول ترکیبات دقیق و سرتیفیکیت رسمی"}
-
-
-
-
-
- دانلود مستقیم فایل PDF
-
-
- )}
)}
diff --git a/frontend/application/components/ProductReviews.tsx b/frontend/application/components/ProductReviews.tsx
index b76141b..ce1628d 100644
--- a/frontend/application/components/ProductReviews.tsx
+++ b/frontend/application/components/ProductReviews.tsx
@@ -102,10 +102,10 @@ export default function ProductReviews({ productId, productName }: ProductReview
return (
{/* Header & Rating Summary */}
-
-
+
+
-
+
@@ -120,13 +120,13 @@ export default function ProductReviews({ productId, productName }: ProductReview
{/* Rating Score Badge & Add Review Button */}
-
-
-
+
+
+
{Array.from({ length: 5 }).map((_, i) => (
))}
-
+
{toPersian(stats.averageRating)} از ۵
-
+
({toPersian(stats.totalReviews)} دیدگاه)
setFormOpen(!formOpen)}
- className="px-5 py-3 bg-canina-blue hover:bg-canina-dark text-white rounded-2xl text-xs font-black transition-all shadow-md shadow-canina-blue/20 flex items-center gap-2"
+ className="px-5 py-2.5 bg-medical-gray-900 hover:bg-canina-blue text-white rounded-2xl text-xs font-black transition-all shadow-md shadow-black/10 flex items-center gap-2 cursor-pointer"
>
{formOpen ? "بستن فرم" : "ثبت دیدگاه جدید"}
diff --git a/frontend/application/components/VideoModalPlayer.tsx b/frontend/application/components/VideoModalPlayer.tsx
index 36d8a64..5bb87eb 100644
--- a/frontend/application/components/VideoModalPlayer.tsx
+++ b/frontend/application/components/VideoModalPlayer.tsx
@@ -173,7 +173,7 @@ export default function VideoModalPlayer({ isOpen, onClose, video }: VideoModalP
if (navigator.share) {
try {
await navigator.share({
- title: video?.title || 'ویدئوی آموزشی کنینا',
+ title: video?.title || 'ویدئوی راهنمای کنینا',
text: video?.description || '',
url: shareUrl
});
@@ -213,6 +213,8 @@ export default function VideoModalPlayer({ isOpen, onClose, video }: VideoModalP
if (!isOpen || !video) return null;
const isIframe = video.videoUrl?.includes("
)}
- {/* Progress / Seek Bar */}
+ {/* Progress / Seek Bar with filled progress track */}
{formatTime(currentTime)}
-
+
+ {/* Background Track */}
+
+ {/* Visual Scrubber Thumb */}
+
@@ -393,8 +407,8 @@ export default function VideoModalPlayer({ isOpen, onClose, video }: VideoModalP
{/* Action Buttons Row */}
- {/* Left Controls (Playback & Jumps) */}
-
+ {/* Left Controls (Playback & Jumps with - on Left and + on Right in RTL) */}
+
{/* Play/Pause */}
:
}
- {/* -10s */}
+ {/* -10s button */}
skipTime(-10)}
- className="p-2 text-white/70 hover:text-white rounded-lg hover:bg-white/10 transition-colors cursor-pointer flex items-center gap-1 text-xs font-mono font-bold"
+ className="px-2.5 py-1.5 text-white/80 hover:text-white rounded-lg hover:bg-white/10 transition-colors cursor-pointer flex items-center gap-1 text-xs font-mono font-bold border border-white/10"
title="۱۰ ثانیه عقب"
>
-
- 10s
+
+ -10s
- {/* +10s */}
+ {/* +10s button */}
skipTime(10)}
- className="p-2 text-white/70 hover:text-white rounded-lg hover:bg-white/10 transition-colors cursor-pointer flex items-center gap-1 text-xs font-mono font-bold"
+ className="px-2.5 py-1.5 text-white/80 hover:text-white rounded-lg hover:bg-white/10 transition-colors cursor-pointer flex items-center gap-1 text-xs font-mono font-bold border border-white/10"
title="۱۰ ثانیه جلو"
>
-
- 10s
+
+ +10s
- {/* Volume slider */}
-
+ {/* Volume Slider: Increases towards the speaker icon */}
+
@@ -456,7 +482,7 @@ export default function VideoModalPlayer({ isOpen, onClose, video }: VideoModalP
setShowRateMenu(!showRateMenu)}
- className="px-2.5 py-1 rounded-xl bg-white/10 hover:bg-white/20 text-white text-xs font-mono font-bold transition-all cursor-pointer flex items-center gap-1"
+ className="px-2.5 py-1 rounded-xl bg-white/10 hover:bg-white/20 text-white text-xs font-mono font-bold transition-all cursor-pointer flex items-center gap-1 border border-white/10"
title="سرعت پخش"
>
{playbackRate}x