Risk slider, footer fix, global padding, mobile improvements

This commit is contained in:
alireza 2026-06-01 10:51:45 +03:30
parent c1f9563303
commit 3c40a655de
53 changed files with 231 additions and 111 deletions

81
.claude/settings.json Normal file
View File

@ -0,0 +1,81 @@
{
"env": {
"ECC_DISABLED_HOOKS": "pre:edit-write:gateguard-fact-force"
},
"permissions": {
"allow": [
"Bash(docker --version)",
"Bash(docker buildx *)",
"Bash(npm -v)",
"Bash(npm ci *)",
"Bash(git commit -m 'Add Docker deployment for Hugging Face Spaces *)",
"Bash(npm start *)",
"Bash(git reset *)",
"Bash(git commit -m 'Add admin panel + wire main site to fetch articles from panel API *)",
"WebFetch(domain:skillooodex-steel.hf.space)",
"Bash(git fetch *)",
"Bash(git merge *)",
"Bash(powershell -NoProfile -Command 'Get-NetTCPConnection -LocalPort 3001 -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess | ForEach-Object { Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue }; Write-Host done')",
"Bash(curl -s http://localhost:3001/api/prices)",
"Bash(powershell -NoProfile -Command 'Get-NetTCPConnection -LocalPort 3001 -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess | ForEach-Object { Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue }; Start-Sleep -Milliseconds 800; Write-Host done')",
"Bash(node -e ' *)",
"Bash(powershell -NoProfile -Command 'Get-NetTCPConnection -LocalPort 3001 -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess | ForEach-Object { Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue }; Write-Host stopped')",
"Bash(curl -sI -H \"User-Agent: Mozilla/5.0\" https://www.tgju.org/gold-global)",
"Bash(curl -sI https://www.google.com)",
"Bash(node -e \"require\\('@tabler/icons-react'\\)\")",
"Bash(git commit -m ' *)",
"Bash(ffmpeg -version)",
"PowerShell(Get-Command ffmpeg -ErrorAction SilentlyContinue)",
"PowerShell(ffmpeg -version 2>&1)",
"PowerShell(winget install --id Gyan.FFmpeg -e --accept-source-agreements --accept-package-agreements 2>&1)",
"PowerShell($env:Path = [System.Environment]::GetEnvironmentVariable\\(\"Path\",\"Machine\"\\) + \";\" + [System.Environment]::GetEnvironmentVariable\\(\"Path\",\"User\"\\); ffmpeg -i \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\ezgif-1650150a406efd73.mp4\" -vf \"scale=1280:-2\" -c:v libx264 -crf 28 -preset slow -an -movflags +faststart \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg.mp4\" -y 2>&1 | Select-Object -Last 5)",
"PowerShell(\\(Get-Item \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg.mp4\"\\).Length / 1MB)",
"Bash(git lfs *)",
"PowerShell(pip install \"huggingface_hub[hf_xet]\" -q 2>&1)",
"Bash(git rm *)",
"Bash(echo \"public/hero-bg.mp4\")",
"Bash(git filter-branch *)",
"Bash(npm list *)",
"Bash(git pull *)",
"Bash(git rebase *)",
"Bash(grep -Hm1 ^description: 'c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad/.claude/skills/*/SKILL.md')",
"Bash(echo \"done\")",
"PowerShell($env:Path = [System.Environment]::GetEnvironmentVariable\\(\"Path\",\"Machine\"\\) + \";\" + [System.Environment]::GetEnvironmentVariable\\(\"Path\",\"User\"\\); ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg.mp4\")",
"PowerShell($env:Path = [System.Environment]::GetEnvironmentVariable\\(\"Path\",\"Machine\"\\) + \";\" + [System.Environment]::GetEnvironmentVariable\\(\"Path\",\"User\"\\); ffmpeg -i \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg.mp4\" -t 74.56 -c copy \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg-trimmed.mp4\" -y 2>&1 | Select-Object -Last 3)",
"PowerShell(Rename-Item \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg-trimmed.mp4\" \"hero-bg.mp4\")",
"PowerShell(python -c \"from huggingface_hub import HfApi; print\\('ok'\\)\" 2>&1)",
"PowerShell($env:Path = [System.Environment]::GetEnvironmentVariable\\(\"Path\",\"Machine\"\\) + \";\" + [System.Environment]::GetEnvironmentVariable\\(\"Path\",\"User\"\\); ffmpeg -i \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg.mp4\" -vf \"boxblur=4:1\" -c:a copy \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg-blur.mp4\" -y 2>&1 | Select-Object -Last 3)",
"PowerShell(Rename-Item \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg-blur.mp4\" \"hero-bg.mp4\")",
"PowerShell($env:Path = [System.Environment]::GetEnvironmentVariable\\(\"Path\",\"Machine\"\\) + \";\" + [System.Environment]::GetEnvironmentVariable\\(\"Path\",\"User\"\\); ffmpeg -i \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\ezgif-1650150a406efd73.mp4\" -t 74.56 -vf \"scale=1280:-2\" -c:v libx264 -crf 28 -preset slow -an -movflags +faststart \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg.mp4\" -y 2>&1 | Select-Object -Last 3)",
"Bash(python -c \"import pdfplumber; pdf = pdfplumber.open\\(r'c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\گزارش کامل برند بوک اندیشکده \\(1\\).pdf'\\); print\\(f'Pages: {len\\(pdf.pages\\)}'\\); [print\\(f'--- Page {i+1} ---\\\\n{p.extract_text\\(\\)}'\\) for i, p in enumerate\\(pdf.pages[:5]\\)]\")",
"PowerShell(pip install pdfplumber -q 2>&1)",
"PowerShell(pip install pymupdf -q 2>&1)",
"PowerShell(1..13 | ForEach-Object { Copy-Item \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\$_.jpg\" \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\$_.jpg\" }; Write-Host \"Done\")",
"Bash(git stash *)",
"Bash(git checkout *)",
"Bash(git commit *)",
"Bash(GIT_LFS_SKIP_SMUDGE=1 git fetch space)",
"Bash(sed -i 's/const FACTORIES = {/const _FACTORIES = {/' src/pages/Home/sections/SteelNewsletterSection.tsx)",
"Bash(sed -i 's/function ImageSlider\\({ lang }/function ImageSlider\\({ lang: _lang }/' src/pages/Home/sections/../../../pages/Home/sections/../../..\\\\/pages\\\\/Pulse\\\\/Pulse.tsx)",
"Bash(python3 -)",
"Bash(GIT_LFS_SKIP_SMUDGE=1 git rebase space/main)",
"Bash(git config *)",
"Bash(pip show *)",
"Bash(python -c ' *)",
"PowerShell($env:Path = [System.Environment]::GetEnvironmentVariable\\(\"Path\",\"Machine\"\\) + \";\" + [System.Environment]::GetEnvironmentVariable\\(\"Path\",\"User\"\\); $dur = & ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg.mp4\" 2>&1; $newDur = [math]::Round\\([double]$dur - 2, 2\\); Write-Host \"Current: $dur New: $newDur\"; ffmpeg -i \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg.mp4\" -t $newDur -c copy \"c:\\\\Users\\\\DATIS STAR\\\\andishkade-foolad\\\\public\\\\hero-bg-new.mp4\" -y 2>&1 | Select-Object -Last 2)",
"Skill(update-config)",
"Skill(update-config:*)",
"Bash(npx prisma *)",
"PowerShell(Get-NetTCPConnection -LocalPort 3001 -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess | ForEach-Object { Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue }; Write-Host \"stopped\")",
"Bash(python3 \"C:\\\\Users\\\\DATIS STAR\\\\.claude\\\\plugins\\\\cache\\\\ui-ux-pro-max-skill\\\\ui-ux-pro-max\\\\2.5.0\\\\.claude\\\\skills\\\\ui-ux-pro-max\\\\scripts\\\\search.py\" \"steel industry admin dashboard CMS analytics RTL Persian\" --design-system -p \"Foolad Panel\")",
"Bash(python \"C:\\\\Users\\\\DATIS STAR\\\\.claude\\\\plugins\\\\cache\\\\ui-ux-pro-max-skill\\\\ui-ux-pro-max\\\\2.5.0\\\\.claude\\\\skills\\\\ui-ux-pro-max\\\\scripts\\\\search.py\" \"steel industry admin dashboard CMS analytics RTL Persian\" --design-system -p \"Foolad Panel\")",
"Read(//c/Users/DATIS STAR/.claude/plugins/cache/ui-ux-pro-max-skill/**)",
"Bash(python assets/scripts/search.py \"steel industry admin dashboard CMS analytics RTL\" --design-system -p \"Foolad Panel\")",
"Bash(Get-ChildItem -Path \"c:\\\\Users\\\\DATIS STAR\\\\پنل\\\\src\\\\app\\\\api\\\\\" -Recurse -File)",
"Read(//c/Users/DATIS STAR/**)"
],
"additionalDirectories": [
"c:\\Users\\DATIS STAR\\پنل"
]
}
}

