Initial commit
This commit is contained in:
parent
4456563c0e
commit
2dd08650cf
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
@ -72,3 +72,5 @@ export default defineConfig([
|
||||||
])
|
])
|
||||||
```
|
```
|
||||||
"# thinktank"
|
"# thinktank"
|
||||||
|
"# thinktank" git init git add git commit -m "first commit" git branch -M main git remote add origin https://github.com/alirezaameria2-dev/thinktank.git git push -u origin main
|
||||||
|
"# thinktank" git init git add git commit -m "first commit" git branch -M main git remote add origin https://github.com/alirezaameria2-dev/thinktank.git git push -u origin main
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
js.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
reactHooks.configs.flat.recommended,
|
||||||
|
reactRefresh.configs.vite,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="fa" dir="rtl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content="اندیشکده فولاد ایران — مرکز مستقل تحلیل راهبردی و سیاستگذاری صنعت فولاد" />
|
||||||
|
|
||||||
|
<!-- Vazir font preloads (critical weights) -->
|
||||||
|
<link rel="preload" href="/fonts/Vazir-Regular.woff2" as="font" type="font/woff2" crossorigin />
|
||||||
|
<link rel="preload" href="/fonts/Vazir-Bold.woff2" as="font" type="font/woff2" crossorigin />
|
||||||
|
|
||||||
|
<title>اندیشکده فولاد ایران — تحلیل راهبردی صنعت فولاد</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"name": "andishkade-foolad",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/react-query": "^5.100.14",
|
||||||
|
"apexcharts": "^5.13.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"dotted-map": "^3.1.0",
|
||||||
|
"framer-motion": "^12.40.0",
|
||||||
|
"jalaali-js": "^1.2.8",
|
||||||
|
"lucide-react": "^1.16.0",
|
||||||
|
"react": "^19.2.6",
|
||||||
|
"react-apexcharts": "^2.1.0",
|
||||||
|
"react-dom": "^19.2.6",
|
||||||
|
"react-router-dom": "^7.15.1",
|
||||||
|
"tailwind-merge": "^3.6.0",
|
||||||
|
"vazir-font": "^30.1.0",
|
||||||
|
"zustand": "^5.0.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^10.0.1",
|
||||||
|
"@tailwindcss/vite": "^4.3.0",
|
||||||
|
"@types/jalaali-js": "^1.2.0",
|
||||||
|
"@types/node": "^24.12.4",
|
||||||
|
"@types/react": "^19.2.14",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
|
"eslint": "^10.3.0",
|
||||||
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
|
"globals": "^17.6.0",
|
||||||
|
"tailwindcss": "^4.3.0",
|
||||||
|
"typescript": "~6.0.2",
|
||||||
|
"typescript-eslint": "^8.59.2",
|
||||||
|
"vite": "^8.0.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||||
|
<rect width="32" height="32" fill="#1a1712"/>
|
||||||
|
<rect x="6" y="8" width="20" height="3" fill="#f4efe7"/>
|
||||||
|
<rect x="6" y="14.5" width="13" height="3" fill="#9b1c1c"/>
|
||||||
|
<rect x="6" y="21" width="20" height="3" fill="#f4efe7"/>
|
||||||
|
</svg>
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,24 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||||
|
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||||
|
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||||
|
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||||
|
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||||
|
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||||
|
</symbol>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -0,0 +1,4 @@
|
||||||
|
// App.tsx is no longer the entry point.
|
||||||
|
// Routing is handled by src/app/router.tsx → src/main.tsx
|
||||||
|
// This file is kept as a placeholder.
|
||||||
|
export default function App() { return null }
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
function SocialIcon({ label }: { label: string }) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label={label}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={{
|
||||||
|
width: 28, height: 28,
|
||||||
|
border: `1px solid ${hovered ? 'rgba(255,255,255,0.4)' : 'rgba(255,255,255,0.12)'}`,
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
fontSize: 10, fontWeight: 700,
|
||||||
|
color: hovered ? '#fff' : 'rgba(255,255,255,0.4)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'border-color 150ms, color 150ms',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FooterLink({ href, label }: { href: string; label: string }) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
return (
|
||||||
|
<li style={{ marginBottom: 10 }}>
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
color: hovered ? '#fff' : 'rgba(255,255,255,0.55)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'color 150ms',
|
||||||
|
display: 'inline-block',
|
||||||
|
lineHeight: 1.4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const COLUMNS = [
|
||||||
|
{
|
||||||
|
heading: 'گزارشها',
|
||||||
|
links: [
|
||||||
|
{ label: 'گزارشهای فصلی', href: '#' },
|
||||||
|
{ label: 'Flash Reports', href: '#' },
|
||||||
|
{ label: 'گزارشهای ریسک', href: '#' },
|
||||||
|
{ label: 'گزارش رایگان', href: '#' },
|
||||||
|
{ label: 'آرشیو کامل', href: '#' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: 'اندیشکده',
|
||||||
|
links: [
|
||||||
|
{ label: 'درباره ما', href: '#' },
|
||||||
|
{ label: 'تیم تحریریه', href: '#' },
|
||||||
|
{ label: 'اشتراک سازمانی', href: '#' },
|
||||||
|
{ label: 'همکاری با ما', href: '#' },
|
||||||
|
{ label: 'تماس', href: '#' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: 'منابع',
|
||||||
|
links: [
|
||||||
|
{ label: 'داشبورد بازار', href: '#' },
|
||||||
|
{ label: 'رویدادها', href: '#' },
|
||||||
|
{ label: 'اسکنر جهانی', href: '#' },
|
||||||
|
{ label: 'خبرنامه', href: '#' },
|
||||||
|
{ label: 'درگاه پرداخت', href: '#' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function Footer() {
|
||||||
|
return (
|
||||||
|
<footer
|
||||||
|
dir="rtl"
|
||||||
|
style={{ backgroundColor: 'var(--ink)', color: 'rgba(255,255,255,0.6)' }}
|
||||||
|
>
|
||||||
|
{/* TOP GRID */}
|
||||||
|
<div style={{ padding: '64px 48px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '2fr 1px 1fr 1px 1fr 1px 1fr',
|
||||||
|
gap: 0,
|
||||||
|
}}
|
||||||
|
className="max-md:grid-cols-1 max-md:gap-10"
|
||||||
|
>
|
||||||
|
{/* Brand */}
|
||||||
|
<div style={{ paddingLeft: 0, paddingRight: 48 }} className="max-md:pr-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',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
IRAN STEEL POLICY INSTITUTE
|
||||||
|
</div>
|
||||||
|
<p style={{ fontSize: 13, lineHeight: 1.75, color: 'rgba(255,255,255,0.5)', marginBottom: 20, maxWidth: 300 }}>
|
||||||
|
مرکز مستقل تحلیل راهبردی و سیاستگذاری صنعت فولاد و فلزات کشور
|
||||||
|
</p>
|
||||||
|
<div style={{ fontSize: 12, color: 'rgba(255,255,255,0.4)', lineHeight: 2, marginBottom: 20 }}>
|
||||||
|
<div>تهران · خیابان ولیعصر</div>
|
||||||
|
<div>info@iransteel.ir</div>
|
||||||
|
<div dir="ltr" style={{ textAlign: 'right' }}>۰۲۱–۸۸۱۲۳۴۵۶</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
||||||
|
<SocialIcon label="in" />
|
||||||
|
<SocialIcon label="tg" />
|
||||||
|
<SocialIcon label="tw" />
|
||||||
|
<SocialIcon label="@" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Dividers + link columns */}
|
||||||
|
{COLUMNS.map((col, i) => (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
key={`div-${i}`}
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{ background: 'rgba(255,255,255,0.08)', width: 1 }}
|
||||||
|
className="max-md:hidden"
|
||||||
|
/>
|
||||||
|
<div key={col.heading} style={{ padding: '0 36px' }} className="max-md:p-0">
|
||||||
|
<h5
|
||||||
|
style={{
|
||||||
|
fontSize: 9, fontWeight: 700, letterSpacing: 3,
|
||||||
|
textTransform: 'uppercase', color: 'rgba(255,255,255,0.35)',
|
||||||
|
margin: '0 0 16px', paddingBottom: 10,
|
||||||
|
borderBottom: '1px solid rgba(255,255,255,0.1)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{col.heading}
|
||||||
|
</h5>
|
||||||
|
<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
|
||||||
|
{col.links.map(link => (
|
||||||
|
<FooterLink key={link.label} href={link.href} label={link.label} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* BOTTOM ROW */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '20px 48px',
|
||||||
|
borderTop: '1px solid rgba(255,255,255,0.08)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontSize: 10,
|
||||||
|
color: 'rgba(255,255,255,0.25)',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>© ۱۴۰۳ اندیشکده فولاد ایران — تمام حقوق محفوظ است</span>
|
||||||
|
<div style={{ display: 'flex', gap: 18 }}>
|
||||||
|
{['حریم خصوصی', 'شرایط استفاده', 'درگاه پرداخت', 'نقشه سایت'].map(l => (
|
||||||
|
<a
|
||||||
|
key={l}
|
||||||
|
href="#"
|
||||||
|
style={{ color: 'rgba(255,255,255,0.25)', textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
{l}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,815 @@
|
||||||
|
import { useState, useEffect, useRef } from 'react'
|
||||||
|
import type { CSSProperties } from 'react'
|
||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
|
import { Search, Menu, X } from 'lucide-react'
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
|
|
||||||
|
/* ─────────────────────────────────────────────────────────
|
||||||
|
DESIGN TOKENS (mirrors CSS variables for inline styles)
|
||||||
|
───────────────────────────────────────────────────────── */
|
||||||
|
const T = {
|
||||||
|
paper: 'var(--paper)',
|
||||||
|
paper2: 'var(--paper-2)',
|
||||||
|
ink: 'var(--ink)',
|
||||||
|
ink2: 'var(--ink-2)',
|
||||||
|
ink3: 'var(--ink-3)',
|
||||||
|
ink4: 'var(--ink-4)',
|
||||||
|
ink5: 'var(--ink-5)',
|
||||||
|
ruleThin: 'var(--rule-thin)',
|
||||||
|
red: 'var(--red)',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/* ─────────────────────────────────────────────────────────
|
||||||
|
DATA
|
||||||
|
───────────────────────────────────────────────────────── */
|
||||||
|
const NAV_ITEMS = [
|
||||||
|
{ label: 'گزارشها', to: '/reports' },
|
||||||
|
{ label: 'گزارش ویژه', to: '/special' },
|
||||||
|
{ label: 'ریسکها', to: '/risks' },
|
||||||
|
{ label: 'رویدادها', to: '/events' },
|
||||||
|
{ label: 'اسکنر جهانی', to: '/scanner' },
|
||||||
|
{ label: 'تیم تحریریه', to: '/team' },
|
||||||
|
{ label: 'داشبورد بازار', to: '/market' },
|
||||||
|
{ label: 'آرشیو', to: '/archive' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const COMPACT_NAV = [
|
||||||
|
{ label: 'گزارشها', to: '/reports' },
|
||||||
|
{ label: 'بازار', to: '/market' },
|
||||||
|
{ label: 'رویدادها', to: '/events' },
|
||||||
|
{ label: 'ریسک', to: '/risks' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const META_LEFT = ['درباره ما', 'کارشناسان', 'همکاری', 'رسانه']
|
||||||
|
|
||||||
|
/* ─────────────────────────────────────────────────────────
|
||||||
|
HELPERS
|
||||||
|
───────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/** Social icon box */
|
||||||
|
function SocialIcon({ label, href }: { label: string; href: string }) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label={label}
|
||||||
|
style={{
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
border: `1px solid ${T.ruleThin}`,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: hovered ? T.paper : T.ink3,
|
||||||
|
background: hovered ? T.ink : 'transparent',
|
||||||
|
cursor: 'pointer',
|
||||||
|
textDecoration: 'none',
|
||||||
|
transition: 'background 120ms, color 120ms',
|
||||||
|
flexShrink: 0,
|
||||||
|
lineHeight: 1,
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Full nav link (expanded navbar) */
|
||||||
|
function FullNavItem({
|
||||||
|
to,
|
||||||
|
label,
|
||||||
|
isLast,
|
||||||
|
}: {
|
||||||
|
to: string
|
||||||
|
label: string
|
||||||
|
isLast: boolean
|
||||||
|
}) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
to={to}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={({ isActive }: { isActive: boolean }): CSSProperties => ({
|
||||||
|
padding: '12px 20px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: isActive || hovered ? T.paper : T.ink,
|
||||||
|
background: isActive ? T.red : hovered ? T.ink : 'transparent',
|
||||||
|
textDecoration: 'none',
|
||||||
|
borderRight: !isLast ? `1px solid ${T.ruleThin}` : 'none',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
transition: 'background 150ms, color 150ms',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
letterSpacing: '0.2px',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</NavLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Compact nav link */
|
||||||
|
function CompactNavItem({
|
||||||
|
to,
|
||||||
|
label,
|
||||||
|
isFirst,
|
||||||
|
}: {
|
||||||
|
to: string
|
||||||
|
label: string
|
||||||
|
isFirst: boolean
|
||||||
|
}) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
to={to}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={({ isActive }: { isActive: boolean }): CSSProperties => ({
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: isActive || hovered ? T.paper : T.ink,
|
||||||
|
background: isActive ? T.ink : hovered ? T.paper2 : 'transparent',
|
||||||
|
textDecoration: 'none',
|
||||||
|
padding: '0 14px',
|
||||||
|
height: 56,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRight: `1px solid ${T.ruleThin}`,
|
||||||
|
borderLeft: isFirst ? `1px solid ${T.ruleThin}` : 'none',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
transition: 'background 120ms, color 120ms',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</NavLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Drawer nav link */
|
||||||
|
function DrawerNavItem({
|
||||||
|
to,
|
||||||
|
label,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
to: string
|
||||||
|
label: string
|
||||||
|
onClose: () => void
|
||||||
|
}) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
to={to}
|
||||||
|
onClick={onClose}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={({ isActive }: { isActive: boolean }): CSSProperties => ({
|
||||||
|
display: 'block',
|
||||||
|
padding: '13px 20px',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: isActive || hovered ? T.paper : T.ink,
|
||||||
|
background: isActive ? T.ink : hovered ? T.paper2 : 'transparent',
|
||||||
|
textDecoration: 'none',
|
||||||
|
borderBottom: `1px solid ${T.ruleThin}`,
|
||||||
|
transition: 'background 120ms, color 120ms',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</NavLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─────────────────────────────────────────────────────────
|
||||||
|
MAIN COMPONENT
|
||||||
|
───────────────────────────────────────────────────────── */
|
||||||
|
export default function Header() {
|
||||||
|
const [compact, setCompact] = useState(false)
|
||||||
|
const [mobileOpen, setMobileOpen] = useState(false)
|
||||||
|
const [isMobile, setIsMobile] = useState(false)
|
||||||
|
const searchRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
/* scroll listener */
|
||||||
|
useEffect(() => {
|
||||||
|
const onScroll = () => setCompact(window.scrollY > 80)
|
||||||
|
window.addEventListener('scroll', onScroll, { passive: true })
|
||||||
|
return () => window.removeEventListener('scroll', onScroll)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
/* viewport listener */
|
||||||
|
useEffect(() => {
|
||||||
|
const mq = window.matchMedia('(max-width: 767px)')
|
||||||
|
const handler = (e: MediaQueryListEvent | MediaQueryList) => setIsMobile(e.matches)
|
||||||
|
handler(mq)
|
||||||
|
mq.addEventListener('change', handler)
|
||||||
|
return () => mq.removeEventListener('change', handler)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
/* body scroll lock */
|
||||||
|
useEffect(() => {
|
||||||
|
document.body.style.overflow = mobileOpen ? 'hidden' : ''
|
||||||
|
return () => { document.body.style.overflow = '' }
|
||||||
|
}, [mobileOpen])
|
||||||
|
|
||||||
|
const showCompact = compact || isMobile
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
style={{
|
||||||
|
position: 'sticky',
|
||||||
|
top: 0,
|
||||||
|
zIndex: 50,
|
||||||
|
background: T.paper,
|
||||||
|
direction: 'rtl',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* ════════════════════════════════════════════
|
||||||
|
EXPANDED STATE
|
||||||
|
════════════════════════════════════════════ */}
|
||||||
|
<AnimatePresence initial={false}>
|
||||||
|
{!showCompact && (
|
||||||
|
<motion.div
|
||||||
|
key="expanded"
|
||||||
|
initial={{ opacity: 0, height: 0 }}
|
||||||
|
animate={{ opacity: 1, height: 'auto' }}
|
||||||
|
exit={{ opacity: 0, height: 0 }}
|
||||||
|
transition={{ duration: 0.22, ease: 'easeInOut' }}
|
||||||
|
style={{ overflow: 'hidden' }}
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* ── TOP META BAR ─────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: `1px solid ${T.ruleThin}`,
|
||||||
|
padding: '8px 48px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Right: login / language */}
|
||||||
|
<ul
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
listStyle: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="/login"
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: T.ink3,
|
||||||
|
textDecoration: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ورود
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li style={{ fontSize: 11, color: T.ruleThin, userSelect: 'none' }}>|</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
style={{ fontSize: 11, color: T.ink4, textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
فارسی
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{/* Center: date */}
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: T.ink4,
|
||||||
|
fontWeight: 400,
|
||||||
|
letterSpacing: '0.3px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
شنبه ۲۱ بهمن ۱۴۰۳ · دوره دوازدهم
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Left: secondary nav */}
|
||||||
|
<ul
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 20,
|
||||||
|
listStyle: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{META_LEFT.map((item) => (
|
||||||
|
<li key={item}>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
style={{ fontSize: 11, color: T.ink4, textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── MASTHEAD MAIN ────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '28px 48px',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr auto 1fr',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 24,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Left col */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '2.5px',
|
||||||
|
color: T.ink5,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
تحلیل مستقل · سیاستگذاری
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: T.ink5,
|
||||||
|
lineHeight: 1.6,
|
||||||
|
marginTop: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
۲۸۰ گزارش منتشرشده · ۴۵ متخصص
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
|
<SocialIcon label="in" href="https://linkedin.com" />
|
||||||
|
<SocialIcon label="tg" href="https://t.me" />
|
||||||
|
<SocialIcon label="tw" href="https://x.com" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Center col */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: 'clamp(28px, 3.5vw, 48px)',
|
||||||
|
fontWeight: 900,
|
||||||
|
lineHeight: 1,
|
||||||
|
letterSpacing: '-1px',
|
||||||
|
color: T.ink,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
اندیشکده فولاد ایران
|
||||||
|
</h1>
|
||||||
|
<hr
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
width: 60,
|
||||||
|
height: 2,
|
||||||
|
background: T.red,
|
||||||
|
border: 'none',
|
||||||
|
margin: '8px 0',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
letterSpacing: '5px',
|
||||||
|
color: T.ink4,
|
||||||
|
fontWeight: 400,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
IRAN STEEL POLICY INSTITUTE
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right col */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ position: 'relative' }}>
|
||||||
|
<input
|
||||||
|
ref={searchRef}
|
||||||
|
type="search"
|
||||||
|
placeholder="جستجو در گزارشها..."
|
||||||
|
aria-label="جستجو"
|
||||||
|
style={{
|
||||||
|
border: `1px solid ${T.ink}`,
|
||||||
|
padding: '8px 12px 8px 32px',
|
||||||
|
fontSize: 12,
|
||||||
|
background: 'transparent',
|
||||||
|
width: 200,
|
||||||
|
color: T.ink,
|
||||||
|
outline: 'none',
|
||||||
|
borderRadius: 0,
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
direction: 'rtl',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Search
|
||||||
|
size={14}
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: 10,
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
color: T.ink4,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
|
<FilledButton>اشتراک سازمانی</FilledButton>
|
||||||
|
<OutlineButton>ورود</OutlineButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── NAV BAR ──────────────────────────────── */}
|
||||||
|
<nav
|
||||||
|
aria-label="ناوبری اصلی"
|
||||||
|
style={{
|
||||||
|
borderTop: `1px solid ${T.ruleThin}`,
|
||||||
|
borderBottom: `3px solid ${T.ink}`,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
overflowX: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{NAV_ITEMS.map((item, i) => (
|
||||||
|
<FullNavItem
|
||||||
|
key={item.to}
|
||||||
|
to={item.to}
|
||||||
|
label={item.label}
|
||||||
|
isLast={i === NAV_ITEMS.length - 1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* ════════════════════════════════════════════
|
||||||
|
COMPACT STATE
|
||||||
|
════════════════════════════════════════════ */}
|
||||||
|
<AnimatePresence initial={false}>
|
||||||
|
{showCompact && (
|
||||||
|
<motion.div
|
||||||
|
key="compact"
|
||||||
|
initial={{ opacity: 0, y: -8 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -8 }}
|
||||||
|
transition={{ duration: 0.18, ease: 'easeOut' }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: 56,
|
||||||
|
borderBottom: `2px solid ${T.ink}`,
|
||||||
|
padding: '0 48px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: 24,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Brand */}
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 1, flexShrink: 0 }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 900,
|
||||||
|
lineHeight: 1,
|
||||||
|
color: T.ink,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
اندیشکده فولاد ایران
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
letterSpacing: '3px',
|
||||||
|
color: T.ink5,
|
||||||
|
fontWeight: 400,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
lineHeight: 1,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
IRAN STEEL POLICY INSTITUTE
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop compact nav */}
|
||||||
|
{!isMobile ? (
|
||||||
|
<nav
|
||||||
|
aria-label="ناوبری فشرده"
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{COMPACT_NAV.map((item, i) => (
|
||||||
|
<CompactNavItem
|
||||||
|
key={item.to}
|
||||||
|
to={item.to}
|
||||||
|
label={item.label}
|
||||||
|
isFirst={i === 0}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<RedCtaButton style={{ marginRight: 16 }}>اشتراک</RedCtaButton>
|
||||||
|
</nav>
|
||||||
|
) : (
|
||||||
|
/* Mobile hamburger */
|
||||||
|
<button
|
||||||
|
aria-label="باز کردن منو"
|
||||||
|
onClick={() => setMobileOpen(true)}
|
||||||
|
style={{
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: T.ink,
|
||||||
|
padding: 4,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Menu size={22} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* ════════════════════════════════════════════
|
||||||
|
MOBILE DRAWER
|
||||||
|
════════════════════════════════════════════ */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{mobileOpen && (
|
||||||
|
<>
|
||||||
|
{/* Overlay */}
|
||||||
|
<motion.div
|
||||||
|
key="overlay"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
onClick={() => setMobileOpen(false)}
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
inset: 0,
|
||||||
|
background: 'rgba(26,23,18,0.55)',
|
||||||
|
zIndex: 100,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Drawer panel */}
|
||||||
|
<motion.div
|
||||||
|
key="drawer"
|
||||||
|
initial={{ x: 280 }}
|
||||||
|
animate={{ x: 0 }}
|
||||||
|
exit={{ x: 280 }}
|
||||||
|
transition={{ duration: 0.25, ease: [0.32, 0, 0.18, 1] }}
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
height: '100dvh',
|
||||||
|
width: 280,
|
||||||
|
background: T.paper,
|
||||||
|
zIndex: 101,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
borderLeft: `2px solid ${T.ink}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Drawer header */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '16px 20px',
|
||||||
|
borderBottom: `1px solid ${T.ruleThin}`,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{ fontSize: 14, fontWeight: 700, color: T.ink }}
|
||||||
|
>
|
||||||
|
اندیشکده فولاد ایران
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
aria-label="بستن منو"
|
||||||
|
onClick={() => setMobileOpen(false)}
|
||||||
|
style={{
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: T.ink,
|
||||||
|
padding: 4,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<X size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Nav list */}
|
||||||
|
<nav style={{ flex: 1, overflowY: 'auto' }}>
|
||||||
|
{NAV_ITEMS.map((item) => (
|
||||||
|
<DrawerNavItem
|
||||||
|
key={item.to}
|
||||||
|
to={item.to}
|
||||||
|
label={item.label}
|
||||||
|
onClose={() => setMobileOpen(false)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Footer CTAs */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '16px 20px',
|
||||||
|
borderTop: `1px solid ${T.ruleThin}`,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RedCtaButton style={{ width: '100%' }}>اشتراک سازمانی</RedCtaButton>
|
||||||
|
<OutlineButton style={{ width: '100%' }}>ورود</OutlineButton>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 12,
|
||||||
|
marginTop: 4,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{META_LEFT.map((item) => (
|
||||||
|
<a
|
||||||
|
key={item}
|
||||||
|
href="#"
|
||||||
|
style={{ fontSize: 11, color: T.ink4, textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─────────────────────────────────────────────────────────
|
||||||
|
MICRO BUTTON COMPONENTS (hover via useState — clean)
|
||||||
|
───────────────────────────────────────────────────────── */
|
||||||
|
function FilledButton({
|
||||||
|
children,
|
||||||
|
style: extraStyle,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
style?: CSSProperties
|
||||||
|
}) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={{
|
||||||
|
background: hovered ? T.red : T.ink,
|
||||||
|
color: T.paper,
|
||||||
|
border: `1px solid ${hovered ? T.red : T.ink}`,
|
||||||
|
padding: '7px 16px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
borderRadius: 0,
|
||||||
|
letterSpacing: '0.3px',
|
||||||
|
transition: 'background 120ms, border-color 120ms',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
...extraStyle,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function OutlineButton({
|
||||||
|
children,
|
||||||
|
style: extraStyle,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
style?: CSSProperties
|
||||||
|
}) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={{
|
||||||
|
background: hovered ? T.ink : 'transparent',
|
||||||
|
color: hovered ? T.paper : T.ink,
|
||||||
|
border: `1px solid ${T.ink}`,
|
||||||
|
padding: '7px 14px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
borderRadius: 0,
|
||||||
|
letterSpacing: '0.3px',
|
||||||
|
transition: 'background 120ms, color 120ms',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textAlign: 'center',
|
||||||
|
...extraStyle,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function RedCtaButton({
|
||||||
|
children,
|
||||||
|
style: extraStyle,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
style?: CSSProperties
|
||||||
|
}) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={{
|
||||||
|
background: hovered ? T.ink : T.red,
|
||||||
|
color: T.paper,
|
||||||
|
border: 'none',
|
||||||
|
padding: '7px 16px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 700,
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
borderRadius: 0,
|
||||||
|
letterSpacing: '0.3px',
|
||||||
|
transition: 'background 120ms',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textAlign: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
...extraStyle,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
import { marketPrices } from '@/data/market';
|
||||||
|
import type { MarketPrice } from '@/data/market';
|
||||||
|
|
||||||
|
function formatValue(item: MarketPrice): string {
|
||||||
|
return item.value.toLocaleString('fa-IR');
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatChange(item: MarketPrice): string {
|
||||||
|
const abs = Math.abs(item.change).toLocaleString('fa-IR');
|
||||||
|
return `${abs} (${Math.abs(item.changePercent).toFixed(1)}٪)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TickerItem({ item }: { item: MarketPrice }) {
|
||||||
|
const changeColor =
|
||||||
|
item.trend === 'up'
|
||||||
|
? '#7dbf8a'
|
||||||
|
: item.trend === 'down'
|
||||||
|
? '#e07070'
|
||||||
|
: 'rgba(255,255,255,0.5)';
|
||||||
|
|
||||||
|
const arrow =
|
||||||
|
item.trend === 'up' ? '↑' : item.trend === 'down' ? '↓' : '—';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '10px',
|
||||||
|
padding: '0 24px',
|
||||||
|
borderRight: '1px solid rgba(255,255,255,0.1)',
|
||||||
|
flexShrink: 0,
|
||||||
|
height: '40px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: 'rgba(255,255,255,0.5)',
|
||||||
|
fontSize: '11px',
|
||||||
|
fontWeight: 400,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#fff',
|
||||||
|
fontVariantNumeric: 'tabular-nums',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatValue(item)}{' '}
|
||||||
|
<span style={{ fontSize: '10px', color: 'rgba(255,255,255,0.4)', fontWeight: 400 }}>
|
||||||
|
{item.unit}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: changeColor,
|
||||||
|
fontVariantNumeric: 'tabular-nums',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{arrow} {formatChange(item)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MarketStrip() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
dir="ltr"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
backgroundColor: 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
height: '40px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'stretch',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Live tag */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--red)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
fontSize: '9px',
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '2.5px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
padding: '0 20px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
بازار زنده
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Ticker area */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="ticker-track"
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Original set */}
|
||||||
|
{marketPrices.map((item, i) => (
|
||||||
|
<TickerItem key={`a-${i}`} item={item} />
|
||||||
|
))}
|
||||||
|
{/* Duplicate for seamless loop */}
|
||||||
|
{marketPrices.map((item, i) => (
|
||||||
|
<TickerItem key={`b-${i}`} item={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Outlet } from 'react-router-dom'
|
||||||
|
import Header from './Header'
|
||||||
|
import { Footer } from './Footer'
|
||||||
|
import { MarketStrip } from './MarketStrip'
|
||||||
|
|
||||||
|
export default function RootLayout() {
|
||||||
|
return (
|
||||||
|
<div style={{ minHeight: '100dvh', display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Header />
|
||||||
|
<MarketStrip />
|
||||||
|
<main style={{ flex: 1 }}>
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { createBrowserRouter } from 'react-router-dom'
|
||||||
|
import RootLayout from './layout/RootLayout'
|
||||||
|
import Home from '@/pages/Home/Home'
|
||||||
|
import Reports from '@/pages/Reports/Reports'
|
||||||
|
import ReportDetail from '@/pages/ReportDetail/ReportDetail'
|
||||||
|
import Market from '@/pages/Market/Market'
|
||||||
|
import Subscribe from '@/pages/Subscribe/Subscribe'
|
||||||
|
import Events from '@/pages/Events/Events'
|
||||||
|
import Team from '@/pages/Team/Team'
|
||||||
|
import Archive from '@/pages/Archive/Archive'
|
||||||
|
import Special from '@/pages/Special/Special'
|
||||||
|
import Risks from '@/pages/Risks/Risks'
|
||||||
|
import Scanner from '@/pages/Scanner/Scanner'
|
||||||
|
|
||||||
|
export const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <RootLayout />,
|
||||||
|
children: [
|
||||||
|
{ index: true, element: <Home /> },
|
||||||
|
{ path: 'reports', element: <Reports /> },
|
||||||
|
{ path: 'reports/:id', element: <ReportDetail /> },
|
||||||
|
{ path: 'market', element: <Market /> },
|
||||||
|
{ path: 'subscribe', element: <Subscribe /> },
|
||||||
|
{ path: 'events', element: <Events /> },
|
||||||
|
{ path: 'team', element: <Team /> },
|
||||||
|
{ path: 'archive', element: <Archive /> },
|
||||||
|
{ path: 'special', element: <Special /> },
|
||||||
|
{ path: 'risks', element: <Risks /> },
|
||||||
|
{ path: 'scanner', element: <Scanner /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.5 KiB |
|
|
@ -0,0 +1,56 @@
|
||||||
|
|
||||||
|
type BadgeVariant = 'default' | 'red' | 'amber' | 'green' | 'outline';
|
||||||
|
|
||||||
|
interface BadgeProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
variant?: BadgeVariant;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantStyles: Record<BadgeVariant, React.CSSProperties> = {
|
||||||
|
default: {
|
||||||
|
backgroundColor: 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
backgroundColor: 'var(--red)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
border: '1px solid var(--red)',
|
||||||
|
},
|
||||||
|
amber: {
|
||||||
|
backgroundColor: 'var(--amber)',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
border: '1px solid var(--amber)',
|
||||||
|
},
|
||||||
|
green: {
|
||||||
|
backgroundColor: 'var(--green-ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
border: '1px solid var(--green-ink)',
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Badge({ children, variant = 'default', className = '' }: BadgeProps) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`inline-block ${className}`}
|
||||||
|
style={{
|
||||||
|
...variantStyles[variant],
|
||||||
|
padding: '2px 6px',
|
||||||
|
fontSize: '9px',
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '1.5px',
|
||||||
|
lineHeight: 1.6,
|
||||||
|
borderRadius: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import type { CSSProperties, ReactNode, MouseEvent } from 'react'
|
||||||
|
|
||||||
|
type ButtonVariant = 'filled' | 'outline' | 'paper' | 'ghost';
|
||||||
|
type ButtonSize = 'sm' | 'md' | 'lg';
|
||||||
|
|
||||||
|
interface ButtonProps {
|
||||||
|
children: ReactNode;
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
size?: ButtonSize;
|
||||||
|
className?: string;
|
||||||
|
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
type?: 'button' | 'submit' | 'reset';
|
||||||
|
disabled?: boolean;
|
||||||
|
asChild?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeStyles: Record<ButtonSize, CSSProperties> = {
|
||||||
|
sm: { padding: '9px 20px', fontSize: '9px' },
|
||||||
|
md: { padding: '11px 24px', fontSize: '11px' },
|
||||||
|
lg: { padding: '12px 28px', fontSize: '12px' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseStyle: CSSProperties = {
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '6px',
|
||||||
|
borderRadius: 0,
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background-color 200ms, color 200ms, border-color 200ms',
|
||||||
|
outline: 'none',
|
||||||
|
border: '1px solid transparent',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
};
|
||||||
|
|
||||||
|
const variantBase: Record<ButtonVariant, CSSProperties> = {
|
||||||
|
filled: {
|
||||||
|
backgroundColor: 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
borderColor: 'var(--ink)',
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
borderColor: 'var(--ink)',
|
||||||
|
},
|
||||||
|
paper: {
|
||||||
|
backgroundColor: 'var(--paper)',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
borderColor: 'var(--paper)',
|
||||||
|
},
|
||||||
|
ghost: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Button({
|
||||||
|
children,
|
||||||
|
variant = 'filled',
|
||||||
|
size = 'md',
|
||||||
|
className = '',
|
||||||
|
onClick,
|
||||||
|
type = 'button',
|
||||||
|
disabled = false,
|
||||||
|
}: ButtonProps) {
|
||||||
|
const [hovered, setHovered] = useState(false);
|
||||||
|
|
||||||
|
const getHoverStyle = (): CSSProperties => {
|
||||||
|
if (!hovered || disabled) return {};
|
||||||
|
switch (variant) {
|
||||||
|
case 'filled':
|
||||||
|
return { backgroundColor: 'var(--ink-2)' };
|
||||||
|
case 'outline':
|
||||||
|
return { backgroundColor: 'var(--ink)', color: 'var(--paper)' };
|
||||||
|
case 'paper':
|
||||||
|
return { backgroundColor: 'var(--paper-2)' };
|
||||||
|
case 'ghost':
|
||||||
|
return { backgroundColor: 'var(--paper-2)' };
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type={type}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={onClick}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
className={`focus-visible:ring-2 focus-visible:ring-offset-1 ${className}`}
|
||||||
|
style={{
|
||||||
|
...baseStyle,
|
||||||
|
...variantBase[variant],
|
||||||
|
...sizeStyles[size],
|
||||||
|
...getHoverStyle(),
|
||||||
|
opacity: disabled ? 0.45 : 1,
|
||||||
|
pointerEvents: disabled ? 'none' : 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
interface PullQuoteProps {
|
||||||
|
text: React.ReactNode;
|
||||||
|
source?: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PullQuote({ text, source, className = '' }: PullQuoteProps) {
|
||||||
|
return (
|
||||||
|
<blockquote
|
||||||
|
className={className}
|
||||||
|
style={{
|
||||||
|
borderTop: '2px solid var(--ink)',
|
||||||
|
borderBottom: '2px solid var(--ink)',
|
||||||
|
padding: '20px 0',
|
||||||
|
margin: '24px 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: '20px',
|
||||||
|
fontWeight: 800,
|
||||||
|
lineHeight: 1.3,
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</p>
|
||||||
|
{source && (
|
||||||
|
<footer
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
marginTop: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{source}
|
||||||
|
</footer>
|
||||||
|
)}
|
||||||
|
</blockquote>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
type RuleWeight = 'heavy' | 'mid' | 'light' | 'red';
|
||||||
|
|
||||||
|
interface RuleProps {
|
||||||
|
weight?: RuleWeight;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles: Record<RuleWeight, React.CSSProperties> = {
|
||||||
|
heavy: {
|
||||||
|
borderTop: '3px solid var(--ink)',
|
||||||
|
borderBottom: 'none',
|
||||||
|
margin: 0,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
mid: {
|
||||||
|
borderTop: '1px solid var(--ink)',
|
||||||
|
borderBottom: 'none',
|
||||||
|
margin: 0,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
borderTop: '1px solid var(--rule-thin)',
|
||||||
|
borderBottom: 'none',
|
||||||
|
margin: 0,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
borderTop: '3px solid var(--red)',
|
||||||
|
borderBottom: 'none',
|
||||||
|
margin: 0,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Rule({ weight = 'mid', className = '' }: RuleProps) {
|
||||||
|
return (
|
||||||
|
<hr
|
||||||
|
style={styles[weight]}
|
||||||
|
className={className}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { ArrowLeft } from 'lucide-react';
|
||||||
|
|
||||||
|
type LabelVariant = 'ink' | 'red';
|
||||||
|
|
||||||
|
interface SectionBannerLink {
|
||||||
|
text: string;
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SectionBannerProps {
|
||||||
|
label: string;
|
||||||
|
labelVariant?: LabelVariant;
|
||||||
|
link?: SectionBannerLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SectionBanner({
|
||||||
|
label,
|
||||||
|
labelVariant = 'ink',
|
||||||
|
link,
|
||||||
|
}: SectionBannerProps) {
|
||||||
|
const labelBg = labelVariant === 'red' ? 'var(--red)' : 'var(--ink)';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'stretch',
|
||||||
|
gap: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Label block */}
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
backgroundColor: labelBg,
|
||||||
|
color: 'var(--paper)',
|
||||||
|
fontSize: '9px',
|
||||||
|
fontWeight: 900,
|
||||||
|
letterSpacing: '3px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
padding: '6px 14px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
lineHeight: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Rule fill */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
borderBottom: 'none',
|
||||||
|
alignSelf: 'center',
|
||||||
|
borderTop: '1px solid var(--rule-thin)',
|
||||||
|
marginBottom: '-1px',
|
||||||
|
}}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Optional link */}
|
||||||
|
{link && (
|
||||||
|
<a
|
||||||
|
href={link.to}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '5px',
|
||||||
|
fontSize: '10px',
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '1px',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
padding: '6px 0 6px 4px',
|
||||||
|
flexShrink: 0,
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'color 150ms',
|
||||||
|
}}
|
||||||
|
onMouseEnter={e => (e.currentTarget.style.color = 'var(--red)')}
|
||||||
|
onMouseLeave={e => (e.currentTarget.style.color = 'var(--ink)')}
|
||||||
|
>
|
||||||
|
{link.text}
|
||||||
|
<ArrowLeft size={12} strokeWidth={2.5} />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
type StampColor = 'red' | 'ink';
|
||||||
|
|
||||||
|
interface StampProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
color?: StampColor;
|
||||||
|
tilt?: boolean;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Stamp({
|
||||||
|
children,
|
||||||
|
color = 'red',
|
||||||
|
tilt = true,
|
||||||
|
className = '',
|
||||||
|
}: StampProps) {
|
||||||
|
const colorValue = color === 'red' ? 'var(--red)' : 'var(--ink)';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`inline-block ${tilt ? 'stamp-tilt' : ''} ${className}`}
|
||||||
|
style={{
|
||||||
|
border: `2px solid ${colorValue}`,
|
||||||
|
color: colorValue,
|
||||||
|
padding: '3px 10px',
|
||||||
|
fontSize: '9px',
|
||||||
|
fontWeight: 900,
|
||||||
|
letterSpacing: '3px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
lineHeight: 1.4,
|
||||||
|
display: 'inline-block',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
|
||||||
|
interface StatCardProps {
|
||||||
|
number: string;
|
||||||
|
label: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatCard({ number, label, className = '' }: StatCardProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={className}
|
||||||
|
style={{
|
||||||
|
borderTop: '2px solid var(--ink)',
|
||||||
|
paddingTop: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 'clamp(32px, 4vw, 44px)',
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
lineHeight: 1,
|
||||||
|
fontVariantNumeric: 'tabular-nums',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{number}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
marginTop: '4px',
|
||||||
|
lineHeight: 1.4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,280 @@
|
||||||
|
import {
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
} from 'react'
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
|
import { X } from 'lucide-react'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
function cn(...inputs: Parameters<typeof clsx>) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── types ─────────────────────────────────────────── */
|
||||||
|
export interface CardData {
|
||||||
|
src: string
|
||||||
|
title: string
|
||||||
|
category: string
|
||||||
|
content: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CarouselContextType {
|
||||||
|
onCardClose: (index: number) => void
|
||||||
|
currentIndex: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── context ───────────────────────────────────────── */
|
||||||
|
const CarouselContext = createContext<CarouselContextType>({
|
||||||
|
onCardClose: () => {},
|
||||||
|
currentIndex: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
/* ─── Carousel ──────────────────────────────────────── */
|
||||||
|
export function Carousel({
|
||||||
|
items,
|
||||||
|
initialScroll = 0,
|
||||||
|
}: {
|
||||||
|
items: ReactNode[]
|
||||||
|
initialScroll?: number
|
||||||
|
}) {
|
||||||
|
const carouselRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [canScrollLeft, setCanScrollLeft] = useState(false)
|
||||||
|
const [canScrollRight, setCanScrollRight] = useState(true)
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (carouselRef.current) {
|
||||||
|
carouselRef.current.scrollLeft = initialScroll
|
||||||
|
checkScrollability()
|
||||||
|
}
|
||||||
|
}, [initialScroll])
|
||||||
|
|
||||||
|
function checkScrollability() {
|
||||||
|
if (!carouselRef.current) return
|
||||||
|
const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current
|
||||||
|
setCanScrollLeft(scrollLeft > 0)
|
||||||
|
setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollLeft() { carouselRef.current?.scrollBy({ left: -340, behavior: 'smooth' }) }
|
||||||
|
function scrollRight() { carouselRef.current?.scrollBy({ left: 340, behavior: 'smooth' }) }
|
||||||
|
|
||||||
|
function handleCardClose(index: number) {
|
||||||
|
if (!carouselRef.current) return
|
||||||
|
const cardWidth = window.innerWidth < 768 ? 230 : 350
|
||||||
|
carouselRef.current.scrollTo({ left: (cardWidth + 16) * (index + 1), behavior: 'smooth' })
|
||||||
|
setCurrentIndex(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CarouselContext.Provider value={{ onCardClose: handleCardClose, currentIndex }}>
|
||||||
|
<div className="relative w-full">
|
||||||
|
{/* Scrollable track */}
|
||||||
|
<div
|
||||||
|
ref={carouselRef}
|
||||||
|
onScroll={checkScrollability}
|
||||||
|
className="flex w-full overflow-x-scroll overscroll-x-auto scroll-smooth py-10 md:py-14 [scrollbar-width:none]"
|
||||||
|
style={{ direction: 'ltr' }}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row justify-start gap-4 px-4 md:px-10">
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={'card' + index}
|
||||||
|
initial={{ opacity: 0, y: 24 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.45, delay: 0.1 * index, ease: 'easeOut' }}
|
||||||
|
className="last:pl-[10%] md:last:pl-[20%]"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Arrow buttons */}
|
||||||
|
<div className="flex justify-end gap-2 px-10 pb-6">
|
||||||
|
<button
|
||||||
|
onClick={scrollLeft}
|
||||||
|
disabled={!canScrollLeft}
|
||||||
|
className="h-10 w-10 rounded-full bg-[var(--paper-3)] border border-[var(--rule-thin)] flex items-center justify-center disabled:opacity-30 transition-opacity hover:bg-[var(--paper-2)]"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} className="h-5 w-5 text-[var(--ink)]">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={scrollRight}
|
||||||
|
disabled={!canScrollRight}
|
||||||
|
className="h-10 w-10 rounded-full bg-[var(--paper-3)] border border-[var(--rule-thin)] flex items-center justify-center disabled:opacity-30 transition-opacity hover:bg-[var(--paper-2)]"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} className="h-5 w-5 text-[var(--ink)]">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CarouselContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Card ──────────────────────────────────────────── */
|
||||||
|
export function Card({
|
||||||
|
card,
|
||||||
|
index,
|
||||||
|
layout = false,
|
||||||
|
}: {
|
||||||
|
card: CardData
|
||||||
|
index: number
|
||||||
|
layout?: boolean
|
||||||
|
}) {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const { onCardClose } = useContext(CarouselContext)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function onKey(e: KeyboardEvent) { if (e.key === 'Escape') handleClose() }
|
||||||
|
document.body.style.overflow = open ? 'hidden' : 'auto'
|
||||||
|
window.addEventListener('keydown', onKey)
|
||||||
|
return () => window.removeEventListener('keydown', onKey)
|
||||||
|
}, [open])
|
||||||
|
|
||||||
|
function handleOpen() { setOpen(true) }
|
||||||
|
function handleClose() { setOpen(false); onCardClose(index) }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* ── Modal ── */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{open && (
|
||||||
|
/* Outer: scroll host */
|
||||||
|
<div className="fixed inset-0 z-50 overflow-y-auto">
|
||||||
|
{/* Backdrop — click to close */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
className="fixed inset-0 bg-black/75 backdrop-blur-md"
|
||||||
|
onClick={handleClose}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Centering wrapper */}
|
||||||
|
<div className="flex min-h-full items-center justify-center p-4 md:p-8">
|
||||||
|
<motion.div
|
||||||
|
layoutId={layout ? `card-${card.title}` : undefined}
|
||||||
|
initial={{ opacity: 0, scale: 0.96, y: 20 }}
|
||||||
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.96, y: 20 }}
|
||||||
|
transition={{ duration: 0.3, ease: [0.32, 0.72, 0, 1] }}
|
||||||
|
className="relative z-10 w-full max-w-2xl bg-white rounded-3xl overflow-hidden shadow-2xl"
|
||||||
|
style={{ direction: 'rtl', fontFamily: 'Vazir, sans-serif', maxHeight: '88vh', overflowY: 'auto' }}
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{/* Close button */}
|
||||||
|
<button
|
||||||
|
onClick={handleClose}
|
||||||
|
className="absolute top-4 left-4 z-20 h-9 w-9 bg-black/10 hover:bg-black/20 rounded-full flex items-center justify-center transition-colors"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4 text-gray-700" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Hero image strip */}
|
||||||
|
<div className="relative h-52 overflow-hidden" style={{ flexShrink: 0 }}>
|
||||||
|
<img
|
||||||
|
src={card.src}
|
||||||
|
alt={card.title}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent" />
|
||||||
|
{/* inset-x-0 ensures text never overflows past modal edges */}
|
||||||
|
<div className="absolute bottom-0 inset-x-0 p-6">
|
||||||
|
<p className="text-white/70 text-xs font-bold tracking-widest uppercase mb-1">
|
||||||
|
{card.category}
|
||||||
|
</p>
|
||||||
|
<p className="text-white text-lg font-bold leading-snug line-clamp-2">
|
||||||
|
{card.title}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Body */}
|
||||||
|
<div className="p-6 md:p-8">
|
||||||
|
{card.content}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* ── Card thumbnail ── */}
|
||||||
|
<motion.button
|
||||||
|
onClick={handleOpen}
|
||||||
|
className={cn(
|
||||||
|
'rounded-3xl overflow-hidden flex flex-col items-start justify-end relative z-10 cursor-pointer',
|
||||||
|
'h-72 w-48 md:h-[420px] md:w-80',
|
||||||
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--red)]',
|
||||||
|
)}
|
||||||
|
style={{ direction: 'rtl' }}
|
||||||
|
whileHover={{ scale: 1.015 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
>
|
||||||
|
{/* Bottom-to-top gradient so text is readable — tall enough to cover 2 lines */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/95 via-black/65 via-[35%] to-transparent z-20 pointer-events-none" />
|
||||||
|
|
||||||
|
{/* Text — bottom of card, small */}
|
||||||
|
<div className="relative z-30 p-5 w-full text-right overflow-hidden">
|
||||||
|
<p className="text-white/65 text-[10px] font-bold tracking-widest uppercase mb-1.5 truncate">
|
||||||
|
{card.category}
|
||||||
|
</p>
|
||||||
|
<p className="text-white text-sm font-bold leading-snug line-clamp-2">
|
||||||
|
{card.title}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Background image */}
|
||||||
|
<BlurImage
|
||||||
|
src={card.src}
|
||||||
|
alt={card.title}
|
||||||
|
className="object-cover absolute inset-0 w-full h-full z-10"
|
||||||
|
/>
|
||||||
|
</motion.button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── BlurImage ─────────────────────────────────────── */
|
||||||
|
function BlurImage({ src, alt, className }: { src: string; alt: string; className?: string }) {
|
||||||
|
const [loaded, setLoaded] = useState(false)
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
alt={alt}
|
||||||
|
onLoad={() => setLoaded(true)}
|
||||||
|
className={cn('transition-[filter] duration-500', loaded ? 'blur-0' : 'blur-md', className)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── useOutsideClick ───────────────────────────────── */
|
||||||
|
export function useOutsideClick(
|
||||||
|
ref: React.RefObject<HTMLDivElement | null>,
|
||||||
|
callback: () => void,
|
||||||
|
) {
|
||||||
|
useEffect(() => {
|
||||||
|
function listener(e: MouseEvent | TouchEvent) {
|
||||||
|
if (!ref.current || ref.current.contains(e.target as Node)) return
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
document.addEventListener('mousedown', listener)
|
||||||
|
document.addEventListener('touchstart', listener)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', listener)
|
||||||
|
document.removeEventListener('touchstart', listener)
|
||||||
|
}
|
||||||
|
}, [ref, callback])
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
import type { ReactNode, CSSProperties } from 'react'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
function cn(...inputs: Parameters<typeof clsx>) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── BentoGrid ─────────────────────────────────────────── */
|
||||||
|
export function BentoGrid({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
children: ReactNode
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'grid grid-cols-1 md:grid-cols-3',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
style={{ gap: 1, background: 'var(--rule-thin)' }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── BentoGridItem ──────────────────────────────────────── */
|
||||||
|
export interface BentoGridItemProps {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
header?: ReactNode /* visual block above content */
|
||||||
|
icon?: ReactNode
|
||||||
|
tag?: string
|
||||||
|
tagColor?: string /* CSS color value */
|
||||||
|
accentLine?: boolean /* left/top colored accent rule */
|
||||||
|
className?: string
|
||||||
|
style?: CSSProperties
|
||||||
|
onClick?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BentoGridItem({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
header,
|
||||||
|
icon,
|
||||||
|
tag,
|
||||||
|
tagColor = 'var(--red)',
|
||||||
|
accentLine = false,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
onClick,
|
||||||
|
}: BentoGridItemProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={onClick}
|
||||||
|
className={cn(
|
||||||
|
'group relative flex flex-col bg-[var(--paper)] p-6 transition-colors duration-200 hover:bg-[var(--paper-2)]',
|
||||||
|
onClick && 'cursor-pointer',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{/* Accent bar */}
|
||||||
|
{accentLine && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
left: 0,
|
||||||
|
height: 3,
|
||||||
|
background: tagColor,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Optional visual header block */}
|
||||||
|
{header && <div className="mb-4">{header}</div>}
|
||||||
|
|
||||||
|
{/* Tag */}
|
||||||
|
{tag && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '2.5px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
color: tagColor,
|
||||||
|
marginBottom: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Icon + body */}
|
||||||
|
<div className="flex flex-col flex-1 gap-2">
|
||||||
|
{icon && <div className="mb-1">{icon}</div>}
|
||||||
|
|
||||||
|
<h3
|
||||||
|
style={{
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '-0.3px',
|
||||||
|
lineHeight: 1.4,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{description && (
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: 1.75,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
fontWeight: 300,
|
||||||
|
marginTop: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
export { Rule } from './Rule';
|
||||||
|
export { Stamp } from './Stamp';
|
||||||
|
export { Badge } from './Badge';
|
||||||
|
export { Button } from './Button';
|
||||||
|
export { SectionBanner } from './SectionBanner';
|
||||||
|
export { PullQuote } from './PullQuote';
|
||||||
|
export { StatCard } from './StatCard';
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
function cn(...inputs: Parameters<typeof clsx>) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MovingCardItem {
|
||||||
|
quote: string
|
||||||
|
name: string
|
||||||
|
title: string
|
||||||
|
level?: 'critical' | 'high' | 'medium' | 'low' | 'opportunity'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InfiniteMovingCardsProps {
|
||||||
|
items: MovingCardItem[]
|
||||||
|
direction?: 'left' | 'right'
|
||||||
|
speed?: 'fast' | 'normal' | 'slow'
|
||||||
|
pauseOnHover?: boolean
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const levelConfig = {
|
||||||
|
critical: { dot: '#7f1d1d', label: 'بحرانی', text: '#7f1d1d', bg: 'rgba(127,29,29,0.08)' },
|
||||||
|
high: { dot: '#9b1c1c', label: 'بالا', text: '#9b1c1c', bg: 'rgba(155,28,28,0.07)' },
|
||||||
|
medium: { dot: '#92400e', label: 'متوسط', text: '#92400e', bg: 'rgba(146,64,14,0.07)' },
|
||||||
|
low: { dot: '#166534', label: 'پایین', text: '#166534', bg: 'rgba(22,101,52,0.07)' },
|
||||||
|
opportunity: { dot: '#1e40af', label: 'فرصت', text: '#1e40af', bg: 'rgba(30,64,175,0.07)' },
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InfiniteMovingCards({
|
||||||
|
items,
|
||||||
|
direction = 'left',
|
||||||
|
speed = 'slow',
|
||||||
|
pauseOnHover = true,
|
||||||
|
className,
|
||||||
|
}: InfiniteMovingCardsProps) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const scrollerRef = useRef<HTMLUListElement>(null)
|
||||||
|
const [ready, setReady] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current || !scrollerRef.current) return
|
||||||
|
|
||||||
|
/* Clone items for seamless loop */
|
||||||
|
const items = Array.from(scrollerRef.current.children)
|
||||||
|
items.forEach(item => {
|
||||||
|
const clone = item.cloneNode(true)
|
||||||
|
scrollerRef.current!.appendChild(clone)
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Set CSS custom properties */
|
||||||
|
const dur = speed === 'fast' ? '25s' : speed === 'normal' ? '45s' : '70s'
|
||||||
|
containerRef.current.style.setProperty('--animation-duration', dur)
|
||||||
|
containerRef.current.style.setProperty('--animation-direction',
|
||||||
|
direction === 'right' ? 'reverse' : 'forwards')
|
||||||
|
|
||||||
|
setReady(true)
|
||||||
|
}, [direction, speed])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className={cn('relative overflow-hidden', className)}
|
||||||
|
style={{
|
||||||
|
/* Fade masks on both edges */
|
||||||
|
WebkitMaskImage:
|
||||||
|
'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
|
||||||
|
maskImage:
|
||||||
|
'linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
ref={scrollerRef}
|
||||||
|
className={cn(
|
||||||
|
'flex min-w-full shrink-0 gap-5 w-max flex-nowrap',
|
||||||
|
ready && 'animate-scroll',
|
||||||
|
pauseOnHover && 'hover:[animation-play-state:paused]',
|
||||||
|
)}
|
||||||
|
style={{ direction: 'ltr' }} /* keep scroll direction LTR regardless of page RTL */
|
||||||
|
>
|
||||||
|
{items.map((item, idx) => {
|
||||||
|
const cfg = item.level ? levelConfig[item.level] : levelConfig.high
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={idx}
|
||||||
|
style={{
|
||||||
|
width: 340,
|
||||||
|
flexShrink: 0,
|
||||||
|
background: 'var(--paper)',
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
borderTop: `3px solid ${cfg.dot}`,
|
||||||
|
padding: '28px 28px 24px',
|
||||||
|
direction: 'rtl',
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Level badge */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 14 }}>
|
||||||
|
<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 / description */}
|
||||||
|
<p style={{
|
||||||
|
fontSize: 13.5,
|
||||||
|
lineHeight: 1.85,
|
||||||
|
color: 'var(--ink-2)',
|
||||||
|
fontWeight: 300,
|
||||||
|
marginBottom: 20,
|
||||||
|
}}>
|
||||||
|
{item.quote}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div style={{
|
||||||
|
paddingTop: 14,
|
||||||
|
borderTop: '1px solid var(--rule-thin)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--ink)' }}>
|
||||||
|
{item.name}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 10, color: 'var(--ink-5)' }}>
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 9, fontWeight: 700, letterSpacing: '1px',
|
||||||
|
textTransform: 'uppercase', color: cfg.text,
|
||||||
|
background: cfg.bg,
|
||||||
|
padding: '4px 10px',
|
||||||
|
}}>
|
||||||
|
{cfg.label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
/**
|
||||||
|
* WorldMap — Aceternity-style dotted world map with animated arc routes.
|
||||||
|
*
|
||||||
|
* DottedMap (height=60, grid='diagonal') emits viewBox="0 0 119 60".
|
||||||
|
* We project lat/lng into that same coordinate space so the arcs sit
|
||||||
|
* exactly on the map dots.
|
||||||
|
*/
|
||||||
|
import { useRef, useMemo, memo } from 'react'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import DottedMap from 'dotted-map'
|
||||||
|
|
||||||
|
/* ─── Public types ───────────────────────────────────── */
|
||||||
|
export interface MapDot {
|
||||||
|
start: { lat: number; lng: number; label?: string }
|
||||||
|
end: { lat: number; lng: number; label?: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorldMapProps {
|
||||||
|
dots?: MapDot[]
|
||||||
|
lineColor?: string
|
||||||
|
dotColor?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Map geometry constants ─────────────────────────── */
|
||||||
|
const VB_W = 119 // DottedMap viewBox width (height=60 → width=119)
|
||||||
|
const VB_H = 60 // DottedMap viewBox height
|
||||||
|
|
||||||
|
/* Equirectangular projection matching DottedMap's output */
|
||||||
|
function project(lat: number, lng: number) {
|
||||||
|
const x = (lng + 180) / 360 * VB_W
|
||||||
|
const y = (90 - lat) / 180 * VB_H
|
||||||
|
return { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quadratic bezier — control point lifted proportional to distance */
|
||||||
|
function arcPath(
|
||||||
|
s: { x: number; y: number },
|
||||||
|
e: { x: number; y: number },
|
||||||
|
) {
|
||||||
|
const dx = e.x - s.x
|
||||||
|
const dy = e.y - s.y
|
||||||
|
const dist = Math.sqrt(dx * dx + dy * dy)
|
||||||
|
const mx = (s.x + e.x) / 2
|
||||||
|
const my = (s.y + e.y) / 2 - dist * 0.35 // lift proportional to length
|
||||||
|
return `M ${s.x} ${s.y} Q ${mx} ${my} ${e.x} ${e.y}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Memoised base map (expensive, only computed once) ─ */
|
||||||
|
const BaseMap = memo(function BaseMap({ dotColor }: { dotColor: string }) {
|
||||||
|
const map = new DottedMap({ height: 60, grid: 'diagonal' })
|
||||||
|
const svg = map.getSVG({
|
||||||
|
color: dotColor,
|
||||||
|
radius: 0.22,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 w-full h-full"
|
||||||
|
/* eslint-disable-next-line react/no-danger */
|
||||||
|
dangerouslySetInnerHTML={{ __html: svg }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/* ─── Component ──────────────────────────────────────── */
|
||||||
|
export default function WorldMap({
|
||||||
|
dots = [],
|
||||||
|
lineColor = '#9b1c1c', // var(--red)
|
||||||
|
dotColor = '#d1cdc7', // muted cream-grey
|
||||||
|
}: WorldMapProps) {
|
||||||
|
const svgRef = useRef<SVGSVGElement>(null)
|
||||||
|
|
||||||
|
/* Pre-compute projected coordinates */
|
||||||
|
const projected = useMemo(
|
||||||
|
() =>
|
||||||
|
dots.map(d => ({
|
||||||
|
start: project(d.start.lat, d.start.lng),
|
||||||
|
end: project(d.end.lat, d.end.lng),
|
||||||
|
startLabel: d.start.label,
|
||||||
|
endLabel: d.end.label,
|
||||||
|
})),
|
||||||
|
[dots],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
/* aspect-ratio 119:60 ≈ 2:1 keeps the map undistorted */
|
||||||
|
<div className="relative w-full" style={{ aspectRatio: `${VB_W} / ${VB_H}` }}>
|
||||||
|
{/* Dotted world map base */}
|
||||||
|
<BaseMap dotColor={dotColor} />
|
||||||
|
|
||||||
|
{/* Arc + dot overlay — same viewBox as DottedMap */}
|
||||||
|
<svg
|
||||||
|
ref={svgRef}
|
||||||
|
viewBox={`0 0 ${VB_W} ${VB_H}`}
|
||||||
|
className="absolute inset-0 w-full h-full pointer-events-none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
{projected.map((_, i) => (
|
||||||
|
<linearGradient
|
||||||
|
key={`g${i}`}
|
||||||
|
id={`grd${i}`}
|
||||||
|
x1="0%" y1="0%" x2="100%" y2="0%"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<stop offset="0%" stopColor={lineColor} stopOpacity="0" />
|
||||||
|
<stop offset="40%" stopColor={lineColor} stopOpacity="0.85" />
|
||||||
|
<stop offset="60%" stopColor={lineColor} stopOpacity="0.85" />
|
||||||
|
<stop offset="100%" stopColor={lineColor} stopOpacity="0" />
|
||||||
|
</linearGradient>
|
||||||
|
))}
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
{projected.map((p, i) => {
|
||||||
|
const d = arcPath(p.start, p.end)
|
||||||
|
return (
|
||||||
|
<g key={i}>
|
||||||
|
{/* Ghost — very faint, instant, gives the "track" feel */}
|
||||||
|
<path
|
||||||
|
d={d}
|
||||||
|
fill="none"
|
||||||
|
stroke={lineColor}
|
||||||
|
strokeWidth={0.25}
|
||||||
|
strokeOpacity={0.12}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Animated arc */}
|
||||||
|
<motion.path
|
||||||
|
d={d}
|
||||||
|
fill="none"
|
||||||
|
stroke={`url(#grd${i})`}
|
||||||
|
strokeWidth={0.5}
|
||||||
|
strokeLinecap="round"
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 1 }}
|
||||||
|
transition={{
|
||||||
|
pathLength: {
|
||||||
|
duration: 1.6,
|
||||||
|
delay: i * 0.3,
|
||||||
|
ease: 'easeInOut',
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatDelay: 2.5,
|
||||||
|
repeatType: 'loop',
|
||||||
|
},
|
||||||
|
opacity: { duration: 0.2, delay: i * 0.3 },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Start endpoint */}
|
||||||
|
<PulsingDot cx={p.start.x} cy={p.start.y} color={lineColor} delay={i * 0.3} />
|
||||||
|
{/* End endpoint */}
|
||||||
|
<PulsingDot cx={p.end.x} cy={p.end.y} color={lineColor} delay={i * 0.3 + 0.8} />
|
||||||
|
</g>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Pulsing dot ────────────────────────────────────── */
|
||||||
|
function PulsingDot({
|
||||||
|
cx, cy, color, delay,
|
||||||
|
}: {
|
||||||
|
cx: number; cy: number; color: string; delay: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
{/* Outer ring — pulses outward */}
|
||||||
|
<motion.circle
|
||||||
|
cx={cx} cy={cy} r={0.5}
|
||||||
|
fill="none"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth={0.3}
|
||||||
|
initial={{ scale: 1, opacity: 0.8 }}
|
||||||
|
animate={{ scale: 3.5, opacity: 0 }}
|
||||||
|
transition={{
|
||||||
|
duration: 2,
|
||||||
|
repeat: Infinity,
|
||||||
|
delay,
|
||||||
|
ease: 'easeOut',
|
||||||
|
}}
|
||||||
|
style={{ transformOrigin: `${cx}px ${cy}px` }}
|
||||||
|
/>
|
||||||
|
{/* Solid centre */}
|
||||||
|
<circle cx={cx} cy={cy} r={0.6} fill={color} opacity={0.9} />
|
||||||
|
<circle cx={cx} cy={cy} r={0.3} fill="white" opacity={0.6} />
|
||||||
|
</g>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
export type EventType = 'conference' | 'exhibition' | 'seminar' | 'international';
|
||||||
|
|
||||||
|
export interface Event {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
date: number;
|
||||||
|
month: string;
|
||||||
|
year: number;
|
||||||
|
type: EventType;
|
||||||
|
location: string;
|
||||||
|
city: string;
|
||||||
|
registrationOpen: boolean;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const events: Event[] = [
|
||||||
|
{
|
||||||
|
id: 'iran-steel-congress-1403',
|
||||||
|
title: 'کنگره بینالمللی فولاد ایران ۱۴۰۳',
|
||||||
|
date: 12,
|
||||||
|
month: 'اسفند',
|
||||||
|
year: 1403,
|
||||||
|
type: 'conference',
|
||||||
|
location: 'مرکز همایشهای بینالمللی تهران',
|
||||||
|
city: 'تهران',
|
||||||
|
registrationOpen: true,
|
||||||
|
description:
|
||||||
|
'بزرگترین گردهمایی سالانه متخصصان، سیاستگذاران و فعالان صنعت فولاد ایران با حضور ۱۲۰۰ نفر شرکتکننده از ۲۵ کشور.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'middle-east-steel-metals-2024',
|
||||||
|
title: 'Middle East Steel & Metals 2024',
|
||||||
|
date: 25,
|
||||||
|
month: 'اسفند',
|
||||||
|
year: 1403,
|
||||||
|
type: 'exhibition',
|
||||||
|
location: 'مرکز نمایشگاهی دبی ورلد ترید سنتر',
|
||||||
|
city: 'دبی',
|
||||||
|
registrationOpen: true,
|
||||||
|
description:
|
||||||
|
'نمایشگاه تخصصی فولاد و فلزات خاورمیانه با حضور بیش از ۳۰۰ شرکت از ۴۰ کشور؛ فرصتی برای معرفی ظرفیتهای صادراتی صنعت فولاد ایران.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'steel-policy-seminar-1404',
|
||||||
|
title: 'نشست تخصصی سیاستگذاری فولاد ۱۴۰۴',
|
||||||
|
date: 8,
|
||||||
|
month: 'فروردین',
|
||||||
|
year: 1404,
|
||||||
|
type: 'seminar',
|
||||||
|
location: 'پژوهشکده توسعه صنعتی ایران',
|
||||||
|
city: 'تهران',
|
||||||
|
registrationOpen: true,
|
||||||
|
description:
|
||||||
|
'نشست تخصصی با حضور نمایندگان وزارت صمت، سازمان ایمیدرو و انجمنهای تخصصی برای بررسی سیاستهای سال ۱۴۰۴ در صنعت فولاد.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'world-steel-forum-2025',
|
||||||
|
title: 'World Steel Forum 2025',
|
||||||
|
date: 20,
|
||||||
|
month: 'فروردین',
|
||||||
|
year: 1404,
|
||||||
|
type: 'international',
|
||||||
|
location: 'مرکز کنفرانس برلین',
|
||||||
|
city: 'برلین',
|
||||||
|
registrationOpen: false,
|
||||||
|
description:
|
||||||
|
'اجلاس سالانه انجمن جهانی فولاد با تمرکز بر کربنزدایی، تجارت جهانی و تحولات ساختاری صنعت فولاد تا افق ۲۰۳۰.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'iran-metallurgy-expo-1404',
|
||||||
|
title: 'نمایشگاه متالورژی ایران',
|
||||||
|
date: 15,
|
||||||
|
month: 'اردیبهشت',
|
||||||
|
year: 1404,
|
||||||
|
type: 'exhibition',
|
||||||
|
location: 'نمایشگاه بینالمللی اصفهان',
|
||||||
|
city: 'اصفهان',
|
||||||
|
registrationOpen: false,
|
||||||
|
description:
|
||||||
|
'نمایشگاه تخصصی متالورژی، فولاد و فلزات غیرآهنی ایران با محوریت فناوریهای نوین تولید و تجهیزات صنعتی.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'oecd-steel-summit-2025',
|
||||||
|
title: 'اجلاس OECD فولاد',
|
||||||
|
date: 3,
|
||||||
|
month: 'خرداد',
|
||||||
|
year: 1404,
|
||||||
|
type: 'international',
|
||||||
|
location: 'دفتر مرکزی OECD',
|
||||||
|
city: 'پاریس',
|
||||||
|
registrationOpen: false,
|
||||||
|
description:
|
||||||
|
'نشست سالانه کمیته فولاد OECD برای بررسی مازاد ظرفیت جهانی، سیاستهای تجاری و استانداردهای پایداری در صنعت فولاد.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
export type PriceTrend = 'up' | 'down' | 'flat';
|
||||||
|
|
||||||
|
export interface MarketPrice {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
unit: string;
|
||||||
|
change: number;
|
||||||
|
changePercent: number;
|
||||||
|
trend: PriceTrend;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PriceHistoryPoint {
|
||||||
|
month: string;
|
||||||
|
price: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GlobalComparison {
|
||||||
|
country: string;
|
||||||
|
flag: string;
|
||||||
|
price: number;
|
||||||
|
unit: string;
|
||||||
|
currency: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExportData {
|
||||||
|
month: string;
|
||||||
|
tonnage: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InstituteStats {
|
||||||
|
totalReports: number;
|
||||||
|
years: number;
|
||||||
|
experts: number;
|
||||||
|
memberOrgs: number;
|
||||||
|
subscribers: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const marketPrices: MarketPrice[] = [
|
||||||
|
{
|
||||||
|
id: 'steel-billet',
|
||||||
|
name: 'شمش فولاد',
|
||||||
|
value: 18400,
|
||||||
|
unit: 'تومان/کیلو',
|
||||||
|
change: 220,
|
||||||
|
changePercent: 1.2,
|
||||||
|
trend: 'up',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rebar-a3',
|
||||||
|
name: 'میلگرد A3',
|
||||||
|
value: 17800,
|
||||||
|
unit: 'تومان/کیلو',
|
||||||
|
change: -90,
|
||||||
|
changePercent: -0.5,
|
||||||
|
trend: 'down',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'beam-14',
|
||||||
|
name: 'تیرآهن ۱۴',
|
||||||
|
value: 16900,
|
||||||
|
unit: 'تومان/کیلو',
|
||||||
|
change: 135,
|
||||||
|
changePercent: 0.8,
|
||||||
|
trend: 'up',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'scrap-iron',
|
||||||
|
name: 'آهن قراضه',
|
||||||
|
value: 550,
|
||||||
|
unit: 'دلار/تن',
|
||||||
|
change: -11,
|
||||||
|
changePercent: -2.0,
|
||||||
|
trend: 'down',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cold-rolled-sheet',
|
||||||
|
name: 'ورق سرد',
|
||||||
|
value: 22100,
|
||||||
|
unit: 'تومان/کیلو',
|
||||||
|
change: 66,
|
||||||
|
changePercent: 0.3,
|
||||||
|
trend: 'up',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'angle-bar-80',
|
||||||
|
name: 'نبشی ۸۰',
|
||||||
|
value: 15600,
|
||||||
|
unit: 'تومان/کیلو',
|
||||||
|
change: -172,
|
||||||
|
changePercent: -1.1,
|
||||||
|
trend: 'down',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mobarakeh-steel',
|
||||||
|
name: 'فولاد مبارکه',
|
||||||
|
value: 12840,
|
||||||
|
unit: 'ریال',
|
||||||
|
change: -143,
|
||||||
|
changePercent: -1.1,
|
||||||
|
trend: 'down',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const priceHistory: PriceHistoryPoint[] = [
|
||||||
|
{ month: 'خرداد ۱۴۰۳', price: 15200 },
|
||||||
|
{ month: 'تیر ۱۴۰۳', price: 15600 },
|
||||||
|
{ month: 'مرداد ۱۴۰۳', price: 16100 },
|
||||||
|
{ month: 'شهریور ۱۴۰۳', price: 15800 },
|
||||||
|
{ month: 'مهر ۱۴۰۳', price: 16400 },
|
||||||
|
{ month: 'آبان ۱۴۰۳', price: 17000 },
|
||||||
|
{ month: 'آذر ۱۴۰۳', price: 17500 },
|
||||||
|
{ month: 'دی ۱۴۰۳', price: 17200 },
|
||||||
|
{ month: 'بهمن ۱۴۰۳', price: 17800 },
|
||||||
|
{ month: 'اسفند ۱۴۰۳', price: 18100 },
|
||||||
|
{ month: 'فروردین ۱۴۰۴', price: 18600 },
|
||||||
|
{ month: 'اردیبهشت ۱۴۰۴', price: 18400 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const globalComparison: GlobalComparison[] = [
|
||||||
|
{
|
||||||
|
country: 'ایران',
|
||||||
|
flag: '🇮🇷',
|
||||||
|
price: 18400,
|
||||||
|
unit: 'تومان/کیلو',
|
||||||
|
currency: 'IRR',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
country: 'ترکیه',
|
||||||
|
flag: '🇹🇷',
|
||||||
|
price: 530,
|
||||||
|
unit: 'دلار/تن',
|
||||||
|
currency: 'USD',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
country: 'هند',
|
||||||
|
flag: '🇮🇳',
|
||||||
|
price: 510,
|
||||||
|
unit: 'دلار/تن',
|
||||||
|
currency: 'USD',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
country: 'چین',
|
||||||
|
flag: '🇨🇳',
|
||||||
|
price: 460,
|
||||||
|
unit: 'دلار/تن',
|
||||||
|
currency: 'USD',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
country: 'روسیه',
|
||||||
|
flag: '🇷🇺',
|
||||||
|
price: 485,
|
||||||
|
unit: 'دلار/تن',
|
||||||
|
currency: 'USD',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const monthlyExport: ExportData[] = [
|
||||||
|
{ month: 'خرداد ۱۴۰۳', tonnage: 285000 },
|
||||||
|
{ month: 'تیر ۱۴۰۳', tonnage: 310000 },
|
||||||
|
{ month: 'مرداد ۱۴۰۳', tonnage: 295000 },
|
||||||
|
{ month: 'شهریور ۱۴۰۳', tonnage: 320000 },
|
||||||
|
{ month: 'مهر ۱۴۰۳', tonnage: 350000 },
|
||||||
|
{ month: 'آبان ۱۴۰۳', tonnage: 375000 },
|
||||||
|
{ month: 'آذر ۱۴۰۳', tonnage: 390000 },
|
||||||
|
{ month: 'دی ۱۴۰۳', tonnage: 360000 },
|
||||||
|
{ month: 'بهمن ۱۴۰۳', tonnage: 415000 },
|
||||||
|
{ month: 'اسفند ۱۴۰۳', tonnage: 420000 },
|
||||||
|
{ month: 'فروردین ۱۴۰۴', tonnage: 400000 },
|
||||||
|
{ month: 'اردیبهشت ۱۴۰۴', tonnage: 410000 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const stats: InstituteStats = {
|
||||||
|
totalReports: 280,
|
||||||
|
years: 12,
|
||||||
|
experts: 45,
|
||||||
|
memberOrgs: 60,
|
||||||
|
subscribers: 2800,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
export type PlanCtaVariant = 'outline' | 'filled' | 'paper';
|
||||||
|
|
||||||
|
export interface Plan {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
price: number;
|
||||||
|
priceLabel: string;
|
||||||
|
period: string;
|
||||||
|
features: string[];
|
||||||
|
badge?: string;
|
||||||
|
featured: boolean;
|
||||||
|
ctaLabel: string;
|
||||||
|
ctaVariant: PlanCtaVariant;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const plans: Plan[] = [
|
||||||
|
{
|
||||||
|
id: 'individual',
|
||||||
|
name: 'فردی',
|
||||||
|
price: 350000,
|
||||||
|
priceLabel: '۳۵۰,۰۰۰ تومان',
|
||||||
|
period: 'ماهانه',
|
||||||
|
features: [
|
||||||
|
'دسترسی به تمام گزارشهای پایه',
|
||||||
|
'دانلود ۵ گزارش در ماه',
|
||||||
|
'دریافت خبرنامه هفتگی',
|
||||||
|
'دسترسی به دادههای قیمت لحظهای',
|
||||||
|
'آرشیو ۱۲ ماه گذشته',
|
||||||
|
],
|
||||||
|
featured: false,
|
||||||
|
ctaLabel: 'شروع اشتراک',
|
||||||
|
ctaVariant: 'outline',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'organizational',
|
||||||
|
name: 'سازمانی',
|
||||||
|
price: 1200000,
|
||||||
|
priceLabel: '۱,۲۰۰,۰۰۰ تومان',
|
||||||
|
period: 'ماهانه',
|
||||||
|
features: [
|
||||||
|
'دسترسی کامل به تمام گزارشها',
|
||||||
|
'دانلود نامحدود گزارش',
|
||||||
|
'دسترسی برای ۵ کاربر همزمان',
|
||||||
|
'گزارشهای ویژه و تحلیلهای فوری',
|
||||||
|
'آرشیو کامل از ابتدای تأسیس',
|
||||||
|
'مشاوره تلفنی ماهانه با تحلیلگران',
|
||||||
|
'داشبورد اختصاصی تحلیل بازار',
|
||||||
|
],
|
||||||
|
badge: 'پرطرفدار',
|
||||||
|
featured: true,
|
||||||
|
ctaLabel: 'شروع اشتراک',
|
||||||
|
ctaVariant: 'filled',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ministry',
|
||||||
|
name: 'وزارتخانه',
|
||||||
|
price: 0,
|
||||||
|
priceLabel: 'تماس بگیرید',
|
||||||
|
period: 'سالانه',
|
||||||
|
features: [
|
||||||
|
'دسترسی نامحدود برای تمام کارکنان',
|
||||||
|
'سفارش گزارشهای تحلیلی اختصاصی',
|
||||||
|
'نشستهای تخصصی حضوری با تیم پژوهشی',
|
||||||
|
'پشتیبانی اولویتدار ۲۴/۷',
|
||||||
|
'داشبورد تصمیمگیری یکپارچه',
|
||||||
|
'API اتصال به سیستمهای داخلی',
|
||||||
|
'آموزش و کارگاههای تخصصی دورهای',
|
||||||
|
'گزارشدهی سفارشی برای سیاستگذاران',
|
||||||
|
],
|
||||||
|
featured: false,
|
||||||
|
ctaLabel: 'درخواست مشاوره',
|
||||||
|
ctaVariant: 'paper',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,335 @@
|
||||||
|
export type ReportCategory =
|
||||||
|
| 'بازار جهانی'
|
||||||
|
| 'سیاستگذاری'
|
||||||
|
| 'انرژی و ESG'
|
||||||
|
| 'صادرات'
|
||||||
|
| 'تولید داخلی'
|
||||||
|
| 'ریسک و بحران';
|
||||||
|
|
||||||
|
export type ReportType = 'quarterly' | 'flash' | 'risk' | 'free' | 'special';
|
||||||
|
|
||||||
|
export interface Report {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
category: ReportCategory;
|
||||||
|
type: ReportType;
|
||||||
|
author: string;
|
||||||
|
authorRole: string;
|
||||||
|
authorInitial: string;
|
||||||
|
publishDate: string;
|
||||||
|
pages: number;
|
||||||
|
price: number;
|
||||||
|
isFree: boolean;
|
||||||
|
summary: string;
|
||||||
|
tags: string[];
|
||||||
|
featured: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const reports: Report[] = [
|
||||||
|
{
|
||||||
|
id: 'iran-steel-global-trade-scenarios-1404',
|
||||||
|
title: 'ایران در نقشه جدید تجارت فولاد جهان: چهار سناریو برای ۱۴۰۴',
|
||||||
|
category: 'بازار جهانی',
|
||||||
|
type: 'special',
|
||||||
|
author: 'دکتر علی محمدی',
|
||||||
|
authorRole: 'مدیر ارشد پژوهش',
|
||||||
|
authorInitial: 'م',
|
||||||
|
publishDate: 'بهمن ۱۴۰۳',
|
||||||
|
pages: 84,
|
||||||
|
price: 850000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'این گزارش ویژه با تحلیل روندهای کلان اقتصاد جهانی، چهار سناریوی محتمل برای جایگاه صادرات فولاد ایران در سال ۱۴۰۴ را بررسی میکند. مدلسازی کمی بر اساس دادههای تجارت ۱۵ کشور رقیب انجام شده و توصیههای سیاستگذاری مشخص ارائه میدهد. این گزارش مرجع اصلی تصمیمگیران و مدیران ارشد صنعت فولاد کشور است.',
|
||||||
|
tags: ['تجارت جهانی', 'سناریوسازی', 'صادرات', 'سیاستگذاری', '۱۴۰۴'],
|
||||||
|
featured: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'steel-energy-consumption-efficiency-1403',
|
||||||
|
title: 'مصرف انرژی در فولادسازی ایران: مقایسه با استانداردهای جهانی',
|
||||||
|
category: 'انرژی و ESG',
|
||||||
|
type: 'free',
|
||||||
|
author: 'مهندس سارا رضایی',
|
||||||
|
authorRole: 'پژوهشگر ارشد انرژی',
|
||||||
|
authorInitial: 'ر',
|
||||||
|
publishDate: 'دی ۱۴۰۳',
|
||||||
|
pages: 28,
|
||||||
|
price: 0,
|
||||||
|
isFree: true,
|
||||||
|
summary:
|
||||||
|
'این گزارش رایگان مصرف انرژی واحدهای فولادسازی ایران را با میانگین جهانی و استانداردهای IEA مقایسه میکند. شکاف بهرهوری انرژی در کورههای قوس الکتریکی و کنورتور شناسایی شده است. راهکارهای کاهش ۱۵ تا ۲۵ درصدی مصرف انرژی نیز پیشنهاد شده است.',
|
||||||
|
tags: ['انرژی', 'بهرهوری', 'ESG', 'کوره قوس الکتریکی', 'استاندارد جهانی'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'global-steel-market-q3-1403',
|
||||||
|
title: 'گزارش فصلی بازار جهانی فولاد: پاییز ۱۴۰۳',
|
||||||
|
category: 'بازار جهانی',
|
||||||
|
type: 'quarterly',
|
||||||
|
author: 'دکتر حسین کریمی',
|
||||||
|
authorRole: 'تحلیلگر ارشد بازار',
|
||||||
|
authorInitial: 'ک',
|
||||||
|
publishDate: 'آذر ۱۴۰۳',
|
||||||
|
pages: 52,
|
||||||
|
price: 450000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'تحلیل جامع قیمتها، تقاضا و عرضه در بازار جهانی فولاد طی فصل پاییز ۱۴۰۳ ارائه شده است. روند کاهش تقاضای چین و افزایش ظرفیت تولید هند بهتفصیل بررسی شده است. چشمانداز قیمتی برای زمستان ۱۴۰۳ نیز ارائه میشود.',
|
||||||
|
tags: ['بازار جهانی', 'قیمتگذاری', 'چین', 'هند', 'پاییز ۱۴۰۳'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'iran-steel-export-sanctions-impact-1403',
|
||||||
|
title: 'اثر تحریمهای جدید بر مسیرهای صادراتی فولاد ایران',
|
||||||
|
category: 'صادرات',
|
||||||
|
type: 'risk',
|
||||||
|
author: 'دکتر فاطمه احمدی',
|
||||||
|
authorRole: 'مدیر تحقیقات ریسک',
|
||||||
|
authorInitial: 'ا',
|
||||||
|
publishDate: 'آذر ۱۴۰۳',
|
||||||
|
pages: 36,
|
||||||
|
price: 380000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'این گزارش اثرات بسته تحریمی اخیر بر کانالهای صادرات فولاد ایران به بازارهای منطقهای را ارزیابی میکند. ریسکهای لجستیکی، بانکی و قراردادی مستند شده است. راهکارهای جایگزین برای حفظ جریان صادرات پیشنهاد میشود.',
|
||||||
|
tags: ['تحریم', 'صادرات', 'ریسک', 'لجستیک', 'بازارهای منطقهای'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'flash-iron-ore-price-surge-1403',
|
||||||
|
title: 'فلش: جهش قیمت سنگآهن و تأثیر بر هزینه تمامشده فولاد ایران',
|
||||||
|
category: 'بازار جهانی',
|
||||||
|
type: 'flash',
|
||||||
|
author: 'مهندس رضا توکلی',
|
||||||
|
authorRole: 'تحلیلگر بازار کامودیتی',
|
||||||
|
authorInitial: 'ت',
|
||||||
|
publishDate: 'آبان ۱۴۰۳',
|
||||||
|
pages: 12,
|
||||||
|
price: 120000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'گزارش فوری درباره افزایش ۱۸ درصدی قیمت سنگآهن در بورس دالیان و اثر فوری آن بر هزینه تمامشده تولید فولاد در ایران. برآورد اثر بر حاشیه سود کارخانههای داخلی ارائه شده است. توصیههای پوشش ریسک قیمتی برای مدیران خرید نیز مطرح است.',
|
||||||
|
tags: ['سنگآهن', 'قیمت', 'هزینه تمامشده', 'فوری'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'domestic-production-dri-outlook-1403',
|
||||||
|
title: 'چشمانداز تولید آهن احیای مستقیم ایران تا افق ۱۴۰۵',
|
||||||
|
category: 'تولید داخلی',
|
||||||
|
type: 'quarterly',
|
||||||
|
author: 'دکتر علی محمدی',
|
||||||
|
authorRole: 'مدیر ارشد پژوهش',
|
||||||
|
authorInitial: 'م',
|
||||||
|
publishDate: 'مهر ۱۴۰۳',
|
||||||
|
pages: 44,
|
||||||
|
price: 420000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'ایران با بیش از ۳۰ میلیون تن ظرفیت DRI، رتبه اول جهان را دارد؛ این گزارش مسیر توسعه این صنعت تا ۱۴۰۵ را مدلسازی میکند. بررسی پروژههای در دست احداث و موانع تأمین گاز بهتفصیل آمده است. ریسکهای بهرهبرداری و پیشنهادهای بهینهسازی نیز ارائه شده است.',
|
||||||
|
tags: ['DRI', 'آهن احیای مستقیم', 'ظرفیت', 'تولید', 'گاز طبیعی'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'esg-carbon-emissions-steel-iran-1403',
|
||||||
|
title: 'کربنزدایی در صنعت فولاد ایران: الزامات و فرصتها',
|
||||||
|
category: 'انرژی و ESG',
|
||||||
|
type: 'special',
|
||||||
|
author: 'مهندس سارا رضایی',
|
||||||
|
authorRole: 'پژوهشگر ارشد انرژی',
|
||||||
|
authorInitial: 'ر',
|
||||||
|
publishDate: 'شهریور ۱۴۰۳',
|
||||||
|
pages: 58,
|
||||||
|
price: 520000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'الزامات آینده CBAM اتحادیه اروپا و اثر آن بر رقابتپذیری صادرات فولاد ایران تحلیل شده است. مسیرهای فناوری کربنزدایی شامل هیدروژن سبز و CCS برای شرایط ایران ارزیابی شدهاند. برنامه عملیاتی دهساله برای کاهش شدت کربن پیشنهاد میشود.',
|
||||||
|
tags: ['ESG', 'کربن', 'CBAM', 'هیدروژن سبز', 'محیط زیست'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'policy-steel-pricing-reform-1403',
|
||||||
|
title: 'اصلاح ساختار قیمتگذاری فولاد: درسهایی از تجربه ترکیه و هند',
|
||||||
|
category: 'سیاستگذاری',
|
||||||
|
type: 'quarterly',
|
||||||
|
author: 'دکتر فاطمه احمدی',
|
||||||
|
authorRole: 'مدیر تحقیقات ریسک',
|
||||||
|
authorInitial: 'ا',
|
||||||
|
publishDate: 'مرداد ۱۴۰۳',
|
||||||
|
pages: 46,
|
||||||
|
price: 390000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'مقایسه تطبیقی ساختار قیمتگذاری فولاد در ایران، ترکیه و هند با تمرکز بر نقش دولت و مکانیسمهای بازار. اثربخشی سیاستهای قیمتگذاری دستوری در حفظ تعادل عرضه-تقاضا ارزیابی شده است. توصیههای سیاستی برای کاهش اعوجاج قیمتی ارائه میشود.',
|
||||||
|
tags: ['سیاستگذاری', 'قیمتگذاری', 'ترکیه', 'هند', 'اصلاح ساختار'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'flash-china-steel-overcapacity-1403',
|
||||||
|
title: 'فلش: مازاد ظرفیت فولاد چین و موج صادراتی به آسیای غربی',
|
||||||
|
category: 'بازار جهانی',
|
||||||
|
type: 'flash',
|
||||||
|
author: 'دکتر حسین کریمی',
|
||||||
|
authorRole: 'تحلیلگر ارشد بازار',
|
||||||
|
authorInitial: 'ک',
|
||||||
|
publishDate: 'تیر ۱۴۰۳',
|
||||||
|
pages: 10,
|
||||||
|
price: 110000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'چین در نیمه اول ۲۰۲۴ رکورد صادرات فولاد را با ۵۴ میلیون تن شکست. این گزارش فوری تأثیر مستقیم این موج صادراتی بر بازارهای ایران، عراق و امارات را تحلیل میکند. توصیه اقدام فوری برای کنسرسیومهای صادراتی ایران ارائه شده است.',
|
||||||
|
tags: ['چین', 'مازاد ظرفیت', 'صادرات', 'آسیای غربی', 'رقابت'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'risk-geopolitical-middle-east-steel-1403',
|
||||||
|
title: 'ریسکهای ژئوپلیتیک خاورمیانه و زنجیره تأمین فولاد ایران',
|
||||||
|
category: 'ریسک و بحران',
|
||||||
|
type: 'risk',
|
||||||
|
author: 'دکتر فاطمه احمدی',
|
||||||
|
authorRole: 'مدیر تحقیقات ریسک',
|
||||||
|
authorInitial: 'ا',
|
||||||
|
publishDate: 'خرداد ۱۴۰۳',
|
||||||
|
pages: 40,
|
||||||
|
price: 360000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'تحلیل ریسک ژئوپلیتیک منطقه خاورمیانه بر امنیت زنجیره تأمین فولاد ایران از زاویه واردات مواد اولیه و صادرات محصول نهایی. سناریوهای بحران در مسیرهای دریایی خلیج فارس و دریای سرخ مدلسازی شدهاند. برنامه تداوم کسبوکار برای تولیدکنندگان ارائه میشود.',
|
||||||
|
tags: ['ژئوپلیتیک', 'خاورمیانه', 'زنجیره تأمین', 'ریسک', 'دریای سرخ'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'export-iraq-market-analysis-1403',
|
||||||
|
title: 'تحلیل بازار فولاد عراق: فرصتهای صادراتی ایران',
|
||||||
|
category: 'صادرات',
|
||||||
|
type: 'quarterly',
|
||||||
|
author: 'مهندس رضا توکلی',
|
||||||
|
authorRole: 'تحلیلگر بازار کامودیتی',
|
||||||
|
authorInitial: 'ت',
|
||||||
|
publishDate: 'اردیبهشت ۱۴۰۳',
|
||||||
|
pages: 38,
|
||||||
|
price: 340000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'عراق با تقاضای سالانه بیش از ۵ میلیون تن فولاد، مهمترین بازار صادراتی ایران است. این گزارش رقابتپذیری فولاد ایران در برابر رقبای ترکیه، چین و اوکراین در بازار عراق را ارزیابی میکند. استراتژیهای بازاریابی و توسعه بازار پیشنهاد شده است.',
|
||||||
|
tags: ['عراق', 'صادرات', 'بازار', 'رقابتپذیری', 'میلگرد'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'domestic-capacity-utilization-1402',
|
||||||
|
title: 'ظرفیت بهرهبرداری واحدهای فولادسازی ایران: آسیبشناسی و راهکار',
|
||||||
|
category: 'تولید داخلی',
|
||||||
|
type: 'quarterly',
|
||||||
|
author: 'دکتر علی محمدی',
|
||||||
|
authorRole: 'مدیر ارشد پژوهش',
|
||||||
|
authorInitial: 'م',
|
||||||
|
publishDate: 'فروردین ۱۴۰۳',
|
||||||
|
pages: 50,
|
||||||
|
price: 410000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'میانگین ضریب بهرهبرداری کارخانههای فولادسازی ایران به ۶۸ درصد رسیده که پایینتر از میانگین جهانی ۷۶ درصد است. این گزارش موانع عملیاتی، تأمین مواد اولیه و بازار را شناسایی کرده است. برنامههای بهبود بهرهبرداری در کوتاهمدت و میانمدت ارائه میشود.',
|
||||||
|
tags: ['ظرفیت', 'بهرهبرداری', 'کارایی', 'تولید', 'کارخانه'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'flash-rebar-demand-construction-1402',
|
||||||
|
title: 'فلش: کاهش تقاضای میلگرد در بخش ساختمانی و اثر بر کارخانهها',
|
||||||
|
category: 'تولید داخلی',
|
||||||
|
type: 'flash',
|
||||||
|
author: 'مهندس سارا رضایی',
|
||||||
|
authorRole: 'پژوهشگر ارشد انرژی',
|
||||||
|
authorInitial: 'ر',
|
||||||
|
publishDate: 'اسفند ۱۴۰۲',
|
||||||
|
pages: 14,
|
||||||
|
price: 115000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'رکود در بازار مسکن منجر به کاهش ۱۲ درصدی تقاضای میلگرد در زمستان ۱۴۰۲ شده است. این گزارش فوری اثر این کاهش بر درآمد و برنامه تولید کارخانههای میلگردساز را ارزیابی میکند. توصیه تنظیم پورتفولیوی محصول برای جبران افت تقاضا مطرح شده است.',
|
||||||
|
tags: ['میلگرد', 'ساختمان', 'تقاضا', 'رکود', 'تولید داخلی'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'policy-privatization-steel-companies-1402',
|
||||||
|
title: 'واگذاری شرکتهای فولادی دولتی: ارزیابی تجربه یک دهه خصوصیسازی',
|
||||||
|
category: 'سیاستگذاری',
|
||||||
|
type: 'special',
|
||||||
|
author: 'دکتر حسین کریمی',
|
||||||
|
authorRole: 'تحلیلگر ارشد بازار',
|
||||||
|
authorInitial: 'ک',
|
||||||
|
publishDate: 'بهمن ۱۴۰۲',
|
||||||
|
pages: 62,
|
||||||
|
price: 560000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'ارزیابی جامع نتایج خصوصیسازی شرکتهای فولادی بزرگ ایران در یک دهه گذشته با شاخصهای بهرهوری، اشتغال و سرمایهگذاری. تجربههای موفق و ناموفق واگذاری مستند شده است. الگوی بهینه مشارکت دولتی-خصوصی در صنعت فولاد پیشنهاد میشود.',
|
||||||
|
tags: ['خصوصیسازی', 'سیاستگذاری', 'دولت', 'مدیریت', 'بهرهوری'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'risk-water-scarcity-steel-industry-1402',
|
||||||
|
title: 'بحران آب و تهدیدات پایداری صنعت فولاد ایران',
|
||||||
|
category: 'ریسک و بحران',
|
||||||
|
type: 'risk',
|
||||||
|
author: 'مهندس سارا رضایی',
|
||||||
|
authorRole: 'پژوهشگر ارشد انرژی',
|
||||||
|
authorInitial: 'ر',
|
||||||
|
publishDate: 'دی ۱۴۰۲',
|
||||||
|
pages: 42,
|
||||||
|
price: 370000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'استرس آبی در کلاستر فولادسازی اصفهان-یزد-خراسان به نقطه بحرانی رسیده است. این گزارش ریسک تعطیلی یا کاهش تولید ناشی از کمبود آب را برای ۱۴ واحد تولیدی ارزیابی میکند. راهکارهای فناوری و مدیریتی برای کاهش مصرف آب پیشنهاد شده است.',
|
||||||
|
tags: ['آب', 'بحران', 'پایداری', 'اصفهان', 'ریسک محیط زیستی'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'global-market-q1-1402',
|
||||||
|
title: 'گزارش فصلی بازار جهانی فولاد: بهار ۱۴۰۲',
|
||||||
|
category: 'بازار جهانی',
|
||||||
|
type: 'quarterly',
|
||||||
|
author: 'دکتر علی محمدی',
|
||||||
|
authorRole: 'مدیر ارشد پژوهش',
|
||||||
|
authorInitial: 'م',
|
||||||
|
publishDate: 'تیر ۱۴۰۲',
|
||||||
|
pages: 54,
|
||||||
|
price: 440000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'بررسی روندهای بازار جهانی فولاد در بهار ۱۴۰۲ با تمرکز بر بازگشایی اقتصاد چین و اثرات آن بر قیمتها. تأثیر افزایش نرخ بهره فدرال رزرو بر سرمایهگذاری در ساختمان و زیرساخت تحلیل شده است. پیشبینی روند قیمتی تا پایان ۱۴۰۲ ارائه میشود.',
|
||||||
|
tags: ['بازار جهانی', 'چین', 'نرخ بهره', 'تقاضا', 'بهار ۱۴۰۲'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'export-africa-emerging-markets-1402',
|
||||||
|
title: 'فرصتهای صادرات فولاد ایران به بازارهای نوظهور آفریقا',
|
||||||
|
category: 'صادرات',
|
||||||
|
type: 'special',
|
||||||
|
author: 'دکتر فاطمه احمدی',
|
||||||
|
authorRole: 'مدیر تحقیقات ریسک',
|
||||||
|
authorInitial: 'ا',
|
||||||
|
publishDate: 'آبان ۱۴۰۲',
|
||||||
|
pages: 48,
|
||||||
|
price: 480000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'آفریقا با رشد سالانه ۶ درصد در تقاضای فولاد، بازار استراتژیک بلندمدت برای صادرکنندگان ایران محسوب میشود. این گزارش ۸ بازار هدف اولویتدار آفریقایی را با تحلیل ریسک و فرصت معرفی میکند. مدل ورود به بازار و شرکای تجاری محتمل نیز پیشنهاد شده است.',
|
||||||
|
tags: ['آفریقا', 'بازارهای نوظهور', 'صادرات', 'تنوعسازی', 'بلندمدت'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'flash-gas-price-hike-steel-cost-1403',
|
||||||
|
title: 'فلش: اثر افزایش قیمت گاز بر قیمت تمامشده فولاد ایران',
|
||||||
|
category: 'انرژی و ESG',
|
||||||
|
type: 'flash',
|
||||||
|
author: 'مهندس رضا توکلی',
|
||||||
|
authorRole: 'تحلیلگر بازار کامودیتی',
|
||||||
|
authorInitial: 'ت',
|
||||||
|
publishDate: 'شهریور ۱۴۰۳',
|
||||||
|
pages: 11,
|
||||||
|
price: 105000,
|
||||||
|
isFree: false,
|
||||||
|
summary:
|
||||||
|
'افزایش ۳۰ درصدی قیمت گاز صنعتی در مهر ۱۴۰۳ هزینه تولید هر تن DRI را حدود ۱۲۰۰ تومان افزایش میدهد. این گزارش فوری اثر این شوک هزینهای بر حاشیه سود تولیدکنندگان DRI و فولاد را محاسبه میکند. راهکارهای کوتاهمدت برای مدیریت این افزایش هزینه پیشنهاد شده است.',
|
||||||
|
tags: ['گاز', 'انرژی', 'هزینه تولید', 'DRI', 'قیمتگذاری'],
|
||||||
|
featured: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
export interface TeamMember {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
role: string;
|
||||||
|
initial: string;
|
||||||
|
bio: string;
|
||||||
|
expertise: string[];
|
||||||
|
reportCount: number;
|
||||||
|
email?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const team: TeamMember[] = [
|
||||||
|
{
|
||||||
|
id: 'ali-mohammadi',
|
||||||
|
name: 'دکتر علی محمدی',
|
||||||
|
role: 'مدیر ارشد پژوهش',
|
||||||
|
initial: 'م',
|
||||||
|
bio: 'دکتری اقتصاد صنعتی از دانشگاه تهران با بیش از ۱۸ سال تجربه در تحلیل بازارهای فولاد و فلزات. پیش از پیوستن به اندیشکده، مشاور ارشد سازمان توسعه و نوسازی معادن و صنایع معدنی ایران (ایمیدرو) بوده است. بیش از ۵۰ گزارش تحلیلی و ۱۲ مقاله علمی در حوزه اقتصاد فولاد منتشر کرده است.',
|
||||||
|
expertise: [
|
||||||
|
'اقتصاد صنعتی',
|
||||||
|
'تحلیل بازار فولاد',
|
||||||
|
'سیاستگذاری صنعتی',
|
||||||
|
'مدلسازی اقتصادی',
|
||||||
|
'تجارت بینالملل',
|
||||||
|
],
|
||||||
|
reportCount: 52,
|
||||||
|
email: 'mohammadi@andishkade-foolad.ir',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'fateme-ahmadi',
|
||||||
|
name: 'دکتر فاطمه احمدی',
|
||||||
|
role: 'مدیر تحقیقات ریسک',
|
||||||
|
initial: 'ا',
|
||||||
|
bio: 'دکتری مدیریت ریسک از دانشگاه علم و صنعت ایران با تخصص در ارزیابی ریسکهای ژئوپلیتیک و زنجیره تأمین. سابقه مشاوره به چندین شرکت بزرگ فولادی کشور در حوزه مدیریت ریسک تجاری و قراردادی را دارد. نویسنده کتاب «مدیریت ریسک در صنایع معدنی و فلزی» است.',
|
||||||
|
expertise: [
|
||||||
|
'مدیریت ریسک',
|
||||||
|
'تحریمپژوهی',
|
||||||
|
'ژئوپلیتیک انرژی',
|
||||||
|
'تأمین مالی پروژه',
|
||||||
|
'مذاکرات تجاری',
|
||||||
|
],
|
||||||
|
reportCount: 38,
|
||||||
|
email: 'ahmadi@andishkade-foolad.ir',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hosein-karimi',
|
||||||
|
name: 'دکتر حسین کریمی',
|
||||||
|
role: 'تحلیلگر ارشد بازار',
|
||||||
|
initial: 'ک',
|
||||||
|
bio: 'دکتری مهندسی مواد از دانشگاه صنعتی اصفهان با تخصص ترکیبی در متالورژی و اقتصاد بازار کامودیتیها. پنج سال سابقه کار در واحد پژوهش و توسعه فولاد مبارکه اصفهان دارد. بهطور منظم گزارشهای فصلی بازار جهانی فولاد را تهیه و منتشر میکند.',
|
||||||
|
expertise: [
|
||||||
|
'بازار جهانی فولاد',
|
||||||
|
'قیمتگذاری کامودیتی',
|
||||||
|
'متالورژی صنعتی',
|
||||||
|
'پیشبینی تقاضا',
|
||||||
|
'بازارهای آسیایی',
|
||||||
|
],
|
||||||
|
reportCount: 41,
|
||||||
|
email: 'karimi@andishkade-foolad.ir',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sara-rezaei',
|
||||||
|
name: 'مهندس سارا رضایی',
|
||||||
|
role: 'پژوهشگر ارشد انرژی',
|
||||||
|
initial: 'ر',
|
||||||
|
bio: 'کارشناسی ارشد مهندسی انرژی از دانشگاه شریف با تمرکز بر بهینهسازی مصرف انرژی در صنایع سنگین. تجربه عملیاتی در حسابرسی انرژی کارخانههای فولادی در اصفهان، خراسان و هرمزگان دارد. پژوهشهای او در حوزه ESG و کربنزدایی صنعت فولاد ایران از مراجع اصلی این حوزه است.',
|
||||||
|
expertise: [
|
||||||
|
'انرژی صنعتی',
|
||||||
|
'ESG و پایداری',
|
||||||
|
'کربنزدایی',
|
||||||
|
'بهرهوری انرژی',
|
||||||
|
'مقررات زیستمحیطی',
|
||||||
|
],
|
||||||
|
reportCount: 29,
|
||||||
|
email: 'rezaei@andishkade-foolad.ir',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'reza-tavakoli',
|
||||||
|
name: 'مهندس رضا توکلی',
|
||||||
|
role: 'تحلیلگر بازار کامودیتی',
|
||||||
|
initial: 'ت',
|
||||||
|
bio: 'کارشناسی ارشد اقتصاد از دانشگاه تهران با تخصص در تحلیل بنیادی و تکنیکال بازارهای کامودیتی. سابقه فعالیت در کارگزاریهای بورس کالا و تهیه گزارشهای فوری برای معاملهگران فولاد را دارد. مسئول پوشش خبری و تحلیلی بازارهای بورس دالیان، LME و بازار داخلی است.',
|
||||||
|
expertise: [
|
||||||
|
'بورس کالا',
|
||||||
|
'تحلیل تکنیکال',
|
||||||
|
'بازارهای آتی',
|
||||||
|
'قیمتگذاری سنگآهن',
|
||||||
|
'بازار داخلی فولاد',
|
||||||
|
],
|
||||||
|
reportCount: 34,
|
||||||
|
email: 'tavakoli@andishkade-foolad.ir',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'maryam-hosseini',
|
||||||
|
name: 'دکتر مریم حسینی',
|
||||||
|
role: 'پژوهشگر ارشد صادرات',
|
||||||
|
initial: 'ح',
|
||||||
|
bio: 'دکتری بازاریابی بینالملل از دانشگاه الزهرا با تجربه گسترده در مطالعات بازارهای صادراتی آسیا، آفریقا و اروپای شرقی. پیش از این در اتاق بازرگانی ایران و کمیته فولاد آن مشغول به فعالیت بوده است. حوزه تخصصی او توسعه بازارهای صادراتی جدید و استراتژیهای نفوذ به بازار است.',
|
||||||
|
expertise: [
|
||||||
|
'بازاریابی بینالملل',
|
||||||
|
'توسعه صادرات',
|
||||||
|
'بازار آفریقا و آسیا',
|
||||||
|
'استراتژی بازار',
|
||||||
|
'مذاکرات تجاری',
|
||||||
|
],
|
||||||
|
reportCount: 22,
|
||||||
|
email: 'hosseini@andishkade-foolad.ir',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,239 @@
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════
|
||||||
|
VAZIR FONT FACES
|
||||||
|
═══════════════════════════════════════════════ */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazir';
|
||||||
|
src: url('/fonts/Vazir-Light.woff2') format('woff2');
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazir';
|
||||||
|
src: url('/fonts/Vazir-Regular.woff2') format('woff2');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazir';
|
||||||
|
src: url('/fonts/Vazir-Medium.woff2') format('woff2');
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazir';
|
||||||
|
src: url('/fonts/Vazir-Bold.woff2') format('woff2');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazir';
|
||||||
|
src: url('/fonts/Vazir-Black.woff2') format('woff2');
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════
|
||||||
|
DESIGN TOKENS
|
||||||
|
═══════════════════════════════════════════════ */
|
||||||
|
:root {
|
||||||
|
--paper: #f4efe7;
|
||||||
|
--paper-2: #ede8de;
|
||||||
|
--paper-3: #e4ddd0;
|
||||||
|
--ink: #1a1712;
|
||||||
|
--ink-2: #2e2a25;
|
||||||
|
--ink-3: #4a4540;
|
||||||
|
--ink-4: #6b6560;
|
||||||
|
--ink-5: #766e68;
|
||||||
|
--ink-6: #c8c2bc;
|
||||||
|
--rule-thin: rgba(26,23,18,0.12);
|
||||||
|
--red: #9b1c1c;
|
||||||
|
--red-2: #b52020;
|
||||||
|
--amber: #92400e;
|
||||||
|
--green-ink: #166534;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════
|
||||||
|
TYPOGRAPHY SYSTEM
|
||||||
|
|
||||||
|
Scale (clamp: mobile → desktop)
|
||||||
|
--fs-display hero headlines 32 → 72px
|
||||||
|
--fs-h1 page-level headings 26 → 52px
|
||||||
|
--fs-h2 section headings 20 → 38px
|
||||||
|
--fs-h3 card/sub headings 15 → 22px
|
||||||
|
--fs-h4 small headings 13 → 17px
|
||||||
|
--fs-body-lg lead paragraphs 14 → 16px
|
||||||
|
--fs-body body text 13 → 15px
|
||||||
|
--fs-sm secondary / meta 11 → 13px
|
||||||
|
--fs-xs labels / captions 9 → 11px
|
||||||
|
═══════════════════════════════════════════════ */
|
||||||
|
:root {
|
||||||
|
/* Font sizes */
|
||||||
|
--fs-display: clamp(32px, 5.5vw, 72px);
|
||||||
|
--fs-h1: clamp(26px, 3.8vw, 52px);
|
||||||
|
--fs-h2: clamp(20px, 2.8vw, 38px);
|
||||||
|
--fs-h3: clamp(15px, 1.5vw, 22px);
|
||||||
|
--fs-h4: clamp(13px, 1.1vw, 17px);
|
||||||
|
--fs-body-lg: clamp(14px, 1.1vw, 16px);
|
||||||
|
--fs-body: clamp(13px, 0.9vw, 15px);
|
||||||
|
--fs-sm: clamp(11px, 0.75vw, 13px);
|
||||||
|
--fs-xs: clamp(9px, 0.6vw, 11px);
|
||||||
|
|
||||||
|
/* Letter spacing */
|
||||||
|
--ls-display: -2.5px;
|
||||||
|
--ls-h1: -2px;
|
||||||
|
--ls-h2: -1px;
|
||||||
|
--ls-h3: -0.3px;
|
||||||
|
--ls-body: 0px;
|
||||||
|
--ls-label: 1.5px;
|
||||||
|
--ls-overline: 2.5px;
|
||||||
|
--ls-widest: 3px;
|
||||||
|
|
||||||
|
/* Line heights */
|
||||||
|
--lh-display: 1.04;
|
||||||
|
--lh-heading: 1.12;
|
||||||
|
--lh-subhead: 1.3;
|
||||||
|
--lh-body: 1.8;
|
||||||
|
--lh-relaxed: 1.85;
|
||||||
|
--lh-label: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Base element styles ──────────────────────── */
|
||||||
|
h1 {
|
||||||
|
font-size: var(--fs-h1);
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: var(--lh-heading);
|
||||||
|
letter-spacing: var(--ls-h1);
|
||||||
|
color: var(--ink);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: var(--fs-h2);
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: var(--lh-heading);
|
||||||
|
letter-spacing: var(--ls-h2);
|
||||||
|
color: var(--ink);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: var(--fs-h3);
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: var(--lh-subhead);
|
||||||
|
letter-spacing: var(--ls-h3);
|
||||||
|
color: var(--ink);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: var(--fs-h4);
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: var(--lh-subhead);
|
||||||
|
color: var(--ink);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: var(--fs-body);
|
||||||
|
font-weight: 400; /* was 300 — too thin on most screens */
|
||||||
|
line-height: var(--lh-body);
|
||||||
|
color: var(--ink-3);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════
|
||||||
|
BASE
|
||||||
|
═══════════════════════════════════════════════ */
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
html { scroll-behavior: smooth; direction: rtl; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Vazir', system-ui, sans-serif;
|
||||||
|
background: var(--paper);
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: var(--fs-body);
|
||||||
|
line-height: var(--lh-body);
|
||||||
|
overflow-x: hidden;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root { min-height: 100dvh; display: flex; flex-direction: column; }
|
||||||
|
|
||||||
|
::-webkit-scrollbar { width: 3px; height: 3px; }
|
||||||
|
::-webkit-scrollbar-thumb { background: var(--ink); }
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid var(--red);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection { background: var(--ink); color: var(--paper); }
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════
|
||||||
|
TYPOGRAPHY UTILITIES
|
||||||
|
═══════════════════════════════════════════════ */
|
||||||
|
/* Overline — small uppercase tracking label */
|
||||||
|
.t-overline {
|
||||||
|
font-size: var(--fs-xs);
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: var(--ls-overline);
|
||||||
|
text-transform: uppercase;
|
||||||
|
line-height: var(--lh-label);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Caption / meta text */
|
||||||
|
.t-caption {
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: var(--lh-label);
|
||||||
|
color: var(--ink-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lead paragraph */
|
||||||
|
.t-lead {
|
||||||
|
font-size: var(--fs-body-lg);
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: var(--lh-relaxed);
|
||||||
|
color: var(--ink-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════
|
||||||
|
TICKER ANIMATION (RTL direction)
|
||||||
|
═══════════════════════════════════════════════ */
|
||||||
|
@keyframes ticker-rtl {
|
||||||
|
0% { transform: translateX(50%); }
|
||||||
|
100% { transform: translateX(-50%); }
|
||||||
|
}
|
||||||
|
.ticker-track { animation: ticker-rtl 34s linear infinite; white-space: nowrap; }
|
||||||
|
@media (prefers-reduced-motion: reduce) { .ticker-track { animation: none; } }
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════
|
||||||
|
STAMP TILT
|
||||||
|
═══════════════════════════════════════════════ */
|
||||||
|
.stamp-tilt { transform: rotate(-1.5deg); }
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════
|
||||||
|
INFINITE SCROLL ANIMATION
|
||||||
|
═══════════════════════════════════════════════ */
|
||||||
|
@keyframes scroll {
|
||||||
|
to { transform: translateX(calc(-50% - 0.5rem)); }
|
||||||
|
}
|
||||||
|
.animate-scroll {
|
||||||
|
animation: scroll var(--animation-duration, 60s) var(--animation-direction, forwards) linear infinite;
|
||||||
|
}
|
||||||
|
.animate-scroll-risk {
|
||||||
|
animation: scroll 72s forwards linear infinite;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.animate-scroll { animation: none; }
|
||||||
|
.animate-scroll-risk { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════
|
||||||
|
PRINT
|
||||||
|
═══════════════════════════════════════════════ */
|
||||||
|
@media print { .no-print { display: none !important; } }
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
/**
|
||||||
|
* Shared utility functions — Persian/Jalali formatting & helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a number as Persian locale (e.g. 18400 → "۱۸,۴۰۰")
|
||||||
|
*/
|
||||||
|
export function formatPersianNumber(n: number): string {
|
||||||
|
return n.toLocaleString('fa-IR')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a Toman price (e.g. 850000 → "۸۵۰,۰۰۰ تومان")
|
||||||
|
*/
|
||||||
|
export function formatPrice(n: number): string {
|
||||||
|
if (n === 0) return 'رایگان'
|
||||||
|
return `${n.toLocaleString('fa-IR')} تومان`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a price compactly (e.g. 1200000 → "۱.۲ م.ت")
|
||||||
|
*/
|
||||||
|
export function formatPriceCompact(n: number): string {
|
||||||
|
if (n === 0) return 'رایگان'
|
||||||
|
if (n >= 1_000_000) {
|
||||||
|
const m = n / 1_000_000
|
||||||
|
return `${m.toLocaleString('fa-IR')} م.ت`
|
||||||
|
}
|
||||||
|
if (n >= 1_000) {
|
||||||
|
const k = n / 1_000
|
||||||
|
return `${k.toLocaleString('fa-IR')} ه.ت`
|
||||||
|
}
|
||||||
|
return `${n.toLocaleString('fa-IR')} تومان`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get CSS color for trend direction
|
||||||
|
*/
|
||||||
|
export function trendColor(trend: 'up' | 'down' | 'flat'): string {
|
||||||
|
if (trend === 'up') return 'var(--green-ink)'
|
||||||
|
if (trend === 'down') return 'var(--red)'
|
||||||
|
return 'var(--ink-4)'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trend arrow character
|
||||||
|
*/
|
||||||
|
export function trendArrow(trend: 'up' | 'down' | 'flat'): string {
|
||||||
|
if (trend === 'up') return '↑'
|
||||||
|
if (trend === 'down') return '↓'
|
||||||
|
return '—'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate string to max length with ellipsis
|
||||||
|
*/
|
||||||
|
export function truncate(str: string, max: number): string {
|
||||||
|
if (str.length <= max) return str
|
||||||
|
return str.slice(0, max).trimEnd() + '...'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get category dot color CSS variable
|
||||||
|
*/
|
||||||
|
export function categoryColor(category: string): string {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
'بازار جهانی': 'var(--ink)',
|
||||||
|
'سیاستگذاری': 'var(--ink-3)',
|
||||||
|
'انرژی و ESG': 'var(--amber)',
|
||||||
|
'صادرات': 'var(--ink-2)',
|
||||||
|
'تولید داخلی': 'var(--ink)',
|
||||||
|
'ریسک و بحران': 'var(--red)',
|
||||||
|
}
|
||||||
|
return map[category] ?? 'var(--ink-5)'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get report type label in Persian
|
||||||
|
*/
|
||||||
|
export function reportTypeLabel(type: string): string {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
quarterly: 'گزارش فصلی',
|
||||||
|
flash: 'Flash Report',
|
||||||
|
risk: 'گزارش ریسک',
|
||||||
|
free: 'رایگان',
|
||||||
|
special: 'گزارش ویژه',
|
||||||
|
}
|
||||||
|
return map[type] ?? type
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clamp a value between min and max
|
||||||
|
*/
|
||||||
|
export function clamp(val: number, min: number, max: number): number {
|
||||||
|
return Math.min(Math.max(val, min), max)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import { RouterProvider } from 'react-router-dom'
|
||||||
|
import './index.css'
|
||||||
|
import { router } from './app/router'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,430 @@
|
||||||
|
import { useState, useMemo } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { reports } from '@/data/reports'
|
||||||
|
import type { ReportCategory, ReportType } from '@/data/reports'
|
||||||
|
|
||||||
|
/* ─── constants ──────────────────────────────────────────── */
|
||||||
|
const TYPE_LABELS: Record<ReportType, string> = {
|
||||||
|
quarterly: 'فصلی',
|
||||||
|
flash: 'فلش',
|
||||||
|
risk: 'ریسک',
|
||||||
|
free: 'رایگان',
|
||||||
|
special: 'ویژه',
|
||||||
|
}
|
||||||
|
|
||||||
|
const TYPE_COLORS: Record<ReportType, string> = {
|
||||||
|
quarterly: '#166534',
|
||||||
|
flash: '#9b1c1c',
|
||||||
|
risk: '#92400e',
|
||||||
|
free: '#1d4ed8',
|
||||||
|
special: '#4a1d96',
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Persian month order for sorting date groups */
|
||||||
|
const MONTH_ORDER: Record<string, number> = {
|
||||||
|
'فروردین': 1, 'اردیبهشت': 2, 'خرداد': 3, 'تیر': 4,
|
||||||
|
'مرداد': 5, 'شهریور': 6, 'مهر': 7, 'آبان': 8,
|
||||||
|
'آذر': 9, 'دی': 10, 'بهمن': 11, 'اسفند': 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDateGroup(dateStr: string): number {
|
||||||
|
const parts = dateStr.trim().split(' ')
|
||||||
|
if (parts.length < 2) return 0
|
||||||
|
const month = MONTH_ORDER[parts[0]] ?? 0
|
||||||
|
const year = parseInt(parts[1]) || 0
|
||||||
|
return year * 100 + month
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── unique categories from data ───────────────────────── */
|
||||||
|
const ALL_CATEGORIES: (ReportCategory | 'همه')[] = [
|
||||||
|
'همه',
|
||||||
|
...Array.from(new Set(reports.map((r) => r.category))) as ReportCategory[],
|
||||||
|
]
|
||||||
|
|
||||||
|
/* ─── price formatter ────────────────────────────────────── */
|
||||||
|
function formatPrice(price: number): string {
|
||||||
|
if (price === 0) return 'رایگان'
|
||||||
|
return price.toLocaleString('fa-IR') + ' ت'
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── report row ─────────────────────────────────────────── */
|
||||||
|
function ReportRow({ report }: { report: (typeof reports)[0] }) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
const typeColor = TYPE_COLORS[report.type]
|
||||||
|
const typeLabel = TYPE_LABELS[report.type]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '64px 1fr 140px 52px 80px',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
background: hovered ? 'var(--paper-2)' : 'transparent',
|
||||||
|
transition: 'background 100ms',
|
||||||
|
minHeight: 40,
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
>
|
||||||
|
{/* type label */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderLeft: '1px solid var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '0.8px',
|
||||||
|
color: typeColor,
|
||||||
|
border: `1px solid ${typeColor}`,
|
||||||
|
padding: '2px 5px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{typeLabel}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* title */}
|
||||||
|
<div style={{ padding: '8px 16px', borderLeft: '1px solid var(--rule-thin)', overflow: 'hidden' }}>
|
||||||
|
<Link
|
||||||
|
to={`/reports/${report.id}`}
|
||||||
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: hovered ? 'var(--red)' : 'var(--ink)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
transition: 'color 100ms',
|
||||||
|
display: 'block',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.title}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* author */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderLeft: '1px solid var(--rule-thin)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
fontWeight: 400,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
display: 'block',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.author}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* pages */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '8px 10px',
|
||||||
|
borderLeft: '1px solid var(--rule-thin)',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-5)', fontWeight: 400 }}>
|
||||||
|
{report.pages.toLocaleString('fa-IR')} ص
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* price */}
|
||||||
|
<div style={{ padding: '8px 12px', textAlign: 'left' }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: report.isFree ? 'var(--green-ink)' : 'var(--ink-3)',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatPrice(report.price)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── main component ─────────────────────────────────────── */
|
||||||
|
export default function Archive() {
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
const [activeCategory, setActiveCategory] = useState<ReportCategory | 'همه'>('همه')
|
||||||
|
|
||||||
|
const filtered = useMemo(() => {
|
||||||
|
let result = [...reports]
|
||||||
|
|
||||||
|
if (activeCategory !== 'همه') {
|
||||||
|
result = result.filter((r) => r.category === activeCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search.trim()) {
|
||||||
|
const q = search.trim().toLowerCase()
|
||||||
|
result = result.filter(
|
||||||
|
(r) =>
|
||||||
|
r.title.toLowerCase().includes(q) ||
|
||||||
|
r.author.toLowerCase().includes(q) ||
|
||||||
|
r.category.toLowerCase().includes(q) ||
|
||||||
|
r.tags.some((t) => t.toLowerCase().includes(q))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}, [search, activeCategory])
|
||||||
|
|
||||||
|
/* group by publishDate, sorted newest first */
|
||||||
|
const groups = useMemo(() => {
|
||||||
|
const map = new Map<string, (typeof reports)[0][]>()
|
||||||
|
for (const report of filtered) {
|
||||||
|
const key = report.publishDate
|
||||||
|
if (!map.has(key)) map.set(key, [])
|
||||||
|
map.get(key)!.push(report)
|
||||||
|
}
|
||||||
|
return Array.from(map.entries()).sort(
|
||||||
|
([a], [b]) => parseDateGroup(b) - parseDateGroup(a)
|
||||||
|
)
|
||||||
|
}, [filtered])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: 1280,
|
||||||
|
margin: '0 auto',
|
||||||
|
padding: '64px 48px',
|
||||||
|
direction: 'rtl',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* ─── Page header ───────────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'stretch',
|
||||||
|
borderBottom: '3px solid var(--ink)',
|
||||||
|
marginBottom: 36,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: 'var(--ink)',
|
||||||
|
padding: '14px 28px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 14,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--paper)',
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
آرشیو گزارشها
|
||||||
|
</h1>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
padding: '2px 10px',
|
||||||
|
letterSpacing: '0.3px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{reports.length.toLocaleString('fa-IR')} گزارش
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Search ────────────────────────────────────────── */}
|
||||||
|
<div style={{ marginBottom: 20 }}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="جستجو در آرشیو..."
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
borderRadius: 0,
|
||||||
|
padding: '12px 18px',
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
background: 'transparent',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
outline: 'none',
|
||||||
|
direction: 'rtl',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Category filter chips ─────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 6,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
marginBottom: 40,
|
||||||
|
paddingBottom: 20,
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ALL_CATEGORIES.map((cat) => {
|
||||||
|
const isActive = activeCategory === cat
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={cat}
|
||||||
|
onClick={() => setActiveCategory(cat as ReportCategory | 'همه')}
|
||||||
|
style={{
|
||||||
|
padding: '6px 16px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: isActive ? 700 : 500,
|
||||||
|
border: isActive ? '1px solid var(--ink)' : '1px solid var(--rule-thin)',
|
||||||
|
background: isActive ? 'var(--ink)' : 'transparent',
|
||||||
|
color: isActive ? 'var(--paper)' : 'var(--ink-4)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
transition: 'all 120ms',
|
||||||
|
outline: 'none',
|
||||||
|
borderRadius: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cat}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Date groups ───────────────────────────────────── */}
|
||||||
|
{groups.length > 0 ? (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
|
||||||
|
{groups.map(([date, groupReports]) => (
|
||||||
|
<section key={date}>
|
||||||
|
{/* section divider */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 14,
|
||||||
|
marginBottom: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '2px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{date}
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
height: 1,
|
||||||
|
background: 'var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span style={{ fontSize: 9, color: 'var(--ink-6)', whiteSpace: 'nowrap' }}>
|
||||||
|
{groupReports.length.toLocaleString('fa-IR')} گزارش
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* column headers */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '64px 1fr 140px 52px 80px',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderTop: '2px solid var(--ink)',
|
||||||
|
borderBottom: '1px solid var(--ink)',
|
||||||
|
marginTop: 8,
|
||||||
|
background: 'var(--paper-2)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[
|
||||||
|
{ label: 'نوع', style: { padding: '6px 12px', borderLeft: '1px solid var(--rule-thin)' } },
|
||||||
|
{ label: 'عنوان گزارش', style: { padding: '6px 16px', borderLeft: '1px solid var(--rule-thin)' } },
|
||||||
|
{ label: 'نویسنده', style: { padding: '6px 12px', borderLeft: '1px solid var(--rule-thin)' } },
|
||||||
|
{ label: 'صفحه', style: { padding: '6px 10px', borderLeft: '1px solid var(--rule-thin)', textAlign: 'center' as const } },
|
||||||
|
{ label: 'قیمت', style: { padding: '6px 12px', textAlign: 'left' as const } },
|
||||||
|
].map((col) => (
|
||||||
|
<div key={col.label} style={col.style}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '1.2px',
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{col.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* report rows */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
borderTop: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{groupReports.map((report) => (
|
||||||
|
<ReportRow key={report.id} report={report} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '80px 0',
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p style={{ fontSize: 15, fontWeight: 300 }}>نتیجهای یافت نشد</p>
|
||||||
|
<p style={{ fontSize: 13, marginTop: 8, color: 'var(--ink-6)' }}>
|
||||||
|
عبارت جستجو یا فیلتر دستهبندی را تغییر دهید
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* responsive: hide author/pages columns on small screens */}
|
||||||
|
<style>{`
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.archive-row {
|
||||||
|
grid-template-columns: 56px 1fr 70px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { events } from '@/data/events'
|
||||||
|
import type { EventType } from '@/data/events'
|
||||||
|
|
||||||
|
/* ─── constants ──────────────────────────────────────────── */
|
||||||
|
type FilterValue = 'همه' | EventType
|
||||||
|
|
||||||
|
const FILTERS: { label: string; value: FilterValue }[] = [
|
||||||
|
{ label: 'همه', value: 'همه' },
|
||||||
|
{ label: 'کنگره', value: 'conference' },
|
||||||
|
{ label: 'نمایشگاه', value: 'exhibition' },
|
||||||
|
{ label: 'سمینار', value: 'seminar' },
|
||||||
|
{ label: 'جهانی', value: 'international' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const TYPE_LABELS: Record<EventType, string> = {
|
||||||
|
conference: 'کنگره',
|
||||||
|
exhibition: 'نمایشگاه',
|
||||||
|
seminar: 'سمینار',
|
||||||
|
international: 'جهانی',
|
||||||
|
}
|
||||||
|
|
||||||
|
const TYPE_BORDER_COLORS: Record<EventType, string> = {
|
||||||
|
conference: 'var(--ink)',
|
||||||
|
exhibition: '#6b4a00',
|
||||||
|
seminar: '#1a4a2a',
|
||||||
|
international: 'var(--red)',
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── event card ─────────────────────────────────────────── */
|
||||||
|
function EventCard({ event }: { event: (typeof events)[0] }) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
const borderColor = TYPE_BORDER_COLORS[event.type]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
background: hovered ? 'var(--paper-2)' : 'var(--paper)',
|
||||||
|
transition: 'background 140ms',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 0,
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
>
|
||||||
|
{/* date + type header */}
|
||||||
|
<div style={{ display: 'flex', borderBottom: '2px solid var(--ink)' }}>
|
||||||
|
{/* date block */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '18px 24px',
|
||||||
|
borderLeft: '1px solid var(--rule-thin)',
|
||||||
|
minWidth: 100,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 52,
|
||||||
|
fontWeight: 900,
|
||||||
|
lineHeight: 1,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
letterSpacing: '-2px',
|
||||||
|
display: 'block',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event.date.toLocaleString('fa-IR')}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
marginTop: 4,
|
||||||
|
display: 'block',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event.month} {event.year.toLocaleString('fa-IR')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* type badge + title area top */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: '18px 20px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '1.2px',
|
||||||
|
color: borderColor,
|
||||||
|
border: `1px solid ${borderColor}`,
|
||||||
|
padding: '2px 8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{TYPE_LABELS[event.type]}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<h3
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 800,
|
||||||
|
lineHeight: 1.45,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
letterSpacing: '-0.3px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event.title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* body */}
|
||||||
|
<div style={{ padding: '16px 20px', flex: 1, display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||||
|
{/* location */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: 4,
|
||||||
|
height: 4,
|
||||||
|
background: 'var(--ink-4)',
|
||||||
|
flexShrink: 0,
|
||||||
|
display: 'inline-block',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span style={{ fontSize: 12, color: 'var(--ink-4)', fontWeight: 400 }}>
|
||||||
|
{event.location}، {event.city}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* description */}
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 300,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
lineHeight: 1.8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* footer */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderTop: '1px solid var(--rule-thin)',
|
||||||
|
padding: '12px 20px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event.registrationOpen ? (
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'var(--red)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
letterSpacing: '-0.2px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ثبتنام
|
||||||
|
<span style={{ fontSize: 15 }}>←</span>
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
بهزودی
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── main component ─────────────────────────────────────── */
|
||||||
|
export default function Events() {
|
||||||
|
const [activeFilter, setActiveFilter] = useState<FilterValue>('همه')
|
||||||
|
|
||||||
|
const filtered = activeFilter === 'همه'
|
||||||
|
? events
|
||||||
|
: events.filter((e) => e.type === activeFilter)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: 1280,
|
||||||
|
margin: '0 auto',
|
||||||
|
padding: '64px 48px',
|
||||||
|
direction: 'rtl',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* ─── Page title banner ─────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'stretch',
|
||||||
|
borderBottom: '3px solid var(--ink)',
|
||||||
|
marginBottom: 36,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: 'var(--ink)',
|
||||||
|
padding: '14px 28px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--paper)',
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
رویدادهای پیشرو
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '14px 0 14px 4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
۱۴۰۳–۱۴۰۴
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Filter tabs ───────────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 0,
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
marginBottom: 40,
|
||||||
|
overflowX: 'auto',
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{FILTERS.map((f) => {
|
||||||
|
const isActive = activeFilter === f.value
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={f.value}
|
||||||
|
onClick={() => setActiveFilter(f.value)}
|
||||||
|
style={{
|
||||||
|
padding: '10px 24px',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: isActive ? 700 : 500,
|
||||||
|
border: 'none',
|
||||||
|
borderBottom: isActive ? '2px solid transparent' : '2px solid transparent',
|
||||||
|
background: isActive ? 'var(--ink)' : 'transparent',
|
||||||
|
color: isActive ? 'var(--paper)' : 'var(--ink-4)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
flexShrink: 0,
|
||||||
|
transition: 'all 120ms',
|
||||||
|
position: 'relative',
|
||||||
|
marginBottom: -1,
|
||||||
|
outline: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{f.label}
|
||||||
|
{f.value !== 'همه' && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginRight: 6,
|
||||||
|
fontSize: 10,
|
||||||
|
color: isActive ? 'rgba(244,239,231,0.6)' : 'var(--ink-6)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
({events.filter((e) => e.type === f.value).length})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Events grid ───────────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
className="events-grid"
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||||
|
gap: 1,
|
||||||
|
background: 'var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{filtered.map((event) => (
|
||||||
|
<EventCard key={event.id} event={event} />
|
||||||
|
))}
|
||||||
|
{/* fill odd trailing cell */}
|
||||||
|
{filtered.length % 2 !== 0 && (
|
||||||
|
<div style={{ background: 'var(--paper)' }} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{filtered.length === 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '80px 0',
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p style={{ fontSize: 15, fontWeight: 300 }}>رویدادی در این دستهبندی یافت نشد</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* responsive */}
|
||||||
|
<style>{`
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.events-grid {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import HeroSection from './sections/HeroSection'
|
||||||
|
import SnapshotBar from './sections/SnapshotBar'
|
||||||
|
import GlobalScannerSection from './sections/GlobalScannerSection'
|
||||||
|
import ReportsCarouselSection from './sections/ReportsCarouselSection'
|
||||||
|
import LatestNewsBentoSection from './sections/LatestNewsBentoSection'
|
||||||
|
import EventsSection from './sections/EventsSection'
|
||||||
|
import RiskSection from './sections/RiskSection'
|
||||||
|
import PlansSection from './sections/PlansSection'
|
||||||
|
import TeamSection from './sections/TeamSection'
|
||||||
|
import NewsletterSection from './sections/NewsletterSection'
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<HeroSection />
|
||||||
|
<SnapshotBar />
|
||||||
|
<GlobalScannerSection />
|
||||||
|
<ReportsCarouselSection />
|
||||||
|
<LatestNewsBentoSection />
|
||||||
|
<EventsSection />
|
||||||
|
<RiskSection />
|
||||||
|
<TeamSection />
|
||||||
|
<PlansSection />
|
||||||
|
<NewsletterSection />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { events } from '@/data/events'
|
||||||
|
import type { Event } from '@/data/events'
|
||||||
|
|
||||||
|
const typeLabel: Record<Event['type'], string> = {
|
||||||
|
conference: 'کنگره',
|
||||||
|
exhibition: 'نمایشگاه',
|
||||||
|
seminar: 'سمینار',
|
||||||
|
international: 'جهانی',
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeBorderColor: Record<Event['type'], string> = {
|
||||||
|
conference: 'var(--ink)',
|
||||||
|
exhibition: '#6b4a00',
|
||||||
|
seminar: '#1a4a2a',
|
||||||
|
international: 'var(--red)',
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeTextColor: Record<Event['type'], string> = {
|
||||||
|
conference: 'var(--ink)',
|
||||||
|
exhibition: '#6b4a00',
|
||||||
|
seminar: '#1a4a2a',
|
||||||
|
international: 'var(--red)',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EventsSection() {
|
||||||
|
const displayed = events.slice(0, 4)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section style={{ borderBottom: '2px solid var(--ink)' }}>
|
||||||
|
<div className="px-12 py-20 max-md:px-5 max-md:py-14">
|
||||||
|
{/* Banner */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', borderBottom: '1px solid var(--rule-thin)', marginBottom: 0 }}>
|
||||||
|
<div style={{ background: 'var(--ink)', color: 'var(--paper)', padding: '8px 20px', fontSize: '9px', fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase' }}>رویدادهای پیشرو</div>
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
<Link to="/events" style={{ fontSize: '10px', fontWeight: 700, letterSpacing: '1.5px', textTransform: 'uppercase', color: 'var(--ink-4)', textDecoration: 'none', padding: '8px 0' }}>تقویم کامل ←</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 4-col grid */}
|
||||||
|
<div
|
||||||
|
style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)' }}
|
||||||
|
className="max-md:grid-cols-2 max-sm:grid-cols-1"
|
||||||
|
>
|
||||||
|
{displayed.map((event, idx) => (
|
||||||
|
<div
|
||||||
|
key={event.id}
|
||||||
|
style={{
|
||||||
|
padding: '32px 28px',
|
||||||
|
borderRight: idx === 0 ? 'none' : '1px solid var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
className="max-md:border-b max-md:border-[var(--rule-thin)] max-md:last:border-b-0"
|
||||||
|
>
|
||||||
|
{/* Date block */}
|
||||||
|
<div style={{ borderBottom: '2px solid var(--ink)', paddingBottom: 12, marginBottom: 16 }}>
|
||||||
|
<div style={{ fontSize: 52, fontWeight: 900, letterSpacing: '-2px', lineHeight: 1, color: 'var(--ink)' }}>
|
||||||
|
{event.date.toLocaleString('fa-IR')}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: 'var(--ink-4)', marginTop: 2 }}>
|
||||||
|
{event.month} {event.year.toLocaleString('fa-IR')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Type pill */}
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
border: `1px solid ${typeBorderColor[event.type]}`,
|
||||||
|
color: typeTextColor[event.type],
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '1px',
|
||||||
|
padding: '3px 8px',
|
||||||
|
lineHeight: 1.4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{typeLabel[event.type]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<div style={{ fontSize: 14, fontWeight: 700, color: 'var(--ink)', lineHeight: 1.4, marginBottom: 6 }}>{event.title}</div>
|
||||||
|
|
||||||
|
{/* Location */}
|
||||||
|
<div style={{ fontSize: 11, color: 'var(--ink-5)', marginBottom: 14 }}>{event.location} · {event.city}</div>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
{event.registrationOpen ? (
|
||||||
|
<Link to="#" style={{ fontSize: 10, fontWeight: 700, letterSpacing: '2px', textTransform: 'uppercase', color: 'var(--red)', textDecoration: 'none', cursor: 'pointer' }}>
|
||||||
|
ثبتنام ←
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span style={{ fontSize: 10, color: 'var(--ink-5)', letterSpacing: '1px', textTransform: 'uppercase', fontWeight: 700 }}>
|
||||||
|
بهزودی
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import WorldMap from '@/components/ui/world-map'
|
||||||
|
|
||||||
|
/* ─── Steel trade routes ─────────────────────────────────────────────
|
||||||
|
Arcs show geopolitically relevant steel flows affecting Iran's market
|
||||||
|
──────────────────────────────────────────────────────────────────── */
|
||||||
|
const ROUTES = [
|
||||||
|
// ── Iran's trade flows (2 routes) ───────────────────────────────
|
||||||
|
// Iran → Iraq (main export market)
|
||||||
|
{ start: { lat: 35.69, lng: 51.39 }, end: { lat: 33.31, lng: 44.36 } },
|
||||||
|
// Iran → Central Asia (Uzbekistan)
|
||||||
|
{ start: { lat: 35.69, lng: 51.39 }, end: { lat: 41.30, lng: 69.24 } },
|
||||||
|
|
||||||
|
// ── China's global reach (3 routes) ─────────────────────────────
|
||||||
|
// Shanghai → Rotterdam (European market dumping)
|
||||||
|
{ start: { lat: 31.23, lng: 121.47 }, end: { lat: 51.92, lng: 4.47 } },
|
||||||
|
// Shanghai → Lagos (China–Africa infrastructure push)
|
||||||
|
{ start: { lat: 31.23, lng: 121.47 }, end: { lat: 6.45, lng: 3.39 } },
|
||||||
|
// Shanghai → Singapore (SE Asia dominance)
|
||||||
|
{ start: { lat: 31.23, lng: 121.47 }, end: { lat: 1.35, lng: 103.82 } },
|
||||||
|
|
||||||
|
// ── Russia flows (2 routes) ──────────────────────────────────────
|
||||||
|
// Moscow → Beijing (raw material / billet supply)
|
||||||
|
{ start: { lat: 55.75, lng: 37.62 }, end: { lat: 39.91, lng: 116.39 } },
|
||||||
|
// Moscow → Tehran (billet supply to Iran)
|
||||||
|
{ start: { lat: 55.75, lng: 37.62 }, end: { lat: 35.69, lng: 51.39 } },
|
||||||
|
|
||||||
|
// ── India's competing routes (2 routes) ──────────────────────────
|
||||||
|
// Mumbai → Dubai (competing with Iran in Gulf)
|
||||||
|
{ start: { lat: 19.07, lng: 72.88 }, end: { lat: 25.20, lng: 55.27 } },
|
||||||
|
// Mumbai → Mombasa (East Africa — directly competes with Iran)
|
||||||
|
{ start: { lat: 19.07, lng: 72.88 }, end: { lat: -4.05, lng: 39.67 } },
|
||||||
|
|
||||||
|
// ── Americas → Asia (iron ore flows) ────────────────────────────
|
||||||
|
// São Paulo → Shanghai (Brazilian iron ore)
|
||||||
|
{ start: { lat: -23.55, lng: -46.63 }, end: { lat: 31.23, lng: 121.47 } },
|
||||||
|
|
||||||
|
// ── South Korea → SE Asia ───────────────────────────────────────
|
||||||
|
{ start: { lat: 37.56, lng: 126.97 }, end: { lat: 1.35, lng: 103.82 } },
|
||||||
|
|
||||||
|
// ── Turkey → Europe ──────────────────────────────────────────────
|
||||||
|
// Istanbul → Berlin (Turkey as EU steel supplier)
|
||||||
|
{ start: { lat: 41.01, lng: 28.97 }, end: { lat: 52.52, lng: 13.40 } },
|
||||||
|
|
||||||
|
// ── Australia → China (iron ore) ────────────────────────────────
|
||||||
|
{ start: { lat: -33.87, lng: 151.21 }, end: { lat: 31.23, lng: 121.47 } },
|
||||||
|
]
|
||||||
|
|
||||||
|
/* ─── Section ────────────────────────────────────────── */
|
||||||
|
export default function GlobalScannerSection() {
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
style={{
|
||||||
|
background: 'var(--paper)',
|
||||||
|
borderBottom: '2px solid var(--ink)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* ── Text header ────────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{ textAlign: 'center' }}
|
||||||
|
className="px-12 pt-[72px] pb-12 max-md:px-5 max-md:pt-10 max-md:pb-8"
|
||||||
|
>
|
||||||
|
{/* Overline */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 10,
|
||||||
|
marginBottom: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: 20, height: 2, background: 'var(--red)' }} />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '3px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
color: 'var(--red)',
|
||||||
|
}}>
|
||||||
|
اسکنر جهانی · پوشش ۲۴ ساعته
|
||||||
|
</span>
|
||||||
|
<div style={{ width: 20, height: 2, background: 'var(--red)' }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Headline */}
|
||||||
|
<h2 style={{
|
||||||
|
fontSize: 'clamp(28px, 3.5vw, 52px)',
|
||||||
|
fontWeight: 900,
|
||||||
|
lineHeight: 1.1,
|
||||||
|
letterSpacing: '-1.5px',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
marginBottom: 16,
|
||||||
|
}}>
|
||||||
|
تمام رویدادهای فولادی دنیا{' '}
|
||||||
|
<motion.span
|
||||||
|
style={{ color: 'var(--ink-6)', display: 'inline' }}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
whileInView={{ opacity: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.8, delay: 0.3 }}
|
||||||
|
>
|
||||||
|
را رصد میکنیم
|
||||||
|
</motion.span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* Subhead */}
|
||||||
|
<p style={{
|
||||||
|
fontSize: 15,
|
||||||
|
lineHeight: 1.85,
|
||||||
|
fontWeight: 400,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
maxWidth: 600,
|
||||||
|
margin: '0 auto',
|
||||||
|
}}>
|
||||||
|
از بورس دالیان چین تا گمرک بصره عراق، از تصمیمات کمیته فولاد OECD
|
||||||
|
تا ظرفیتسازیهای هند — هر سیگنالی که بر رقابتپذیری ایران اثر
|
||||||
|
میگذارد، پیش از بقیه به دست شما میرسد.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── World map (full width, no side padding) ─── */}
|
||||||
|
<div className="px-6 max-md:px-0">
|
||||||
|
<WorldMap
|
||||||
|
dots={ROUTES}
|
||||||
|
lineColor="#9b1c1c"
|
||||||
|
dotColor="#c8c2bc"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── Stats strip ──────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderTop: '1px solid var(--rule-thin)',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||||
|
}}
|
||||||
|
className="max-md:grid-cols-2"
|
||||||
|
>
|
||||||
|
{[
|
||||||
|
{ n: '۱۸۰+', label: 'بازار رصدشده', sub: 'در ۵ قاره' },
|
||||||
|
{ n: '۷/۲۴', label: 'پایش لحظهای', sub: 'قیمت و حجم' },
|
||||||
|
{ n: '۴۰+', label: 'منبع داده', sub: 'بورسها و گمرکها' },
|
||||||
|
{ n: '۱۵', label: 'کشور رقیب', sub: 'زیر ذرهبین' },
|
||||||
|
].map((s, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={i}
|
||||||
|
initial={{ opacity: 0, y: 12 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.4, delay: i * 0.08 }}
|
||||||
|
style={{
|
||||||
|
padding: '28px 24px',
|
||||||
|
borderRight: i > 0 ? '1px solid var(--rule-thin)' : 'none',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
className="max-md:border-r-0 max-md:border-b max-md:border-[var(--rule-thin)]"
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
letterSpacing: '-1px',
|
||||||
|
lineHeight: 1,
|
||||||
|
marginBottom: 4,
|
||||||
|
}}>
|
||||||
|
{s.n}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--ink-2)', marginBottom: 2 }}>
|
||||||
|
{s.label}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 10, color: 'var(--ink-5)' }}>
|
||||||
|
{s.sub}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,315 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { reports } from '@/data/reports'
|
||||||
|
|
||||||
|
const featured = reports.find(r => r.featured)!
|
||||||
|
|
||||||
|
/* Dramatic steel mill / industrial image */
|
||||||
|
const BG_IMAGE =
|
||||||
|
'https://images.unsplash.com/photo-1504328345606-18bbc8c9d7d1?q=85&w=2400&auto=format&fit=crop'
|
||||||
|
|
||||||
|
const STATS = [
|
||||||
|
{ n: '۴', label: 'سناریو محتمل' },
|
||||||
|
{ n: '۱۵', label: 'کشور رقیب' },
|
||||||
|
{ n: '۸۴', label: 'صفحه تحلیلی' },
|
||||||
|
{ n: '۱۲', label: 'توصیه سیاستی' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function HeroSection() {
|
||||||
|
const [buyHov, setBuyHov] = useState(false)
|
||||||
|
const [freeHov, setFreeHov] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
height: 'min(88vh, 780px)',
|
||||||
|
minHeight: 540,
|
||||||
|
overflow: 'hidden',
|
||||||
|
borderBottom: '3px solid var(--ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* ── Background image ── */}
|
||||||
|
<img
|
||||||
|
src={BG_IMAGE}
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: 'cover',
|
||||||
|
objectPosition: 'center 40%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* ── Gradient overlay — dark vignette from bottom-right ── */}
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
background: [
|
||||||
|
'linear-gradient(to top, rgba(10,8,6,0.96) 0%, rgba(10,8,6,0.5) 45%, transparent 70%)',
|
||||||
|
'linear-gradient(to left, rgba(10,8,6,0.7) 0%, rgba(10,8,6,0.15) 55%, transparent 80%)',
|
||||||
|
].join(', '),
|
||||||
|
}} />
|
||||||
|
|
||||||
|
{/* ── Top-right badge ── */}
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 32,
|
||||||
|
left: 48,
|
||||||
|
display: 'flex',
|
||||||
|
gap: 10,
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
className="max-md:left-5 max-md:top-5"
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
background: 'var(--red)',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '2.5px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
padding: '5px 14px',
|
||||||
|
}}>
|
||||||
|
گزارش ویژه
|
||||||
|
</div>
|
||||||
|
<span style={{ fontSize: 10, color: 'rgba(255,255,255,0.45)', letterSpacing: '1px' }}>
|
||||||
|
بهمن ۱۴۰۳
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── Main content — anchored bottom ── */}
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr 240px',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
gap: 48,
|
||||||
|
}}
|
||||||
|
className="px-14 pb-[52px] max-md:grid-cols-1 max-md:px-5 max-md:pb-8"
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* ── Left: headline + meta ── */}
|
||||||
|
<div>
|
||||||
|
{/* Overline */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 20 }}
|
||||||
|
>
|
||||||
|
<div style={{ width: 24, height: 2, background: 'var(--red)', flexShrink: 0 }} />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '2.5px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
color: 'rgba(255,255,255,0.5)',
|
||||||
|
}}>
|
||||||
|
گزارش راهبردی · تحلیل سناریو
|
||||||
|
</span>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Headline */}
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.1 }}
|
||||||
|
style={{
|
||||||
|
fontSize: 'clamp(36px, 5vw, 72px)',
|
||||||
|
fontWeight: 900,
|
||||||
|
lineHeight: 1.04,
|
||||||
|
letterSpacing: '-2.5px',
|
||||||
|
color: 'white',
|
||||||
|
marginBottom: 24,
|
||||||
|
maxWidth: 700,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{featured.title.split(':').map((part, i) => (
|
||||||
|
<span key={i}>
|
||||||
|
{i === 0 ? part + ':' : (
|
||||||
|
<span style={{ color: 'rgba(255,255,255,0.45)' }}>{part}</span>
|
||||||
|
)}
|
||||||
|
{i === 0 && <br />}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
{/* Summary — one line */}
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.25 }}
|
||||||
|
style={{
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: 1.8,
|
||||||
|
fontWeight: 400,
|
||||||
|
color: 'rgba(255,255,255,0.6)',
|
||||||
|
maxWidth: 580,
|
||||||
|
marginBottom: 32,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
مدلسازی کمی بر اساس دادههای تجارت ۱۵ کشور رقیب — از بدبینانه تا خوشبینانه،
|
||||||
|
با توصیههای سیاستی مستقیم برای وزارت صمت و سازمان بنادر.
|
||||||
|
</motion.p>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div style={{ width: '100%', height: 1, background: 'rgba(255,255,255,0.12)', marginBottom: 24 }} />
|
||||||
|
|
||||||
|
{/* Author + Price + CTA in one bar */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 8 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.4, delay: 0.35 }}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 24,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Author */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||||
|
<div style={{
|
||||||
|
width: 36, height: 36,
|
||||||
|
background: 'var(--red)',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 14, fontWeight: 900,
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}>
|
||||||
|
{featured.authorInitial}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 12, fontWeight: 700, color: 'white', lineHeight: 1.2 }}>
|
||||||
|
{featured.author}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 10, color: 'rgba(255,255,255,0.4)', lineHeight: 1.3 }}>
|
||||||
|
{featured.authorRole}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Separator */}
|
||||||
|
<div style={{ width: 1, height: 32, background: 'rgba(255,255,255,0.15)' }} />
|
||||||
|
|
||||||
|
{/* Price */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'baseline', gap: 5 }}>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: 900,
|
||||||
|
letterSpacing: '-1px',
|
||||||
|
color: 'white',
|
||||||
|
lineHeight: 1,
|
||||||
|
}}>
|
||||||
|
{featured.price.toLocaleString('fa-IR')}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: 11, color: 'rgba(255,255,255,0.4)' }}>تومان</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Buttons */}
|
||||||
|
<div style={{ display: 'flex', gap: 10 }}>
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setBuyHov(true)}
|
||||||
|
onMouseLeave={() => setBuyHov(false)}
|
||||||
|
style={{
|
||||||
|
background: buyHov ? 'var(--paper-2)' : 'var(--paper)',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
border: 'none',
|
||||||
|
padding: '11px 26px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 700,
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
cursor: 'pointer',
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
transition: 'background 160ms',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
خرید گزارش
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setFreeHov(true)}
|
||||||
|
onMouseLeave={() => setFreeHov(false)}
|
||||||
|
style={{
|
||||||
|
background: freeHov ? 'rgba(255,255,255,0.1)' : 'transparent',
|
||||||
|
color: 'white',
|
||||||
|
border: '1px solid rgba(255,255,255,0.3)',
|
||||||
|
padding: '11px 26px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 400,
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
cursor: 'pointer',
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
transition: 'background 160ms',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
دانلود خلاصه رایگان
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── Right: floating stats panel ── */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: -16 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.45 }}
|
||||||
|
style={{
|
||||||
|
background: 'rgba(255,255,255,0.06)',
|
||||||
|
backdropFilter: 'blur(12px)',
|
||||||
|
WebkitBackdropFilter: 'blur(12px)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.1)',
|
||||||
|
padding: '20px',
|
||||||
|
marginBottom: 2,
|
||||||
|
}}
|
||||||
|
className="max-md:hidden"
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '2px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
color: 'rgba(255,255,255,0.35)',
|
||||||
|
marginBottom: 16,
|
||||||
|
paddingBottom: 10,
|
||||||
|
borderBottom: '1px solid rgba(255,255,255,0.1)',
|
||||||
|
}}>
|
||||||
|
در این گزارش
|
||||||
|
</div>
|
||||||
|
{STATS.map((s, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
padding: '10px 0',
|
||||||
|
borderBottom: i < STATS.length - 1 ? '1px solid rgba(255,255,255,0.07)' : 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: 11, color: 'rgba(255,255,255,0.5)', fontWeight: 300 }}>
|
||||||
|
{s.label}
|
||||||
|
</span>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: 900,
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
color: 'white',
|
||||||
|
lineHeight: 1,
|
||||||
|
}}>
|
||||||
|
{s.n}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,240 @@
|
||||||
|
import type { CSSProperties } from 'react'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { reports } from '@/data/reports'
|
||||||
|
|
||||||
|
const featured = reports.find(r => r.featured)!
|
||||||
|
const flashReport = reports.find(r => r.type === 'flash')!
|
||||||
|
|
||||||
|
/* ─── Price snapshot ────────────────────────────────────── */
|
||||||
|
const PRICES = [
|
||||||
|
{ label: 'HRC آسیا', value: '۵۹۰', delta: '+۳.۲٪', up: true },
|
||||||
|
{ label: 'میلگرد ایران', value: '۲۱,۸۰۰', delta: '+۰.۸٪', up: true },
|
||||||
|
{ label: 'سنگآهن دالیان', value: '۱۰۸', delta: '+۱۸٪', up: true },
|
||||||
|
{ label: 'HRC اروپا', value: '۶۲۴', delta: '−۱.۴٪', up: false },
|
||||||
|
]
|
||||||
|
|
||||||
|
/* ─── Shared card style (NO gridColumn here — set via className) */
|
||||||
|
const CARD: CSSProperties = {
|
||||||
|
background: 'var(--paper)',
|
||||||
|
padding: '28px 28px 24px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
direction: 'rtl',
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
position: 'relative',
|
||||||
|
}
|
||||||
|
|
||||||
|
function Tag({ text, color = 'var(--red)' }: { text: string; color?: string }) {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '2.5px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
color,
|
||||||
|
marginBottom: 12,
|
||||||
|
}}>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Accent({ color = 'var(--red)' }: { color?: string }) {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute', top: 0, right: 0, left: 0,
|
||||||
|
height: 3, background: color,
|
||||||
|
}} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Section ────────────────────────────────────────────── */
|
||||||
|
export default function LatestNewsBentoSection() {
|
||||||
|
return (
|
||||||
|
<section style={{ background: 'var(--paper-2)', borderBottom: '2px solid var(--ink)' }}>
|
||||||
|
|
||||||
|
{/* ── Header ── */}
|
||||||
|
<div className="px-12 pt-16 max-md:px-5 max-md:pt-10">
|
||||||
|
<div style={{
|
||||||
|
display: 'flex', alignItems: 'center', gap: 16,
|
||||||
|
marginBottom: 32, paddingBottom: 20, borderBottom: '2px solid var(--ink)',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
background: 'var(--ink)', color: 'var(--paper)',
|
||||||
|
fontSize: 9, fontWeight: 700, letterSpacing: '3px',
|
||||||
|
textTransform: 'uppercase', padding: '6px 16px',
|
||||||
|
}}>
|
||||||
|
آخرین اخبار
|
||||||
|
</div>
|
||||||
|
<h2 style={{ fontSize: 'clamp(16px,2vw,22px)', fontWeight: 900, letterSpacing: '-0.5px' }}>
|
||||||
|
رصد روزانه بازار و صنعت
|
||||||
|
</h2>
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
<span style={{ fontSize: 10, fontWeight: 500, color: 'var(--ink-5)' }}>بهمن ۱۴۰۳</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── Bento grid ── */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 16 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
className="px-12 pb-14 max-md:px-5 max-md:pb-10"
|
||||||
|
>
|
||||||
|
{/*
|
||||||
|
3-col grid on desktop, 1-col on mobile.
|
||||||
|
Items ① and ⑥ use col-span-1 md:col-span-2.
|
||||||
|
NO gridColumn in inline style — inline style overrides Tailwind.
|
||||||
|
*/}
|
||||||
|
<div
|
||||||
|
className="grid grid-cols-1 md:grid-cols-3"
|
||||||
|
style={{ gap: 1, background: 'var(--rule-thin)' }}
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* ① Featured report — col-span-2 on desktop ─── */}
|
||||||
|
<div style={{ ...CARD, minHeight: 260 }} className="col-span-1 md:col-span-2">
|
||||||
|
<Accent />
|
||||||
|
<Tag text="تازهترین گزارش ویژه" />
|
||||||
|
{/* Ghost number */}
|
||||||
|
<div style={{
|
||||||
|
fontSize: 96, fontWeight: 900, lineHeight: 0.8,
|
||||||
|
letterSpacing: '-5px', color: 'rgba(26,23,18,0.05)',
|
||||||
|
marginBottom: 12, userSelect: 'none', direction: 'ltr',
|
||||||
|
}}>01</div>
|
||||||
|
<h3 style={{ fontSize: 'clamp(16px,2vw,20px)', fontWeight: 900, letterSpacing: '-0.5px', lineHeight: 1.35, color: 'var(--ink)', marginBottom: 12, flex: 1 }}>
|
||||||
|
{featured.title}
|
||||||
|
</h3>
|
||||||
|
<p style={{ fontSize: 13, lineHeight: 1.75, fontWeight: 400, color: 'var(--ink-3)', marginBottom: 20,
|
||||||
|
display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
|
||||||
|
{featured.summary}
|
||||||
|
</p>
|
||||||
|
{/* Footer */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, paddingTop: 16, borderTop: '1px solid var(--rule-thin)' }}>
|
||||||
|
<div style={{ width: 30, height: 30, background: 'var(--red)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', fontSize: 12, fontWeight: 900, flexShrink: 0 }}>
|
||||||
|
{featured.authorInitial}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--ink)' }}>{featured.author}</div>
|
||||||
|
<div style={{ fontSize: 10, color: 'var(--ink-5)' }}>{featured.publishDate} · {featured.pages} صفحه</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
<span style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)' }}>خواندن ←</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ② Market prices ─────────────────────────────── */}
|
||||||
|
<div style={CARD}>
|
||||||
|
<Accent color="var(--green-ink)" />
|
||||||
|
<Tag text="قیمتهای لحظهای" color="var(--green-ink)" />
|
||||||
|
<div style={{ fontSize: 14, fontWeight: 700, color: 'var(--ink)', marginBottom: 16 }}>
|
||||||
|
آخرین نرخهای بازار
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
|
||||||
|
{PRICES.map(p => (
|
||||||
|
<div key={p.label} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '9px 0', borderBottom: '1px solid var(--rule-thin)' }}>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-4)', fontWeight: 500 }}>{p.label}</span>
|
||||||
|
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
|
||||||
|
<span style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)', direction: 'ltr' }}>{p.value}</span>
|
||||||
|
<span style={{ fontSize: 10, fontWeight: 700, color: p.up ? 'var(--green-ink)' : 'var(--red)', direction: 'ltr' }}>{p.delta}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ③ Flash report ──────────────────────────────── */}
|
||||||
|
<div style={CARD}>
|
||||||
|
<Accent color="var(--amber)" />
|
||||||
|
<Tag text={`فلش · ${flashReport.publishDate}`} color="var(--amber)" />
|
||||||
|
<div style={{ fontSize: 38, fontWeight: 900, letterSpacing: '-2px', color: 'var(--amber)', lineHeight: 1, marginBottom: 12, direction: 'ltr' }}>
|
||||||
|
+۱۸٪
|
||||||
|
</div>
|
||||||
|
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--ink)', lineHeight: 1.4, marginBottom: 8, flex: 1 }}>
|
||||||
|
جهش قیمت سنگآهن در بورس دالیان
|
||||||
|
</h3>
|
||||||
|
<p style={{ fontSize: 12, color: 'var(--ink-4)', fontWeight: 400, lineHeight: 1.6 }}>
|
||||||
|
برآورد اثر بر هزینه تمامشده تولیدکنندگان DRI ایران
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ④ Event ─────────────────────────────────────── */}
|
||||||
|
<div style={CARD}>
|
||||||
|
<Accent color="var(--ink-3)" />
|
||||||
|
<Tag text="رویداد · آینده" color="var(--ink-3)" />
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 16 }}>
|
||||||
|
<div style={{ background: 'var(--ink)', color: 'var(--paper)', padding: '8px 12px', textAlign: 'center', lineHeight: 1.2, flexShrink: 0, minWidth: 48 }}>
|
||||||
|
<div style={{ fontSize: 22, fontWeight: 900 }}>۲۵</div>
|
||||||
|
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '1px', textTransform: 'uppercase' }}>بهمن</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink)' }}>تهران</div>
|
||||||
|
<div style={{ fontSize: 10, color: 'var(--ink-5)' }}>هتل اسپیناس پالاس</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--ink)', lineHeight: 1.4, flex: 1 }}>
|
||||||
|
همایش ملی فولاد ایران ۱۴۰۳
|
||||||
|
</h3>
|
||||||
|
<p style={{ fontSize: 12, color: 'var(--ink-4)', fontWeight: 400, lineHeight: 1.6, marginTop: 6 }}>
|
||||||
|
نشست سالانه تولیدکنندگان، صادرکنندگان و سیاستگذاران
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ⑤ Risk alert ────────────────────────────────── */}
|
||||||
|
<div style={CARD}>
|
||||||
|
<Accent color="#7f1d1d" />
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 12 }}>
|
||||||
|
<div style={{ width: 8, height: 8, borderRadius: '50%', background: '#7f1d1d', flexShrink: 0 }} />
|
||||||
|
<span style={{ fontSize: 9, fontWeight: 700, letterSpacing: '2.5px', textTransform: 'uppercase', color: '#7f1d1d' }}>
|
||||||
|
هشدار ریسک · بحرانی
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'inline-block', background: 'rgba(127,29,29,0.08)', padding: '4px 10px', fontSize: 10, fontWeight: 700, color: '#7f1d1d', marginBottom: 14 }}>
|
||||||
|
دی ۱۴۰۳
|
||||||
|
</div>
|
||||||
|
<h3 style={{ fontSize: 14, fontWeight: 700, color: 'var(--ink)', lineHeight: 1.4, flex: 1 }}>
|
||||||
|
مسدود شدن ۳ مسیر پرداخت صادراتی
|
||||||
|
</h3>
|
||||||
|
<p style={{ fontSize: 12, color: 'var(--ink-4)', fontWeight: 400, lineHeight: 1.6, marginTop: 8 }}>
|
||||||
|
بسته تحریمی جدید مسیرهای بانکی صادرات فولاد را محدود کرده است
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ⑥ Expert quote — col-span-2 on desktop ────── */}
|
||||||
|
<div style={CARD} className="col-span-1 md:col-span-2">
|
||||||
|
<Accent color="var(--ink-6)" />
|
||||||
|
<Tag text="یادداشت تحلیلگر" color="var(--ink-4)" />
|
||||||
|
<div style={{ fontSize: 56, lineHeight: 0.7, color: 'var(--rule-thin)', fontWeight: 900, marginBottom: 10, direction: 'ltr', userSelect: 'none' }}>"</div>
|
||||||
|
<p style={{ fontSize: 'clamp(14px,1.2vw,16px)', fontWeight: 700, lineHeight: 1.65, letterSpacing: '-0.2px', color: 'var(--ink)', marginBottom: 20, flex: 1 }}>
|
||||||
|
فولاد ایران در آستانه تصمیمی تاریخی است: یا باید بازارهای جایگزین را با
|
||||||
|
جدیت دنبال کند، یا شاهد کاهش تدریجی سهم صادراتی خود در خاورمیانه باشد.
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, paddingTop: 16, borderTop: '1px solid var(--rule-thin)' }}>
|
||||||
|
<div style={{ width: 28, height: 28, background: 'var(--red)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', fontSize: 11, fontWeight: 900, flexShrink: 0 }}>م</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--ink)' }}>دکتر علی محمدی</div>
|
||||||
|
<div style={{ fontSize: 10, color: 'var(--ink-5)' }}>مدیر ارشد پژوهش · اندیشکده فولاد</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ⑦ Key stat ──────────────────────────────────── */}
|
||||||
|
<div style={CARD}>
|
||||||
|
<Accent color="var(--ink-3)" />
|
||||||
|
<Tag text="آمار کلیدی" color="var(--ink-3)" />
|
||||||
|
<div style={{ fontSize: 52, fontWeight: 900, letterSpacing: '-3px', lineHeight: 1, color: 'var(--ink)', marginBottom: 6, direction: 'ltr' }}>
|
||||||
|
۵۴M
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink-3)', marginBottom: 8 }}>
|
||||||
|
تن — صادرات فولاد چین
|
||||||
|
</div>
|
||||||
|
<p style={{ fontSize: 12, color: 'var(--ink-5)', fontWeight: 400, lineHeight: 1.65, flex: 1 }}>
|
||||||
|
در ۶ ماه اول ۲۰۲۴ — رکوردی که قیمتهای آسیای غربی را ۱۲٪ کاهش داد
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { SectionBanner } from '@/components/ui/SectionBanner'
|
||||||
|
import { reports, type Report } from '@/data/reports'
|
||||||
|
|
||||||
|
const displayReports = reports.filter(r => !r.featured).slice(0, 6)
|
||||||
|
|
||||||
|
const typeLabel: Record<string, string> = {
|
||||||
|
quarterly: 'گزارش فصلی',
|
||||||
|
flash: 'فوری',
|
||||||
|
risk: 'ریسک',
|
||||||
|
free: 'رایگان',
|
||||||
|
special: 'ویژه',
|
||||||
|
}
|
||||||
|
|
||||||
|
const dotColor: Record<string, string> = {
|
||||||
|
quarterly: 'var(--ink)',
|
||||||
|
flash: 'var(--red)',
|
||||||
|
risk: 'var(--red)',
|
||||||
|
free: 'var(--green-ink)',
|
||||||
|
special: 'var(--amber)',
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPrice(n: number) {
|
||||||
|
return n.toLocaleString('fa-IR')
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReportCardProps {
|
||||||
|
report: Report
|
||||||
|
colIndex: number
|
||||||
|
rowIndex: number
|
||||||
|
totalRows: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReportCard({ report, colIndex, rowIndex, totalRows }: ReportCardProps) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
const isLastRow = rowIndex === totalRows - 1
|
||||||
|
const isRightCol = colIndex === 0 // RTL: right col is index 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '40px',
|
||||||
|
borderBottom: isLastRow ? 'none' : '1px solid var(--rule-thin)',
|
||||||
|
borderLeft: isRightCol ? '1px solid var(--rule-thin)' : 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background-color 150ms',
|
||||||
|
backgroundColor: hovered ? 'var(--paper-2)' : 'transparent',
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
>
|
||||||
|
{/* Type badge */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
marginBottom: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '4px',
|
||||||
|
height: '4px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: dotColor[report.type] ?? 'var(--ink)',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '9px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '2px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
fontWeight: 700,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{typeLabel[report.type] ?? report.type} · {report.category}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<Link to={`/reports/${report.id}`} style={{ textDecoration: 'none' }}>
|
||||||
|
<h3
|
||||||
|
style={{
|
||||||
|
fontSize: 'clamp(18px, 2vw, 20px)',
|
||||||
|
fontWeight: 800,
|
||||||
|
lineHeight: 1.35,
|
||||||
|
letterSpacing: '-0.3px',
|
||||||
|
color: hovered ? 'var(--red)' : 'var(--ink)',
|
||||||
|
marginBottom: '12px',
|
||||||
|
transition: 'color 150ms',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.title}
|
||||||
|
</h3>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Deck */}
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: 300,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
lineHeight: 1.8,
|
||||||
|
marginBottom: '20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.summary.slice(0, 140)}
|
||||||
|
{report.summary.length > 140 ? '…' : ''}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '1px',
|
||||||
|
backgroundColor: 'var(--rule-thin)',
|
||||||
|
marginBottom: '16px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Footer row */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
gap: '12px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Price block */}
|
||||||
|
<div>
|
||||||
|
{report.isFree ? (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--green-ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
دانلود رایگان
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatPrice(report.price)}
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
fontWeight: 400,
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
marginRight: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
تومان
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '10px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
marginTop: '3px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.pages} صفحه · {report.publishDate}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Buy link */}
|
||||||
|
<Link
|
||||||
|
to={`/reports/${report.id}`}
|
||||||
|
style={{
|
||||||
|
fontSize: '10px',
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '2px',
|
||||||
|
color: 'var(--red)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'text-decoration 150ms',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
onMouseEnter={e =>
|
||||||
|
(e.currentTarget.style.textDecoration = 'underline')
|
||||||
|
}
|
||||||
|
onMouseLeave={e => (e.currentTarget.style.textDecoration = 'none')}
|
||||||
|
>
|
||||||
|
{report.isFree ? 'دانلود' : 'خرید گزارش'} ←
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LatestReportsSection() {
|
||||||
|
const rows = Math.ceil(displayReports.length / 2)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
style={{
|
||||||
|
padding: '96px 48px',
|
||||||
|
borderBottom: '2px solid var(--ink)',
|
||||||
|
direction: 'rtl',
|
||||||
|
}}
|
||||||
|
className="max-md:px-6 max-md:py-16"
|
||||||
|
>
|
||||||
|
{/* Section header */}
|
||||||
|
<div style={{ marginBottom: '40px' }}>
|
||||||
|
<SectionBanner
|
||||||
|
label="آخرین گزارشها"
|
||||||
|
link={{ text: 'مشاهده آرشیو کامل ←', to: '/reports' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 2-col grid */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
borderBottom: 'none',
|
||||||
|
borderLeft: 'none',
|
||||||
|
}}
|
||||||
|
className="max-md:grid-cols-1"
|
||||||
|
>
|
||||||
|
{displayReports.map((report, idx) => {
|
||||||
|
const colIndex = idx % 2
|
||||||
|
const rowIndex = Math.floor(idx / 2)
|
||||||
|
return (
|
||||||
|
<ReportCard
|
||||||
|
key={report.id}
|
||||||
|
report={report}
|
||||||
|
colIndex={colIndex}
|
||||||
|
rowIndex={rowIndex}
|
||||||
|
totalRows={rows}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export default function NewsletterSection() {
|
||||||
|
const [email, setEmail] = useState('')
|
||||||
|
const [submitted, setSubmitted] = useState(false)
|
||||||
|
|
||||||
|
function handleSubmit(e: React.FormEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
if (email.trim()) {
|
||||||
|
setSubmitted(true)
|
||||||
|
setEmail('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section style={{ borderBottom: '3px solid var(--ink)' }}>
|
||||||
|
{/* Outer grid — strip inline padding, use Tailwind */}
|
||||||
|
<div
|
||||||
|
style={{ display: 'grid', gridTemplateColumns: '1fr 1px 1fr', minHeight: 180 }}
|
||||||
|
className="px-12 max-md:px-5 max-md:grid-cols-1"
|
||||||
|
>
|
||||||
|
{/* Left: description */}
|
||||||
|
<div
|
||||||
|
style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center' }}
|
||||||
|
className="py-12 pr-12 max-md:py-10 max-md:pr-0"
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: 26, fontWeight: 900, color: 'var(--ink)', letterSpacing: '-0.5px', marginBottom: 8 }}>
|
||||||
|
خبرنامه اندیشکده
|
||||||
|
</div>
|
||||||
|
<p style={{ fontSize: 13, color: 'var(--ink-3)', fontWeight: 400, lineHeight: 1.75 }}>
|
||||||
|
هر هفته: خلاصه تحولات بازار فولاد، گزارشهای جدید و هشدارهای فوری — مستقیم در صندوق ورودی شما
|
||||||
|
</p>
|
||||||
|
<div style={{ fontSize: 11, color: 'var(--ink-5)', marginTop: 10 }}>
|
||||||
|
↑ بیش از ۲,۸۰۰ مدیر، مقام دولتی و کارشناس عضو
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div style={{ background: 'var(--rule-thin)' }} className="max-md:hidden" />
|
||||||
|
|
||||||
|
{/* Right: form */}
|
||||||
|
<div
|
||||||
|
style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 10 }}
|
||||||
|
className="py-12 pl-12 max-md:pt-0 max-md:pb-10 max-md:pl-0"
|
||||||
|
>
|
||||||
|
{submitted ? (
|
||||||
|
<div style={{ fontSize: 15, color: 'var(--green-ink)', fontWeight: 600, padding: '12px 0' }}>
|
||||||
|
✓ عضویت شما با موفقیت ثبت شد. از همراهی شما متشکریم.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<form onSubmit={handleSubmit} style={{ display: 'flex', gap: 0 }}>
|
||||||
|
<label htmlFor="nl-email" style={{ position: 'absolute', width: 1, height: 1, overflow: 'hidden', clip: 'rect(0,0,0,0)' }}>
|
||||||
|
ایمیل سازمانی شما
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="nl-email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={e => setEmail(e.target.value)}
|
||||||
|
placeholder="ایمیل سازمانی شما"
|
||||||
|
required
|
||||||
|
autoComplete="email"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: '12px 14px',
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
borderLeft: 'none',
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
fontSize: 13,
|
||||||
|
background: 'var(--paper)',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
outline: 'none',
|
||||||
|
borderRadius: 0,
|
||||||
|
minWidth: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
style={{
|
||||||
|
background: 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
border: 'none',
|
||||||
|
padding: '12px 24px',
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 700,
|
||||||
|
cursor: 'pointer',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
borderRadius: 0,
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
عضویت رایگان
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
<p style={{ fontSize: 10, color: 'var(--ink-5)' }}>اطلاعات شما محرمانه است و فروخته نمیشود</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,338 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { plans } from '@/data/plans'
|
||||||
|
|
||||||
|
/* ─── Checkmark icon ─────────────────────────────────── */
|
||||||
|
function Check({ featured }: { featured: boolean }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="16" height="16" viewBox="0 0 16 16" fill="none"
|
||||||
|
style={{ flexShrink: 0, marginTop: 1 }}
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
cx="8" cy="8" r="7.5"
|
||||||
|
stroke={featured ? 'rgba(255,255,255,0.35)' : 'var(--rule-thin)'}
|
||||||
|
fill={featured ? 'rgba(255,255,255,0.1)' : 'transparent'}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5 8l2 2 4-4"
|
||||||
|
stroke={featured ? '#f4efe7' : 'var(--green-ink)'}
|
||||||
|
strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Individual plan card ───────────────────────────── */
|
||||||
|
function PlanCard({ plan, isMiddle }: { plan: (typeof plans)[0]; isMiddle: boolean }) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
const featured = plan.featured
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: featured ? 'var(--ink)' : 'white',
|
||||||
|
border: featured ? 'none' : '1px solid var(--rule-thin)',
|
||||||
|
borderTop: featured ? 'none' : '1px solid var(--rule-thin)',
|
||||||
|
padding: '32px 28px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
position: 'relative',
|
||||||
|
boxShadow: isMiddle ? '0 8px 40px rgba(26,23,18,0.12)' : 'none',
|
||||||
|
zIndex: isMiddle ? 1 : 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Badge */}
|
||||||
|
{plan.badge && (
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: -12,
|
||||||
|
right: '50%',
|
||||||
|
transform: 'translateX(50%)',
|
||||||
|
background: 'var(--red)',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '2px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
padding: '4px 14px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}>
|
||||||
|
{plan.badge}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Plan name */}
|
||||||
|
<div style={{
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '1.5px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
color: featured ? 'rgba(244,239,231,0.5)' : 'var(--ink-5)',
|
||||||
|
marginBottom: 8,
|
||||||
|
}}>
|
||||||
|
{plan.name}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tagline */}
|
||||||
|
<div style={{
|
||||||
|
fontSize: 13,
|
||||||
|
color: featured ? 'rgba(244,239,231,0.55)' : 'var(--ink-4)',
|
||||||
|
marginBottom: 24,
|
||||||
|
fontWeight: 300,
|
||||||
|
}}>
|
||||||
|
{plan.id === 'individual' && 'برای کارشناسان و محققان'}
|
||||||
|
{plan.id === 'organizational' && 'برای شرکتها و سازمانها'}
|
||||||
|
{plan.id === 'ministry' && 'برای نهادهای دولتی'}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Price */}
|
||||||
|
<div style={{ marginBottom: 6 }}>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 42,
|
||||||
|
fontWeight: 900,
|
||||||
|
letterSpacing: '-2px',
|
||||||
|
lineHeight: 1,
|
||||||
|
color: featured ? 'var(--paper)' : 'var(--ink)',
|
||||||
|
fontVariantNumeric: 'tabular-nums',
|
||||||
|
}}>
|
||||||
|
{plan.price === 0 ? 'تماس' : plan.price.toLocaleString('fa-IR')}
|
||||||
|
</span>
|
||||||
|
{plan.price > 0 && (
|
||||||
|
<span style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 400,
|
||||||
|
color: featured ? 'rgba(244,239,231,0.5)' : 'var(--ink-4)',
|
||||||
|
marginRight: 6,
|
||||||
|
}}>
|
||||||
|
تومان
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: featured ? 'rgba(244,239,231,0.4)' : 'var(--ink-5)',
|
||||||
|
marginBottom: 28,
|
||||||
|
}}>
|
||||||
|
{plan.period}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA button */}
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '12px',
|
||||||
|
border: featured
|
||||||
|
? 'none'
|
||||||
|
: hovered ? 'none' : '1px solid var(--ink)',
|
||||||
|
background: featured
|
||||||
|
? hovered ? 'var(--paper-2)' : 'var(--paper)'
|
||||||
|
: hovered ? 'var(--ink)' : 'transparent',
|
||||||
|
color: featured
|
||||||
|
? 'var(--ink)'
|
||||||
|
: hovered ? 'var(--paper)' : 'var(--ink)',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 700,
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: 0,
|
||||||
|
marginBottom: 28,
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
transition: 'background 180ms, color 180ms, border 180ms',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{plan.ctaLabel}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div style={{
|
||||||
|
borderTop: featured ? '1px solid rgba(244,239,231,0.12)' : '1px solid var(--rule-thin)',
|
||||||
|
marginBottom: 24,
|
||||||
|
}} />
|
||||||
|
|
||||||
|
{/* Features */}
|
||||||
|
<ul style={{ listStyle: 'none', display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||||
|
{plan.features.map(f => (
|
||||||
|
<li key={f} style={{ display: 'flex', alignItems: 'flex-start', gap: 10, textAlign: 'right' }}>
|
||||||
|
<Check featured={featured} />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: 1.5,
|
||||||
|
color: featured ? 'rgba(244,239,231,0.75)' : 'var(--ink-3)',
|
||||||
|
fontWeight: 300,
|
||||||
|
}}>
|
||||||
|
{f}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Enterprise bottom card ─────────────────────────── */
|
||||||
|
const enterpriseFeatures = [
|
||||||
|
'پروژههای نامحدود',
|
||||||
|
'پشتیبانی تیمی اختصاصی',
|
||||||
|
'فاکتور رسمی سازمانی',
|
||||||
|
'بدون سقف توکن یا داده',
|
||||||
|
'دعوت اعضای نامحدود',
|
||||||
|
'قرارداد SLA معتبر',
|
||||||
|
'نقشهای سفارشی کاربری',
|
||||||
|
'امنیت پیشرفته سازمانی',
|
||||||
|
'API اتصال به سیستم داخلی',
|
||||||
|
'گزارشدهی اختصاصی',
|
||||||
|
'کارگاههای تخصصی دورهای',
|
||||||
|
'مشاوره راهبردی مستقیم',
|
||||||
|
]
|
||||||
|
|
||||||
|
function EnterpriseCard() {
|
||||||
|
const [hov, setHov] = useState(false)
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
background: 'white',
|
||||||
|
padding: '36px 40px',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr 1fr',
|
||||||
|
gap: '40px',
|
||||||
|
alignItems: 'start',
|
||||||
|
}}
|
||||||
|
className="max-md:grid-cols-1 max-md:gap-6"
|
||||||
|
>
|
||||||
|
{/* Left: description */}
|
||||||
|
<div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 11, fontWeight: 700, letterSpacing: '2px',
|
||||||
|
textTransform: 'uppercase', color: 'var(--ink-5)', marginBottom: 10,
|
||||||
|
}}>
|
||||||
|
وزارتخانه / سازمان دولتی
|
||||||
|
</div>
|
||||||
|
<h3 style={{
|
||||||
|
fontSize: 22, fontWeight: 900, letterSpacing: '-0.5px',
|
||||||
|
color: 'var(--ink)', marginBottom: 12,
|
||||||
|
}}>
|
||||||
|
راهکار سازمانی کامل
|
||||||
|
</h3>
|
||||||
|
<p style={{
|
||||||
|
fontSize: 13, lineHeight: 1.8, color: 'var(--ink-3)',
|
||||||
|
fontWeight: 300, marginBottom: 24,
|
||||||
|
}}>
|
||||||
|
به راهکارهای سفارشی، پشتیبانی اختصاصی یا قیمتگذاری حجمی نیاز دارید؟
|
||||||
|
با کارشناسان ما درباره پلن منحصربهفرد سازمانتان صحبت کنید.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setHov(true)}
|
||||||
|
onMouseLeave={() => setHov(false)}
|
||||||
|
style={{
|
||||||
|
padding: '13px 28px',
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
background: hov ? 'var(--ink)' : 'transparent',
|
||||||
|
color: hov ? 'var(--paper)' : 'var(--ink)',
|
||||||
|
fontSize: 12, fontWeight: 700,
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: 0,
|
||||||
|
transition: 'background 180ms, color 180ms',
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
تماس با تیم فروش
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right: feature grid */}
|
||||||
|
<div style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr 1fr',
|
||||||
|
gap: '10px 20px',
|
||||||
|
}}>
|
||||||
|
{enterpriseFeatures.map(f => (
|
||||||
|
<div key={f} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
|
<Check featured={false} />
|
||||||
|
<span style={{ fontSize: 12, color: 'var(--ink-3)', lineHeight: 1.4, fontWeight: 300 }}>
|
||||||
|
{f}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Section ────────────────────────────────────────── */
|
||||||
|
export default function PlansSection() {
|
||||||
|
const allThree = plans
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section style={{
|
||||||
|
background: 'var(--paper-2)',
|
||||||
|
borderBottom: '2px solid var(--ink)',
|
||||||
|
}}>
|
||||||
|
<div className="px-12 py-20 max-md:px-5 max-md:py-14">
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div style={{ textAlign: 'center', marginBottom: 56 }}>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 9, fontWeight: 700, letterSpacing: '3px',
|
||||||
|
textTransform: 'uppercase', color: 'var(--red)',
|
||||||
|
marginBottom: 14,
|
||||||
|
}}>
|
||||||
|
اشتراک و دسترسی
|
||||||
|
</div>
|
||||||
|
<h2 style={{
|
||||||
|
fontSize: 'clamp(26px, 3vw, 40px)',
|
||||||
|
fontWeight: 900,
|
||||||
|
letterSpacing: '-1.5px',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
marginBottom: 12,
|
||||||
|
}}>
|
||||||
|
پلن مناسب خود را انتخاب کنید
|
||||||
|
</h2>
|
||||||
|
<p style={{
|
||||||
|
fontSize: 14, lineHeight: 1.8,
|
||||||
|
color: 'var(--ink-4)', fontWeight: 300,
|
||||||
|
maxWidth: 480, margin: '0 auto',
|
||||||
|
}}>
|
||||||
|
از دسترسی پایه برای پژوهشگران تا راهکار کامل برای وزارتخانهها —
|
||||||
|
هر سطحی از نیاز را پوشش میدهیم.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 3-col plan cards */}
|
||||||
|
<div style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
gap: 0,
|
||||||
|
marginBottom: 32,
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
className="max-md:grid-cols-1 max-md:gap-0"
|
||||||
|
>
|
||||||
|
{allThree.map((plan, idx) => (
|
||||||
|
<PlanCard
|
||||||
|
key={plan.id}
|
||||||
|
plan={plan}
|
||||||
|
isMiddle={idx === 1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Enterprise bottom card */}
|
||||||
|
<EnterpriseCard />
|
||||||
|
|
||||||
|
{/* Money-back note */}
|
||||||
|
<div style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: 20,
|
||||||
|
fontSize: 11,
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
fontWeight: 300,
|
||||||
|
}}>
|
||||||
|
✓ ضمانت بازگشت وجه ۱۴ روزه · بدون قرارداد اجباری
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,279 @@
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Carousel, Card } from '@/components/ui/apple-cards-carousel'
|
||||||
|
import type { CardData } from '@/components/ui/apple-cards-carousel'
|
||||||
|
import { reports } from '@/data/reports'
|
||||||
|
import type { Report } from '@/data/reports'
|
||||||
|
|
||||||
|
/* ─── Per-report images (industrial, steel-relevant) ─ */
|
||||||
|
const reportImage: Record<string, string> = {
|
||||||
|
'iran-steel-global-trade-scenarios-1404':
|
||||||
|
'https://images.unsplash.com/photo-1578662996442-48f60103fc96?q=80&w=1600&fit=crop',
|
||||||
|
'steel-energy-consumption-efficiency-1403':
|
||||||
|
'https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?q=80&w=1600&fit=crop',
|
||||||
|
'global-steel-market-q3-1403':
|
||||||
|
'https://images.unsplash.com/photo-1504328345606-18bbc8c9d7d1?q=80&w=1600&fit=crop',
|
||||||
|
'iran-steel-export-sanctions-impact-1403':
|
||||||
|
'https://images.unsplash.com/photo-1494412519320-aa613dfb7738?q=80&w=1600&fit=crop',
|
||||||
|
'flash-iron-ore-price-surge-1403':
|
||||||
|
'https://images.unsplash.com/photo-1611974789855-9c2a0a7236a3?q=80&w=1600&fit=crop',
|
||||||
|
'domestic-production-dri-outlook-1403':
|
||||||
|
'https://images.unsplash.com/photo-1567789884554-0b844b597180?q=80&w=1600&fit=crop',
|
||||||
|
'esg-carbon-emissions-steel-iran-1403':
|
||||||
|
'https://images.unsplash.com/photo-1466611653911-95081537e5b7?q=80&w=1600&fit=crop',
|
||||||
|
'policy-steel-pricing-reform-1403':
|
||||||
|
'https://images.unsplash.com/photo-1551288049-bebda4e38f71?q=80&w=1600&fit=crop',
|
||||||
|
'flash-china-steel-overcapacity-1403':
|
||||||
|
'https://images.unsplash.com/photo-1570168007204-dfb528c6958f?q=80&w=1600&fit=crop',
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackImage =
|
||||||
|
'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?q=80&w=1600&fit=crop'
|
||||||
|
|
||||||
|
const typeLabel: Record<string, string> = {
|
||||||
|
quarterly: 'گزارش فصلی',
|
||||||
|
flash: 'گزارش فوری',
|
||||||
|
risk: 'گزارش ریسک',
|
||||||
|
free: 'رایگان',
|
||||||
|
special: 'گزارش ویژه',
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeColor: Record<string, string> = {
|
||||||
|
flash: 'var(--red)',
|
||||||
|
risk: '#92400e',
|
||||||
|
free: '#166534',
|
||||||
|
quarterly: 'var(--ink-4)',
|
||||||
|
special: 'var(--ink)',
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Key findings per category (mock but plausible) ─ */
|
||||||
|
const keyFindings: Record<string, string[]> = {
|
||||||
|
'بازار جهانی': [
|
||||||
|
'کاهش ۱۲٪ تقاضای چین در نیمه دوم ۲۰۲۴ فشار قیمتی جهانی ایجاد کرده',
|
||||||
|
'هند با رشد ۱۸٪ ظرفیت تولید، رقیب اصلی ایران در بازارهای منطقه شده',
|
||||||
|
'پیشبینی بازگشت تقاضا در Q2 2025 با ریکاوری بخش ساختمان آمریکا',
|
||||||
|
],
|
||||||
|
'انرژی و ESG': [
|
||||||
|
'شکاف مصرف انرژی واحدهای ایرانی ۲۵٪ بیشتر از میانگین جهانی است',
|
||||||
|
'اجرای CBAM از ۲۰۲۶ میتواند ۱۵٪ رقابتپذیری صادرات را کاهش دهد',
|
||||||
|
'مسیر هیدروژن سبز برای ایران تا ۱۴۱۰ اقتصادی نخواهد بود',
|
||||||
|
],
|
||||||
|
'صادرات': [
|
||||||
|
'عراق، افغانستان و پاکستان ۶۵٪ صادرات فولاد ایران را تشکیل میدهند',
|
||||||
|
'مسیرهای جایگزین آفریقای شرقی پتانسیل ۲ میلیون تن اضافی دارد',
|
||||||
|
'تحریمهای جدید ۳ مسیر اصلی پرداخت را مسدود کرده است',
|
||||||
|
],
|
||||||
|
'تولید داخلی': [
|
||||||
|
'ایران با ۳۰ میلیون تن DRI رتبه اول جهانی را حفظ کرده است',
|
||||||
|
'کمبود گاز زمستانی میتواند تا ۲۰٪ ظرفیت تولید را کاهش دهد',
|
||||||
|
'۸ پروژه در دست احداث تا ۱۴۰۵ به ظرفیت میرسند',
|
||||||
|
],
|
||||||
|
'سیاستگذاری': [
|
||||||
|
'قیمتگذاری دستوری هزینه ۱.۲ میلیارد دلار در سال به صنعت تحمیل میکند',
|
||||||
|
'تجربه ترکیه نشان میدهد آزادسازی تدریجی قیمت بهترین مسیر است',
|
||||||
|
'اصلاح تعرفههای سنگآهن میتواند ۸٪ به حاشیه سود اضافه کند',
|
||||||
|
],
|
||||||
|
'ریسک و بحران': [
|
||||||
|
'احتمال تشدید تحریمها در ۶ ماه آینده ۶۵٪ برآورد میشود',
|
||||||
|
'ریسک کمبود ارز برای واردات مواد اولیه در سطح بالا قرار دارد',
|
||||||
|
'شکست مذاکرات هستهای میتواند ۳۰٪ صادرات را تحت تأثیر قرار دهد',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Report contents (TOC-style) ───────────────────── */
|
||||||
|
const reportContents: Record<string, string[]> = {
|
||||||
|
quarterly: ['مروری بر بازار فصل', 'تحلیل قیمتها و حجم معاملات', 'رویدادهای کلیدی', 'پیشبینی فصل آینده', 'دادههای خام ضمیمه'],
|
||||||
|
flash: ['خلاصه اجرایی', 'رویداد و محرک', 'تأثیر بر بازار داخلی', 'توصیههای فوری'],
|
||||||
|
risk: ['ارزیابی ریسک', 'تحلیل سناریو', 'احتمالسنجی', 'راهکارهای پوشش ریسک', 'پیوست داده'],
|
||||||
|
free: ['خلاصه یافتهها', 'روششناسی', 'مقایسه بینالمللی', 'توصیههای سیاستی'],
|
||||||
|
special: ['تحلیل جامع', 'سناریوسازی کمی', 'مقایسه تطبیقی', 'توصیههای راهبردی', 'مدل Excel پیوست'],
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Expanded card body ─────────────────────────────── */
|
||||||
|
function ReportCardContent({ report }: { report: Report }) {
|
||||||
|
const findings = keyFindings[report.category] ?? []
|
||||||
|
const contents = reportContents[report.type] ?? []
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ direction: 'rtl', fontFamily: 'Vazir, sans-serif' }}>
|
||||||
|
|
||||||
|
{/* Tags */}
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 20 }}>
|
||||||
|
{report.tags.map(tag => (
|
||||||
|
<span key={tag} style={{
|
||||||
|
fontSize: 10, fontWeight: 600,
|
||||||
|
padding: '3px 10px', borderRadius: 999,
|
||||||
|
background: '#f1f5f9', color: '#475569',
|
||||||
|
}}>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Summary */}
|
||||||
|
<p style={{
|
||||||
|
fontSize: 14, lineHeight: 1.9, fontWeight: 300,
|
||||||
|
color: '#374151', marginBottom: 24,
|
||||||
|
paddingBottom: 24, borderBottom: '1px solid #f1f5f9',
|
||||||
|
}}>
|
||||||
|
{report.summary}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Key findings */}
|
||||||
|
{findings.length > 0 && (
|
||||||
|
<div style={{ marginBottom: 24 }}>
|
||||||
|
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '2px', textTransform: 'uppercase', color: '#9b1c1c', marginBottom: 12 }}>
|
||||||
|
یافتههای کلیدی
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||||
|
{findings.map((f, i) => (
|
||||||
|
<div key={i} style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>
|
||||||
|
<div style={{ width: 5, height: 5, borderRadius: '50%', background: '#9b1c1c', flexShrink: 0, marginTop: 6 }} />
|
||||||
|
<span style={{ fontSize: 13, lineHeight: 1.7, color: '#374151', fontWeight: 300 }}>{f}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Report contents */}
|
||||||
|
{contents.length > 0 && (
|
||||||
|
<div style={{ marginBottom: 24, padding: '16px 20px', background: '#f8fafc', borderRadius: 12 }}>
|
||||||
|
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '2px', textTransform: 'uppercase', color: '#64748b', marginBottom: 10 }}>
|
||||||
|
محتوای این گزارش
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||||
|
{contents.map((item, i) => (
|
||||||
|
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||||
|
<span style={{ fontSize: 10, color: '#94a3b8', fontWeight: 700, minWidth: 18 }}>
|
||||||
|
{(i + 1).toLocaleString('fa-IR')}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: 12, color: '#475569', lineHeight: 1.5 }}>{item}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Meta grid */}
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 10, marginBottom: 20 }}>
|
||||||
|
{[
|
||||||
|
{ label: 'نویسنده', value: report.author },
|
||||||
|
{ label: 'صفحات', value: `${report.pages} صفحه` },
|
||||||
|
{ label: 'تاریخ انتشار', value: report.publishDate },
|
||||||
|
].map(item => (
|
||||||
|
<div key={item.label} style={{ background: '#f8fafc', borderRadius: 10, padding: '10px 14px', textAlign: 'center' }}>
|
||||||
|
<div style={{ fontSize: 9, color: '#94a3b8', marginBottom: 4, fontWeight: 700, letterSpacing: '1px', textTransform: 'uppercase' }}>
|
||||||
|
{item.label}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, fontWeight: 700, color: '#1e293b' }}>{item.value}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Author */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex', alignItems: 'center', gap: 12,
|
||||||
|
padding: '14px 18px', background: '#f8fafc', borderRadius: 12, marginBottom: 20,
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
width: 40, height: 40, background: '#1a1712', color: '#f4efe7',
|
||||||
|
borderRadius: 999, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
fontSize: 16, fontWeight: 900, flexShrink: 0,
|
||||||
|
}}>
|
||||||
|
{report.authorInitial}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 13, fontWeight: 700, color: '#1e293b' }}>{report.author}</div>
|
||||||
|
<div style={{ fontSize: 11, color: '#64748b' }}>{report.authorRole}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
{report.isFree ? (
|
||||||
|
<Link to={`/reports/${report.id}`} style={{
|
||||||
|
background: '#166534', color: 'white',
|
||||||
|
padding: '13px 28px', fontSize: 13, fontWeight: 700,
|
||||||
|
fontFamily: 'Vazir, sans-serif', borderRadius: 999,
|
||||||
|
textDecoration: 'none', display: 'inline-block',
|
||||||
|
}}>
|
||||||
|
دانلود رایگان
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button style={{
|
||||||
|
background: '#1a1712', color: '#f4efe7', border: 'none',
|
||||||
|
padding: '13px 28px', fontSize: 13, fontWeight: 700,
|
||||||
|
fontFamily: 'Vazir, sans-serif', cursor: 'pointer', borderRadius: 999,
|
||||||
|
}}>
|
||||||
|
خرید — {report.price.toLocaleString('fa-IR')} تومان
|
||||||
|
</button>
|
||||||
|
<Link to={`/reports/${report.id}`} style={{
|
||||||
|
color: '#9b1c1c', fontSize: 12, fontWeight: 700, textDecoration: 'none',
|
||||||
|
}}>
|
||||||
|
مشاهده جزئیات ←
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Section ────────────────────────────────────────── */
|
||||||
|
export default function ReportsCarouselSection() {
|
||||||
|
const carouselReports = reports.filter(r => !r.featured).slice(0, 10)
|
||||||
|
|
||||||
|
const cards: CardData[] = carouselReports.map(report => ({
|
||||||
|
src: reportImage[report.id] ?? fallbackImage,
|
||||||
|
title: report.title,
|
||||||
|
category: typeLabel[report.type] ?? report.type,
|
||||||
|
content: <ReportCardContent report={report} />,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const cardElements = cards.map((card, index) => (
|
||||||
|
<Card key={card.src + index} card={card} index={index} layout />
|
||||||
|
))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section style={{ borderBottom: '2px solid var(--ink)' }}>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="px-12 max-md:px-5">
|
||||||
|
<div style={{
|
||||||
|
display: 'flex', alignItems: 'center',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)', paddingTop: 52,
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
background: 'var(--ink)', color: 'var(--paper)',
|
||||||
|
padding: '8px 20px', fontSize: 9, fontWeight: 700,
|
||||||
|
letterSpacing: '3px', textTransform: 'uppercase',
|
||||||
|
}}>
|
||||||
|
تحلیلهای اخیر
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
<Link to="/reports" style={{
|
||||||
|
fontSize: 10, fontWeight: 700, letterSpacing: '1.5px',
|
||||||
|
textTransform: 'uppercase', color: 'var(--ink-4)',
|
||||||
|
textDecoration: 'none', padding: '8px 0',
|
||||||
|
}}>
|
||||||
|
همه گزارشها ←
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Legend */}
|
||||||
|
<div className="px-12 pt-[14px] max-md:px-5">
|
||||||
|
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}>
|
||||||
|
{Object.entries(typeLabel).map(([key, label]) => (
|
||||||
|
<div key={key} style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
|
||||||
|
<div style={{ width: 6, height: 6, borderRadius: '50%', background: typeColor[key] }} />
|
||||||
|
<span style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 600 }}>{label}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Carousel items={cardElements} />
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
|
/* ─── Risk data ──────────────────────────────────────────── */
|
||||||
|
const riskItems = [
|
||||||
|
{
|
||||||
|
quote: 'چین در نیمه اول ۲۰۲۴ رکورد ۵۴ میلیون تن صادرات فولاد را شکست. این موج صادراتی قیمت بازارهای آسیای غربی را ۱۲٪ کاهش داده و مستقیماً سهم ایران در عراق را تهدید میکند.',
|
||||||
|
name: 'بازار جهانی — چین',
|
||||||
|
date: 'بهمن ۱۴۰۳',
|
||||||
|
level: 'high' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: 'اجرای مکانیسم CBAM اتحادیه اروپا از ۲۰۲۶ برای صنایع فولاد آغاز میشود. محصولات ایرانی با ضریب کربن بالاتر از میانگین جهانی، در بازار اروپا با افزایش هزینه ۱۵ تا ۲۲ دلار به ازای هر تن مواجه خواهند شد.',
|
||||||
|
name: 'مقررات — اتحادیه اروپا',
|
||||||
|
date: 'افق: ۲۰۲۶',
|
||||||
|
level: 'medium' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: 'بسته تحریمی جدید، ۳ مسیر اصلی پرداخت بینالمللی شرکتهای فولادی ایران را مسدود کرده است. ریسک تأخیر در دریافت وجوه صادراتی به بالاترین سطح در ۵ سال اخیر رسیده است.',
|
||||||
|
name: 'ریسک ژئوپلیتیک — تحریم',
|
||||||
|
date: 'دی ۱۴۰۳',
|
||||||
|
level: 'critical' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: 'هند با افزایش ۱۸٪ ظرفیت تولید فولاد در سال ۲۰۲۴، به سومین تولیدکننده بزرگ جهان تبدیل شد. رقابت هند در بازارهای هدف ایران شامل عراق، افغانستان و آفریقای شرقی بهشدت تشدید شده.',
|
||||||
|
name: 'رقابت — هند',
|
||||||
|
date: 'افق میانمدت',
|
||||||
|
level: 'high' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: 'محدودیتهای زمستانی گاز صنعتی در ۳ ماه گذشته باعث توقف موقت ۱۴ واحد تولیدی فولاد در اصفهان، خراسان و هرمزگان شده است. ریسک کمبود انرژی همچنان ادامه دارد.',
|
||||||
|
name: 'زیرساخت — انرژی داخلی',
|
||||||
|
date: 'پایش مستمر',
|
||||||
|
level: 'high' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: 'نرخ برابری ریال در برابر دلار در ۳ ماه اخیر ۸٪ کاهش یافته است. این نوسان از یک سو هزینه واردات مواد اولیه را افزایش و از سوی دیگر رقابتپذیری صادراتی را بهبود میبخشد.',
|
||||||
|
name: 'ریسک مالی — ارز',
|
||||||
|
date: 'بهمن ۱۴۰۳',
|
||||||
|
level: 'medium' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: 'کاهش واردات فولاد ترکیه از بازار عراق، فرصت افزایش سهم ایران از ۴۰٪ به ۵۵٪ را فراهم کرده است. پنجره فرصت تا اواخر سال ۱۴۰۴ باز است.',
|
||||||
|
name: 'فرصت — بازار عراق',
|
||||||
|
date: 'افق: ۱۴۰۴',
|
||||||
|
level: 'opportunity' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: 'کشورهای آفریقای شرقی (کنیا، اتیوپی، تانزانیا) در مجموع ۸ میلیون تن واردات فولاد در سال دارند. سهم فعلی ایران تنها ۳٪ است در حالی که زیرساخت لجستیکی برای افزایش به ۱۵٪ وجود دارد.',
|
||||||
|
name: 'فرصت — آفریقای شرقی',
|
||||||
|
date: 'بررسی استراتژیک',
|
||||||
|
level: 'opportunity' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: 'قیمت سنگآهن در بورس دالیان در ماه اخیر ۱۸٪ جهش داشته است. این افزایش مستقیماً هزینه تمامشده واحدهای وابسته به واردات سنگآهن را متأثر میکند.',
|
||||||
|
name: 'کامودیتی — سنگآهن',
|
||||||
|
date: 'آبان ۱۴۰۳',
|
||||||
|
level: 'medium' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: 'روسیه پس از تحریمهای غرب، مسیرهای صادراتی خود را به آسیا و خاورمیانه تغییر داده و با قیمتهای پایینتر از نرخ جهانی وارد بازارهای هدف ایران شده است.',
|
||||||
|
name: 'رقابت — روسیه',
|
||||||
|
date: 'در حال پایش',
|
||||||
|
level: 'high' as const,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const LEVEL = {
|
||||||
|
critical: { dot: '#7f1d1d', label: 'بحرانی', bg: 'rgba(127,29,29,0.08)' },
|
||||||
|
high: { dot: '#9b1c1c', label: 'بالا', bg: 'rgba(155,28,28,0.07)' },
|
||||||
|
medium: { dot: '#92400e', label: 'متوسط', bg: 'rgba(146,64,14,0.07)' },
|
||||||
|
low: { dot: '#166534', label: 'پایین', bg: 'rgba(22,101,52,0.07)' },
|
||||||
|
opportunity: { dot: '#1e40af', label: 'فرصت', bg: 'rgba(30,64,175,0.07)' },
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Section ────────────────────────────────────────────── */
|
||||||
|
export default function RiskSection() {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const listRef = useRef<HTMLUListElement>(null)
|
||||||
|
const [ready, setReady] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current || !listRef.current) return
|
||||||
|
/* Clone all items for seamless loop */
|
||||||
|
const original = Array.from(listRef.current.children)
|
||||||
|
original.forEach(item => {
|
||||||
|
listRef.current!.appendChild(item.cloneNode(true))
|
||||||
|
})
|
||||||
|
setReady(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section style={{ borderBottom: '2px solid var(--ink)', background: 'var(--paper-2)' }}>
|
||||||
|
<div className="px-12 pt-14 max-md:px-5 max-md:pt-10">
|
||||||
|
|
||||||
|
{/* ── Header bar ── */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
background: 'var(--red)',
|
||||||
|
color: 'white',
|
||||||
|
padding: '8px 20px',
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '3px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
}}>
|
||||||
|
پایش ریسک · لحظهای
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
marginRight: 16,
|
||||||
|
fontSize: 10,
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
fontWeight: 600,
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
}}>
|
||||||
|
{riskItems.length} سیگنال فعال
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
<Link to="/risks" style={{
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '1.5px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
padding: '8px 0',
|
||||||
|
}}>
|
||||||
|
همه ریسکها ←
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── Legend ── */}
|
||||||
|
<div style={{ display: 'flex', gap: 20, flexWrap: 'wrap', padding: '14px 0 0' }}>
|
||||||
|
{[
|
||||||
|
{ color: '#7f1d1d', label: 'بحرانی' },
|
||||||
|
{ color: '#9b1c1c', label: 'بالا' },
|
||||||
|
{ color: '#92400e', label: 'متوسط' },
|
||||||
|
{ color: '#166534', label: 'پایین' },
|
||||||
|
{ color: '#1e40af', label: 'فرصت' },
|
||||||
|
].map(l => (
|
||||||
|
<div key={l.label} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
|
<div style={{ width: 8, height: 8, borderRadius: '50%', background: l.color }} />
|
||||||
|
<span style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 600 }}>{l.label}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── Auto-scroll ticker ── */}
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
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%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
ref={listRef}
|
||||||
|
className={ready ? 'animate-scroll-risk' : ''}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 20,
|
||||||
|
width: 'max-content',
|
||||||
|
flexWrap: 'nowrap',
|
||||||
|
listStyle: 'none',
|
||||||
|
direction: 'ltr',
|
||||||
|
/* pause on hover */
|
||||||
|
animationPlayState: 'running',
|
||||||
|
}}
|
||||||
|
onMouseEnter={e => (e.currentTarget.style.animationPlayState = 'paused')}
|
||||||
|
onMouseLeave={e => (e.currentTarget.style.animationPlayState = 'running')}
|
||||||
|
>
|
||||||
|
{riskItems.map((item, idx) => {
|
||||||
|
const cfg = LEVEL[item.level]
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={idx}
|
||||||
|
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',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 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,
|
||||||
|
}}>
|
||||||
|
{cfg.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quote */}
|
||||||
|
<p style={{
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: 1.85,
|
||||||
|
color: 'var(--ink-2)',
|
||||||
|
fontWeight: 400,
|
||||||
|
marginBottom: 16,
|
||||||
|
flex: 1,
|
||||||
|
}}>
|
||||||
|
{item.quote}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<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)' }}>{item.name}</div>
|
||||||
|
<div style={{ fontSize: 10, color: 'var(--ink-5)' }}>{item.date}</div>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '1px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
color: cfg.dot,
|
||||||
|
background: cfg.bg,
|
||||||
|
padding: '4px 10px',
|
||||||
|
}}>
|
||||||
|
{cfg.label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { marketPrices } from '@/data/market'
|
||||||
|
|
||||||
|
const prices = marketPrices.slice(0, 4)
|
||||||
|
|
||||||
|
export default function SnapshotBar() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: '2px solid var(--ink)',
|
||||||
|
backgroundColor: 'var(--paper-2)',
|
||||||
|
direction: 'rtl',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '0 48px',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||||
|
}}
|
||||||
|
className="max-md:grid-cols-2 max-md:px-6"
|
||||||
|
>
|
||||||
|
{prices.map((price, idx) => {
|
||||||
|
const isLast = idx === prices.length - 1
|
||||||
|
const isUp = price.trend === 'up'
|
||||||
|
const isDown = price.trend === 'down'
|
||||||
|
const arrow = isUp ? '↑' : isDown ? '↓' : '—'
|
||||||
|
const changeColor = isUp
|
||||||
|
? 'var(--green-ink)'
|
||||||
|
: isDown
|
||||||
|
? 'var(--red)'
|
||||||
|
: 'var(--ink-4)'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={price.id}
|
||||||
|
style={{
|
||||||
|
padding: '24px 0',
|
||||||
|
borderRight: isLast ? 'none' : '1px solid var(--rule-thin)',
|
||||||
|
paddingLeft: idx === 0 ? '0' : '28px',
|
||||||
|
paddingRight: idx === prices.length - 1 ? '0' : '28px',
|
||||||
|
}}
|
||||||
|
className={`max-md:${
|
||||||
|
idx % 2 === 1
|
||||||
|
? 'border-r-0'
|
||||||
|
: idx < 2
|
||||||
|
? 'border-b border-[var(--rule-thin)]'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{/* Label */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '9px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '2px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
marginBottom: '6px',
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{price.name}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Value */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '22px',
|
||||||
|
fontWeight: 900,
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
lineHeight: 1.1,
|
||||||
|
marginBottom: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{price.value.toLocaleString('fa-IR')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Change row */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
fontWeight: 700,
|
||||||
|
color: changeColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{arrow}{' '}
|
||||||
|
{Math.abs(price.changePercent).toLocaleString('fa-IR', {
|
||||||
|
minimumFractionDigits: 1,
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
})}
|
||||||
|
٪
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '10px',
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
fontWeight: 400,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{price.unit}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { reports } from '@/data/reports'
|
||||||
|
|
||||||
|
const featured = reports.find(r => r.featured) ?? reports[0]
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ num: '۱', title: 'تحلیل ۴ سناریوی اصلی', desc: 'از بدبینانه تا خوشبینانه با احتمالسنجی کمی و مدلسازی قیمتی' },
|
||||||
|
{ num: '۲', title: 'نقشه بازارهای هدف جدید', desc: 'جایگزینهای اروپا: آفریقای شرقی، آسیای مرکزی، عراق و افغانستان' },
|
||||||
|
{ num: '۳', title: '۱۲ توصیه سیاستی مستقیم', desc: 'برای وزارت صمت، گمرک، سازمان بنادر و بانک مرکزی' },
|
||||||
|
{ num: '۴', title: 'مقایسه تطبیقی با رقبا', desc: 'ایران در برابر ترکیه، هند، روسیه و برزیل در هر سناریو' },
|
||||||
|
{ num: '۵', title: 'مدل Excel پیوست', desc: 'دادههای خام و مدل قابل شخصیسازی برای تیمهای تحلیلی' },
|
||||||
|
]
|
||||||
|
|
||||||
|
function BuyButton({ label, dark }: { label: string; dark?: boolean }) {
|
||||||
|
const [hov, setHov] = useState(false)
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setHov(true)}
|
||||||
|
onMouseLeave={() => setHov(false)}
|
||||||
|
style={{
|
||||||
|
padding: '12px 24px',
|
||||||
|
border: dark ? 'none' : '1px solid rgba(255,255,255,0.3)',
|
||||||
|
background: dark
|
||||||
|
? hov ? 'var(--paper-2)' : 'var(--paper)'
|
||||||
|
: hov ? 'rgba(255,255,255,0.08)' : 'transparent',
|
||||||
|
color: dark ? 'var(--ink)' : 'var(--paper)',
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: dark ? 700 : 400,
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: 0,
|
||||||
|
transition: 'background 200ms',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SpecialSpreadSection() {
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
style={{
|
||||||
|
background: 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
borderBottom: '3px solid var(--ink)',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1.2fr 1fr',
|
||||||
|
}}
|
||||||
|
className="max-md:grid-cols-1"
|
||||||
|
>
|
||||||
|
{/* LEFT */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '72px 72px 72px 48px',
|
||||||
|
borderLeft: '1px solid rgba(255,255,255,0.08)',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
className="max-md:p-8"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{/* Overline */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 28 }}>
|
||||||
|
<div style={{ width: 28, height: 2, background: 'var(--red)', flexShrink: 0 }} />
|
||||||
|
<span style={{ fontSize: 10, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'rgba(255,255,255,0.5)' }}>
|
||||||
|
گزارش استراتژیک · زمستان ۱۴۰۳
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Headline */}
|
||||||
|
<h2
|
||||||
|
style={{
|
||||||
|
fontSize: 'clamp(32px,3.5vw,52px)',
|
||||||
|
fontWeight: 900,
|
||||||
|
lineHeight: 1.1,
|
||||||
|
letterSpacing: '-1.5px',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
marginBottom: 24,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ایران در نقشه<br />
|
||||||
|
جدید تجارت<br />
|
||||||
|
فولاد جهان:<br />
|
||||||
|
<span style={{ color: 'rgba(255,255,255,0.35)' }}>چهار سناریو</span><br />
|
||||||
|
برای ۱۴۰۴
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<p style={{ fontSize: 15, lineHeight: 1.85, color: 'rgba(255,255,255,0.65)', fontWeight: 300, marginBottom: 36 }}>
|
||||||
|
{featured.summary}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{/* Author */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingTop: 24, borderTop: '1px solid rgba(255,255,255,0.12)', marginBottom: 28 }}>
|
||||||
|
<div style={{ width: 38, height: 38, background: 'var(--red)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 15, fontWeight: 900, color: 'white', flexShrink: 0 }}>
|
||||||
|
{featured.authorInitial}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--paper)' }}>{featured.author}</div>
|
||||||
|
<div style={{ fontSize: 11, color: 'rgba(255,255,255,0.45)' }}>{featured.authorRole}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Price + CTA */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 20, flexWrap: 'wrap' }}>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 10, color: 'rgba(255,255,255,0.4)', letterSpacing: '2px', textTransform: 'uppercase', marginBottom: 4 }}>قیمت گزارش</div>
|
||||||
|
<div style={{ fontSize: 34, fontWeight: 900, color: 'var(--paper)', letterSpacing: '-1px' }}>
|
||||||
|
{featured.price.toLocaleString('fa-IR')} تومان
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
|
||||||
|
<BuyButton label="خرید گزارش" dark />
|
||||||
|
<BuyButton label="خلاصه رایگان" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* RIGHT */}
|
||||||
|
<div style={{ padding: '72px 48px 72px 40px', display: 'flex', flexDirection: 'column' }} className="max-md:p-8 max-md:pt-0">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '3px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
color: 'rgba(255,255,255,0.35)',
|
||||||
|
paddingBottom: 12,
|
||||||
|
borderBottom: '1px solid rgba(255,255,255,0.12)',
|
||||||
|
marginBottom: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
آنچه در این گزارش مییابید
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{items.map((item, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
padding: '18px 0',
|
||||||
|
borderBottom: i < items.length - 1 ? '1px solid rgba(255,255,255,0.08)' : 'none',
|
||||||
|
display: 'flex',
|
||||||
|
gap: 16,
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: 32, fontWeight: 900, color: 'rgba(255,255,255,0.1)', lineHeight: 1, flexShrink: 0, width: 32, textAlign: 'left', direction: 'ltr' }}>
|
||||||
|
{item.num}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 14, fontWeight: 700, color: 'rgba(255,255,255,0.85)', marginBottom: 4 }}>{item.title}</div>
|
||||||
|
<div style={{ fontSize: 12, color: 'rgba(255,255,255,0.4)', lineHeight: 1.65 }}>{item.desc}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { team } from '@/data/team'
|
||||||
|
|
||||||
|
function MemberCard({ member, idx }: { member: (typeof team)[0]; idx: number }) {
|
||||||
|
const borderRight = idx % 2 === 0 ? '1px solid var(--rule-thin)' : 'none'
|
||||||
|
const borderTop = idx >= 2 ? '1px solid var(--rule-thin)' : 'none'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{ padding: '40px', borderRight, borderTop, cursor: 'pointer', transition: 'background 200ms' }}
|
||||||
|
onMouseEnter={e => (e.currentTarget.style.background = 'var(--paper-2)')}
|
||||||
|
onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}
|
||||||
|
className="max-md:border-r-0 max-md:border-t max-md:border-[var(--rule-thin)] max-md:first:border-t-0 max-md:p-6"
|
||||||
|
>
|
||||||
|
<div style={{ width: 56, height: 56, background: 'var(--paper-3)', border: '1px solid var(--rule-thin)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 22, fontWeight: 900, color: 'var(--ink)', marginBottom: 16 }}>
|
||||||
|
{member.initial}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 15, fontWeight: 800, color: 'var(--ink)', letterSpacing: '-0.3px', marginBottom: 3 }}>{member.name}</div>
|
||||||
|
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '1.5px', textTransform: 'uppercase', color: 'var(--red)', marginBottom: 12 }}>{member.role}</div>
|
||||||
|
<p style={{ fontSize: 13, color: 'var(--ink-3)', lineHeight: 1.75, fontWeight: 400, marginBottom: 14 }}>{member.bio}</p>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginBottom: 14 }}>
|
||||||
|
{member.expertise.slice(0, 4).map(tag => (
|
||||||
|
<span key={tag} style={{ fontSize: 9, padding: '2px 7px', background: 'var(--paper-3)', color: 'var(--ink-4)', fontWeight: 600, letterSpacing: '0.5px' }}>{tag}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div style={{ paddingTop: 12, borderTop: '1px solid var(--rule-thin)', fontSize: 11, color: 'var(--ink-5)' }}>
|
||||||
|
<strong style={{ fontSize: 20, fontWeight: 900, color: 'var(--ink)', marginLeft: 4 }}>{member.reportCount}</strong>
|
||||||
|
گزارش منتشرشده
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TeamSection() {
|
||||||
|
return (
|
||||||
|
<section style={{ borderBottom: '2px solid var(--ink)' }}>
|
||||||
|
<div className="px-12 py-20 max-md:px-5 max-md:py-14">
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', borderBottom: '1px solid var(--rule-thin)', marginBottom: '40px' }}>
|
||||||
|
<div style={{ background: 'var(--ink)', color: 'var(--paper)', padding: '8px 20px', fontSize: '9px', fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase' }}>تیم تحریریه</div>
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
<Link to="/team" style={{ fontSize: '10px', fontWeight: 700, letterSpacing: '1.5px', textTransform: 'uppercase', color: 'var(--ink-4)', textDecoration: 'none', padding: '8px 0' }}>همه کارشناسان ←</Link>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }} className="max-md:grid-cols-1">
|
||||||
|
{team.slice(0, 4).map((member, idx) => (
|
||||||
|
<MemberCard key={member.id} member={member} idx={idx} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,757 @@
|
||||||
|
import { useState, useMemo } from 'react'
|
||||||
|
import type React from 'react'
|
||||||
|
import ReactApexChart from 'react-apexcharts'
|
||||||
|
import type { ApexOptions } from 'apexcharts'
|
||||||
|
import { TrendingUp, TrendingDown, Minus } from 'lucide-react'
|
||||||
|
import { marketPrices, priceHistory, globalComparison, monthlyExport } from '@/data/market'
|
||||||
|
import type { MarketPrice, PriceTrend } from '@/data/market'
|
||||||
|
|
||||||
|
/* ─── chart shared theme ─────────────────────────────────── */
|
||||||
|
const CHART_FONT = 'Vazir, sans-serif'
|
||||||
|
const GRID_COLOR = 'rgba(26,23,18,0.08)'
|
||||||
|
const LABEL_COLOR = '#766e68'
|
||||||
|
|
||||||
|
/* ─── time range filter ──────────────────────────────────── */
|
||||||
|
type Range = '1M' | '3M' | '6M' | '1Y'
|
||||||
|
const RANGES: { label: string; value: Range; months: number }[] = [
|
||||||
|
{ label: '۱ ماه', value: '1M', months: 1 },
|
||||||
|
{ label: '۳ ماه', value: '3M', months: 3 },
|
||||||
|
{ label: '۶ ماه', value: '6M', months: 6 },
|
||||||
|
{ label: '۱ سال', value: '1Y', months: 12 },
|
||||||
|
]
|
||||||
|
|
||||||
|
/* ─── trend icon ─────────────────────────────────────────── */
|
||||||
|
function TrendIcon({ trend }: { trend: PriceTrend }) {
|
||||||
|
if (trend === 'up') return <TrendingUp size={12} />
|
||||||
|
if (trend === 'down') return <TrendingDown size={12} />
|
||||||
|
return <Minus size={12} />
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── price card ─────────────────────────────────────────── */
|
||||||
|
function PriceCard({ item }: { item: MarketPrice }) {
|
||||||
|
const isUp = item.trend === 'up'
|
||||||
|
const isDown = item.trend === 'down'
|
||||||
|
const trendColor = isUp ? 'var(--green-ink)' : isDown ? 'var(--red)' : 'var(--ink-4)'
|
||||||
|
const trendBg = isUp
|
||||||
|
? 'rgba(22,101,52,0.08)'
|
||||||
|
: isDown
|
||||||
|
? 'rgba(155,28,28,0.08)'
|
||||||
|
: 'rgba(26,23,18,0.06)'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderLeft: '1px solid var(--rule-thin)',
|
||||||
|
padding: 32,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p style={{ fontSize: 13, color: 'var(--ink-3)', marginBottom: 4 }}>{item.name}</p>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
lineHeight: 1,
|
||||||
|
fontVariantNumeric: 'tabular-nums',
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.value.toLocaleString('fa-IR')}
|
||||||
|
</p>
|
||||||
|
<p style={{ fontSize: 11, color: 'var(--ink-4)' }}>{item.unit}</p>
|
||||||
|
|
||||||
|
{/* Change badge */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 5,
|
||||||
|
padding: '4px 10px',
|
||||||
|
background: trendBg,
|
||||||
|
color: trendColor,
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 700,
|
||||||
|
width: 'fit-content',
|
||||||
|
marginTop: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TrendIcon trend={item.trend} />
|
||||||
|
<span>
|
||||||
|
{item.changePercent > 0 ? '+' : ''}
|
||||||
|
{item.changePercent.toFixed(1)}٪
|
||||||
|
</span>
|
||||||
|
<span style={{ color: trendColor, opacity: 0.7, fontSize: 10 }}>
|
||||||
|
({item.change > 0 ? '+' : ''}
|
||||||
|
{item.change.toLocaleString('fa-IR')})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── comparison tool ────────────────────────────────────── */
|
||||||
|
function ComparisonTool() {
|
||||||
|
const [productA, setProductA] = useState(marketPrices[0].id)
|
||||||
|
const [productB, setProductB] = useState(marketPrices[1].id)
|
||||||
|
|
||||||
|
const itemA = marketPrices.find((m) => m.id === productA)!
|
||||||
|
const itemB = marketPrices.find((m) => m.id === productB)!
|
||||||
|
|
||||||
|
const selectStyle: React.CSSProperties = {
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
padding: '8px 14px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
borderRadius: 0,
|
||||||
|
cursor: 'pointer',
|
||||||
|
outline: 'none',
|
||||||
|
direction: 'rtl',
|
||||||
|
flex: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 16,
|
||||||
|
marginBottom: 32,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
value={productA}
|
||||||
|
onChange={(e) => setProductA(e.target.value)}
|
||||||
|
style={selectStyle}
|
||||||
|
>
|
||||||
|
{marketPrices.map((m) => (
|
||||||
|
<option key={m.id} value={m.id}>
|
||||||
|
{m.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
padding: '0 8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
در برابر
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
value={productB}
|
||||||
|
onChange={(e) => setProductB(e.target.value)}
|
||||||
|
style={selectStyle}
|
||||||
|
>
|
||||||
|
{marketPrices.map((m) => (
|
||||||
|
<option key={m.id} value={m.id}>
|
||||||
|
{m.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Side-by-side comparison */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr auto 1fr',
|
||||||
|
gap: 0,
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ padding: 32, textAlign: 'center' }}>
|
||||||
|
<p style={{ fontSize: 13, color: 'var(--ink-3)', marginBottom: 8 }}>{itemA.name}</p>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
letterSpacing: '-1px',
|
||||||
|
lineHeight: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{itemA.value.toLocaleString('fa-IR')}
|
||||||
|
</p>
|
||||||
|
<p style={{ fontSize: 11, color: 'var(--ink-5)', marginTop: 4 }}>{itemA.unit}</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
marginTop: 12,
|
||||||
|
padding: '4px 12px',
|
||||||
|
background:
|
||||||
|
itemA.trend === 'up'
|
||||||
|
? 'rgba(22,101,52,0.08)'
|
||||||
|
: itemA.trend === 'down'
|
||||||
|
? 'rgba(155,28,28,0.08)'
|
||||||
|
: 'rgba(26,23,18,0.06)',
|
||||||
|
color:
|
||||||
|
itemA.trend === 'up'
|
||||||
|
? 'var(--green-ink)'
|
||||||
|
: itemA.trend === 'down'
|
||||||
|
? 'var(--red)'
|
||||||
|
: 'var(--ink-4)',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 700,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TrendIcon trend={itemA.trend} />
|
||||||
|
{itemA.changePercent > 0 ? '+' : ''}
|
||||||
|
{itemA.changePercent.toFixed(1)}٪
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* VS divider */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
borderLeft: '1px solid var(--rule-thin)',
|
||||||
|
width: 60,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
letterSpacing: '2px',
|
||||||
|
writingMode: 'vertical-rl',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
VS
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ padding: 32, textAlign: 'center' }}>
|
||||||
|
<p style={{ fontSize: 13, color: 'var(--ink-3)', marginBottom: 8 }}>{itemB.name}</p>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
letterSpacing: '-1px',
|
||||||
|
lineHeight: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{itemB.value.toLocaleString('fa-IR')}
|
||||||
|
</p>
|
||||||
|
<p style={{ fontSize: 11, color: 'var(--ink-5)', marginTop: 4 }}>{itemB.unit}</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
marginTop: 12,
|
||||||
|
padding: '4px 12px',
|
||||||
|
background:
|
||||||
|
itemB.trend === 'up'
|
||||||
|
? 'rgba(22,101,52,0.08)'
|
||||||
|
: itemB.trend === 'down'
|
||||||
|
? 'rgba(155,28,28,0.08)'
|
||||||
|
: 'rgba(26,23,18,0.06)',
|
||||||
|
color:
|
||||||
|
itemB.trend === 'up'
|
||||||
|
? 'var(--green-ink)'
|
||||||
|
: itemB.trend === 'down'
|
||||||
|
? 'var(--red)'
|
||||||
|
: 'var(--ink-4)',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 700,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TrendIcon trend={itemB.trend} />
|
||||||
|
{itemB.changePercent > 0 ? '+' : ''}
|
||||||
|
{itemB.changePercent.toFixed(1)}٪
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: 16,
|
||||||
|
fontStyle: 'italic',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
نمودار مقایسهای در نسخههای آینده تکمیل میشود
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── main component ─────────────────────────────────────── */
|
||||||
|
export default function Market() {
|
||||||
|
const [priceRange, setPriceRange] = useState<Range>('1Y')
|
||||||
|
|
||||||
|
/* Filter price history based on selected range */
|
||||||
|
const filteredHistory = useMemo(() => {
|
||||||
|
const range = RANGES.find((r) => r.value === priceRange)!
|
||||||
|
return priceHistory.slice(-range.months)
|
||||||
|
}, [priceRange])
|
||||||
|
|
||||||
|
/* Line chart options */
|
||||||
|
const lineOptions: ApexOptions = {
|
||||||
|
chart: {
|
||||||
|
type: 'area',
|
||||||
|
toolbar: { show: false },
|
||||||
|
fontFamily: CHART_FONT,
|
||||||
|
background: 'transparent',
|
||||||
|
animations: { enabled: true, speed: 400 },
|
||||||
|
},
|
||||||
|
colors: ['#9b1c1c'],
|
||||||
|
stroke: { width: 2, curve: 'smooth' },
|
||||||
|
fill: {
|
||||||
|
type: 'gradient',
|
||||||
|
gradient: {
|
||||||
|
shadeIntensity: 1,
|
||||||
|
opacityFrom: 0.15,
|
||||||
|
opacityTo: 0,
|
||||||
|
stops: [0, 100],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
borderColor: GRID_COLOR,
|
||||||
|
strokeDashArray: 3,
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: filteredHistory.map((p) => p.month),
|
||||||
|
labels: {
|
||||||
|
style: { fontFamily: CHART_FONT, colors: LABEL_COLOR, fontSize: '11px' },
|
||||||
|
rotate: -35,
|
||||||
|
},
|
||||||
|
axisBorder: { color: GRID_COLOR },
|
||||||
|
axisTicks: { color: GRID_COLOR },
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
opposite: true,
|
||||||
|
labels: {
|
||||||
|
style: { fontFamily: CHART_FONT, colors: LABEL_COLOR, fontSize: '11px' },
|
||||||
|
formatter: (val: number) => val.toLocaleString('fa-IR'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: 'light',
|
||||||
|
style: { fontFamily: CHART_FONT, fontSize: '12px' },
|
||||||
|
y: { formatter: (val: number) => val.toLocaleString('fa-IR') + ' تومان/کیلو' },
|
||||||
|
},
|
||||||
|
dataLabels: { enabled: false },
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineSeries = [
|
||||||
|
{
|
||||||
|
name: 'شمش فولاد',
|
||||||
|
data: filteredHistory.map((p) => p.price),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/* Bar chart options */
|
||||||
|
const barOptions: ApexOptions = {
|
||||||
|
chart: {
|
||||||
|
type: 'bar',
|
||||||
|
toolbar: { show: false },
|
||||||
|
fontFamily: CHART_FONT,
|
||||||
|
background: 'transparent',
|
||||||
|
animations: { enabled: true, speed: 400 },
|
||||||
|
},
|
||||||
|
colors: ['#2e2a25'],
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
borderRadius: 0,
|
||||||
|
columnWidth: '65%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
borderColor: GRID_COLOR,
|
||||||
|
strokeDashArray: 3,
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: monthlyExport.map((e) => e.month),
|
||||||
|
labels: {
|
||||||
|
style: { fontFamily: CHART_FONT, colors: LABEL_COLOR, fontSize: '11px' },
|
||||||
|
rotate: -35,
|
||||||
|
},
|
||||||
|
axisBorder: { color: GRID_COLOR },
|
||||||
|
axisTicks: { color: GRID_COLOR },
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
opposite: true,
|
||||||
|
labels: {
|
||||||
|
style: { fontFamily: CHART_FONT, colors: LABEL_COLOR, fontSize: '11px' },
|
||||||
|
formatter: (val: number) => (val / 1000).toFixed(0) + 'k',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: 'light',
|
||||||
|
style: { fontFamily: CHART_FONT, fontSize: '12px' },
|
||||||
|
y: {
|
||||||
|
formatter: (val: number) =>
|
||||||
|
(val / 1000).toLocaleString('fa-IR') + ' هزار تن',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: { enabled: false },
|
||||||
|
}
|
||||||
|
|
||||||
|
const barSeries = [
|
||||||
|
{
|
||||||
|
name: 'صادرات',
|
||||||
|
data: monthlyExport.map((e) => e.tonnage),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/* Section heading style */
|
||||||
|
const SectionHeading = ({
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
subtitle?: string
|
||||||
|
children?: React.ReactNode
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: 32,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h2
|
||||||
|
style={{
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
{subtitle && (
|
||||||
|
<p style={{ fontSize: 12, color: 'var(--ink-5)', marginTop: 4 }}>{subtitle}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ direction: 'rtl' }}>
|
||||||
|
{/* ─── Page Header ───────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: 1280,
|
||||||
|
margin: '0 auto',
|
||||||
|
padding: '48px 48px 32px',
|
||||||
|
borderBottom: '3px solid var(--ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: 36,
|
||||||
|
fontWeight: 900,
|
||||||
|
letterSpacing: '-1px',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
marginBottom: 6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
داشبورد بازار
|
||||||
|
</h1>
|
||||||
|
<p style={{ fontSize: 15, fontWeight: 300, color: 'var(--ink-3)' }}>
|
||||||
|
قیمتهای لحظهای، روند تاریخی و تحلیل صادرات فولاد ایران
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
padding: '6px 14px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: 7,
|
||||||
|
height: 7,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'var(--green-ink)',
|
||||||
|
display: 'inline-block',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
آخرین بروزرسانی: ۴ خرداد ۱۴۰۴
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Section 1: Price Grid ──────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: '2px solid var(--ink)',
|
||||||
|
padding: '48px 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '0 48px' }}>
|
||||||
|
<SectionHeading
|
||||||
|
title="قیمتهای امروز"
|
||||||
|
subtitle="قیمت بازار داخلی — تومان به کیلوگرم / دلار به تن"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||||
|
borderTop: '1px solid var(--rule-thin)',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
className="price-grid"
|
||||||
|
>
|
||||||
|
{marketPrices.map((item) => (
|
||||||
|
<PriceCard key={item.id} item={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Section 2: Price History Chart ────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: '2px solid var(--ink)',
|
||||||
|
padding: '64px 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '0 48px' }}>
|
||||||
|
<SectionHeading
|
||||||
|
title="روند قیمت شمش فولاد"
|
||||||
|
subtitle="تومان به کیلوگرم"
|
||||||
|
>
|
||||||
|
{/* Range selector */}
|
||||||
|
<div style={{ display: 'flex', gap: 0 }}>
|
||||||
|
{RANGES.map((r, rIdx) => (
|
||||||
|
<button
|
||||||
|
key={r.value}
|
||||||
|
onClick={() => setPriceRange(r.value)}
|
||||||
|
style={{
|
||||||
|
padding: '6px 16px',
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 700,
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
borderLeft: rIdx === 0 ? '1px solid var(--ink)' : 'none',
|
||||||
|
background: priceRange === r.value ? 'var(--ink)' : 'transparent',
|
||||||
|
color: priceRange === r.value ? 'var(--paper)' : 'var(--ink)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
borderRadius: 0,
|
||||||
|
transition: 'all 120ms',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{r.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SectionHeading>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
padding: '24px 8px 8px',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ReactApexChart
|
||||||
|
options={lineOptions}
|
||||||
|
series={lineSeries}
|
||||||
|
type="area"
|
||||||
|
height={320}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Section 3: Global Comparison Table ─────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: '2px solid var(--ink)',
|
||||||
|
padding: '64px 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '0 48px' }}>
|
||||||
|
<SectionHeading
|
||||||
|
title="مقایسه جهانی"
|
||||||
|
subtitle="قیمت شمش فولاد در بازارهای اصلی"
|
||||||
|
/>
|
||||||
|
<table
|
||||||
|
style={{
|
||||||
|
borderCollapse: 'collapse',
|
||||||
|
width: '100%',
|
||||||
|
fontSize: 13,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<thead>
|
||||||
|
<tr style={{ background: 'var(--ink)', color: 'var(--paper)' }}>
|
||||||
|
{['کشور', 'قیمت شمش (دلار/تن)', 'ارز', 'مقایسه با ایران'].map((h) => (
|
||||||
|
<th
|
||||||
|
key={h}
|
||||||
|
style={{
|
||||||
|
padding: '14px 20px',
|
||||||
|
textAlign: 'right',
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: 11,
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{h}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{globalComparison.map((item, i) => {
|
||||||
|
const isIran = item.country === 'ایران'
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
key={item.country}
|
||||||
|
style={{
|
||||||
|
background: i % 2 === 0 ? 'var(--paper)' : 'var(--paper-2)',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<td style={{ padding: '14px 20px', fontWeight: isIran ? 700 : 400 }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||||
|
<span style={{ fontSize: 18 }}>{item.flag}</span>
|
||||||
|
{item.country}
|
||||||
|
{isIran && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'var(--paper)',
|
||||||
|
background: 'var(--red)',
|
||||||
|
padding: '2px 6px',
|
||||||
|
letterSpacing: '1px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ایران
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style={{ padding: '14px 20px', fontWeight: 700 }}>
|
||||||
|
{item.currency === 'USD'
|
||||||
|
? `$${item.price.toLocaleString()}`
|
||||||
|
: item.price.toLocaleString('fa-IR') + ' تومان/کیلو'}
|
||||||
|
</td>
|
||||||
|
<td style={{ padding: '14px 20px', color: 'var(--ink-4)' }}>
|
||||||
|
{item.currency}
|
||||||
|
</td>
|
||||||
|
<td style={{ padding: '14px 20px' }}>
|
||||||
|
{isIran ? (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 10,
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
مبنا
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: item.price > 500 ? 'var(--green-ink)' : 'var(--red)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.price > 500 ? 'بالاتر از میانگین' : 'پایینتر از میانگین'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Section 4: Export Bar Chart ─────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: '2px solid var(--ink)',
|
||||||
|
padding: '64px 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '0 48px' }}>
|
||||||
|
<SectionHeading
|
||||||
|
title="صادرات ماهانه"
|
||||||
|
subtitle="هزار تن — بر اساس دادههای گمرک ایران"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
padding: '24px 8px 8px',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ReactApexChart options={barOptions} series={barSeries} type="bar" height={280} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Section 5: Comparison Tool ──────────────────── */}
|
||||||
|
<div style={{ padding: '64px 0' }}>
|
||||||
|
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '0 48px' }}>
|
||||||
|
<SectionHeading
|
||||||
|
title="ابزار مقایسه"
|
||||||
|
subtitle="مقایسه قیمت لحظهای دو محصول"
|
||||||
|
/>
|
||||||
|
<ComparisonTool />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Responsive styles ─────────────────────────── */}
|
||||||
|
<style>{`
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.price-grid {
|
||||||
|
grid-template-columns: 1fr 1fr !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.price-grid {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,721 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { Link, useParams, Navigate } from 'react-router-dom'
|
||||||
|
import { Lock, Download, FileText, ChevronRight, Tag, User } from 'lucide-react'
|
||||||
|
import { reports } from '@/data/reports'
|
||||||
|
import type { Report } from '@/data/reports'
|
||||||
|
|
||||||
|
/* ─── helpers ────────────────────────────────────────────── */
|
||||||
|
function formatPrice(price: number): string {
|
||||||
|
return price.toLocaleString('fa-IR') + ' تومان'
|
||||||
|
}
|
||||||
|
|
||||||
|
const TYPE_LABELS: Record<string, string> = {
|
||||||
|
quarterly: 'گزارش فصلی',
|
||||||
|
flash: 'Flash Report',
|
||||||
|
risk: 'گزارش ریسک',
|
||||||
|
free: 'گزارش رایگان',
|
||||||
|
special: 'گزارش ویژه',
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Persian lorem paragraphs ──────────────────────────── */
|
||||||
|
const LOREM_PARAGRAPHS = [
|
||||||
|
'صنعت فولاد ایران در دورهای از تحولات عمیق ساختاری قرار دارد. ظرفیت تولیدی کشور در سالهای اخیر به رکوردهای تاریخی دست یافته، اما همزمان چالشهای پیچیدهای در حوزه صادرات، تأمین مالی و دسترسی به بازارهای جهانی در برابر صنعت قرار گرفته است. این گزارش با بهرهگیری از دادههای اولیه و تحلیلهای کمی، وضعیت کنونی را از زوایای مختلف بررسی میکند.',
|
||||||
|
'در این مطالعه، رویکرد ترکیبی از تحلیل سریهای زمانی قیمتی، مدلسازی اقتصادسنجی و تحلیل سناریو بهکار رفته است. دادههای مورد استفاده از منابع معتبر بینالمللی از جمله World Steel Association، Platts و گزارشهای رسمی وزارت صنعت، معدن و تجارت جمعآوری شدهاند. دوره تحلیل پنج سال اخیر را در بر میگیرد.',
|
||||||
|
'یافتههای کلیدی این گزارش نشان میدهد که تقاضای داخلی فولاد در بخش ساختمان و زیرساخت در میانمدت روند کاهشی دارد، در حالی که تقاضای صنعتی و ماشینسازی رشد ملایمی را تجربه خواهد کرد. این تحول در ترکیب تقاضا مستلزم بازنگری در استراتژی محصول کارخانههای فولادی کشور است.',
|
||||||
|
'توصیههای سیاستگذاری این گزارش در سه محور اصلی ارائه میشود: اول، اصلاح ساختار قیمتگذاری انرژی و مواد اولیه برای بهبود رقابتپذیری صادراتی؛ دوم، توسعه مسیرهای دسترسی به بازارهای هدف جایگزین در آفریقا و آسیای جنوبشرقی؛ سوم، تقویت ظرفیت مذاکراتی بخش خصوصی در قراردادهای بینالمللی.',
|
||||||
|
]
|
||||||
|
|
||||||
|
const TOC_ITEMS = [
|
||||||
|
'خلاصه مدیریتی',
|
||||||
|
'بررسی وضعیت کنونی بازار',
|
||||||
|
'تحلیل روندهای عرضه و تقاضا',
|
||||||
|
'مدلسازی سناریوهای آینده',
|
||||||
|
'تأثیرات ژئوپلیتیک و محیط کسبوکار',
|
||||||
|
'تحلیل رقابتپذیری و موقعیت ایران',
|
||||||
|
'توصیههای سیاستگذاری و اجرایی',
|
||||||
|
'پیوستها: دادهها و روششناسی',
|
||||||
|
]
|
||||||
|
|
||||||
|
/* ─── related reports sidebar ────────────────────────────── */
|
||||||
|
function RelatedReports({ current }: { current: Report }) {
|
||||||
|
const related = reports
|
||||||
|
.filter((r) => r.id !== current.id && r.category === current.category)
|
||||||
|
.slice(0, 3)
|
||||||
|
|
||||||
|
if (related.length === 0) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: 16 }}>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '2px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
marginBottom: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
گزارشهای مرتبط
|
||||||
|
</p>
|
||||||
|
{related.map((r, i) => (
|
||||||
|
<div
|
||||||
|
key={r.id}
|
||||||
|
style={{
|
||||||
|
borderBottom: i < related.length - 1 ? '1px solid var(--rule-thin)' : 'none',
|
||||||
|
padding: '12px 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to={`/reports/${r.id}`}
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
lineHeight: 1.4,
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{r.title}
|
||||||
|
</Link>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: r.isFree ? 'var(--green-ink)' : 'var(--ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{r.isFree ? 'رایگان' : formatPrice(r.price)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── main component ─────────────────────────────────────── */
|
||||||
|
export default function ReportDetail() {
|
||||||
|
const { id } = useParams<{ id: string }>()
|
||||||
|
const [buyHovered, setBuyHovered] = useState(false)
|
||||||
|
const [dlHovered, setDlHovered] = useState(false)
|
||||||
|
|
||||||
|
const report = reports.find((r) => r.id === id)
|
||||||
|
|
||||||
|
if (!report) return <Navigate to="/reports" replace />
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: 1280,
|
||||||
|
margin: '0 auto',
|
||||||
|
padding: '48px 48px 80px',
|
||||||
|
direction: 'rtl',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* ─── Breadcrumb ────────────────────────────────── */}
|
||||||
|
<nav
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
marginBottom: 40,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to="/reports"
|
||||||
|
style={{ fontSize: 11, color: 'var(--ink-5)', textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
گزارشها
|
||||||
|
</Link>
|
||||||
|
<ChevronRight size={10} style={{ color: 'var(--ink-5)', transform: 'scaleX(-1)' }} />
|
||||||
|
<Link
|
||||||
|
to={`/reports?category=${report.category}`}
|
||||||
|
style={{ fontSize: 11, color: 'var(--ink-5)', textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
{report.category}
|
||||||
|
</Link>
|
||||||
|
<ChevronRight size={10} style={{ color: 'var(--ink-5)', transform: 'scaleX(-1)' }} />
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
fontWeight: 600,
|
||||||
|
maxWidth: 300,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.title}
|
||||||
|
</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* ─── Two-column layout ─────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '2fr 1fr',
|
||||||
|
gap: 48,
|
||||||
|
alignItems: 'start',
|
||||||
|
}}
|
||||||
|
className="report-detail-grid"
|
||||||
|
>
|
||||||
|
{/* ═══ LEFT: Main content ══════════════════════════ */}
|
||||||
|
<div>
|
||||||
|
{/* Kicker */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16 }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '2px',
|
||||||
|
color: 'var(--red)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{TYPE_LABELS[report.type] || report.type} · {report.category}
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
height: 1,
|
||||||
|
background: 'var(--red)',
|
||||||
|
opacity: 0.3,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Headline */}
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: 'clamp(28px, 3vw, 40px)',
|
||||||
|
fontWeight: 900,
|
||||||
|
lineHeight: 1.2,
|
||||||
|
letterSpacing: '-1px',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
marginBottom: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.title}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* Meta row */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 16,
|
||||||
|
marginBottom: 24,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Author initial */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
background: 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 700,
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.authorInitial}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style={{ fontSize: 12, fontWeight: 700, color: 'var(--ink)', lineHeight: 1.3 }}>
|
||||||
|
{report.author}
|
||||||
|
</p>
|
||||||
|
<p style={{ fontSize: 11, color: 'var(--ink-5)', lineHeight: 1.3 }}>
|
||||||
|
{report.authorRole}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 1,
|
||||||
|
height: 28,
|
||||||
|
background: 'var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-5)' }}>{report.publishDate}</span>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 1,
|
||||||
|
height: 28,
|
||||||
|
background: 'var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-5)' }}>
|
||||||
|
<FileText size={11} style={{ display: 'inline', marginLeft: 4 }} />
|
||||||
|
{report.pages} صفحه
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Thick rule */}
|
||||||
|
<div style={{ height: 2, background: 'var(--ink)', marginBottom: 32 }} />
|
||||||
|
|
||||||
|
{/* Abstract */}
|
||||||
|
<section style={{ marginBottom: 40 }}>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '2.5px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
marginBottom: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
چکیده
|
||||||
|
</p>
|
||||||
|
{LOREM_PARAGRAPHS.map((para, i) => (
|
||||||
|
<p
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: 300,
|
||||||
|
lineHeight: 1.85,
|
||||||
|
color: 'var(--ink-2)',
|
||||||
|
marginBottom: 18,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i === 0 ? report.summary : para}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Table of Contents */}
|
||||||
|
<section style={{ marginBottom: 40 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderTop: '2px solid var(--ink)',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
padding: '14px 0',
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '2.5px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
فهرست مطالب
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ol
|
||||||
|
style={{
|
||||||
|
listStyle: 'none',
|
||||||
|
padding: 0,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{TOC_ITEMS.map((item, i) => (
|
||||||
|
<li
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
gap: 12,
|
||||||
|
padding: '10px 0',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
fontSize: 13,
|
||||||
|
color: 'var(--ink-2)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
minWidth: 20,
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(i + 1).toLocaleString('fa-IR')}
|
||||||
|
</span>
|
||||||
|
<span>{item}</span>
|
||||||
|
<span style={{ flex: 1, borderBottom: '1px dotted var(--rule-thin)', marginBottom: 3 }} />
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-5)' }}>
|
||||||
|
{((i + 1) * 8 + 3).toLocaleString('fa-IR')}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Free preview + paywall */}
|
||||||
|
{!report.isFree && (
|
||||||
|
<section style={{ marginBottom: 40 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderTop: '2px solid var(--ink)',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
padding: '14px 0',
|
||||||
|
marginBottom: 16,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '2.5px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
پیشنمایش رایگان
|
||||||
|
</p>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '1px',
|
||||||
|
color: 'var(--red)',
|
||||||
|
border: '1px solid var(--red)',
|
||||||
|
padding: '2px 8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
۳ صفحه اول
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ position: 'relative', overflow: 'hidden' }}>
|
||||||
|
{/* Fake preview content */}
|
||||||
|
<div style={{ padding: '20px 0' }}>
|
||||||
|
{[90, 100, 75, 100, 60, 100, 85, 40].map((width, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
height: 12,
|
||||||
|
background: 'var(--rule-thin)',
|
||||||
|
marginBottom: 10,
|
||||||
|
width: `${width}%`,
|
||||||
|
borderRadius: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* Gradient overlay */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: '70%',
|
||||||
|
background:
|
||||||
|
'linear-gradient(to bottom, transparent 0%, var(--paper) 80%)',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
paddingBottom: 20,
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Lock size={18} style={{ color: 'var(--ink-4)' }} />
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
برای دسترسی کامل اشتراک بگیرید
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
to="/subscribe"
|
||||||
|
style={{
|
||||||
|
padding: '10px 28px',
|
||||||
|
background: 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 700,
|
||||||
|
textDecoration: 'none',
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
مشاهده طرحهای اشتراک
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Tags */}
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 16 }}>
|
||||||
|
<Tag size={12} style={{ color: 'var(--ink-5)', marginTop: 2, flexShrink: 0 }} />
|
||||||
|
{report.tags.map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag}
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
padding: '3px 10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ═══ RIGHT: Sidebar ══════════════════════════════ */}
|
||||||
|
<div
|
||||||
|
style={{ position: 'sticky', top: 76 }}
|
||||||
|
className="report-sidebar"
|
||||||
|
>
|
||||||
|
{/* Price card */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: '2px solid var(--ink)',
|
||||||
|
padding: 32,
|
||||||
|
background: 'var(--paper)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '2px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
marginBottom: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
قیمت گزارش
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Price */}
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 36,
|
||||||
|
fontWeight: 900,
|
||||||
|
letterSpacing: '-1px',
|
||||||
|
color: report.isFree ? 'var(--green-ink)' : 'var(--ink)',
|
||||||
|
lineHeight: 1,
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.isFree ? 'رایگان' : formatPrice(report.price)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Meta */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderTop: '1px solid var(--rule-thin)',
|
||||||
|
paddingTop: 12,
|
||||||
|
marginBottom: 20,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-5)' }}>تعداد صفحات</span>
|
||||||
|
<span style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink)' }}>
|
||||||
|
{report.pages} صفحه
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-5)' }}>تاریخ انتشار</span>
|
||||||
|
<span style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink)' }}>
|
||||||
|
{report.publishDate}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-5)' }}>فرمت</span>
|
||||||
|
<span style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink)' }}>PDF</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Buy button */}
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setBuyHovered(true)}
|
||||||
|
onMouseLeave={() => setBuyHovered(false)}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '14px 0',
|
||||||
|
background: buyHovered ? 'var(--red)' : 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
border: buyHovered ? '1px solid var(--red)' : '1px solid var(--ink)',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 700,
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
borderRadius: 0,
|
||||||
|
transition: 'background 150ms, border-color 150ms',
|
||||||
|
letterSpacing: '0.3px',
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.isFree ? 'دانلود رایگان' : 'خرید و دانلود'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Download summary button */}
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setDlHovered(true)}
|
||||||
|
onMouseLeave={() => setDlHovered(false)}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '12px 0',
|
||||||
|
background: dlHovered ? 'var(--paper-2)' : 'transparent',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
borderRadius: 0,
|
||||||
|
transition: 'background 150ms',
|
||||||
|
letterSpacing: '0.3px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Download size={13} />
|
||||||
|
دانلود خلاصه
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Note */}
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 10,
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
فاکتور رسمی صادر میشود
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Author card */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
padding: 24,
|
||||||
|
marginTop: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
gap: 14,
|
||||||
|
marginBottom: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
background: 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 700,
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.authorInitial}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)' }}>
|
||||||
|
{report.author}
|
||||||
|
</p>
|
||||||
|
<p style={{ fontSize: 11, color: 'var(--ink-5)', marginTop: 2 }}>
|
||||||
|
{report.authorRole}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 300,
|
||||||
|
lineHeight: 1.7,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
marginBottom: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
متخصص در تحلیل بازارهای فولاد با بیش از یک دهه تجربه در پژوهش و سیاستگذاری صنعتی.
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
to="/team"
|
||||||
|
style={{
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '1px',
|
||||||
|
color: 'var(--red)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<User size={10} />
|
||||||
|
مشاهده پروفایل کامل
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Related reports */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
padding: 24,
|
||||||
|
marginTop: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RelatedReports current={report} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Responsive styles ─────────────────────────── */}
|
||||||
|
<style>{`
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.report-detail-grid {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
}
|
||||||
|
.report-sidebar {
|
||||||
|
position: static !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.report-sticky-buy {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 20;
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: var(--paper);
|
||||||
|
border-top: 2px solid var(--ink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,581 @@
|
||||||
|
import { useState, useMemo } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Search, ChevronRight, ChevronLeft } from 'lucide-react'
|
||||||
|
import { reports } from '@/data/reports'
|
||||||
|
import type { ReportCategory, ReportType } from '@/data/reports'
|
||||||
|
|
||||||
|
/* ─── constants ──────────────────────────────────────────── */
|
||||||
|
const CATEGORIES: { label: string; value: ReportCategory | 'همه' }[] = [
|
||||||
|
{ label: 'همه', value: 'همه' },
|
||||||
|
{ label: 'بازار جهانی', value: 'بازار جهانی' },
|
||||||
|
{ label: 'سیاستگذاری', value: 'سیاستگذاری' },
|
||||||
|
{ label: 'انرژی و ESG', value: 'انرژی و ESG' },
|
||||||
|
{ label: 'صادرات', value: 'صادرات' },
|
||||||
|
{ label: 'تولید داخلی', value: 'تولید داخلی' },
|
||||||
|
{ label: 'ریسک و بحران', value: 'ریسک و بحران' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const TYPES: { label: string; value: ReportType | 'همه' | 'رایگان' }[] = [
|
||||||
|
{ label: 'همه', value: 'همه' },
|
||||||
|
{ label: 'رایگان', value: 'رایگان' },
|
||||||
|
{ label: 'Flash Report', value: 'flash' },
|
||||||
|
{ label: 'فصلی', value: 'quarterly' },
|
||||||
|
{ label: 'گزارش ویژه', value: 'special' },
|
||||||
|
{ label: 'ریسک', value: 'risk' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const SORT_OPTIONS = [
|
||||||
|
{ label: 'جدیدترین', value: 'newest' },
|
||||||
|
{ label: 'قدیمیترین', value: 'oldest' },
|
||||||
|
{ label: 'گرانترین', value: 'price-desc' },
|
||||||
|
{ label: 'ارزانترین', value: 'price-asc' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const TYPE_COLORS: Record<ReportType, string> = {
|
||||||
|
quarterly: '#166534',
|
||||||
|
flash: '#9b1c1c',
|
||||||
|
risk: '#92400e',
|
||||||
|
free: '#1d4ed8',
|
||||||
|
special: '#4a1d96',
|
||||||
|
}
|
||||||
|
|
||||||
|
const TYPE_LABELS: Record<ReportType, string> = {
|
||||||
|
quarterly: 'فصلی',
|
||||||
|
flash: 'فلش',
|
||||||
|
risk: 'ریسک',
|
||||||
|
free: 'رایگان',
|
||||||
|
special: 'ویژه',
|
||||||
|
}
|
||||||
|
|
||||||
|
const PAGE_SIZE = 9
|
||||||
|
|
||||||
|
/* ─── price formatter ────────────────────────────────────── */
|
||||||
|
function formatPrice(price: number): string {
|
||||||
|
if (price === 0) return 'رایگان'
|
||||||
|
return price.toLocaleString('fa-IR') + ' تومان'
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── report card ────────────────────────────────────────── */
|
||||||
|
function ReportCard({ report }: { report: (typeof reports)[0] }) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
style={{
|
||||||
|
borderLeft: '1px solid var(--rule-thin)',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
padding: '32px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 0,
|
||||||
|
background: 'var(--paper)',
|
||||||
|
cursor: 'default',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* top meta */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: 6,
|
||||||
|
height: 6,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: TYPE_COLORS[report.type],
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '1.5px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.category}
|
||||||
|
</span>
|
||||||
|
{report.isFree && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginLeft: 0,
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '1px',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
background: 'var(--green-ink)',
|
||||||
|
padding: '2px 6px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
رایگان
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginRight: report.isFree ? 0 : 'auto',
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '1px',
|
||||||
|
color: TYPE_COLORS[report.type],
|
||||||
|
border: `1px solid ${TYPE_COLORS[report.type]}`,
|
||||||
|
padding: '2px 6px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{TYPE_LABELS[report.type]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* title */}
|
||||||
|
<Link
|
||||||
|
to={`/reports/${report.id}`}
|
||||||
|
style={{
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: 800,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
letterSpacing: '-0.3px',
|
||||||
|
color: hovered ? 'var(--red)' : 'var(--ink)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
marginBottom: 10,
|
||||||
|
display: 'block',
|
||||||
|
transition: 'color 120ms',
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
>
|
||||||
|
{report.title}
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* meta row */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-5)' }}>{report.author}</span>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-6)' }}>·</span>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-5)' }}>{report.publishDate}</span>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-6)' }}>·</span>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-5)' }}>{report.pages} صفحه</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* divider */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: 1,
|
||||||
|
background: 'var(--rule-thin)',
|
||||||
|
margin: '16px 0',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* price row */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: report.isFree ? 'var(--green-ink)' : 'var(--ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatPrice(report.price)}
|
||||||
|
</span>
|
||||||
|
<Link
|
||||||
|
to={`/reports/${report.id}`}
|
||||||
|
style={{
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '1px',
|
||||||
|
color: 'var(--red)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
borderBottom: '1px solid var(--red)',
|
||||||
|
paddingBottom: 1,
|
||||||
|
transition: 'opacity 120ms',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.isFree ? 'دانلود' : 'خرید'}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── main component ─────────────────────────────────────── */
|
||||||
|
export default function Reports() {
|
||||||
|
const [activeCategory, setActiveCategory] = useState<ReportCategory | 'همه'>('همه')
|
||||||
|
const [activeType, setActiveType] = useState<ReportType | 'همه' | 'رایگان'>('همه')
|
||||||
|
const [sortBy, setSortBy] = useState('newest')
|
||||||
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
|
||||||
|
const filtered = useMemo(() => {
|
||||||
|
let result = [...reports]
|
||||||
|
|
||||||
|
if (activeCategory !== 'همه') {
|
||||||
|
result = result.filter((r) => r.category === activeCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeType !== 'همه') {
|
||||||
|
if (activeType === 'رایگان') {
|
||||||
|
result = result.filter((r) => r.isFree)
|
||||||
|
} else {
|
||||||
|
result = result.filter((r) => r.type === activeType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchQuery.trim()) {
|
||||||
|
const q = searchQuery.trim().toLowerCase()
|
||||||
|
result = result.filter(
|
||||||
|
(r) =>
|
||||||
|
r.title.toLowerCase().includes(q) ||
|
||||||
|
r.author.toLowerCase().includes(q) ||
|
||||||
|
r.category.toLowerCase().includes(q) ||
|
||||||
|
r.tags.some((t) => t.toLowerCase().includes(q))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (sortBy) {
|
||||||
|
case 'oldest':
|
||||||
|
result = result.reverse()
|
||||||
|
break
|
||||||
|
case 'price-desc':
|
||||||
|
result = result.sort((a, b) => b.price - a.price)
|
||||||
|
break
|
||||||
|
case 'price-asc':
|
||||||
|
result = result.sort((a, b) => a.price - b.price)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}, [activeCategory, activeType, sortBy, searchQuery])
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(filtered.length / PAGE_SIZE)
|
||||||
|
const paged = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
|
||||||
|
|
||||||
|
// Reset to page 1 on filter change
|
||||||
|
const handleCategoryChange = (cat: ReportCategory | 'همه') => {
|
||||||
|
setActiveCategory(cat)
|
||||||
|
setPage(1)
|
||||||
|
}
|
||||||
|
const handleTypeChange = (type: ReportType | 'همه' | 'رایگان') => {
|
||||||
|
setActiveType(type)
|
||||||
|
setPage(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: 1280,
|
||||||
|
margin: '0 auto',
|
||||||
|
padding: '64px 48px',
|
||||||
|
direction: 'rtl',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Page header */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: '3px solid var(--ink)',
|
||||||
|
paddingBottom: 24,
|
||||||
|
marginBottom: 48,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: 36,
|
||||||
|
fontWeight: 900,
|
||||||
|
letterSpacing: '-1px',
|
||||||
|
marginBottom: 6,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
گزارشها
|
||||||
|
</h1>
|
||||||
|
<p style={{ fontSize: 15, fontWeight: 300, color: 'var(--ink-3)' }}>
|
||||||
|
تحلیلهای تخصصی بازار فولاد ایران و جهان — گزارشهای فصلی، ریسک، و فلش
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Filter bar ──────────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'sticky',
|
||||||
|
top: 56,
|
||||||
|
background: 'var(--paper)',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
paddingBottom: 16,
|
||||||
|
marginBottom: 48,
|
||||||
|
zIndex: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Category pills */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 6,
|
||||||
|
overflowX: 'auto',
|
||||||
|
paddingBottom: 10,
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{CATEGORIES.map((cat) => (
|
||||||
|
<button
|
||||||
|
key={cat.value}
|
||||||
|
onClick={() => handleCategoryChange(cat.value as ReportCategory | 'همه')}
|
||||||
|
style={{
|
||||||
|
padding: '6px 16px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
borderRadius: 0,
|
||||||
|
border:
|
||||||
|
activeCategory === cat.value
|
||||||
|
? '1px solid var(--ink)'
|
||||||
|
: '1px solid var(--ink-4)',
|
||||||
|
background: activeCategory === cat.value ? 'var(--ink)' : 'transparent',
|
||||||
|
color: activeCategory === cat.value ? 'var(--paper)' : 'var(--ink-4)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
transition: 'all 120ms',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cat.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Second row: type + sort + search */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
paddingTop: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Type filter */}
|
||||||
|
<div style={{ display: 'flex', gap: 6, overflowX: 'auto', scrollbarWidth: 'none' }}>
|
||||||
|
{TYPES.map((t) => (
|
||||||
|
<button
|
||||||
|
key={t.value}
|
||||||
|
onClick={() => handleTypeChange(t.value as ReportType | 'همه' | 'رایگان')}
|
||||||
|
style={{
|
||||||
|
padding: '5px 12px',
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 600,
|
||||||
|
borderRadius: 0,
|
||||||
|
border:
|
||||||
|
activeType === t.value
|
||||||
|
? '1px solid var(--ink)'
|
||||||
|
: '1px solid var(--rule-thin)',
|
||||||
|
background: activeType === t.value ? 'var(--ink)' : 'transparent',
|
||||||
|
color: activeType === t.value ? 'var(--paper)' : 'var(--ink-5)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
transition: 'all 120ms',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
|
||||||
|
{/* Sort */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
|
<span style={{ fontSize: 11, color: 'var(--ink-5)', whiteSpace: 'nowrap' }}>
|
||||||
|
مرتبسازی
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
value={sortBy}
|
||||||
|
onChange={(e) => setSortBy(e.target.value)}
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
padding: '6px 12px',
|
||||||
|
fontSize: 11,
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
borderRadius: 0,
|
||||||
|
cursor: 'pointer',
|
||||||
|
outline: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{SORT_OPTIONS.map((o) => (
|
||||||
|
<option key={o.value} value={o.value}>
|
||||||
|
{o.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search */}
|
||||||
|
<div style={{ position: 'relative' }}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="جستجو..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearchQuery(e.target.value)
|
||||||
|
setPage(1)
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
padding: '8px 36px 8px 12px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
background: 'transparent',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
borderRadius: 0,
|
||||||
|
outline: 'none',
|
||||||
|
width: 200,
|
||||||
|
direction: 'rtl',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Search
|
||||||
|
size={14}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: 10,
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Report grid ─────────────────────────────────── */}
|
||||||
|
{paged.length > 0 ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
borderLeft: 'none',
|
||||||
|
borderBottom: 'none',
|
||||||
|
}}
|
||||||
|
className="reports-grid"
|
||||||
|
>
|
||||||
|
{paged.map((report) => (
|
||||||
|
<ReportCard key={report.id} report={report} />
|
||||||
|
))}
|
||||||
|
{/* Fill empty cells in last row to keep grid consistent */}
|
||||||
|
{paged.length % 3 !== 0 &&
|
||||||
|
Array.from({ length: 3 - (paged.length % 3) }).map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={`empty-${i}`}
|
||||||
|
style={{
|
||||||
|
borderLeft: '1px solid var(--rule-thin)',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '80px 0',
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p style={{ fontSize: 18, fontWeight: 300 }}>نتیجهای یافت نشد</p>
|
||||||
|
<p style={{ fontSize: 13, marginTop: 8, color: 'var(--ink-5)' }}>
|
||||||
|
جستجو یا فیلترهای خود را تغییر دهید
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ─── Pagination ──────────────────────────────────── */}
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 48,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 16,
|
||||||
|
borderTop: '1px solid var(--rule-thin)',
|
||||||
|
paddingTop: 32,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
||||||
|
disabled={page === 1}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
padding: '8px 18px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
background: 'transparent',
|
||||||
|
color: page === 1 ? 'var(--ink-5)' : 'var(--ink)',
|
||||||
|
borderColor: page === 1 ? 'var(--rule-thin)' : 'var(--ink)',
|
||||||
|
cursor: page === 1 ? 'not-allowed' : 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
borderRadius: 0,
|
||||||
|
opacity: page === 1 ? 0.4 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChevronRight size={14} />
|
||||||
|
قبلی
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span style={{ fontSize: 12, color: 'var(--ink-5)' }}>
|
||||||
|
صفحه {page.toLocaleString('fa-IR')} از {totalPages.toLocaleString('fa-IR')}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
||||||
|
disabled={page === totalPages}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
padding: '8px 18px',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
border: '1px solid var(--ink)',
|
||||||
|
background: 'transparent',
|
||||||
|
color: page === totalPages ? 'var(--ink-5)' : 'var(--ink)',
|
||||||
|
borderColor: page === totalPages ? 'var(--rule-thin)' : 'var(--ink)',
|
||||||
|
cursor: page === totalPages ? 'not-allowed' : 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
borderRadius: 0,
|
||||||
|
opacity: page === totalPages ? 0.4 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
بعدی
|
||||||
|
<ChevronLeft size={14} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ─── Mobile responsive styles ────────────────────── */}
|
||||||
|
<style>{`
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.reports-grid {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 1024px) and (min-width: 768px) {
|
||||||
|
.reports-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,340 @@
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { reports } from '@/data/reports';
|
||||||
|
|
||||||
|
const riskMatrix = [
|
||||||
|
{ id: 1, title: 'تشدید تحریمهای صادراتی', category: 'ژئوپلیتیک', probability: 'بالا', impact: 'بسیار بالا', level: 'critical' },
|
||||||
|
{ id: 2, title: 'سقوط قیمت جهانی فولاد', category: 'بازار', probability: 'متوسط', impact: 'بالا', level: 'high' },
|
||||||
|
{ id: 3, title: 'کمبود انرژی صنعتی', category: 'زیرساخت', probability: 'بالا', impact: 'متوسط', level: 'high' },
|
||||||
|
{ id: 4, title: 'اجرای CBAM اروپا', category: 'مقررات', probability: 'قطعی', impact: 'متوسط', level: 'medium' },
|
||||||
|
{ id: 5, title: 'رقابت صادرات چین', category: 'بازار', probability: 'قطعی', impact: 'بالا', level: 'high' },
|
||||||
|
{ id: 6, title: 'نوسانات نرخ ارز', category: 'مالی', probability: 'بالا', impact: 'متوسط', level: 'medium' },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type RiskLevel = 'critical' | 'high' | 'medium' | 'low';
|
||||||
|
|
||||||
|
const levelConfig: Record<RiskLevel, { color: string; label: string }> = {
|
||||||
|
critical: { color: '#7f1d1d', label: 'بحرانی' },
|
||||||
|
high: { color: 'var(--red)', label: 'بالا' },
|
||||||
|
medium: { color: 'var(--amber)', label: 'متوسط' },
|
||||||
|
low: { color: 'var(--green-ink)', label: 'پایین' },
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatPrice(price: number): string {
|
||||||
|
if (price === 0) return 'رایگان';
|
||||||
|
return price.toLocaleString('fa-IR') + ' تومان';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Risks() {
|
||||||
|
const riskReports = reports.filter((r) => r.type === 'risk');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
dir="rtl"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
minHeight: '100vh',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Page Header */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: '4px double var(--ink)',
|
||||||
|
padding: '40px 48px 32px',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
letterSpacing: '0.12em',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
fontWeight: 600,
|
||||||
|
marginBottom: '12px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
اندیشکده فولاد ایران — پایش ریسک
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', flexWrap: 'wrap', gap: '16px' }}>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: '36px',
|
||||||
|
fontWeight: 900,
|
||||||
|
margin: 0,
|
||||||
|
lineHeight: 1.3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
پایش ریسکهای صنعت فولاد
|
||||||
|
</h1>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
padding: '4px 12px',
|
||||||
|
background: 'var(--paper-2)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
بهروز شده: بهمن ۱۴۰۳
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Legend */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '24px',
|
||||||
|
marginTop: '24px',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(Object.keys(levelConfig) as RiskLevel[]).map((level) => (
|
||||||
|
<div key={level} style={{ display: 'flex', alignItems: 'center', gap: '7px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '10px',
|
||||||
|
height: '10px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: levelConfig[level].color,
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span style={{ fontSize: '13px', color: 'var(--ink-3)' }}>{levelConfig[level].label}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '40px 48px 64px' }}>
|
||||||
|
{/* Risk Matrix Table */}
|
||||||
|
<div style={{ marginBottom: '56px' }}>
|
||||||
|
<h2
|
||||||
|
style={{
|
||||||
|
fontSize: '18px',
|
||||||
|
fontWeight: 800,
|
||||||
|
margin: '0 0 20px',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ display: 'inline-block', width: '4px', height: '20px', background: 'var(--red)' }} />
|
||||||
|
ماتریس ریسکهای کلیدی
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div style={{ border: '1px solid var(--ink)', overflow: 'hidden' }}>
|
||||||
|
{/* Table Header */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '48px 1fr 100px 100px 120px 130px',
|
||||||
|
background: 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
padding: '12px 16px',
|
||||||
|
gap: '0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{['#', 'عنوان ریسک', 'دسته', 'احتمال', 'شدت تأثیر', 'سطح ریسک'].map((col, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '0.05em',
|
||||||
|
padding: '0 8px',
|
||||||
|
borderRight: i > 0 ? '1px solid rgba(255,255,255,0.12)' : 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{col}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Table Rows */}
|
||||||
|
{riskMatrix.map((risk, idx) => {
|
||||||
|
const cfg = levelConfig[risk.level as RiskLevel];
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={risk.id}
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '48px 1fr 100px 100px 120px 130px',
|
||||||
|
background: idx % 2 === 0 ? 'var(--paper)' : 'var(--paper-2)',
|
||||||
|
borderTop: '1px solid var(--rule-thin)',
|
||||||
|
padding: '14px 16px',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* # */}
|
||||||
|
<div style={{ fontSize: '13px', color: 'var(--ink-5)', fontWeight: 600, padding: '0 8px' }}>
|
||||||
|
{risk.id}
|
||||||
|
</div>
|
||||||
|
{/* Title */}
|
||||||
|
<div style={{ fontSize: '14px', fontWeight: 600, color: 'var(--ink)', padding: '0 8px', borderRight: '1px solid var(--rule-thin)' }}>
|
||||||
|
{risk.title}
|
||||||
|
</div>
|
||||||
|
{/* Category */}
|
||||||
|
<div style={{ fontSize: '12px', color: 'var(--ink-4)', padding: '0 8px', borderRight: '1px solid var(--rule-thin)' }}>
|
||||||
|
{risk.category}
|
||||||
|
</div>
|
||||||
|
{/* Probability */}
|
||||||
|
<div style={{ fontSize: '12px', color: 'var(--ink-3)', padding: '0 8px', borderRight: '1px solid var(--rule-thin)' }}>
|
||||||
|
{risk.probability}
|
||||||
|
</div>
|
||||||
|
{/* Impact */}
|
||||||
|
<div style={{ fontSize: '12px', color: 'var(--ink-3)', padding: '0 8px', borderRight: '1px solid var(--rule-thin)' }}>
|
||||||
|
{risk.impact}
|
||||||
|
</div>
|
||||||
|
{/* Level */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
padding: '0 8px',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '9px',
|
||||||
|
height: '9px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: cfg.color,
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span style={{ fontSize: '12px', fontWeight: 700, color: cfg.color }}>
|
||||||
|
{cfg.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Risk Reports Section */}
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderTop: '3px solid var(--ink)',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
padding: '18px 0',
|
||||||
|
marginBottom: '24px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
gap: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2 style={{ fontSize: '20px', fontWeight: 800, margin: 0 }}>
|
||||||
|
گزارشهای ریسک اندیشکده
|
||||||
|
</h2>
|
||||||
|
<span style={{ fontSize: '13px', color: 'var(--ink-5)' }}>
|
||||||
|
{riskReports.length} گزارش تحلیلی
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0' }}>
|
||||||
|
{riskReports.map((report, idx) => (
|
||||||
|
<Link
|
||||||
|
key={report.id}
|
||||||
|
to={`/reports/${report.id}`}
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '90px 1fr 120px 140px 140px',
|
||||||
|
gap: '0',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '16px 0',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
background: idx % 2 === 0 ? 'var(--paper)' : 'var(--paper-2)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: 'inherit',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Type badge */}
|
||||||
|
<div style={{ padding: '0 0 0 12px' }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '10px',
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'var(--paper)',
|
||||||
|
background: '#7f1d1d',
|
||||||
|
padding: '3px 8px',
|
||||||
|
letterSpacing: '0.06em',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ریسک
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/* Title */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
padding: '0 16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.title}
|
||||||
|
</div>
|
||||||
|
{/* Date */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
padding: '0 16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.publishDate}
|
||||||
|
</div>
|
||||||
|
{/* Pages */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
padding: '0 16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.pages} صفحه
|
||||||
|
</div>
|
||||||
|
{/* Price + link */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
gap: '12px',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
padding: '0 16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: '13px', fontWeight: 700, color: 'var(--ink)' }}>
|
||||||
|
{formatPrice(report.price)}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: 'var(--red)',
|
||||||
|
fontWeight: 600,
|
||||||
|
textDecoration: 'underline',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
مشاهده ←
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,430 @@
|
||||||
|
import { marketPrices, globalComparison } from '@/data/market';
|
||||||
|
import type { MarketPrice } from '@/data/market';
|
||||||
|
|
||||||
|
const riskAlerts = [
|
||||||
|
{
|
||||||
|
title: 'فشار صادرات چین',
|
||||||
|
desc: 'افزایش ۱۸٪ صادرات ارزان چین فشار قیمتی روی بازار آسیا',
|
||||||
|
level: 'high',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'محدودیت انرژی داخلی',
|
||||||
|
desc: 'کاهش تولید زمستانی به دلیل محدودیت گاز صنعتی',
|
||||||
|
level: 'medium',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'فرصت بازار عراق',
|
||||||
|
desc: 'کاهش واردات ترکیه، فرصت افزایش سهم بازار برای ایران',
|
||||||
|
level: 'low',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type AlertLevel = 'high' | 'medium' | 'low';
|
||||||
|
|
||||||
|
const alertBorderColor: Record<AlertLevel, string> = {
|
||||||
|
high: 'var(--red)',
|
||||||
|
medium: 'var(--amber)',
|
||||||
|
low: 'var(--green-ink)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const alertLabelColor: Record<AlertLevel, string> = {
|
||||||
|
high: 'var(--red)',
|
||||||
|
medium: 'var(--amber)',
|
||||||
|
low: 'var(--green-ink)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const alertLabel: Record<AlertLevel, string> = {
|
||||||
|
high: 'بالا',
|
||||||
|
medium: 'متوسط',
|
||||||
|
low: 'فرصت',
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatNumber(n: number): string {
|
||||||
|
return n.toLocaleString('fa-IR');
|
||||||
|
}
|
||||||
|
|
||||||
|
function PriceTicker({ item, col, row, totalCols, totalRows }: { item: MarketPrice; col: number; row: number; totalCols: number; totalRows: number }) {
|
||||||
|
const isUp = item.trend === 'up';
|
||||||
|
const isDown = item.trend === 'down';
|
||||||
|
const trendArrow = isUp ? '▲' : isDown ? '▼' : '—';
|
||||||
|
const trendColor = isUp ? 'var(--green-ink)' : isDown ? 'var(--red)' : 'var(--ink-5)';
|
||||||
|
const changePrefix = item.change > 0 ? '+' : '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '24px 20px',
|
||||||
|
borderLeft: col < totalCols - 1 ? '1px solid var(--rule-thin)' : 'none',
|
||||||
|
borderBottom: row < totalRows - 1 ? '1px solid var(--rule-thin)' : 'none',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Product name */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
marginBottom: '10px',
|
||||||
|
letterSpacing: '0.03em',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Price */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '34px',
|
||||||
|
fontWeight: 900,
|
||||||
|
fontVariantNumeric: 'tabular-nums',
|
||||||
|
fontFeatureSettings: '"tnum"',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
lineHeight: 1,
|
||||||
|
marginBottom: '6px',
|
||||||
|
fontFamily: 'monospace, Vazir, sans-serif',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatNumber(item.value)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Unit */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
marginBottom: '14px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.unit}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Change + trend */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||||
|
<span style={{ fontSize: '22px', color: trendColor, lineHeight: 1 }}>
|
||||||
|
{trendArrow}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: 700,
|
||||||
|
color: trendColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{changePrefix}{formatNumber(item.change)}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: trendColor,
|
||||||
|
background: isUp
|
||||||
|
? 'rgba(34,197,94,0.08)'
|
||||||
|
: isDown
|
||||||
|
? 'rgba(220,38,38,0.08)'
|
||||||
|
: 'transparent',
|
||||||
|
padding: '2px 7px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{changePrefix}{item.changePercent.toFixed(1)}٪
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Scanner() {
|
||||||
|
// Use first 6 prices for the grid
|
||||||
|
const gridPrices = marketPrices.slice(0, 6);
|
||||||
|
const COLS = 3;
|
||||||
|
const ROWS = Math.ceil(gridPrices.length / COLS);
|
||||||
|
|
||||||
|
// For CSS bar chart: find max price in globalComparison for proportional width
|
||||||
|
// Filter to USD-only for a fair comparison (exclude IRR)
|
||||||
|
const usdPrices = globalComparison.filter((g) => g.currency === 'USD');
|
||||||
|
const maxUSD = Math.max(...usdPrices.map((g) => g.price));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
dir="rtl"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
minHeight: '100vh',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Page Header */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: '4px double var(--ink)',
|
||||||
|
padding: '40px 48px 32px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
letterSpacing: '0.12em',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
fontWeight: 600,
|
||||||
|
marginBottom: '12px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
اندیشکده فولاد ایران — دادههای بازار
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: '36px',
|
||||||
|
fontWeight: 900,
|
||||||
|
margin: '0 0 8px',
|
||||||
|
lineHeight: 1.3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
اسکنر بازار جهانی فولاد
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: '15px',
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
دادههای لحظهای قیمتهای بازار داخلی و جهانی
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
padding: '4px 12px',
|
||||||
|
background: 'var(--paper-2)',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
بهروز شده: بهمن ۱۴۰۳
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '40px 48px 64px' }}>
|
||||||
|
{/* Price Ticker Grid */}
|
||||||
|
<div style={{ marginBottom: '56px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
gap: '12px',
|
||||||
|
marginBottom: '20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ display: 'inline-block', width: '4px', height: '20px', background: 'var(--ink)' }} />
|
||||||
|
<h2 style={{ fontSize: '18px', fontWeight: 800, margin: 0 }}>قیمتهای لحظهای بازار</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: '2px solid var(--ink)',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
}}
|
||||||
|
className="max-md:grid-cols-1"
|
||||||
|
>
|
||||||
|
{gridPrices.map((item, idx) => {
|
||||||
|
const col = idx % COLS;
|
||||||
|
const row = Math.floor(idx / COLS);
|
||||||
|
return (
|
||||||
|
<PriceTicker
|
||||||
|
key={item.id}
|
||||||
|
item={item}
|
||||||
|
col={col}
|
||||||
|
row={row}
|
||||||
|
totalCols={COLS}
|
||||||
|
totalRows={ROWS}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Global Comparison Bar Chart */}
|
||||||
|
<div style={{ marginBottom: '56px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderTop: '3px solid var(--ink)',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
padding: '16px 0',
|
||||||
|
marginBottom: '28px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
gap: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2 style={{ fontSize: '18px', fontWeight: 800, margin: 0 }}>مقایسه جهانی — قیمت میلگرد</h2>
|
||||||
|
<span style={{ fontSize: '12px', color: 'var(--ink-5)' }}>دلار / تن — بازار صادراتی</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
|
||||||
|
{usdPrices.map((entry) => {
|
||||||
|
const barPct = Math.round((entry.price / maxUSD) * 100);
|
||||||
|
return (
|
||||||
|
<div key={entry.country} style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||||
|
{/* Flag + Country */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '110px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: '20px', lineHeight: 1 }}>{entry.flag}</span>
|
||||||
|
<span style={{ fontSize: '14px', fontWeight: 600, color: 'var(--ink)' }}>
|
||||||
|
{entry.country}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bar */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
height: '28px',
|
||||||
|
background: 'var(--paper-3)',
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
height: '100%',
|
||||||
|
width: `${barPct}%`,
|
||||||
|
background: entry.country === 'چین' ? '#7f1d1d' : 'var(--ink)',
|
||||||
|
transition: 'width 0.4s ease',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Value */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '90px',
|
||||||
|
textAlign: 'left',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
fontWeight: 800,
|
||||||
|
fontVariantNumeric: 'tabular-nums',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${entry.price}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: '11px', color: 'var(--ink-5)', marginRight: '4px' }}>/تن</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Active Risk Cards */}
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderTop: '3px solid var(--red)',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
padding: '16px 0',
|
||||||
|
marginBottom: '24px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
gap: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2 style={{ fontSize: '18px', fontWeight: 800, margin: 0 }}>ریسکهای بازار فعال</h2>
|
||||||
|
<span style={{ fontSize: '12px', color: 'var(--ink-5)' }}>هشدارهای جاری</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px' }}
|
||||||
|
className="max-md:grid-cols-1"
|
||||||
|
>
|
||||||
|
{riskAlerts.map((alert) => {
|
||||||
|
const level = alert.level as AlertLevel;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={alert.title}
|
||||||
|
style={{
|
||||||
|
background: 'var(--paper)',
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
borderRight: `4px solid ${alertBorderColor[level]}`,
|
||||||
|
padding: '20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
style={{
|
||||||
|
fontSize: '15px',
|
||||||
|
fontWeight: 700,
|
||||||
|
margin: 0,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{alert.title}
|
||||||
|
</h3>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
fontWeight: 700,
|
||||||
|
color: alertLabelColor[level],
|
||||||
|
border: `1px solid ${alertLabelColor[level]}`,
|
||||||
|
padding: '2px 8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{alertLabel[level]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: '13px',
|
||||||
|
lineHeight: 1.8,
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{alert.desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,383 @@
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { reports } from '@/data/reports';
|
||||||
|
import type { Report } from '@/data/reports';
|
||||||
|
|
||||||
|
const typeLabel: Record<string, string> = {
|
||||||
|
special: 'ویژه',
|
||||||
|
quarterly: 'فصلی',
|
||||||
|
flash: 'فوری',
|
||||||
|
risk: 'ریسک',
|
||||||
|
free: 'رایگان',
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatPrice(price: number): string {
|
||||||
|
if (price === 0) return 'رایگان';
|
||||||
|
return price.toLocaleString('fa-IR') + ' تومان';
|
||||||
|
}
|
||||||
|
|
||||||
|
function SpecialCard({ report }: { report: Report }) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={`/reports/${report.id}`}
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: 'inherit',
|
||||||
|
border: '2px solid var(--ink)',
|
||||||
|
padding: '28px',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
transition: 'background 0.15s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', gap: '10px', alignItems: 'center', marginBottom: '14px' }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '0.08em',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
background: 'var(--red)',
|
||||||
|
padding: '2px 8px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{typeLabel[report.type] ?? report.type}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
paddingRight: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.category}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2
|
||||||
|
style={{
|
||||||
|
fontSize: '22px',
|
||||||
|
fontWeight: 700,
|
||||||
|
lineHeight: 1.5,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
margin: '0 0 14px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.title}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: '14px',
|
||||||
|
lineHeight: 1.9,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
margin: '0 0 20px',
|
||||||
|
display: '-webkit-box',
|
||||||
|
WebkitLineClamp: 3,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.summary}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderTop: '1px solid var(--rule-thin)',
|
||||||
|
paddingTop: '16px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '12px',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '34px',
|
||||||
|
height: '34px',
|
||||||
|
background: 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: '15px',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.authorInitial}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: '13px', fontWeight: 600, color: 'var(--ink)' }}>{report.author}</div>
|
||||||
|
<div style={{ fontSize: '11px', color: 'var(--ink-5)' }}>{report.authorRole}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<span style={{ fontSize: '13px', color: 'var(--ink-4)' }}>{report.pages} صفحه</span>
|
||||||
|
<span style={{ fontSize: '13px', color: 'var(--ink-4)' }}>{report.publishDate}</span>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '10px',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
paddingRight: '12px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: '15px', fontWeight: 700, color: report.isFree ? 'var(--green-ink)' : 'var(--ink)' }}>
|
||||||
|
{formatPrice(report.price)}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: 600,
|
||||||
|
padding: '4px 14px',
|
||||||
|
background: 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.isFree ? 'دریافت رایگان' : 'خرید گزارش'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CompactCard({ report }: { report: Report }) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={`/reports/${report.id}`}
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: 'inherit',
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
borderTop: '3px solid var(--ink-3)',
|
||||||
|
padding: '18px',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '10px' }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '10px',
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
background: 'var(--paper-2)',
|
||||||
|
padding: '2px 7px',
|
||||||
|
letterSpacing: '0.06em',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{typeLabel[report.type] ?? report.type}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: '11px', color: 'var(--ink-5)' }}>{report.category}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3
|
||||||
|
style={{
|
||||||
|
fontSize: '15px',
|
||||||
|
fontWeight: 700,
|
||||||
|
lineHeight: 1.6,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
margin: '0 0 10px',
|
||||||
|
display: '-webkit-box',
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: '13px',
|
||||||
|
lineHeight: 1.8,
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
margin: '0 0 14px',
|
||||||
|
display: '-webkit-box',
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{report.summary}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderTop: '1px solid var(--rule-thin)',
|
||||||
|
paddingTop: '12px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: '12px', color: 'var(--ink-5)' }}>{report.publishDate}</span>
|
||||||
|
<span style={{ fontSize: '13px', fontWeight: 700, color: report.isFree ? 'var(--green-ink)' : 'var(--red)' }}>
|
||||||
|
{formatPrice(report.price)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Special() {
|
||||||
|
const specialReports = reports.filter((r) => r.type === 'special');
|
||||||
|
const otherReports = reports.filter((r) => r.type !== 'special');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
dir="rtl"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Vazir, sans-serif',
|
||||||
|
background: 'var(--paper)',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
minHeight: '100vh',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Hero Banner */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: 'var(--ink)',
|
||||||
|
padding: '60px 48px',
|
||||||
|
borderBottom: '4px solid var(--red)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
letterSpacing: '0.12em',
|
||||||
|
color: 'rgba(255,255,255,0.5)',
|
||||||
|
marginBottom: '16px',
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
اندیشکده فولاد ایران — پژوهشهای تخصصی
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: '48px',
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--paper)',
|
||||||
|
margin: '0 0 16px',
|
||||||
|
lineHeight: 1.3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
گزارشهای ویژه
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: '17px',
|
||||||
|
color: 'rgba(255,255,255,0.7)',
|
||||||
|
margin: 0,
|
||||||
|
maxWidth: '680px',
|
||||||
|
lineHeight: 1.8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
تحلیلهای عمیق و سناریوپردازیهای راهبردی برای تصمیمگیرندگان صنعت فولاد
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: '28px',
|
||||||
|
display: 'flex',
|
||||||
|
gap: '32px',
|
||||||
|
fontSize: '13px',
|
||||||
|
color: 'rgba(255,255,255,0.5)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{specialReports.length} گزارش ویژه</span>
|
||||||
|
<span style={{ borderRight: '1px solid rgba(255,255,255,0.2)', paddingRight: '32px' }}>
|
||||||
|
{reports.length} گزارش در مجموع
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '0 24px' }}>
|
||||||
|
{/* Section A — Special Reports */}
|
||||||
|
<div style={{ padding: '48px 0 40px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
gap: '16px',
|
||||||
|
marginBottom: '32px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: '4px', height: '28px', background: 'var(--red)', flexShrink: 0 }} />
|
||||||
|
<h2 style={{ fontSize: '24px', fontWeight: 800, margin: 0, color: 'var(--ink)' }}>
|
||||||
|
گزارشهای ویژه
|
||||||
|
</h2>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '13px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
paddingRight: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
تحلیلهای سناریومحور و راهبردی
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="max-md:grid-cols-1"
|
||||||
|
style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '24px' }}
|
||||||
|
>
|
||||||
|
{specialReports.map((report) => (
|
||||||
|
<SpecialCard key={report.id} report={report} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Thick Rule Separator */}
|
||||||
|
<div style={{ borderTop: '4px double var(--ink)', margin: '0 0 48px' }} />
|
||||||
|
|
||||||
|
{/* Section B — Other Reports */}
|
||||||
|
<div style={{ paddingBottom: '64px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
gap: '16px',
|
||||||
|
marginBottom: '32px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: '4px', height: '28px', background: 'var(--ink-4)', flexShrink: 0 }} />
|
||||||
|
<h2 style={{ fontSize: '22px', fontWeight: 800, margin: 0, color: 'var(--ink)' }}>
|
||||||
|
سایر گزارشهای تحلیلی
|
||||||
|
</h2>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '13px',
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
paddingRight: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
گزارشهای فصلی، فوری و تحلیل ریسک
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="max-md:grid-cols-1"
|
||||||
|
style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '20px' }}
|
||||||
|
>
|
||||||
|
{otherReports.map((report) => (
|
||||||
|
<CompactCard key={report.id} report={report} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,680 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Check, X, ChevronDown, ChevronUp, Phone } from 'lucide-react'
|
||||||
|
import { plans } from '@/data/plans'
|
||||||
|
import type { Plan } from '@/data/plans'
|
||||||
|
|
||||||
|
/* ─── feature comparison data ────────────────────────────── */
|
||||||
|
const FEATURES: { label: string; basic: string; pro: string; gov: string }[] = [
|
||||||
|
{ label: 'دسترسی به گزارشهای جدید', basic: 'check', pro: 'check', gov: 'check' },
|
||||||
|
{ label: 'Flash Reports فوری', basic: 'x', pro: 'check', gov: 'check' },
|
||||||
|
{ label: 'آرشیو کامل', basic: 'محدود (۱ سال)', pro: 'نامحدود', gov: 'نامحدود' },
|
||||||
|
{ label: 'داشبورد بازار', basic: 'check', pro: 'check', gov: 'check' },
|
||||||
|
{ label: 'جلسه با تیم تحریریه', basic: 'x', pro: 'ماهانه', gov: 'فصلی' },
|
||||||
|
{ label: 'گزارشهای سفارشی', basic: 'x', pro: 'x', gov: 'check' },
|
||||||
|
{ label: 'ارائه حضوری', basic: 'x', pro: 'x', gov: 'check' },
|
||||||
|
{ label: 'تعداد کاربران', basic: '۱', pro: 'تا ۵', gov: 'نامحدود' },
|
||||||
|
{ label: 'فاکتور رسمی', basic: 'x', pro: 'check', gov: 'check' },
|
||||||
|
{ label: 'پشتیبانی اختصاصی', basic: 'x', pro: 'check', gov: 'check' },
|
||||||
|
]
|
||||||
|
|
||||||
|
/* ─── FAQ data ───────────────────────────────────────────── */
|
||||||
|
const FAQS = [
|
||||||
|
{
|
||||||
|
q: 'آیا میتوانم گزارش را پیش از خرید ببینم؟',
|
||||||
|
a: 'بله، برای تمام گزارشها یک پیشنمایش رایگان شامل چکیده، فهرست مطالب و چند صفحه اول در دسترس است. همچنین برخی گزارشها بهطور کامل رایگان منتشر میشوند.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'فاکتور رسمی چگونه صادر میشود؟',
|
||||||
|
a: 'پس از تکمیل خرید، فاکتور رسمی با مشخصات سازمانی شما حداکثر ظرف ۲۴ ساعت کاری از طریق ایمیل ارسال میشود. برای اشتراکهای سازمانی، قرارداد رسمی نیز منعقد میشود.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'نحوه پرداخت چگونه است؟',
|
||||||
|
a: 'پرداخت از طریق درگاه بانکی اینترنتی (شبکه شتاب) و انتقال بانکی مستقیم امکانپذیر است. برای اشتراکهای سازمانی پرداخت بهصورت حواله بانکی با صدور فاکتور رسمی انجام میشود.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'آیا اشتراک سازمانی قابل لغو است؟',
|
||||||
|
a: 'بله، اشتراک سازمانی با اطلاع ۳۰ روز پیشین قابل فسخ است. مبالغ پرداختی بهصورت پرو-راتا بر اساس ماههای باقیمانده محاسبه و بازگشت داده میشود.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'گزارشهای سفارشی چه مدت طول میکشد؟',
|
||||||
|
a: 'بسته به پیچیدگی موضوع، گزارشهای سفارشی معمولاً ۴ تا ۸ هفته زمان نیاز دارند. پس از جلسه اولیه تعریف دامنه کار، برنامه زمانی دقیق ارائه میشود.',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/* ─── plan extra features ────────────────────────────────── */
|
||||||
|
const PLAN_EXTRA_FEATURES: Record<string, string[]> = {
|
||||||
|
basic: [
|
||||||
|
'دسترسی به گزارشهای فصلی بازار',
|
||||||
|
'خلاصه گزارشهای تخصصی',
|
||||||
|
'خبرنامه هفتگی',
|
||||||
|
'پایگاه داده قیمتهای بازار',
|
||||||
|
'آرشیو ۱ ساله گزارشها',
|
||||||
|
'داشبورد بازار پایه',
|
||||||
|
'دانلود PDF گزارشها',
|
||||||
|
'پشتیبانی ایمیلی',
|
||||||
|
],
|
||||||
|
professional: [
|
||||||
|
'تمام گزارشهای تخصصی و ریسک',
|
||||||
|
'گزارشهای فوری (Flash)',
|
||||||
|
'مدلهای کمی و دادههای خام',
|
||||||
|
'آرشیو کامل ۵ ساله',
|
||||||
|
'API دسترسی به داده قیمت',
|
||||||
|
'پشتیبانی اولویتدار',
|
||||||
|
'تا ۵ کاربر سازمانی',
|
||||||
|
'فاکتور رسمی خودکار',
|
||||||
|
'داشبورد پیشرفته بازار',
|
||||||
|
'خبرنامه اختصاصی پیش از انتشار',
|
||||||
|
],
|
||||||
|
government: [
|
||||||
|
'دسترسی نامحدود تیمی',
|
||||||
|
'گزارشهای سفارشی اختصاصی',
|
||||||
|
'جلسات مشاوره ماهانه',
|
||||||
|
'ارائه حضوری نتایج تحقیقات',
|
||||||
|
'آموزش و کارگاه تخصصی',
|
||||||
|
'پشتیبانی ۲۴ ساعته اختصاصی',
|
||||||
|
'دسترسی پیش از انتشار',
|
||||||
|
'مدیر حساب اختصاصی',
|
||||||
|
'قرارداد و فاکتور سازمانی',
|
||||||
|
'گزارش وضعیت اعضاء',
|
||||||
|
'لایسنس چندکاربره نامحدود',
|
||||||
|
'پنل مدیریت تیم',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── cell renderer for feature table ───────────────────── */
|
||||||
|
function FeatureCell({ value }: { value: string }) {
|
||||||
|
if (value === 'check') {
|
||||||
|
return (
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
padding: '13px 24px',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Check size={15} strokeWidth={2.5} style={{ color: 'var(--green-ink)' }} />
|
||||||
|
</td>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (value === 'x') {
|
||||||
|
return (
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
padding: '13px 24px',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
textAlign: 'center',
|
||||||
|
opacity: 0.35,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<X size={13} strokeWidth={2} style={{ color: 'var(--ink-4)' }} />
|
||||||
|
</td>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
padding: '13px 24px',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</td>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── plan card ──────────────────────────────────────────── */
|
||||||
|
function PlanCard({ plan }: { plan: Plan }) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
const features = PLAN_EXTRA_FEATURES[plan.id] || plan.features
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: plan.featured ? 'var(--ink)' : 'var(--paper)',
|
||||||
|
padding: 40,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Badge */}
|
||||||
|
{plan.badge && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: -1,
|
||||||
|
right: 32,
|
||||||
|
background: 'var(--red)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '1.5px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
padding: '4px 12px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{plan.badge}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Plan name */}
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '2.5px',
|
||||||
|
color: plan.featured ? 'rgba(244,239,231,0.5)' : 'var(--ink-5)',
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
طرح
|
||||||
|
</p>
|
||||||
|
<h3
|
||||||
|
style={{
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: plan.featured ? 'var(--paper)' : 'var(--ink)',
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
marginBottom: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{plan.name}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Price */}
|
||||||
|
<div style={{ marginBottom: 24 }}>
|
||||||
|
{typeof plan.price === 'number' ? (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: plan.featured ? 'var(--paper)' : 'var(--ink)',
|
||||||
|
letterSpacing: '-1px',
|
||||||
|
lineHeight: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{plan.price.toLocaleString('fa-IR')}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: plan.featured ? 'rgba(244,239,231,0.6)' : 'var(--ink-4)',
|
||||||
|
marginRight: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{plan.period}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: plan.featured ? 'var(--paper)' : 'var(--ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
تماس بگیرید
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: plan.featured ? 'rgba(244,239,231,0.55)' : 'var(--ink-5)',
|
||||||
|
marginTop: 6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{plan.period}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: 1,
|
||||||
|
background: plan.featured ? 'rgba(244,239,231,0.15)' : 'var(--rule-thin)',
|
||||||
|
marginBottom: 24,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Features */}
|
||||||
|
<ul style={{ listStyle: 'none', padding: 0, margin: '0 0 32px', flex: 1 }}>
|
||||||
|
{features.map((feat, i) => (
|
||||||
|
<li
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
gap: 10,
|
||||||
|
padding: '7px 0',
|
||||||
|
borderBottom:
|
||||||
|
i < features.length - 1
|
||||||
|
? `1px solid ${plan.featured ? 'rgba(244,239,231,0.08)' : 'var(--rule-thin)'}`
|
||||||
|
: 'none',
|
||||||
|
fontSize: 12,
|
||||||
|
color: plan.featured ? 'rgba(244,239,231,0.85)' : 'var(--ink-3)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Check
|
||||||
|
size={12}
|
||||||
|
style={{
|
||||||
|
marginTop: 2,
|
||||||
|
flexShrink: 0,
|
||||||
|
color: plan.featured ? 'rgba(244,239,231,0.6)' : 'var(--green-ink)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{feat}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '14px 0',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 700,
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
borderRadius: 0,
|
||||||
|
letterSpacing: '0.3px',
|
||||||
|
transition: 'all 150ms',
|
||||||
|
border: plan.featured
|
||||||
|
? hovered
|
||||||
|
? '1px solid var(--paper-2)'
|
||||||
|
: '1px solid var(--paper)'
|
||||||
|
: hovered
|
||||||
|
? '1px solid var(--ink)'
|
||||||
|
: '1px solid var(--rule-thin)',
|
||||||
|
background: plan.featured
|
||||||
|
? hovered
|
||||||
|
? 'var(--paper-2)'
|
||||||
|
: 'var(--paper)'
|
||||||
|
: hovered
|
||||||
|
? 'var(--ink)'
|
||||||
|
: 'transparent',
|
||||||
|
color: plan.featured ? 'var(--ink)' : hovered ? 'var(--paper)' : 'var(--ink)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{plan.ctaLabel}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── FAQ item ───────────────────────────────────────────── */
|
||||||
|
function FaqItem({ q, a, index }: { q: string; a: string; index: number }) {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ borderBottom: '1px solid var(--rule-thin)' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: '20px 0',
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
textAlign: 'right',
|
||||||
|
gap: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||||
|
<span style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink-5)', minWidth: 20 }}>
|
||||||
|
{(index + 1).toLocaleString('fa-IR')}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: 14, fontWeight: 600, color: 'var(--ink)' }}>{q}</span>
|
||||||
|
</div>
|
||||||
|
<span style={{ color: 'var(--ink-4)', flexShrink: 0 }}>
|
||||||
|
{open ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{open && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '0 0 20px 36px',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 300,
|
||||||
|
lineHeight: 1.8,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{a}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── CTA button with hover state ───────────────────────── */
|
||||||
|
function CtaButton({ label }: { label: string }) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={{
|
||||||
|
padding: '14px 32px',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 700,
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
borderRadius: 0,
|
||||||
|
letterSpacing: '0.3px',
|
||||||
|
transition: 'all 150ms',
|
||||||
|
background: hovered ? 'var(--paper-2)' : 'var(--paper)',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
border: hovered ? '1px solid var(--paper-2)' : '1px solid var(--paper)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── main component ─────────────────────────────────────── */
|
||||||
|
export default function Subscribe() {
|
||||||
|
return (
|
||||||
|
<div style={{ direction: 'rtl' }}>
|
||||||
|
{/* ─── Page Header ───────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: 1280,
|
||||||
|
margin: '0 auto',
|
||||||
|
padding: '64px 48px 48px',
|
||||||
|
borderBottom: '3px solid var(--ink)',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: 36,
|
||||||
|
fontWeight: 900,
|
||||||
|
letterSpacing: '-1px',
|
||||||
|
color: 'var(--ink)',
|
||||||
|
marginBottom: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
اشتراک و دسترسی
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: 300,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
maxWidth: 560,
|
||||||
|
margin: '0 auto',
|
||||||
|
lineHeight: 1.7,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
دسترسی به جامعترین تحلیلهای بازار فولاد ایران — از گزارشهای فصلی تا مشاوره سازمانی
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Section 1: Plans ──────────────────────────── */}
|
||||||
|
<div style={{ padding: '64px 0', borderBottom: '2px solid var(--ink)' }}>
|
||||||
|
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '0 48px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
borderLeft: 'none',
|
||||||
|
}}
|
||||||
|
className="plans-grid"
|
||||||
|
>
|
||||||
|
{plans.map((plan) => (
|
||||||
|
<div
|
||||||
|
key={plan.id}
|
||||||
|
style={{ borderLeft: '1px solid var(--rule-thin)' }}
|
||||||
|
>
|
||||||
|
<PlanCard plan={plan} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Section 2: Feature Comparison Table ─────────── */}
|
||||||
|
<div style={{ padding: '64px 0', borderBottom: '2px solid var(--ink)' }}>
|
||||||
|
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '0 48px' }}>
|
||||||
|
<div style={{ marginBottom: 40 }}>
|
||||||
|
<h2
|
||||||
|
style={{
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
marginBottom: 6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
مقایسه کامل امکانات
|
||||||
|
</h2>
|
||||||
|
<p style={{ fontSize: 13, color: 'var(--ink-5)' }}>
|
||||||
|
مقایسه تفصیلی امکانات هر سه طرح اشتراک
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ overflowX: 'auto' }}>
|
||||||
|
<table style={{ borderCollapse: 'collapse', width: '100%', minWidth: 600 }}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
style={{
|
||||||
|
padding: '16px 20px',
|
||||||
|
textAlign: 'right',
|
||||||
|
background: 'var(--paper-3)',
|
||||||
|
borderBottom: '2px solid var(--ink)',
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
امکانات
|
||||||
|
</th>
|
||||||
|
{plans.map((plan) => (
|
||||||
|
<th
|
||||||
|
key={plan.id}
|
||||||
|
style={{
|
||||||
|
padding: '16px 24px',
|
||||||
|
textAlign: 'center',
|
||||||
|
background: plan.featured ? 'var(--ink)' : 'var(--paper-3)',
|
||||||
|
borderBottom: '2px solid var(--ink)',
|
||||||
|
borderRight: '1px solid var(--rule-thin)',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: plan.featured ? 'var(--paper)' : 'var(--ink)',
|
||||||
|
letterSpacing: '-0.3px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{plan.name}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{FEATURES.map((feat, i) => (
|
||||||
|
<tr
|
||||||
|
key={feat.label}
|
||||||
|
style={{
|
||||||
|
background: i % 2 === 0 ? 'var(--paper)' : 'var(--paper-2)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
padding: '13px 20px',
|
||||||
|
fontSize: 13,
|
||||||
|
color: 'var(--ink-2)',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{feat.label}
|
||||||
|
</td>
|
||||||
|
<FeatureCell value={feat.basic} />
|
||||||
|
<FeatureCell value={feat.pro} />
|
||||||
|
<FeatureCell value={feat.gov} />
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Section 3: FAQ ──────────────────────────────── */}
|
||||||
|
<div style={{ padding: '64px 0', borderBottom: '2px solid var(--ink)' }}>
|
||||||
|
<div style={{ maxWidth: 860, margin: '0 auto', padding: '0 48px' }}>
|
||||||
|
<div style={{ marginBottom: 40 }}>
|
||||||
|
<h2
|
||||||
|
style={{
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
marginBottom: 6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
پرسشهای متداول
|
||||||
|
</h2>
|
||||||
|
<p style={{ fontSize: 13, color: 'var(--ink-5)' }}>
|
||||||
|
پاسخ به سوالهای رایج درباره اشتراک و گزارشها
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style={{ borderTop: '2px solid var(--ink)' }}>
|
||||||
|
{FAQS.map((faq, i) => (
|
||||||
|
<FaqItem key={i} q={faq.q} a={faq.a} index={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Section 4: CTA Band ─────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: 'var(--ink)',
|
||||||
|
padding: '80px 48px',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ maxWidth: 700, margin: '0 auto' }}>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '3px',
|
||||||
|
color: 'rgba(244,239,231,0.4)',
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
اندیشکده فولاد ایران
|
||||||
|
</p>
|
||||||
|
<h2
|
||||||
|
style={{
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--paper)',
|
||||||
|
letterSpacing: '-1px',
|
||||||
|
lineHeight: 1.2,
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
آماده شروع هستید؟
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 15,
|
||||||
|
color: 'rgba(244,239,231,0.65)',
|
||||||
|
fontWeight: 300,
|
||||||
|
lineHeight: 1.7,
|
||||||
|
marginBottom: 40,
|
||||||
|
maxWidth: 500,
|
||||||
|
margin: '0 auto 40px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
به بیش از ۲۸۰۰ متخصص و مدیر صنعت فولاد بپیوندید که تصمیمات خود را با تحلیلهای
|
||||||
|
اندیشکده میگیرند.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 16,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CtaButton label="اشتراک سازمانی" />
|
||||||
|
<OutlineCtaLink label="تماس با ما" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Responsive styles ─────────────────────────── */}
|
||||||
|
<style>{`
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.plans-grid {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 1024px) and (min-width: 768px) {
|
||||||
|
.plans-grid {
|
||||||
|
grid-template-columns: 1fr 1fr !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── outline CTA link ───────────────────────────────────── */
|
||||||
|
function OutlineCtaLink({ label }: { label: string }) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to="/contact"
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
style={{
|
||||||
|
padding: '14px 32px',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'var(--paper)',
|
||||||
|
background: 'transparent',
|
||||||
|
border: hovered
|
||||||
|
? '1px solid rgba(244,239,231,0.6)'
|
||||||
|
: '1px solid rgba(244,239,231,0.35)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
letterSpacing: '0.3px',
|
||||||
|
transition: 'border-color 150ms',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Phone size={14} />
|
||||||
|
{label}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,333 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { team } from '@/data/team'
|
||||||
|
|
||||||
|
/* ─── derived stats ──────────────────────────────────────── */
|
||||||
|
const totalReports = team.reduce((sum, m) => sum + m.reportCount, 0)
|
||||||
|
const memberCount = team.length
|
||||||
|
const combinedYears = 85
|
||||||
|
|
||||||
|
/* ─── member card ────────────────────────────────────────── */
|
||||||
|
function MemberCard({ member }: { member: (typeof team)[0] }) {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
const [bioExpanded, setBioExpanded] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
background: hovered ? 'var(--paper-2)' : 'var(--paper)',
|
||||||
|
transition: 'background 140ms',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
>
|
||||||
|
{/* card header */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '24px 20px 16px',
|
||||||
|
borderBottom: '1px solid var(--rule-thin)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
gap: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* initial avatar */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 56,
|
||||||
|
height: 56,
|
||||||
|
background: 'var(--ink)',
|
||||||
|
color: 'var(--paper)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: 900,
|
||||||
|
flexShrink: 0,
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{member.initial}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<h3
|
||||||
|
style={{
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: 800,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
lineHeight: 1.3,
|
||||||
|
letterSpacing: '-0.3px',
|
||||||
|
marginBottom: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{member.name}
|
||||||
|
</h3>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: 'var(--red)',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '1.5px',
|
||||||
|
display: 'block',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{member.role}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* bio */}
|
||||||
|
<div style={{ padding: '14px 20px', borderBottom: '1px solid var(--rule-thin)' }}>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 300,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
lineHeight: 1.85,
|
||||||
|
display: '-webkit-box',
|
||||||
|
WebkitLineClamp: bioExpanded ? undefined : 3,
|
||||||
|
WebkitBoxOrient: 'vertical' as const,
|
||||||
|
overflow: bioExpanded ? 'visible' : 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{member.bio}
|
||||||
|
</p>
|
||||||
|
{!bioExpanded && member.bio.length > 120 && (
|
||||||
|
<button
|
||||||
|
onClick={() => setBioExpanded(true)}
|
||||||
|
style={{
|
||||||
|
marginTop: 6,
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
padding: 0,
|
||||||
|
textDecoration: 'underline',
|
||||||
|
textDecorationStyle: 'dotted',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
بیشتر
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* expertise tags */}
|
||||||
|
<div style={{ padding: '12px 20px', borderBottom: '1px solid var(--rule-thin)' }}>
|
||||||
|
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
||||||
|
{member.expertise.map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag}
|
||||||
|
style={{
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: 'var(--ink-3)',
|
||||||
|
background: 'var(--paper-3)',
|
||||||
|
padding: '3px 8px',
|
||||||
|
letterSpacing: '0.2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* footer: report count + email */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '12px 20px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginTop: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'baseline', gap: 4 }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
lineHeight: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{member.reportCount.toLocaleString('fa-IR')}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: 11, fontWeight: 400, color: 'var(--ink-5)' }}>
|
||||||
|
گزارش منتشرشده
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{member.email && (
|
||||||
|
<a
|
||||||
|
href={`mailto:${member.email}`}
|
||||||
|
style={{
|
||||||
|
fontSize: 10,
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
fontWeight: 400,
|
||||||
|
letterSpacing: '0.2px',
|
||||||
|
direction: 'ltr',
|
||||||
|
display: 'inline-block',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{member.email}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── main component ─────────────────────────────────────── */
|
||||||
|
export default function Team() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: 1280,
|
||||||
|
margin: '0 auto',
|
||||||
|
padding: '64px 48px',
|
||||||
|
direction: 'rtl',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* ─── Section header ────────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'stretch',
|
||||||
|
borderBottom: '3px solid var(--ink)',
|
||||||
|
marginBottom: 40,
|
||||||
|
gap: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: 'var(--ink)',
|
||||||
|
padding: '14px 28px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--paper)',
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
تیم تحریریه
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '14px 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 300,
|
||||||
|
color: 'var(--ink-4)',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
متخصصان برجسته صنعت فولاد ایران
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Stats row ─────────────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
border: '1px solid var(--rule-thin)',
|
||||||
|
marginBottom: 48,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[
|
||||||
|
{ value: totalReports.toLocaleString('fa-IR'), label: 'گزارش منتشرشده' },
|
||||||
|
{ value: memberCount.toLocaleString('fa-IR'), label: 'عضو تحریریه' },
|
||||||
|
{ value: `${combinedYears.toLocaleString('fa-IR')} سال`, label: 'تجربه ترکیبی' },
|
||||||
|
].map((stat, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
padding: '28px 32px',
|
||||||
|
borderLeft: i < 2 ? '1px solid var(--rule-thin)' : 'none',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 42,
|
||||||
|
fontWeight: 900,
|
||||||
|
color: 'var(--ink)',
|
||||||
|
lineHeight: 1,
|
||||||
|
letterSpacing: '-1.5px',
|
||||||
|
display: 'block',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{stat.value}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 400,
|
||||||
|
color: 'var(--ink-5)',
|
||||||
|
letterSpacing: '0.3px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{stat.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ─── Member grid ───────────────────────────────────── */}
|
||||||
|
<div
|
||||||
|
className="team-grid"
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
gap: 1,
|
||||||
|
background: 'var(--rule-thin)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{team.map((member) => (
|
||||||
|
<MemberCard key={member.id} member={member} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>{`
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.team-grid {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 1024px) and (min-width: 768px) {
|
||||||
|
.team-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "es2023",
|
||||||
|
"lib": ["ES2023", "DOM"],
|
||||||
|
"module": "esnext",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
/* Path aliases */
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "es2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "esnext",
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react(), tailwindcss()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue