Risk slider, footer fix, global padding, mobile improvements
|
|
@ -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\\پنل"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1018 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 950 KiB |
|
After Width: | Height: | Size: 883 KiB |
|
After Width: | Height: | Size: 948 KiB |
|
After Width: | Height: | Size: 954 KiB |
|
After Width: | Height: | Size: 936 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 968 KiB |
|
After Width: | Height: | Size: 972 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 984 KiB |
|
After Width: | Height: | Size: 881 KiB |
|
After Width: | Height: | Size: 990 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 968 KiB |
|
After Width: | Height: | Size: 971 KiB |
|
After Width: | Height: | Size: 945 KiB |
|
After Width: | Height: | Size: 925 KiB |
|
After Width: | Height: | Size: 960 KiB |
|
After Width: | Height: | Size: 977 KiB |
|
After Width: | Height: | Size: 948 KiB |
|
After Width: | Height: | Size: 935 KiB |
|
After Width: | Height: | Size: 909 KiB |
|
After Width: | Height: | Size: 977 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1004 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:400daccc315388c24c972cb62de1902d922bcabb8062c353283a0323f468209d
|
||||
size 37935185
|
||||
|
|
@ -80,18 +80,18 @@ function NewsletterBox() {
|
|||
export function Footer() {
|
||||
return (
|
||||
<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={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', flexWrap: 'wrap', gap: 40 }}>
|
||||
<div style={{ padding: '56px 0 48px' }} className="max-w-7xl mx-auto w-full px-6 max-md:px-5">
|
||||
<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 */}
|
||||
<div style={{ maxWidth: 300 }}>
|
||||
{/* Brand */}
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontSize: 22, fontWeight: 900, color: '#fff', lineHeight: 1.2, marginBottom: 4 }}>
|
||||
اندیشکده فولاد آینده
|
||||
</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
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 6 }}>
|
||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
||||
<SocialIcon label="in" />
|
||||
<SocialIcon label="tg" />
|
||||
<SocialIcon label="tw" />
|
||||
|
|
@ -99,8 +99,8 @@ export function Footer() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Links + newsletter under تماس با ما */}
|
||||
<nav aria-label="پیوندهای پایین" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
{/* Links + newsletter */}
|
||||
<nav aria-label="پیوندهای پایین" style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
{FOOTER_LINKS.map(link => (
|
||||
<FooterLink key={link.label} href={link.href} label={link.label} />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -260,9 +260,31 @@ body {
|
|||
.animate-scroll-risk {
|
||||
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) {
|
||||
.animate-scroll { animation: none; }
|
||||
.animate-scroll-risk { animation: none; }
|
||||
[style*="riskCardIn"] { animation: none !important; }
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export default function FactoryNewsletterLayout() {
|
|||
return (
|
||||
<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 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 · ماهنامه ══ */}
|
||||
<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>
|
||||
</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) => (
|
||||
<div
|
||||
key={i}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useState, useRef, useCallback } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useLang } from '@/context/LangContext'
|
||||
|
||||
|
|
@ -180,25 +180,46 @@ export default function RiskSection() {
|
|||
return () => { cancelled = true }
|
||||
}, [])
|
||||
|
||||
/* Duplicate items for seamless infinite loop (handled in JSX, not effect, to
|
||||
stay immune to StrictMode double-invocation that would otherwise clone twice). */
|
||||
const loopItems = [...riskItems, ...riskItems, ...riskItems]
|
||||
const VISIBLE = 3 // cards visible on desktop
|
||||
const INTERVAL = 4000
|
||||
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 (
|
||||
<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="mq-stack max-md:grid-cols-1 max-md:gap-4"
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr auto',
|
||||
alignItems: 'end',
|
||||
gap: 32,
|
||||
paddingBottom: 28,
|
||||
borderBottom: '1px solid var(--rule-thin)',
|
||||
marginBottom: 28,
|
||||
}}
|
||||
style={{ display: 'grid', gridTemplateColumns: '1fr auto', alignItems: 'end', gap: 32, paddingBottom: 28, borderBottom: '1px solid var(--rule-thin)', marginBottom: 28 }}
|
||||
>
|
||||
<div>
|
||||
<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 }}>
|
||||
{t.heading}
|
||||
</h2>
|
||||
<p style={{ fontSize: 14, lineHeight: 1.7, color: 'var(--ink-4)', maxWidth: 580 }}>
|
||||
{t.sub}
|
||||
</p>
|
||||
<p style={{ fontSize: 14, lineHeight: 1.7, color: 'var(--ink-4)', maxWidth: 580 }}>{t.sub}</p>
|
||||
</div>
|
||||
<Link to="/risks" style={{ fontSize: 11, fontWeight: 700, letterSpacing: '1.5px', textTransform: 'uppercase', color: 'var(--red)', textDecoration: 'none', whiteSpace: 'nowrap', paddingBottom: 4 }}>
|
||||
{t.all}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Legend row */}
|
||||
{/* Legend */}
|
||||
<div style={{ display: 'flex', gap: 22, flexWrap: 'wrap', marginBottom: 8 }}>
|
||||
{(['critical','high','medium','low','opportunity'] as const).map((key, i) => ({
|
||||
color: levelCfg[key].dot, label: t.legend[i],
|
||||
})).map(l => (
|
||||
<div key={l.label} style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
|
||||
<div style={{ width: 8, height: 8, borderRadius: '50%', background: l.color }} />
|
||||
<span style={{ fontSize: 12, color: 'var(--ink-4)', fontWeight: 600 }}>{l.label}</span>
|
||||
{(['critical','high','medium','low','opportunity'] as const).map((key, i) => (
|
||||
<div key={key} style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
|
||||
<div style={{ width: 8, height: 8, borderRadius: '50%', background: levelCfg[key].dot }} />
|
||||
<span style={{ fontSize: 12, color: 'var(--ink-4)', fontWeight: 600 }}>{t.legend[i]}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Auto-scroll ticker ── */}
|
||||
{/* ── Slider ── */}
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
padding: '28px 0 40px',
|
||||
/* Fade masks on both edges */
|
||||
WebkitMaskImage:
|
||||
'linear-gradient(to right, transparent 0%, black 7%, black 93%, transparent 100%)',
|
||||
maskImage:
|
||||
'linear-gradient(to right, transparent 0%, black 7%, black 93%, transparent 100%)',
|
||||
className="max-w-7xl mx-auto px-12 max-md:px-5"
|
||||
style={{ padding: '28px 48px 48px', position: 'relative' }}
|
||||
onMouseEnter={() => setPaused(true)}
|
||||
onMouseLeave={() => setPaused(false)}
|
||||
onTouchStart={e => { touchStartX.current = e.touches[0].clientX }}
|
||||
onTouchEnd={e => {
|
||||
const dx = touchStartX.current - e.changedTouches[0].clientX
|
||||
if (Math.abs(dx) > 50) go(dx > 0 ? 'next' : 'prev')
|
||||
}}
|
||||
>
|
||||
<ul
|
||||
className="animate-scroll-risk"
|
||||
{/* Cards row */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 20,
|
||||
width: 'max-content',
|
||||
flexWrap: 'nowrap',
|
||||
listStyle: 'none',
|
||||
direction: 'ltr',
|
||||
/* pause on hover */
|
||||
animationPlayState: 'running',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||
gap: 16,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.animationPlayState = 'paused')}
|
||||
onMouseLeave={e => (e.currentTarget.style.animationPlayState = 'running')}
|
||||
className="max-md:grid-cols-1"
|
||||
>
|
||||
{loopItems.map((item, idx) => {
|
||||
{visibleItems.map((item, idx) => {
|
||||
const cfg = levelCfg[item.level]
|
||||
return (
|
||||
<li
|
||||
key={idx}
|
||||
<div
|
||||
key={`${current}-${idx}`}
|
||||
className={idx > 0 ? 'max-md:hidden' : ''}
|
||||
style={{
|
||||
width: 340,
|
||||
flexShrink: 0,
|
||||
background: 'var(--paper)',
|
||||
border: '1px solid var(--rule-thin)',
|
||||
borderTop: `3px solid ${cfg.dot}`,
|
||||
padding: '24px 24px 20px',
|
||||
direction: 'rtl',
|
||||
fontFamily: 'Vazir, sans-serif',
|
||||
display: 'flex',
|
||||
background: 'var(--paper)',
|
||||
border: '1px solid var(--rule-thin)',
|
||||
borderTop: `3px solid ${cfg.dot}`,
|
||||
padding: '24px 24px 20px',
|
||||
direction: 'rtl',
|
||||
display: 'flex',
|
||||
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={{
|
||||
width: 7,
|
||||
height: 7,
|
||||
borderRadius: '50%',
|
||||
background: cfg.dot,
|
||||
flexShrink: 0,
|
||||
}} />
|
||||
<span style={{
|
||||
fontSize: 9,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '2px',
|
||||
textTransform: 'uppercase',
|
||||
color: cfg.dot,
|
||||
}}>
|
||||
<div style={{ width: 7, height: 7, borderRadius: '50%', background: cfg.dot, flexShrink: 0 }} />
|
||||
<span style={{ fontSize: 9, fontWeight: 700, letterSpacing: '2px', textTransform: 'uppercase', color: cfg.dot }}>
|
||||
{cfg.label}
|
||||
</span>
|
||||
</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}
|
||||
</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 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>
|
||||
<div style={{
|
||||
fontSize: 9,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '1px',
|
||||
textTransform: 'uppercase',
|
||||
color: cfg.dot,
|
||||
background: cfg.bg,
|
||||
padding: '4px 10px',
|
||||
}}>
|
||||
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '1px', textTransform: 'uppercase', color: cfg.dot, background: cfg.bg, padding: '4px 10px' }}>
|
||||
{cfg.label}
|
||||
</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>
|
||||
</section>
|
||||
)
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 1.5 MiB |