BIN
1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
10.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 KiB

BIN
11.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 823 KiB

BIN
12.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
13.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 KiB

BIN
4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 KiB

BIN
9.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 KiB

BIN
brand_pages/page_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1018 KiB

BIN
brand_pages/page_02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
brand_pages/page_03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
brand_pages/page_04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 KiB

BIN
brand_pages/page_05.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 KiB

BIN
brand_pages/page_06.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 KiB

BIN
brand_pages/page_07.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

BIN
brand_pages/page_08.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 KiB

BIN
brand_pages/page_09.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
brand_pages/page_10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
brand_pages/page_11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
brand_pages/page_12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 KiB

BIN
brand_pages/page_13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 KiB

BIN
brand_pages/page_14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
brand_pages/page_15.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 KiB

BIN
brand_pages/page_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 KiB

BIN
brand_pages/page_17.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 KiB

BIN
brand_pages/page_18.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
brand_pages/page_19.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 KiB

BIN
brand_pages/page_20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 KiB

BIN
brand_pages/page_21.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 KiB

BIN
brand_pages/page_22.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 KiB

BIN
brand_pages/page_23.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 960 KiB

BIN
brand_pages/page_24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 KiB

BIN
brand_pages/page_25.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 KiB

BIN
brand_pages/page_26.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 KiB

BIN
brand_pages/page_27.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 KiB

BIN
brand_pages/page_28.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 KiB

BIN
brand_pages/page_29.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
brand_pages/page_30.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
brand_pages/page_31.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
brand_pages/page_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1004 KiB

BIN
brand_pages/page_33.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:400daccc315388c24c972cb62de1902d922bcabb8062c353283a0323f468209d
size 37935185

View File

@ -80,18 +80,18 @@ function NewsletterBox() {
export function Footer() { export function Footer() {
return ( return (
<footer dir="rtl" style={{ backgroundColor: 'var(--ink)', color: 'rgba(255,255,255,0.6)' }}> <footer dir="rtl" style={{ backgroundColor: 'var(--ink)', color: 'rgba(255,255,255,0.6)' }}>
<div style={{ padding: '56px 0 48px' }} className="max-w-7xl mx-auto w-full px-12 max-md:px-5"> <div style={{ padding: '56px 0 48px' }} className="max-w-7xl mx-auto w-full px-6 max-md:px-5">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', flexWrap: 'wrap', gap: 40 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 40, alignItems: 'flex-start' }} className="max-md:grid-cols-1 max-md:gap-10">
{/* Brand — no description paragraph */} {/* Brand */}
<div style={{ maxWidth: 300 }}> <div style={{ minWidth: 0 }}>
<div style={{ fontSize: 22, fontWeight: 900, color: '#fff', lineHeight: 1.2, marginBottom: 4 }}> <div style={{ fontSize: 22, fontWeight: 900, color: '#fff', lineHeight: 1.2, marginBottom: 4 }}>
اندیشکده فولاد آینده اندیشکده فولاد آینده
</div> </div>
<div style={{ fontSize: 10, letterSpacing: 3, color: 'rgba(255,255,255,0.3)', textTransform: 'uppercase', marginBottom: 20, direction: 'ltr', textAlign: 'right' }}> <div style={{ fontSize: 10, letterSpacing: 3, color: 'rgba(255,255,255,0.3)', textTransform: 'uppercase', marginBottom: 20, direction: 'ltr', textAlign: 'right', wordBreak: 'break-all' }}>
FUTURE STEEL POLICY INSTITUTE FUTURE STEEL POLICY INSTITUTE
</div> </div>
<div style={{ display: 'flex', gap: 6 }}> <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<SocialIcon label="in" /> <SocialIcon label="in" />
<SocialIcon label="tg" /> <SocialIcon label="tg" />
<SocialIcon label="tw" /> <SocialIcon label="tw" />
@ -99,8 +99,8 @@ export function Footer() {
</div> </div>
</div> </div>
{/* Links + newsletter under تماس با ما */} {/* Links + newsletter */}
<nav aria-label="پیوندهای پایین" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}> <nav aria-label="پیوندهای پایین" style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
{FOOTER_LINKS.map(link => ( {FOOTER_LINKS.map(link => (
<FooterLink key={link.label} href={link.href} label={link.label} /> <FooterLink key={link.label} href={link.href} label={link.label} />
))} ))}

View File

@ -260,9 +260,31 @@ body {
.animate-scroll-risk { .animate-scroll-risk {
animation: scroll 72s forwards linear infinite; animation: scroll 72s forwards linear infinite;
} }
/* ── Global breathing room ───────────────────────────── */
/* Cards, list items, table cells — give them air */
.card, [class*="card"] { padding: 1.5rem !important; }
/* Mobile: section inner containers get more horizontal space */
@media (max-width: 768px) {
section > div,
section > .max-w-7xl {
padding-left: 1.25rem !important;
padding-right: 1.25rem !important;
}
/* Extra vertical gap between stacked items */
[class*="grid-cols-1"] > * + * {
margin-top: 0.5rem;
}
}
@keyframes riskCardIn {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.animate-scroll { animation: none; } .animate-scroll { animation: none; }
.animate-scroll-risk { animation: none; } .animate-scroll-risk { animation: none; }
[style*="riskCardIn"] { animation: none !important; }
} }
/* /*

View File

@ -98,7 +98,7 @@ export default function FactoryNewsletterLayout() {
return ( return (
<section dir={lang === 'fa' ? 'rtl' : 'ltr'} style={{ background: 'var(--paper-2)' }}> <section dir={lang === 'fa' ? 'rtl' : 'ltr'} style={{ background: 'var(--paper-2)' }}>
<div className="max-w-7xl mx-auto px-12 max-md:px-5" style={{ paddingTop: 56, paddingBottom: 64 }}> <div className="max-w-7xl mx-auto px-12 max-md:px-5" style={{ paddingTop: 56, paddingBottom: 64 }}>
<div style={{ display: 'grid', gridTemplateColumns: '60% 1fr', gap: 40 }} className="max-md:grid-cols-1"> <div className="grid grid-cols-[60%_1fr] max-md:grid-cols-1 gap-10">
{/* ══ LEFT · ماهنامه ══ */} {/* ══ LEFT · ماهنامه ══ */}
<div> <div>
@ -110,7 +110,7 @@ export default function FactoryNewsletterLayout() {
<a href="#" style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)', textDecoration: 'none', whiteSpace: 'nowrap' }}>{t.factoryAll}</a> <a href="#" style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)', textDecoration: 'none', whiteSpace: 'nowrap' }}>{t.factoryAll}</a>
</div> </div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }} className="max-sm:grid-cols-1"> <div className="grid grid-cols-2 max-md:grid-cols-1 gap-3">
{REPORTS[lang].map((r, i) => ( {REPORTS[lang].map((r, i) => (
<div <div
key={i} key={i}

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from 'react' import { useEffect, useState, useRef, useCallback } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { useLang } from '@/context/LangContext' import { useLang } from '@/context/LangContext'
@ -180,25 +180,46 @@ export default function RiskSection() {
return () => { cancelled = true } return () => { cancelled = true }
}, []) }, [])
/* Duplicate items for seamless infinite loop (handled in JSX, not effect, to const VISIBLE = 3 // cards visible on desktop
stay immune to StrictMode double-invocation that would otherwise clone twice). */ const INTERVAL = 4000
const loopItems = [...riskItems, ...riskItems, ...riskItems] const total = riskItems.length
const [current, setCurrent] = useState(0)
const [paused, setPaused] = useState(false)
const [animDir, setAnimDir] = useState<'next' | 'prev'>('next')
const [isAnim, setIsAnim] = useState(false)
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
// touch support
const touchStartX = useRef(0)
const go = useCallback((dir: 'next' | 'prev') => {
if (isAnim) return
setAnimDir(dir)
setIsAnim(true)
setTimeout(() => {
setCurrent(c => dir === 'next' ? (c + 1) % total : (c - 1 + total) % total)
setIsAnim(false)
}, 320)
}, [isAnim, total])
useEffect(() => {
if (paused) return
timerRef.current = setInterval(() => go('next'), INTERVAL)
return () => { if (timerRef.current) clearInterval(timerRef.current) }
}, [paused, go])
const visibleItems = Array.from({ length: VISIBLE }, (_, i) =>
riskItems[(current + i) % total]
)
return ( return (
<section style={{ background: 'var(--paper)' }}> <section style={{ background: 'var(--paper)' }}>
{/* ── Editorial header ── */} {/* ── Header ── */}
<div className="max-w-7xl mx-auto px-12 pt-24 max-md:px-5 max-md:pt-16"> <div className="max-w-7xl mx-auto px-12 pt-24 max-md:px-5 max-md:pt-16">
<div <div
className="mq-stack max-md:grid-cols-1 max-md:gap-4" className="mq-stack max-md:grid-cols-1 max-md:gap-4"
style={{ style={{ display: 'grid', gridTemplateColumns: '1fr auto', alignItems: 'end', gap: 32, paddingBottom: 28, borderBottom: '1px solid var(--rule-thin)', marginBottom: 28 }}
display: 'grid',
gridTemplateColumns: '1fr auto',
alignItems: 'end',
gap: 32,
paddingBottom: 28,
borderBottom: '1px solid var(--rule-thin)',
marginBottom: 28,
}}
> >
<div> <div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 18 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 18 }}>
@ -210,134 +231,127 @@ export default function RiskSection() {
<h2 style={{ fontSize: 'clamp(22px, 2.5vw, 32px)', fontWeight: 900, letterSpacing: '-0.5px', color: 'var(--ink)', lineHeight: 1.2, marginBottom: 12 }}> <h2 style={{ fontSize: 'clamp(22px, 2.5vw, 32px)', fontWeight: 900, letterSpacing: '-0.5px', color: 'var(--ink)', lineHeight: 1.2, marginBottom: 12 }}>
{t.heading} {t.heading}
</h2> </h2>
<p style={{ fontSize: 14, lineHeight: 1.7, color: 'var(--ink-4)', maxWidth: 580 }}> <p style={{ fontSize: 14, lineHeight: 1.7, color: 'var(--ink-4)', maxWidth: 580 }}>{t.sub}</p>
{t.sub}
</p>
</div> </div>
<Link to="/risks" style={{ fontSize: 11, fontWeight: 700, letterSpacing: '1.5px', textTransform: 'uppercase', color: 'var(--red)', textDecoration: 'none', whiteSpace: 'nowrap', paddingBottom: 4 }}> <Link to="/risks" style={{ fontSize: 11, fontWeight: 700, letterSpacing: '1.5px', textTransform: 'uppercase', color: 'var(--red)', textDecoration: 'none', whiteSpace: 'nowrap', paddingBottom: 4 }}>
{t.all} {t.all}
</Link> </Link>
</div> </div>
{/* Legend row */} {/* Legend */}
<div style={{ display: 'flex', gap: 22, flexWrap: 'wrap', marginBottom: 8 }}> <div style={{ display: 'flex', gap: 22, flexWrap: 'wrap', marginBottom: 8 }}>
{(['critical','high','medium','low','opportunity'] as const).map((key, i) => ({ {(['critical','high','medium','low','opportunity'] as const).map((key, i) => (
color: levelCfg[key].dot, label: t.legend[i], <div key={key} style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
})).map(l => ( <div style={{ width: 8, height: 8, borderRadius: '50%', background: levelCfg[key].dot }} />
<div key={l.label} style={{ display: 'flex', alignItems: 'center', gap: 7 }}> <span style={{ fontSize: 12, color: 'var(--ink-4)', fontWeight: 600 }}>{t.legend[i]}</span>
<div style={{ width: 8, height: 8, borderRadius: '50%', background: l.color }} />
<span style={{ fontSize: 12, color: 'var(--ink-4)', fontWeight: 600 }}>{l.label}</span>
</div> </div>
))} ))}
</div> </div>
</div> </div>
{/* ── Auto-scroll ticker ── */} {/* ── Slider ── */}
<div <div
style={{ className="max-w-7xl mx-auto px-12 max-md:px-5"
position: 'relative', style={{ padding: '28px 48px 48px', position: 'relative' }}
overflow: 'hidden', onMouseEnter={() => setPaused(true)}
padding: '28px 0 40px', onMouseLeave={() => setPaused(false)}
/* Fade masks on both edges */ onTouchStart={e => { touchStartX.current = e.touches[0].clientX }}
WebkitMaskImage: onTouchEnd={e => {
'linear-gradient(to right, transparent 0%, black 7%, black 93%, transparent 100%)', const dx = touchStartX.current - e.changedTouches[0].clientX
maskImage: if (Math.abs(dx) > 50) go(dx > 0 ? 'next' : 'prev')
'linear-gradient(to right, transparent 0%, black 7%, black 93%, transparent 100%)',
}} }}
> >
<ul {/* Cards row */}
className="animate-scroll-risk" <div
style={{ style={{
display: 'flex', display: 'grid',
gap: 20, gridTemplateColumns: 'repeat(3, 1fr)',
width: 'max-content', gap: 16,
flexWrap: 'nowrap', overflow: 'hidden',
listStyle: 'none',
direction: 'ltr',
/* pause on hover */
animationPlayState: 'running',
}} }}
onMouseEnter={e => (e.currentTarget.style.animationPlayState = 'paused')} className="max-md:grid-cols-1"
onMouseLeave={e => (e.currentTarget.style.animationPlayState = 'running')}
> >
{loopItems.map((item, idx) => { {visibleItems.map((item, idx) => {
const cfg = levelCfg[item.level] const cfg = levelCfg[item.level]
return ( return (
<li <div
key={idx} key={`${current}-${idx}`}
className={idx > 0 ? 'max-md:hidden' : ''}
style={{ style={{
width: 340, background: 'var(--paper)',
flexShrink: 0, border: '1px solid var(--rule-thin)',
background: 'var(--paper)', borderTop: `3px solid ${cfg.dot}`,
border: '1px solid var(--rule-thin)', padding: '24px 24px 20px',
borderTop: `3px solid ${cfg.dot}`, direction: 'rtl',
padding: '24px 24px 20px', display: 'flex',
direction: 'rtl',
fontFamily: 'Vazir, sans-serif',
display: 'flex',
flexDirection: 'column', flexDirection: 'column',
animation: `riskCardIn 320ms ease both`,
animationDelay: `${idx * 60}ms`,
}} }}
> >
{/* Level badge */}
<div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 12 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 12 }}>
<div style={{ <div style={{ width: 7, height: 7, borderRadius: '50%', background: cfg.dot, flexShrink: 0 }} />
width: 7, <span style={{ fontSize: 9, fontWeight: 700, letterSpacing: '2px', textTransform: 'uppercase', color: cfg.dot }}>
height: 7,
borderRadius: '50%',
background: cfg.dot,
flexShrink: 0,
}} />
<span style={{
fontSize: 9,
fontWeight: 700,
letterSpacing: '2px',
textTransform: 'uppercase',
color: cfg.dot,
}}>
{cfg.label} {cfg.label}
</span> </span>
</div> </div>
{/* Quote */} <p style={{ fontSize: 13, lineHeight: 1.85, color: 'var(--ink-2)', fontWeight: 400, marginBottom: 16, flex: 1 }}>
<p style={{
fontSize: 13,
lineHeight: 1.85,
color: 'var(--ink-2)',
fontWeight: 400,
marginBottom: 16,
flex: 1,
}}>
{lang === 'en' && item.quoteEn ? item.quoteEn : item.quote} {lang === 'en' && item.quoteEn ? item.quoteEn : item.quote}
</p> </p>
{/* Footer */} <div style={{ paddingTop: 12, borderTop: '1px solid var(--rule-thin)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{
paddingTop: 12,
borderTop: '1px solid var(--rule-thin)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}>
<div> <div>
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink)' }}>{lang === 'en' && item.nameEn ? item.nameEn : item.name}</div> <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink)' }}>{lang === 'en' && item.nameEn ? item.nameEn : item.name}</div>
<div style={{ fontSize: 10, color: 'var(--ink-5)' }}>{lang === 'en' && item.dateEn ? item.dateEn : item.date}</div> <div style={{ fontSize: 10, color: 'var(--ink-5)' }}>{lang === 'en' && item.dateEn ? item.dateEn : item.date}</div>
</div> </div>
<div style={{ <div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '1px', textTransform: 'uppercase', color: cfg.dot, background: cfg.bg, padding: '4px 10px' }}>
fontSize: 9,
fontWeight: 700,
letterSpacing: '1px',
textTransform: 'uppercase',
color: cfg.dot,
background: cfg.bg,
padding: '4px 10px',
}}>
{cfg.label} {cfg.label}
</div> </div>
</div> </div>
</li> </div>
) )
})} })}
</ul> </div>
{/* Controls */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 24 }}>
{/* Dots */}
<div style={{ display: 'flex', gap: 6 }}>
{riskItems.map((_, i) => (
<button
key={i}
onClick={() => { setAnimDir('next'); setCurrent(i) }}
style={{
width: i === current ? 24 : 8, height: 8,
borderRadius: 4, border: 'none', cursor: 'pointer',
background: i === current ? 'var(--ink)' : 'var(--rule-thin)',
transition: 'all 300ms ease', padding: 0,
}}
/>
))}
</div>
{/* Arrows */}
<div style={{ display: 'flex', gap: 8 }}>
<button
onClick={() => go('prev')}
style={{ width: 36, height: 36, border: '1px solid var(--rule-thin)', background: 'transparent', cursor: 'pointer', fontSize: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'background 150ms' }}
onMouseEnter={e => (e.currentTarget.style.background = 'var(--paper-2)')}
onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}
>
{lang === 'fa' ? '→' : '←'}
</button>
<button
onClick={() => go('next')}
style={{ width: 36, height: 36, border: '1px solid var(--rule-thin)', background: 'transparent', cursor: 'pointer', fontSize: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'background 150ms' }}
onMouseEnter={e => (e.currentTarget.style.background = 'var(--paper-2)')}
onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}
>
{lang === 'fa' ? '←' : '→'}
</button>
</div>
</div>
</div> </div>
</section> </section>
) )

BIN
موکاپ-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB