From d9865c5bfe87aee072918eab89e5bb415e39fc65 Mon Sep 17 00:00:00 2001
From: alireza
Date: Tue, 25 Aug 2026 16:15:14 +0330
Subject: [PATCH] feat: show 1 post with auto-slider, touch swipe and dots on
mobile view
---
.../pages/Home/sections/TechnogenSection.tsx | 387 +++++++++++-------
1 file changed, 241 insertions(+), 146 deletions(-)
diff --git a/frontend/src/pages/Home/sections/TechnogenSection.tsx b/frontend/src/pages/Home/sections/TechnogenSection.tsx
index e37cfd9..6f8fd3f 100644
--- a/frontend/src/pages/Home/sections/TechnogenSection.tsx
+++ b/frontend/src/pages/Home/sections/TechnogenSection.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react'
+import { useState, useEffect, useRef } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Download, FileText, ExternalLink, X, Eye, ChevronLeft, ChevronRight } from 'lucide-react'
import { useLang } from '@/context/LangContext'
@@ -7,7 +7,6 @@ import { FALLBACK_TECHNOGEN_REPORTS, type TechnogenReport } from '@/data/technog
const NAVY = '#032340'
const GOLD = '#CD9E53'
const EASE = [0.22, 1, 0.36, 1] as const
-const ITEMS_PER_PAGE = 5
const faNum = (n: number | string) => String(n).replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[+d])
@@ -18,6 +17,9 @@ export default function TechnogenSection() {
const [selectedReport, setSelectedReport] = useState(null)
const [page, setPage] = useState(0)
const [direction, setDirection] = useState(0)
+ const [isMobile, setIsMobile] = useState(false)
+ const [isPaused, setIsPaused] = useState(false)
+ const touchStartX = useRef(null)
useEffect(() => {
let unmounted = false
@@ -37,8 +39,36 @@ export default function TechnogenSection() {
}
}, [])
- const totalPages = Math.max(1, Math.ceil(reports.length / ITEMS_PER_PAGE))
- const currentReports = reports.slice(page * ITEMS_PER_PAGE, page * ITEMS_PER_PAGE + ITEMS_PER_PAGE)
+ // Detect mobile screen (< 768px)
+ useEffect(() => {
+ const checkMobile = () => {
+ const mobile = window.innerWidth < 768
+ setIsMobile(mobile)
+ }
+ checkMobile()
+ window.addEventListener('resize', checkMobile)
+ return () => window.removeEventListener('resize', checkMobile)
+ }, [])
+
+ const itemsPerPage = isMobile ? 1 : 5
+ const totalPages = Math.max(1, Math.ceil(reports.length / itemsPerPage))
+
+ // Reset page if out of bounds on resize
+ useEffect(() => {
+ setPage((p) => Math.min(p, totalPages - 1))
+ }, [totalPages])
+
+ // Auto-slide on mobile every 4.5 seconds
+ useEffect(() => {
+ if (!isMobile || isPaused || selectedReport || totalPages <= 1) return
+ const interval = setInterval(() => {
+ setDirection(1)
+ setPage((p) => (p + 1) % totalPages)
+ }, 4500)
+ return () => clearInterval(interval)
+ }, [isMobile, isPaused, selectedReport, totalPages])
+
+ const currentReports = reports.slice(page * itemsPerPage, page * itemsPerPage + itemsPerPage)
const goNext = () => {
setDirection(1)
@@ -50,6 +80,27 @@ export default function TechnogenSection() {
setPage((p) => (p - 1 + totalPages) % totalPages)
}
+ const handleTouchStart = (e: React.TouchEvent) => {
+ touchStartX.current = e.touches[0].clientX
+ setIsPaused(true)
+ }
+
+ const handleTouchEnd = (e: React.TouchEvent) => {
+ setIsPaused(false)
+ if (touchStartX.current === null) return
+ const diff = touchStartX.current - e.changedTouches[0].clientX
+ if (Math.abs(diff) > 40) {
+ if (isFa) {
+ if (diff > 0) goNext()
+ else goPrev()
+ } else {
+ if (diff > 0) goPrev()
+ else goNext()
+ }
+ }
+ touchStartX.current = null
+ }
+
const openPreview = (report: TechnogenReport) => {
setSelectedReport(report)
}
@@ -166,7 +217,7 @@ export default function TechnogenSection() {
- {/* Slider controls (visible if more than 5 reports) */}
+ {/* Slider controls (visible on desktop if >5 or always on mobile if >1) */}
{totalPages > 1 && (
@@ -216,172 +267,216 @@ export default function TechnogenSection() {
)}
- {/* ── 5-Card Row / Slider Strip (Clean Typography, Breathing Border Glow) ── */}
-
-
- {currentReports.map((report, idx) => {
- const isSpecial = !!report.isFeatured
- return (
-
- {/* Top Cover Image */}
+ {/* ── Cards Row / Mobile Single Card View ── */}
+
setIsPaused(true)}
+ onMouseLeave={() => setIsPaused(false)}
+ >
+
+
+ {currentReports.map((report, idx) => {
+ const isSpecial = !!report.isFeatured
+ return (
openPreview(report)}
+ key={report.id}
+ className="technogen-card-glow"
style={{
- width: '100%',
- aspectRatio: '1 / 1.15',
- overflow: 'hidden',
- background: NAVY,
position: 'relative',
- cursor: 'pointer',
- }}
- >
-
-
-
- {/* Card Content Body */}
-
-
openPreview(report)}
style={{
- fontSize: 15,
- fontWeight: 900,
- color: '#FFFFFF',
- lineHeight: 1.5,
- margin: 0,
+ width: '100%',
+ aspectRatio: '1 / 1.15',
+ overflow: 'hidden',
+ background: NAVY,
+ position: 'relative',
cursor: 'pointer',
- minHeight: 48,
- display: 'flex',
- alignItems: 'center',
}}
- title={isFa ? report.titleFa : (report.titleEn || report.titleFa)}
>
- {isFa ? report.titleFa : (report.titleEn || report.titleFa)}
-
+
+
- {/* Bottom Action Buttons */}
+ {/* Card Content Body */}
- {report.fileUrl && (
-
openPreview(report)}
+ style={{
+ fontSize: 15,
+ fontWeight: 900,
+ color: '#FFFFFF',
+ lineHeight: 1.5,
+ margin: 0,
+ cursor: 'pointer',
+ minHeight: 48,
+ display: 'flex',
+ alignItems: 'center',
+ }}
+ title={isFa ? report.titleFa : (report.titleEn || report.titleFa)}
+ >
+ {isFa ? report.titleFa : (report.titleEn || report.titleFa)}
+
+
+ {/* Bottom Action Buttons */}
+
+ {report.fileUrl && (
+
+
+ {isFa ? 'دانلود PDF' : 'Download'}
+
+ )}
+
+
openPreview(report)}
style={{
display: 'inline-flex',
alignItems: 'center',
- gap: 5,
- color: GOLD,
- fontSize: 12,
- fontWeight: 800,
- textDecoration: 'none',
- transition: 'opacity 0.15s',
+ gap: 4,
+ background: 'transparent',
+ border: 'none',
+ color: 'rgba(255,255,255,0.7)',
+ fontSize: 11,
+ fontWeight: 700,
+ cursor: 'pointer',
+ padding: '2px 6px',
+ borderRadius: 4,
}}
- className="hover:!opacity-80"
+ className="hover:!text-white"
>
-
- {isFa ? 'دانلود PDF' : 'Download'}
-
- )}
-
- openPreview(report)}
- style={{
- display: 'inline-flex',
- alignItems: 'center',
- gap: 4,
- background: 'transparent',
- border: 'none',
- color: 'rgba(255,255,255,0.7)',
- fontSize: 11,
- fontWeight: 700,
- cursor: 'pointer',
- padding: '2px 6px',
- borderRadius: 4,
- }}
- className="hover:!text-white"
- >
-
- {isFa ? 'مشاهده' : 'View'}
-
+
+ {isFa ? 'مشاهده' : 'View'}
+
+
-
- {/* Bottom Gold Accent Bar */}
-
-
- )
- })}
-
-
+ {/* Bottom Gold Accent Bar */}
+
+
+ )
+ })}
+
+
+
+ {/* Mobile Dot Indicators */}
+ {isMobile && reports.length > 1 && (
+
+ {reports.map((_, idx) => (
+ {
+ setDirection(idx > page ? 1 : -1)
+ setPage(idx)
+ }}
+ style={{
+ width: page === idx ? 20 : 6,
+ height: 6,
+ borderRadius: 3,
+ background: page === idx ? GOLD : 'rgba(255,255,255,0.25)',
+ border: 'none',
+ padding: 0,
+ cursor: 'pointer',
+ transition: 'all 0.25s ease',
+ }}
+ title={`Go to report ${idx + 1}`}
+ />
+ ))}
+
+ )}
+
{/* ── Document Reader / Preview Modal ── */}