581 lines
28 KiB
TypeScript
581 lines
28 KiB
TypeScript
import { useState, useEffect } from 'react'
|
||
import { useLang } from '@/context/LangContext'
|
||
import SnapshotBar from '@/pages/Home/sections/SnapshotBar'
|
||
|
||
const STATS = {
|
||
fa: [
|
||
{ label: 'تولید فولاد خام', value: '۳۱.۲', unit: 'میلیون تن', delta: '+۴.۱٪' },
|
||
{ label: 'صادرات فولاد', value: '۸.۷', unit: 'میلیون تن', delta: '+۱.۸٪' },
|
||
{ label: 'واردات سنگآهن', value: '۱۲.۴', unit: 'میلیون تن', delta: '-۲.۳٪' },
|
||
{ label: 'ظرفیت نصبشده', value: '۳۸', unit: 'میلیون تن', delta: '—' },
|
||
],
|
||
en: [
|
||
{ label: 'Crude Steel Production', value: '31.2', unit: 'Million tons', delta: '+4.1%' },
|
||
{ label: 'Steel Exports', value: '8.7', unit: 'Million tons', delta: '+1.8%' },
|
||
{ label: 'Iron Ore Imports', value: '12.4', unit: 'Million tons', delta: '-2.3%' },
|
||
{ label: 'Installed Capacity', value: '38', unit: 'Million tons', delta: '—' },
|
||
],
|
||
}
|
||
|
||
const T = {
|
||
fa: {
|
||
overline: 'MARKET & STEEL CHAIN', heading: 'بازار و زنجیره فولاد',
|
||
sub: 'سامانه جامع آمار و اطلاعات صنعت فولاد ایران — دادههای لحظهای تولید، صادرات، واردات و قیمتها',
|
||
statsLabel: 'آمار کلیدی صنعت · ۱۴۰۳',
|
||
gateAccess: 'دسترسی اعضا',
|
||
gateHeading: 'دسترسی کامل به سامانه بازار و زنجیره فولاد',
|
||
gateSub: 'اعضای اندیشکده به پایگاه داده جامع فولاد ایران شامل آمار لحظهای تولید، صادرات، واردات، قیمتهای داخلی و بینالمللی و ۵۰۰+ شاخص صنعتی دسترسی دارند.',
|
||
features: [
|
||
'دادههای لحظهای تولید کارخانههای بزرگ',
|
||
'آمار صادرات تفکیکشده بر اساس کشور مقصد',
|
||
'نمودارهای تعاملی و قابل دانلود',
|
||
'گزارشهای تحلیلی ماهانه اختصاصی',
|
||
],
|
||
cta: 'درخواست عضویت سازمانی ←',
|
||
ctaDone: '✓ درخواست شما ثبت شد. تیم ما ظرف ۴۸ ساعت با شما تماس میگیرد.',
|
||
},
|
||
en: {
|
||
overline: 'MARKET & STEEL CHAIN', heading: 'Market & Steel Chain',
|
||
sub: 'Comprehensive statistics and data platform for Iran\'s steel industry — live production, export, import, and price data',
|
||
statsLabel: 'KEY INDUSTRY STATS · 1403',
|
||
gateAccess: 'MEMBERS ONLY',
|
||
gateHeading: 'Full Access to Market & Steel Chain',
|
||
gateSub: 'Institute members have access to Iran\'s comprehensive steel database including live production stats, export/import data, domestic and international prices, and 500+ industrial indicators.',
|
||
features: [
|
||
'Real-time production data from major plants',
|
||
'Export statistics broken down by destination country',
|
||
'Interactive downloadable charts',
|
||
'Exclusive monthly analytical reports',
|
||
],
|
||
cta: 'Request Organizational Membership →',
|
||
ctaDone: '✓ Your request has been received. Our team will contact you within 48 hours.',
|
||
},
|
||
}
|
||
|
||
const TX = {
|
||
fa: {
|
||
tableHeaders: ['عنوان', 'بیشترین', 'کمترین', 'آخرین', '۳۰ روز', '۶۰ روز', '۹۰ روز', '۱۸۰ روز', '۳۶۰ روز'],
|
||
paywallCta: 'برای مشاهده کامل، عضویت سازمانی دریافت کنید ←',
|
||
coverageTitle: 'پوشش قیمتی زنجیره فولاد',
|
||
productLabel: 'محصول', indexLabel: 'شاخص', sourceLabel: 'منبع داده',
|
||
membersOnly: 'MEMBERS ONLY',
|
||
coverageDesc: 'دسترسی کامل به قیمتهای لحظهای، تاریخی و گزارشهای تحلیلی هفتگی برای تمام محصولات.',
|
||
coverageFeatures: ['قیمتهای لحظهای و تاریخی', 'گزارشهای هفتگی اختصاصی', 'نمودارهای تعاملی دانلودی', 'پشتیبانی تحلیلی'],
|
||
sectionsLabel: 'حوزههای پوشش',
|
||
contentSections: ['صنعت فولاد', 'بازار سرمایه', 'فلزات و کامودیتی', 'ارزهای دیجیتال', 'بازار ارز و طلا', 'اقتصاد کلان'],
|
||
dsTitle: 'دسترسی به گزارشهای Platts، Fastmarkets و منابع برتر جهانی',
|
||
dsBody: 'بیشتر سازمانهای ایرانی به این منابع دسترسی ندارند — اشتراک جداگانهشان چندین هزار دلار در سال هزینه دارد. اعضای اندیشکده از گزارشهای روزانه و هفتگی Platts، Fastmarkets، Bainfo China و Dlpibica بهرهمند میشوند؛ دادههایی که مستقیماً قیمتگذاری، خرید و تصمیمات صادراتی را تغییر میدهند.',
|
||
requestBtn: 'درخواست عضویت سازمانی ←',
|
||
contactNote: 'تیم ما ظرف ۴۸ ساعت تماس میگیرد',
|
||
},
|
||
en: {
|
||
tableHeaders: ['Title', 'High', 'Low', 'Last', '30D', '60D', '90D', '180D', '360D'],
|
||
paywallCta: 'Subscribe for full access →',
|
||
coverageTitle: 'Steel Chain Price Coverage',
|
||
productLabel: 'Products', indexLabel: 'Indicators', sourceLabel: 'Data Sources',
|
||
membersOnly: 'MEMBERS ONLY',
|
||
coverageDesc: 'Full access to real-time, historical prices and weekly analytical reports for all products.',
|
||
coverageFeatures: ['Real-time & historical prices', 'Exclusive weekly reports', 'Interactive downloadable charts', 'Dedicated analytical support'],
|
||
sectionsLabel: 'Coverage Areas',
|
||
contentSections: ['Steel Industry', 'Capital Markets', 'Metals & Commodities', 'Digital Assets', 'FX & Gold', 'Macro Economy'],
|
||
dsTitle: 'Access to Platts, Fastmarkets and Top Global Data Sources',
|
||
dsBody: 'Most Iranian organizations lack direct access to these sources — individual subscriptions cost thousands of dollars per year. Institute members benefit from daily and weekly reports from Platts, Fastmarkets, Bainfo China, and Dlpibica; data that directly impacts pricing, procurement, and export decisions.',
|
||
requestBtn: 'Request Organizational Membership →',
|
||
contactNote: 'Our team will contact you within 48 hours',
|
||
},
|
||
}
|
||
|
||
const SLIDES = Array.from({ length: 13 }, (_, i) => `/${i + 1}.jpg`)
|
||
|
||
/* ─── price table data ───────────────────────────────────── */
|
||
const PRICE_TABLE_ROWS = [
|
||
{ title: 'سنگ نیکل - Carbonate, Mn ۱۳%min EXW China', high: 855.06, low: 898.57, last: 739.35, d30: 9.99, d60: 7.89, d90: 5.23, d180: -6.92, d360: 7.88 },
|
||
{ title: 'فرو نیکل - S.A. ۳۶%min In Qinzhou Port', high: 892.53, low: 826.70, last: 751.05, d30: -6.31, d60: 8.48, d90: 2.43, d180: 5.13, d360: 3.89 },
|
||
{ title: 'کاند نیکل - Carbonate, Mn ۱۳%min EXW China', high: 804.93, low: 801.05, last: 961.74, d30: -6.35, d60: 0.12, d90: 4.44, d180: 6.81, d360: 5.95 },
|
||
{ title: 'سنگ نیکل - S.A. ۳۷%min In Qinzhou Port', high: 774.65, low: 759.59, last: 742.73, d30: -6.03, d60: -5.78, d90: 1.21, d180: 5.82, d360: 5.47 },
|
||
{ title: 'سولفات نیکل - Gabonese ۴۴%min In Qinzhou Port', high: 873.00, low: 895.82, last: 720.87, d30: -9.57, d60: 5.91, d90: 8.58, d180: 9.15, d360: -2.43 },
|
||
{ title: 'کاند نیکل - Carbonate, Mn ۱۳%min EXW China', high: 926.83, low: 825.50, last: 745.22, d30: -5.03, d60: -1.54, d90: -0.53, d180: -5.85, d360: 5.54 },
|
||
{ title: 'فرو نیکل - min FOB S.A.%۳۸', high: 885.21, low: 753.25, last: 753.76, d30: 3.45, d60: 1.07, d90: 8.25, d180: -7.04, d360: -1.44 },
|
||
{ title: 'فرو نیکل - Carbonate, Mn ۱۳%min EXW China', high: 786.10, low: 739.25, last: 939.99, d30: 8.78, d60: -0.53, d90: 8.58, d180: -9.65, d360: 2.88 },
|
||
]
|
||
|
||
function PctCell({ v }: { v: number }) {
|
||
const pos = v > 0
|
||
const zero = v === 0
|
||
return (
|
||
<td style={{
|
||
padding: '10px 12px',
|
||
textAlign: 'center',
|
||
background: zero ? 'transparent' : pos ? 'rgba(46,160,67,0.15)' : 'rgba(218,54,51,0.13)',
|
||
color: zero ? 'var(--ink-4)' : pos ? '#1a7f37' : '#cf222e',
|
||
fontWeight: 700,
|
||
fontSize: 12,
|
||
fontVariantNumeric: 'tabular-nums',
|
||
whiteSpace: 'nowrap',
|
||
}}>
|
||
{v > 0 ? '+' : ''}{v.toFixed(2)}
|
||
</td>
|
||
)
|
||
}
|
||
|
||
function PriceTable({ blurFrom, lang }: { blurFrom: number; lang: 'fa' | 'en' }) {
|
||
const tx = TX[lang]
|
||
return (
|
||
<div style={{ marginBottom: 48, position: 'relative', border: '1px solid var(--rule-thin)', overflowX: 'auto' }}>
|
||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12, direction: 'rtl', minWidth: 820 }}>
|
||
<thead>
|
||
<tr style={{ background: 'rgba(7,29,73,0.06)', borderBottom: '2px solid var(--ink)' }}>
|
||
{tx.tableHeaders.map((h, i) => (
|
||
<th key={h} style={{
|
||
padding: '12px 14px',
|
||
textAlign: i === 0 ? 'right' : 'center',
|
||
fontWeight: 700,
|
||
fontSize: 11,
|
||
color: 'var(--ink-3)',
|
||
whiteSpace: 'nowrap',
|
||
}}>{h}</th>
|
||
))}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{PRICE_TABLE_ROWS.map((row, i) => {
|
||
const shouldBlur = i >= blurFrom
|
||
return (
|
||
<tr
|
||
key={i}
|
||
style={{
|
||
borderBottom: '1px solid var(--rule-thin)',
|
||
background: i % 2 === 0 ? 'var(--paper)' : 'var(--paper-2)',
|
||
filter: shouldBlur ? 'blur(4px)' : 'none',
|
||
userSelect: shouldBlur ? 'none' : 'auto',
|
||
pointerEvents: shouldBlur ? 'none' : 'auto',
|
||
}}
|
||
>
|
||
<td style={{ padding: '10px 14px', fontWeight: 500, color: 'var(--ink)', fontSize: 11.5, direction: 'rtl', whiteSpace: 'nowrap' }}>{row.title}</td>
|
||
<td style={{ padding: '10px 12px', textAlign: 'center', fontWeight: 700, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' }}>{row.high.toFixed(2)}</td>
|
||
<td style={{ padding: '10px 12px', textAlign: 'center', fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' }}>{row.low.toFixed(2)}</td>
|
||
<td style={{ padding: '10px 12px', textAlign: 'center', fontWeight: 700, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' }}>{row.last.toFixed(2)}</td>
|
||
<PctCell v={row.d30} />
|
||
<PctCell v={row.d60} />
|
||
<PctCell v={row.d90} />
|
||
<PctCell v={row.d180} />
|
||
<PctCell v={row.d360} />
|
||
</tr>
|
||
)
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
|
||
{/* paywall overlay over blurred rows */}
|
||
<div style={{
|
||
position: 'absolute',
|
||
bottom: 0,
|
||
left: 0,
|
||
right: 0,
|
||
height: `${((PRICE_TABLE_ROWS.length - blurFrom) / PRICE_TABLE_ROWS.length) * 100}%`,
|
||
background: 'linear-gradient(to bottom, transparent 0%, rgba(var(--paper-rgb,255,255,255),0.7) 40%)',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
pointerEvents: 'none',
|
||
}}>
|
||
<div style={{
|
||
background: 'var(--ink)',
|
||
color: 'var(--paper)',
|
||
padding: '8px 20px',
|
||
fontSize: 11,
|
||
fontWeight: 700,
|
||
letterSpacing: '1.5px',
|
||
pointerEvents: 'auto',
|
||
}}>
|
||
{tx.paywallCta}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function ImageSlider() {
|
||
const [current, setCurrent] = useState(0)
|
||
|
||
useEffect(() => {
|
||
const id = setInterval(() => setCurrent(c => (c + 1) % SLIDES.length), 3500)
|
||
return () => clearInterval(id)
|
||
}, [])
|
||
|
||
return (
|
||
<div style={{ position: 'relative', overflow: 'hidden', border: '1px solid var(--rule-thin)', background: '#000', height: '100%' }}>
|
||
{/* Slides */}
|
||
{SLIDES.map((src, i) => (
|
||
<img
|
||
key={src}
|
||
src={src}
|
||
alt=""
|
||
aria-hidden="true"
|
||
style={{
|
||
position: i === 0 ? 'relative' : 'absolute',
|
||
inset: 0, width: '100%',
|
||
height: i === 0 ? 'auto' : '100%',
|
||
objectFit: 'cover', display: 'block',
|
||
userSelect: 'none', pointerEvents: 'none',
|
||
opacity: i === current ? 1 : 0,
|
||
transition: 'opacity 0.8s ease',
|
||
}}
|
||
/>
|
||
))}
|
||
|
||
{/* Radial blur overlay */}
|
||
<div style={{
|
||
position: 'absolute', inset: 0,
|
||
backdropFilter: 'blur(10px)',
|
||
WebkitBackdropFilter: 'blur(10px)',
|
||
maskImage: 'radial-gradient(ellipse 55% 45% at 50% 50%, transparent 25%, black 72%)',
|
||
WebkitMaskImage: 'radial-gradient(ellipse 55% 45% at 50% 50%, transparent 25%, black 72%)',
|
||
}} />
|
||
|
||
{/* Vignette */}
|
||
<div style={{
|
||
position: 'absolute', inset: 0,
|
||
background: 'radial-gradient(ellipse 70% 60% at 50% 50%, transparent 30%, rgba(7,29,73,0.55) 100%)',
|
||
}} />
|
||
|
||
{/* Dot indicators */}
|
||
<div style={{
|
||
position: 'absolute', bottom: 12, left: '50%', transform: 'translateX(-50%)',
|
||
display: 'flex', gap: 6, zIndex: 10,
|
||
}}>
|
||
{SLIDES.map((_, i) => (
|
||
<button
|
||
key={i}
|
||
onClick={() => setCurrent(i)}
|
||
style={{
|
||
width: i === current ? 18 : 6, height: 6,
|
||
background: i === current ? 'var(--color-blue)' : 'rgba(255,255,255,0.4)',
|
||
border: 'none', cursor: 'pointer', padding: 0,
|
||
transition: 'width 0.3s, background 0.3s',
|
||
}}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
|
||
function MembershipForm({ lang }: { lang: 'fa' | 'en' }) {
|
||
const [name, setName] = useState('')
|
||
const [phone, setPhone] = useState('')
|
||
const [email, setEmail] = useState('')
|
||
const [status, setStatus] = useState<'idle'|'sending'|'done'|'error'>('idle')
|
||
|
||
const lbl = lang === 'fa'
|
||
? { name: 'نام و نامخانوادگی', phone: 'شماره همراه', email: 'آدرس ایمیل', btn: 'ارسال درخواست', sending: 'در حال ارسال...', done: '✓ درخواست شما ثبت شد. بهزودی با شما تماس میگیریم.', error: 'خطا در ارسال. لطفاً مجدد تلاش کنید.' }
|
||
: { name: 'Full Name', phone: 'Phone Number', email: 'Email Address', btn: 'Send Request', sending: 'Sending...', done: '✓ Request received. We will contact you soon.', error: 'Send failed. Please try again.' }
|
||
|
||
async function submit() {
|
||
if (!name || !phone) return
|
||
setStatus('sending')
|
||
const PANEL_API = import.meta.env.VITE_PANEL_API || 'http://localhost:3001'
|
||
try {
|
||
const res = await fetch(`${PANEL_API}/api/contact`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ name, phone, email }),
|
||
})
|
||
setStatus(res.ok ? 'done' : 'error')
|
||
} catch {
|
||
setStatus('error')
|
||
}
|
||
}
|
||
|
||
if (status === 'done') return (
|
||
<div style={{ background: 'rgba(7,29,73,0.06)', padding: '16px 20px', fontSize: 14, color: 'var(--ink)', fontWeight: 600, borderRight: '4px solid var(--color-blue)' }}>
|
||
{lbl.done}
|
||
</div>
|
||
)
|
||
|
||
return (
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 420 }}>
|
||
{(['name','phone','email'] as const).map(field => (
|
||
<input
|
||
key={field}
|
||
type={field === 'email' ? 'email' : field === 'phone' ? 'tel' : 'text'}
|
||
placeholder={lbl[field]}
|
||
value={field === 'name' ? name : field === 'phone' ? phone : email}
|
||
onChange={e => field === 'name' ? setName(e.target.value) : field === 'phone' ? setPhone(e.target.value) : setEmail(e.target.value)}
|
||
style={{
|
||
border: '1px solid var(--rule-thin)', padding: '11px 14px',
|
||
fontSize: 13, fontFamily: 'inherit', background: 'var(--paper)',
|
||
color: 'var(--ink)', outline: 'none', width: '100%',
|
||
direction: lang === 'fa' ? 'rtl' : 'ltr',
|
||
}}
|
||
/>
|
||
))}
|
||
{status === 'error' && <div style={{ fontSize: 12, color: 'var(--red)' }}>{lbl.error}</div>}
|
||
<button
|
||
onClick={submit}
|
||
disabled={status === 'sending'}
|
||
style={{
|
||
background: 'var(--color-navy)', color: '#fff', border: 'none',
|
||
padding: '13px 32px', fontSize: 13, fontWeight: 700,
|
||
fontFamily: 'inherit', cursor: status === 'sending' ? 'default' : 'pointer',
|
||
opacity: status === 'sending' ? 0.7 : 1, transition: 'opacity 150ms',
|
||
alignSelf: 'flex-start',
|
||
}}
|
||
>
|
||
{status === 'sending' ? lbl.sending : lbl.btn}
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
/* ─── product coverage section ──────────────────────────── */
|
||
const PRODUCT_CATEGORIES = [
|
||
{
|
||
title: 'محصولات فولادی',
|
||
items: ['مفتول', 'میلگرد', 'مقاطع', 'لوله', 'کویل نورد گرم', 'ورق', 'پوششدار', 'نوار'],
|
||
},
|
||
{
|
||
title: 'فروآلیاژ فولاد',
|
||
items: ['فروبور', 'فروکروم', 'فرو منگنز', 'فرونیوبیوم', 'فروفسفر', 'فروسیلیکون', 'فروتیتانیوم', 'فرووتنگستن', 'فروواناديوم', 'کروم-سیلیکون', 'کلسیم-سیلیکون', 'سنگ کروم', 'سنگ منگنز'],
|
||
},
|
||
{
|
||
title: 'صنعت فولاد',
|
||
items: ['میله استیل', 'کویل استیل', 'لوله استیل', 'ضایعات استیل', 'فولاد بلبرینگ', 'فولاد سرد هیدینگ', 'فولاد چرخدنده', 'فولاد سیلیکون', 'فولاد سازهای'],
|
||
},
|
||
{
|
||
title: 'مواد اولیه',
|
||
items: ['زغال سنگ', 'کک', 'آهن', 'سنگ آهن', 'بیلت فولادی', 'ضایعات فولادی'],
|
||
},
|
||
]
|
||
|
||
const CONTENT_SECTIONS = [
|
||
'صنعت فولاد', 'بازار سرمایه', 'فلزات و کامودیتی',
|
||
'ارزهای دیجیتال', 'بازار ارز و طلا', 'اقتصاد کلان',
|
||
]
|
||
const DATA_SOURCES = ['Platts', 'Fastmarkets', 'Bainfo China', 'Dlpibica']
|
||
|
||
function CategoryColumn({ cat }: { cat: typeof PRODUCT_CATEGORIES[0] }) {
|
||
return (
|
||
<div style={{ padding: '14px 18px' }}>
|
||
<div style={{
|
||
fontSize: 10, fontWeight: 800, color: 'var(--ink)',
|
||
marginBottom: 10, paddingBottom: 6,
|
||
borderBottom: '2px solid var(--red)',
|
||
display: 'inline-block',
|
||
}}>{cat.title}</div>
|
||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
|
||
{cat.items.map((item, ii) => (
|
||
<span key={ii} style={{
|
||
fontSize: 11, color: 'var(--ink-2)',
|
||
background: 'var(--paper-2)',
|
||
border: '1px solid var(--rule-thin)',
|
||
padding: '2px 8px',
|
||
whiteSpace: 'nowrap',
|
||
}}>{item}</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function ProductCoverage({ lang }: { lang: 'fa' | 'en' }) {
|
||
const tx = TX[lang]
|
||
const totalCount = PRODUCT_CATEGORIES.reduce((acc, c) => acc + c.items.length, 0)
|
||
|
||
return (
|
||
<div style={{ marginBottom: 56, border: '1px solid var(--rule-thin)', overflow: 'hidden' }}>
|
||
|
||
{/* ── top header ── */}
|
||
<div style={{
|
||
background: 'var(--ink)', color: 'var(--paper)',
|
||
padding: '16px 28px',
|
||
display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 12,
|
||
}}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||
<div style={{ width: 3, height: 28, background: 'var(--red)' }} />
|
||
<div>
|
||
<div style={{ fontSize: 9, letterSpacing: '3px', color: 'rgba(255,255,255,0.4)', marginBottom: 2 }}>PRICE COVERAGE</div>
|
||
<div style={{ fontSize: 14, fontWeight: 900 }}>{tx.coverageTitle}</div>
|
||
</div>
|
||
</div>
|
||
<div style={{ display: 'flex', gap: 28 }}>
|
||
{[{ n: `${totalCount}+`, l: tx.productLabel }, { n: '۵۰۰+', l: tx.indexLabel }, { n: '۴', l: tx.sourceLabel }].map(s => (
|
||
<div key={s.l} style={{ textAlign: 'center' }}>
|
||
<div style={{ fontSize: 20, fontWeight: 900, lineHeight: 1 }}>{s.n}</div>
|
||
<div style={{ fontSize: 9, color: 'rgba(255,255,255,0.45)', marginTop: 3 }}>{s.l}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── main body: 2-col products + CTA sidebar ── */}
|
||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 280px' }} className="max-lg:!grid-cols-1">
|
||
|
||
{/* left: 2×2 product grid */}
|
||
<div style={{ borderLeft: '1px solid var(--rule-thin)' }} className="max-lg:border-l-0">
|
||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
|
||
{PRODUCT_CATEGORIES.map((cat, ci) => (
|
||
<div key={ci} style={{
|
||
borderBottom: ci < 2 ? '1px solid var(--rule-thin)' : 'none',
|
||
borderLeft: ci % 2 === 1 ? '1px solid var(--rule-thin)' : 'none',
|
||
}}>
|
||
<CategoryColumn cat={cat} />
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* right: CTA sidebar */}
|
||
<div style={{
|
||
background: 'rgba(7,29,73,0.04)',
|
||
borderRight: '1px solid var(--rule-thin)',
|
||
padding: '28px 24px',
|
||
display: 'flex', flexDirection: 'column', gap: 18,
|
||
}} className="max-lg:border-r-0 max-lg:border-t max-lg:border-t-[var(--rule-thin)]">
|
||
|
||
<div>
|
||
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '2.5px', color: 'var(--red)', marginBottom: 10 }}>MEMBERS ONLY</div>
|
||
<p style={{ fontSize: 12, color: 'var(--ink-3)', lineHeight: 1.75, margin: 0 }}>{tx.coverageDesc}</p>
|
||
</div>
|
||
|
||
<div style={{ borderTop: '1px solid var(--rule-thin)', paddingTop: 16, display: 'flex', flexDirection: 'column', gap: 9 }}>
|
||
{tx.coverageFeatures.map(f => (
|
||
<div key={f} style={{ display: 'flex', alignItems: 'center', gap: 9, fontSize: 12, color: 'var(--ink)' }}>
|
||
<span style={{ color: '#166534', fontWeight: 900, fontSize: 12 }}>✓</span>{f}
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div style={{ borderTop: '1px solid var(--rule-thin)', paddingTop: 16 }}>
|
||
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '2px', color: 'var(--ink-4)', marginBottom: 10 }}>{tx.sectionsLabel}</div>
|
||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||
{tx.contentSections.map(s => (
|
||
<span key={s} style={{
|
||
fontSize: 10, fontWeight: 600, padding: '3px 9px',
|
||
border: '1px solid var(--rule-thin)',
|
||
color: 'var(--ink-3)',
|
||
}}>{s}</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── full-width DATA SOURCES bar ── */}
|
||
<div style={{
|
||
background: 'var(--ink)',
|
||
color: 'var(--paper)',
|
||
padding: '36px 40px',
|
||
borderTop: '3px solid var(--red)',
|
||
display: 'grid',
|
||
gridTemplateColumns: '1fr auto',
|
||
gap: 40,
|
||
alignItems: 'center',
|
||
}} className="max-md:!grid-cols-1">
|
||
|
||
<div>
|
||
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '3px', color: 'var(--red)', marginBottom: 10 }}>DATA SOURCES</div>
|
||
<h3 style={{ fontSize: 20, fontWeight: 900, margin: '0 0 10px', lineHeight: 1.3 }}>{tx.dsTitle}</h3>
|
||
<p style={{ fontSize: 13, color: 'rgba(255,255,255,0.6)', lineHeight: 1.85, margin: 0, maxWidth: 640 }}>{tx.dsBody}</p>
|
||
</div>
|
||
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, minWidth: 180 }}>
|
||
{DATA_SOURCES.map(src => (
|
||
<div key={src} style={{
|
||
display: 'flex', alignItems: 'center', gap: 10,
|
||
padding: '10px 16px',
|
||
border: '1px solid rgba(255,255,255,0.15)',
|
||
fontSize: 13, fontWeight: 800, letterSpacing: '0.5px',
|
||
}}>
|
||
<span style={{ width: 6, height: 6, background: '#4ade80', borderRadius: '50%', flexShrink: 0 }} />
|
||
{src}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default function Pulse() {
|
||
const { lang } = useLang()
|
||
const t = T[lang]
|
||
const stats = STATS[lang]
|
||
return (
|
||
<div dir={lang === 'fa' ? 'rtl' : 'ltr'} style={{ background: 'var(--paper)', minHeight: '80vh' }}>
|
||
<div style={{ borderBottom: '3px solid var(--ink)' }}>
|
||
<div className="max-w-7xl mx-auto px-12 max-md:px-5" style={{ paddingTop: 48, paddingBottom: 40 }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16 }}>
|
||
<div style={{ width: 36, height: 2, background: 'var(--red)' }} />
|
||
<span style={{ fontSize: 10, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'var(--ink-4)' }}>{t.overline}</span>
|
||
</div>
|
||
<h1 style={{ fontSize: 'clamp(28px,4vw,52px)', fontWeight: 900, letterSpacing: '-1.5px', color: 'var(--ink)', marginBottom: 12 }}>{t.heading}</h1>
|
||
<p style={{ fontSize: 15, color: 'var(--ink-3)', lineHeight: 1.7, maxWidth: 600 }}>{t.sub}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="max-w-7xl mx-auto px-12 max-md:px-5" style={{ paddingTop: 48, paddingBottom: 64 }}>
|
||
<div style={{ marginBottom: 32, display: 'flex', alignItems: 'center', gap: 0, border: '1px solid var(--rule-thin)', overflow: 'hidden' }}>
|
||
<div style={{ padding: '12px 18px', borderLeft: '1px solid var(--rule-thin)', fontSize: 9, fontWeight: 700, letterSpacing: '2px', color: 'var(--ink-4)', whiteSpace: 'nowrap', background: 'var(--paper-2)' }}>
|
||
{t.statsLabel}
|
||
</div>
|
||
{stats.map((s, i) => (
|
||
<div key={i} style={{
|
||
padding: '12px 20px', borderLeft: '1px solid var(--rule-thin)',
|
||
flex: 1, display: 'flex', alignItems: 'center', gap: 12,
|
||
filter: i >= 2 ? 'blur(4px)' : 'none',
|
||
userSelect: i >= 2 ? 'none' : 'auto',
|
||
pointerEvents: i >= 2 ? 'none' : 'auto',
|
||
}}>
|
||
<div style={{ fontSize: 22, fontWeight: 900, letterSpacing: '-0.5px', color: 'var(--ink)', lineHeight: 1, fontVariantNumeric: 'tabular-nums' }}>{s.value}</div>
|
||
<div>
|
||
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--ink-3)' }}>{s.label}</div>
|
||
<div style={{ fontSize: 9, color: 'var(--ink-5)' }}>{s.unit}</div>
|
||
<div style={{ fontSize: 9, fontWeight: 700, color: s.delta.startsWith('+') ? '#2d6a4f' : s.delta.startsWith('-') ? 'var(--red)' : 'var(--ink-5)', marginTop: 2 }}>{s.delta}</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Live market snapshot */}
|
||
<div style={{ marginBottom: 48, overflow: 'hidden', border: '1px solid var(--rule-thin)' }}>
|
||
<SnapshotBar />
|
||
</div>
|
||
|
||
{/* Price table with paywall blur */}
|
||
<PriceTable blurFrom={4} lang={lang} />
|
||
|
||
{/* Slider + form side by side on desktop — form on RIGHT (col-1 in RTL) */}
|
||
<div style={{ display: 'grid', gridTemplateColumns: '320px 1fr', gap: 0, marginBottom: 48, border: '1px solid var(--rule-thin)', alignItems: 'stretch' }} className="max-md:!grid-cols-1">
|
||
<div style={{ padding: '32px 24px', background: 'var(--ink)', color: 'var(--paper)', display: 'flex', flexDirection: 'column', gap: 20, borderLeft: '1px solid rgba(255,255,255,0.08)' }}>
|
||
<div>
|
||
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '3px', color: 'var(--red)', marginBottom: 10 }}>MEMBERS ONLY ACCESS</div>
|
||
<h3 style={{ fontSize: 16, fontWeight: 900, margin: '0 0 8px', lineHeight: 1.3 }}>{t.gateHeading}</h3>
|
||
<p style={{ fontSize: 12, color: 'rgba(255,255,255,0.55)', lineHeight: 1.7, margin: 0 }}>{t.gateSub}</p>
|
||
</div>
|
||
<div style={{ borderTop: '1px solid rgba(255,255,255,0.1)', paddingTop: 16, display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||
{t.features.map(f => (
|
||
<div key={f} style={{ display: 'flex', gap: 8, fontSize: 12, color: 'rgba(255,255,255,0.8)' }}>
|
||
<span style={{ color: '#4ade80', fontWeight: 900, flexShrink: 0 }}>✓</span>{f}
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div style={{ borderTop: '1px solid rgba(255,255,255,0.1)', paddingTop: 16 }}>
|
||
<MembershipForm lang={lang} />
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<ImageSlider />
|
||
</div>
|
||
</div>
|
||
|
||
{/* Product coverage */}
|
||
<ProductCoverage lang={lang} />
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|