Navbar restructure, footer redesign, cobe globe, approach section

- Navbar: نبض صنعت (->/pulse), رسانه و رویداد dropdown (videocast/sessions/events),
  generalized NavDropdown; logo -> Frame 2095585206 (2).png
- Footer: 3-col RTL layout (brand+socials / nav / contact+badges+newsletter),
  white logo, gold copyright bar
- RadarTechSection: replace tech-frontier with «رویکرد ما» approach cards
- MapEventsSection: swap static map for animated cobe WebGL globe (blue theme)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
This commit is contained in:
alireza 2026-06-10 14:56:41 +03:30
parent 121082abc9
commit 9043f62b27
7 changed files with 279 additions and 204 deletions

18
package-lock.json generated
View File

@ -12,6 +12,7 @@
"@tanstack/react-query": "^5.100.14", "@tanstack/react-query": "^5.100.14",
"apexcharts": "^5.13.0", "apexcharts": "^5.13.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cobe": "^2.0.1",
"dotted-map": "^3.1.0", "dotted-map": "^3.1.0",
"framer-motion": "^12.40.0", "framer-motion": "^12.40.0",
"gsap": "^3.15.0", "gsap": "^3.15.0",
@ -1712,6 +1713,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/cobe": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/cobe/-/cobe-2.0.1.tgz",
"integrity": "sha512-aaa6vcIlaC8C1SF50LDH0Anybo/EAXnrxqe+bwvr4+YUtZydqjeBjTTD7ziCCkbRrRGSns3I3F6cZsf3W+L+ag==",
"license": "MIT"
},
"node_modules/convert-source-map": { "node_modules/convert-source-map": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@ -3318,6 +3325,17 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"optional": true,
"peer": true,
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/vazir-font": { "node_modules/vazir-font": {
"version": "30.1.0", "version": "30.1.0",
"resolved": "https://registry.npmjs.org/vazir-font/-/vazir-font-30.1.0.tgz", "resolved": "https://registry.npmjs.org/vazir-font/-/vazir-font-30.1.0.tgz",

View File

@ -14,6 +14,7 @@
"@tanstack/react-query": "^5.100.14", "@tanstack/react-query": "^5.100.14",
"apexcharts": "^5.13.0", "apexcharts": "^5.13.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cobe": "^2.0.1",
"dotted-map": "^3.1.0", "dotted-map": "^3.1.0",
"framer-motion": "^12.40.0", "framer-motion": "^12.40.0",
"gsap": "^3.15.0", "gsap": "^3.15.0",

View File

@ -1,68 +1,92 @@
import { useState } from 'react' import { useState } from 'react'
import { Send, Share2, AtSign, MessageCircle, MapPin, Phone, Mail } from 'lucide-react'
function SocialIcon({ label, name, href }: { label: string; name: string; href: string }) { const NAVY = '#071d49'
const GOLD = '#c9a227'
/* round social buttons (gold filled, like the screenshot) */
const SOCIALS = [
{ Icon: Send, name: 'تلگرام', href: '#' },
{ Icon: Share2, name: 'لینکدین', href: '#' },
{ Icon: AtSign, name: 'توییتر', href: '#' },
{ Icon: MessageCircle, name: 'آپارات', href: '#' },
]
function SocialIcon({ Icon, name, href }: { Icon: typeof Send; name: string; href: string }) {
const [hovered, setHovered] = useState(false) const [hovered, setHovered] = useState(false)
return ( return (
<a <a
href={href} target="_blank" rel="noopener noreferrer" aria-label={name} title={name} href={href} target="_blank" rel="noopener noreferrer" aria-label={name} title={name}
onMouseEnter={() => setHovered(true)} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)}
onMouseLeave={() => setHovered(false)}
style={{ style={{
width: 36, height: 36, borderRadius: 8, width: 42, height: 42, borderRadius: '50%', flexShrink: 0,
border: `1px solid ${hovered ? 'rgba(255,255,255,0.4)' : 'rgba(255,255,255,0.15)'}`, background: GOLD, color: NAVY, display: 'flex', alignItems: 'center', justifyContent: 'center',
background: hovered ? 'rgba(255,255,255,0.08)' : 'transparent', textDecoration: 'none', cursor: 'pointer', transition: 'transform 150ms, opacity 150ms',
display: 'flex', alignItems: 'center', justifyContent: 'center', transform: hovered ? 'translateY(-3px)' : 'none', opacity: hovered ? 0.9 : 1,
fontSize: 11, fontWeight: 700, textDecoration: 'none',
color: hovered ? '#fff' : 'rgba(255,255,255,0.5)',
cursor: 'pointer', transition: 'all 150ms', flexShrink: 0,
}} }}
>{label}</a> >
<Icon size={19} strokeWidth={2} />
</a>
) )
} }
const SOCIALS = [ /* center nav — the navbar items */
{ label: 'in', name: 'لینکدین', href: '#' }, const NAV_LINKS = [
{ label: 'X', name: 'توییتر', href: '#' }, { label: 'رادار آینده', href: '/radar' },
{ label: 'آپ', name: 'آپارات', href: '#' }, { label: 'آمار و داده', href: '#' },
{ label: 'tg', name: 'تلگرام', href: '#' }, { label: 'چندرسانه‌ای', href: '#' },
{ label: 'در یک نگاه', href: '/at-a-glance' },
{ label: 'شبکه خبرگان', href: '#' },
{ label: 'درباره ما', href: '#' },
]
function NavItem({ href, label }: { href: string; label: string }) {
const [hovered, setHovered] = useState(false)
return (
<a href={href}
onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)}
style={{
display: 'flex', alignItems: 'center', gap: 8, padding: '6px 0',
fontSize: 13.5, color: hovered ? '#fff' : 'rgba(255,255,255,0.6)',
textDecoration: 'none', transition: 'color 150ms', flexDirection: 'row-reverse', justifyContent: 'flex-end',
}}
>
{label}
<span style={{ width: 4, height: 4, borderRadius: '50%', background: GOLD, flexShrink: 0 }} />
</a>
)
}
/* contact rows */
const CONTACT = [
{ Icon: MapPin, label: 'نشانی:', value: 'اصفهان، خیابان سعادت‌آباد' },
{ Icon: Phone, label: 'شماره تماس:', value: '۰۳۱ - ۳۸۸۸۴۵۳۹', ltr: true },
{ Icon: Mail, label: 'پست الکترونیک:', value: 'info@steelfutures.com', ltr: true, accent: true },
] ]
function NewsletterBox() { function NewsletterBox() {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const [done, setDone] = useState(false) const [done, setDone] = useState(false)
return ( return (
<div style={{ marginTop: 8 }}> <div>
<div style={{ <div style={{ fontSize: 13, fontWeight: 700, color: '#fff', marginBottom: 12 }}>عضویت در خبرنامه اندیشکده</div>
fontSize: 11, fontWeight: 700, <div style={{ display: 'flex', border: '1px solid rgba(255,255,255,0.18)', borderRadius: 8, overflow: 'hidden', background: 'rgba(255,255,255,0.05)' }}>
color: 'rgba(255,255,255,0.4)',
letterSpacing: '2px', textTransform: 'uppercase',
marginBottom: 12,
}}>
خبرنامه فولاد آینده
</div>
<div style={{ display: 'flex', border: '1px solid rgba(255,255,255,0.18)' }}>
<input <input
type="email" value={email} type="email" value={email} onChange={e => setEmail(e.target.value)}
onChange={e => setEmail(e.target.value)} placeholder="ایمیل خود را وارد کنید..." disabled={done}
placeholder="ایمیل خود را وارد کنید..."
disabled={done}
style={{ style={{
flex: 1, border: 'none', padding: '10px 14px', fontSize: 13, flex: 1, border: 'none', padding: '11px 14px', fontSize: 13, fontFamily: 'inherit',
fontFamily: 'inherit', background: 'rgba(255,255,255,0.07)', background: 'transparent', color: '#fff', outline: 'none', direction: 'rtl', minWidth: 0,
color: '#fff', outline: 'none', direction: 'rtl', minWidth: 0,
}} }}
/> />
<button <button
onClick={() => { if (email) setDone(true) }} onClick={() => { if (email) setDone(true) }} disabled={done}
disabled={done}
style={{ style={{
background: done ? 'rgba(255,255,255,0.15)' : '#00AEEF', background: GOLD, color: NAVY, border: 'none', padding: '11px 22px',
color: '#fff', border: 'none', padding: '10px 16px', fontSize: 12, fontWeight: 800, fontFamily: 'inherit', cursor: done ? 'default' : 'pointer', whiteSpace: 'nowrap', flexShrink: 0,
fontSize: 12, fontWeight: 700, fontFamily: 'inherit',
cursor: done ? 'default' : 'pointer', whiteSpace: 'nowrap', flexShrink: 0,
}} }}
> >
{done ? '✓ ثبت شد' : 'عضویت'} {done ? '✓ ثبت شد' : 'ثبت'}
</button> </button>
</div> </div>
</div> </div>
@ -71,53 +95,72 @@ function NewsletterBox() {
export function Footer() { export function Footer() {
return ( return (
<footer dir="rtl" style={{ backgroundColor: 'var(--ink)', color: 'rgba(255,255,255,0.6)' }}> <footer dir="rtl" style={{ backgroundColor: NAVY, color: 'rgba(255,255,255,0.6)' }}>
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '60px 48px 52px' }} <div style={{ maxWidth: 1280, margin: '0 auto', padding: '60px 48px 48px' }} className="max-md:!px-6 max-md:!py-10">
className="max-md:!px-6 max-md:!py-10">
<div className="grid grid-cols-2 max-md:grid-cols-1 gap-12 max-md:gap-8"> <div className="grid grid-cols-[1.3fr_0.8fr_1.2fr] max-lg:grid-cols-2 max-md:grid-cols-1 gap-12 max-md:gap-10">
{/* ── BRAND (right side in RTL) ── */}
<div style={{ textAlign: 'right' }}>
<img src="/logo%20white.png" alt="اندیشکده فولاد آینده" style={{ height: 64, width: 'auto', display: 'inline-block', marginBottom: 12 }} />
<div style={{ fontSize: 22, fontWeight: 900, color: '#fff', lineHeight: 1.3, marginBottom: 4 }}>اندیشکده فولاد آینده</div>
<div style={{ fontSize: 10, letterSpacing: 2, color: 'rgba(255,255,255,0.35)', textTransform: 'uppercase', marginBottom: 18 }}>The Steel Futures Institute</div>
{/* Brand */}
<div>
<div style={{ fontSize: 24, fontWeight: 900, color: '#fff', lineHeight: 1.3, marginBottom: 6 }}>
اندیشکده فولاد آینده
</div>
<div style={{
fontSize: 10, letterSpacing: 2,
color: 'rgba(255,255,255,0.3)',
textTransform: 'uppercase',
marginBottom: 18,
lineHeight: 1.6,
}}>
The Steel Futures Institute
</div>
<p style={{ <p style={{
fontSize: 14, lineHeight: 2, color: 'rgba(255,255,255,0.6)', fontSize: 13, lineHeight: 2, color: 'rgba(255,255,255,0.62)',
maxWidth: 460, margin: '0 0 26px', border: '1px solid rgba(255,255,255,0.15)', borderRadius: 14, padding: '16px 18px', margin: '0 0 26px',
}}> }}>
فضایی همدلانه برای اندیشیدن به فردای صنعت؛ جایی که دانش، آیندهپژوهی و نوآوری به بینشهایی برای توسعه صنعتی تبدیل میشوند. فضایی همدلانه برای اندیشیدن به فردای صنعت؛ جایی که دانش، آیندهپژوهی و نوآوری به بینشهایی برای توسعه صنعتی تبدیل میشوند.
</p> </p>
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
{SOCIALS.map(s => ( <div style={{ fontSize: 13, fontWeight: 700, color: '#fff', marginBottom: 14 }}>ما را در شبکههای اجتماعی دنبال کنید.</div>
<SocialIcon key={s.name} label={s.label} name={s.name} href={s.href} /> <div style={{ display: 'flex', gap: 12, justifyContent: 'flex-start' }}>
{SOCIALS.map((s) => (
<SocialIcon key={s.name} Icon={s.Icon} name={s.name} href={s.href} />
))} ))}
</div> </div>
</div> </div>
{/* Newsletter */} {/* ── NAV (center) ── */}
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'flex-start' }}> <nav aria-label="پیوندهای پایین" style={{ display: 'flex', flexDirection: 'column' }}>
{NAV_LINKS.map((l) => (
<NavItem key={l.label} href={l.href} label={l.label} />
))}
</nav>
{/* ── CONTACT + badges + newsletter (left side in RTL) ── */}
<div>
<div style={{ fontSize: 14, fontWeight: 800, color: '#fff', marginBottom: 18 }}>اطلاعات تماس</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginBottom: 26 }}>
{CONTACT.map((c) => (
<div key={c.label} style={{ display: 'flex', alignItems: 'center', gap: 10, fontSize: 13 }}>
<c.Icon size={15} color={GOLD} style={{ flexShrink: 0 }} />
<span style={{ color: 'rgba(255,255,255,0.45)' }}>{c.label}</span>
<span style={{ color: c.accent ? GOLD : 'rgba(255,255,255,0.75)', direction: c.ltr ? 'ltr' : 'rtl' }}>{c.value}</span>
</div>
))}
</div>
{/* certification badges (placeholders — swap for real images) */}
<div style={{ display: 'flex', gap: 12, marginBottom: 26 }}>
{[0, 1, 2].map((i) => (
<div key={i} style={{ width: 66, height: 66, borderRadius: 10, background: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
<span style={{ fontSize: 9, color: 'rgba(7,29,73,0.4)', fontWeight: 700 }}>نشان</span>
</div>
))}
</div>
<NewsletterBox /> <NewsletterBox />
</div> </div>
</div> </div>
</div> </div>
<div style={{ borderTop: '1px solid rgba(255,255,255,0.08)' }}> {/* copyright bar */}
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '20px 48px', textAlign: 'center', fontSize: 13, color: 'rgba(255,255,255,0.7)', lineHeight: 1.8 }} <div style={{ background: GOLD, color: NAVY }}>
className="max-md:!px-6"> <div style={{ maxWidth: 1280, margin: '0 auto', padding: '14px 48px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap', fontSize: 12, fontWeight: 700 }}
<div>تمامی حقوق این وب سایت محفوظ و متعلق به شرکت فولاد مبارکه است.</div> className="max-md:!px-6 max-md:!justify-center max-md:!text-center">
<div style={{ color: 'rgba(255,255,255,0.4)', marginTop: 4, fontSize: 12 }}> <span>تمامی حقوق مادی و معنوی این وبسایت برای شرکت فولاد مبارکه اصفهان محفوظ میباشد.</span>
با افتخار قدرت گرفته از دیدوان؛ چشم همیشه باز مدیران <span style={{ opacity: 0.8 }}>طراحی شده توسط شرکت دانشبنیان نواندیشان آتی نگار فرتاک</span>
</div>
</div> </div>
</div> </div>
</footer> </footer>

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from 'react' import React, { useState, useEffect, useRef } from 'react'
import type { CSSProperties } from 'react' import type { CSSProperties } from 'react'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { Search, Menu, X, ChevronDown, Radar, Leaf, Globe, Cpu, BarChart3 } from 'lucide-react' import { Search, Menu, X, ChevronDown, Radar, Leaf, Globe, Cpu, BarChart3, Video, Mic, CalendarDays } from 'lucide-react'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { useLang } from '@/context/LangContext' import { useLang } from '@/context/LangContext'
@ -17,8 +17,10 @@ const T = {
red: 'var(--red)', red: 'var(--red)',
} as const } as const
type Section = { fa: string; en: string; to: string; Icon: React.ElementType }
/* ─── dropdown sections under رادار آینده ─── */ /* ─── dropdown sections under رادار آینده ─── */
const SECTIONS: { fa: string; en: string; to: string; Icon: React.ElementType }[] = [ const SECTIONS: Section[] = [
{ fa: 'آینده‌پژوهی و رصد هوشمند', en: 'Foresight & Intelligence', to: '/radar', Icon: Radar }, { fa: 'آینده‌پژوهی و رصد هوشمند', en: 'Foresight & Intelligence', to: '/radar', Icon: Radar },
{ fa: 'پایداری و فولاد سبز', en: 'Sustainability & Green Steel', to: '/sustainability', Icon: Leaf }, { fa: 'پایداری و فولاد سبز', en: 'Sustainability & Green Steel', to: '/sustainability', Icon: Leaf },
{ fa: 'ژئوپلیتیک و اقتصاد جهانی', en: 'Geopolitics & Global Economy', to: '/geopolitics', Icon: Globe }, { fa: 'ژئوپلیتیک و اقتصاد جهانی', en: 'Geopolitics & Global Economy', to: '/geopolitics', Icon: Globe },
@ -26,14 +28,22 @@ const SECTIONS: { fa: string; en: string; to: string; Icon: React.ElementType }[
{ fa: 'بازار و زنجیره فولاد', en: 'Market & Steel Chain', to: '/pulse', Icon: BarChart3 }, { fa: 'بازار و زنجیره فولاد', en: 'Market & Steel Chain', to: '/pulse', Icon: BarChart3 },
] ]
/* ─── dropdown sections under رسانه و رویداد ─── */
const MEDIA: Section[] = [
{ fa: 'ویدیوکست و پادکست', en: 'Videocast & Podcast', to: '#', Icon: Video },
{ fa: 'نشست‌های تخصصی', en: 'Expert Sessions', to: '#', Icon: Mic },
{ fa: 'رویدادها', en: 'Events', to: '#', Icon: CalendarDays },
]
/* ─── top-level nav items ─── */ /* ─── top-level nav items ─── */
const TOP_NAV: { fa: string; en: string; to?: string; dropdown?: boolean }[] = [ type TopNav = { fa: string; en: string; to?: string; dropdown?: 'radar' | 'media' }
{ fa: 'رادار آینده', en: 'Radar', dropdown: true }, const TOP_NAV: TopNav[] = [
{ fa: 'تحلیل‌های استراتژیک', en: 'Strategic Analysis', to: '#' }, { fa: 'رادار آینده', en: 'Radar', dropdown: 'radar' },
{ fa: 'چندرسانه‌ای', en: 'Multimedia', to: '#' }, { fa: 'نبض صنعت', en: 'Industry Pulse', to: '/pulse' },
{ fa: 'در یک نگاه', en: 'At a Glance', to: '/at-a-glance' }, { fa: 'رسانه و رویداد', en: 'Media & Events', dropdown: 'media' },
{ fa: 'شبکه خبرگان', en: 'Expert Network', to: '#' }, { fa: 'در یک نگاه', en: 'At a Glance', to: '/at-a-glance' },
{ fa: 'درباره ما', en: 'About', to: '#' }, { fa: 'شبکه خبرگان', en: 'Expert Network', to: '#' },
{ fa: 'درباره ما', en: 'About', to: '#' },
] ]
const META_LEFT: { fa: string; en: string }[] = [ const META_LEFT: { fa: string; en: string }[] = [
@ -43,8 +53,8 @@ const META_LEFT: { fa: string; en: string }[] = [
{ fa: 'رسانه', en: 'Press' }, { fa: 'رسانه', en: 'Press' },
] ]
/* ─── رادار آینده dropdown card ─── */ /* ─── generic nav dropdown card ─── */
function RadarDropdown({ lang }: { lang: 'fa' | 'en' }) { function NavDropdown({ lang, label, sections }: { lang: 'fa' | 'en'; label: string; sections: Section[] }) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
@ -70,7 +80,7 @@ function RadarDropdown({ lang }: { lang: 'fa' | 'en' }) {
borderBottom: open ? `2px solid ${T.red}` : '2px solid transparent', borderBottom: open ? `2px solid ${T.red}` : '2px solid transparent',
}} }}
> >
{lang === 'fa' ? 'رادار آینده' : 'Radar'} {label}
<ChevronDown size={12} style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform 200ms' }} /> <ChevronDown size={12} style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform 200ms' }} />
</button> </button>
@ -92,9 +102,9 @@ function RadarDropdown({ lang }: { lang: 'fa' | 'en' }) {
direction: lang === 'fa' ? 'rtl' : 'ltr', direction: lang === 'fa' ? 'rtl' : 'ltr',
}} }}
> >
{SECTIONS.map((item, i) => ( {sections.map((item, i) => (
<NavLink <NavLink
key={item.to} key={i}
to={item.to} to={item.to}
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
style={({ isActive }: { isActive: boolean }): CSSProperties => ({ style={({ isActive }: { isActive: boolean }): CSSProperties => ({
@ -102,7 +112,7 @@ function RadarDropdown({ lang }: { lang: 'fa' | 'en' }) {
padding: '15px 20px', textDecoration: 'none', padding: '15px 20px', textDecoration: 'none',
color: isActive ? T.red : '#3d3d3d', color: isActive ? T.red : '#3d3d3d',
background: isActive ? 'rgba(155,28,28,0.04)' : 'transparent', background: isActive ? 'rgba(155,28,28,0.04)' : 'transparent',
borderBottom: i < SECTIONS.length - 1 ? '1px solid rgba(7,29,73,0.07)' : 'none', borderBottom: i < sections.length - 1 ? '1px solid rgba(7,29,73,0.07)' : 'none',
transition: 'background 100ms', fontSize: 14, fontWeight: 600, transition: 'background 100ms', fontSize: 14, fontWeight: 600,
})} })}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = 'rgba(7,29,73,0.03)' }} onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = 'rgba(7,29,73,0.03)' }}
@ -219,7 +229,7 @@ export default function Header() {
to="/" to="/"
style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 12, textDecoration: 'none', flexShrink: 0, lineHeight: 1 }} style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 12, textDecoration: 'none', flexShrink: 0, lineHeight: 1 }}
> >
<img src="/logo.png?v=2" alt="" aria-hidden="true" <img src="/Frame%202095585206%20(2).png" alt="" aria-hidden="true"
style={{ height: 52, width: 'auto', display: 'block', flexShrink: 0, marginTop: -8 }} style={{ height: 52, width: 'auto', display: 'block', flexShrink: 0, marginTop: -8 }}
className="max-md:h-[40px]" className="max-md:h-[40px]"
/> />
@ -242,7 +252,7 @@ export default function Header() {
> >
{TOP_NAV.map((item) => {TOP_NAV.map((item) =>
item.dropdown item.dropdown
? <RadarDropdown key="radar" lang={lang} /> ? <NavDropdown key={item.fa} lang={lang} label={lang === 'fa' ? item.fa : item.en} sections={item.dropdown === 'radar' ? SECTIONS : MEDIA} />
: <PlainNavItem key={item.fa} label={lang === 'fa' ? item.fa : item.en} to={item.to!} /> : <PlainNavItem key={item.fa} label={lang === 'fa' ? item.fa : item.en} to={item.to!} />
)} )}
</nav> </nav>

View File

@ -0,0 +1,72 @@
import { useEffect, useRef } from 'react'
import createGlobe from 'cobe'
/* Lightweight WebGL globe (cobe) auto-rotating, blue dotted landmasses with
glowing cyan markers, matching the reference. */
export default function Globe({ size = 600 }: { size?: number }) {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
let phi = 0
let raf = 0
let width = canvas.offsetWidth
const onResize = () => { width = canvas.offsetWidth }
window.addEventListener('resize', onResize)
const globe = createGlobe(canvas, {
devicePixelRatio: 2,
width: width * 2,
height: width * 2,
phi: 0,
theta: 0.25,
dark: 1,
diffuse: 1.4,
mapSamples: 18000,
mapBrightness: 7,
baseColor: [0.06, 0.13, 0.4], // deep blue landmass dots
markerColor: [0.22, 0.68, 1], // glowing cyan/blue markers
glowColor: [0.18, 0.36, 0.85], // blue atmosphere glow
markers: [
{ location: [35.69, 51.39], size: 0.10 }, // Tehran
{ location: [31.23, 121.47], size: 0.09 }, // Shanghai / Dalian
{ location: [33.31, 44.36], size: 0.06 }, // Baghdad / Basra
{ location: [51.92, 4.47], size: 0.06 }, // Rotterdam
{ location: [25.20, 55.27], size: 0.06 }, // Dubai
{ location: [41.01, 28.97], size: 0.06 }, // Istanbul
{ location: [19.07, 72.88], size: 0.07 }, // Mumbai
{ location: [-23.55, -46.63], size: 0.07 },// São Paulo
{ location: [55.75, 37.62], size: 0.06 }, // Moscow
{ location: [37.56, 126.97], size: 0.06 }, // Seoul
{ location: [-1.29, 36.82], size: 0.06 }, // Nairobi
{ location: [40.71, -74.0], size: 0.07 }, // New York
],
})
// cobe v2 drives the loop externally via globe.update()
const tick = () => {
phi += 0.0045
globe.update({ phi, width: width * 2, height: width * 2 })
raf = requestAnimationFrame(tick)
}
raf = requestAnimationFrame(tick)
requestAnimationFrame(() => { canvas.style.opacity = '1' })
return () => {
cancelAnimationFrame(raf)
globe.destroy()
window.removeEventListener('resize', onResize)
}
}, [])
return (
<div style={{ width: '100%', maxWidth: size, aspectRatio: '1', margin: '0 auto', position: 'relative' }}>
<canvas
ref={canvasRef}
style={{ width: '100%', height: '100%', contain: 'layout paint size', opacity: 0, transition: 'opacity 1s ease' }}
/>
</div>
)
}

View File

@ -1,25 +1,9 @@
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import WorldMap from '@/components/ui/world-map' import Globe from '@/components/ui/globe'
import { events } from '@/data/events' import { events } from '@/data/events'
import { useLang } from '@/context/LangContext' import { useLang } from '@/context/LangContext'
const ROUTES = [
{ start: { lat: 35.69, lng: 51.39 }, end: { lat: 33.31, lng: 44.36 } },
{ start: { lat: 35.69, lng: 51.39 }, end: { lat: 41.30, lng: 69.24 } },
{ start: { lat: 31.23, lng: 121.47 }, end: { lat: 51.92, lng: 4.47 } },
{ start: { lat: 31.23, lng: 121.47 }, end: { lat: 6.45, lng: 3.39 } },
{ start: { lat: 31.23, lng: 121.47 }, end: { lat: 1.35, lng: 103.82 } },
{ start: { lat: 55.75, lng: 37.62 }, end: { lat: 39.91, lng: 116.39 } },
{ start: { lat: 55.75, lng: 37.62 }, end: { lat: 35.69, lng: 51.39 } },
{ start: { lat: 19.07, lng: 72.88 }, end: { lat: 25.20, lng: 55.27 } },
{ start: { lat: 19.07, lng: 72.88 }, end: { lat: -4.05, lng: 39.67 } },
{ start: { lat: -23.55, lng: -46.63 }, end: { lat: 31.23, lng: 121.47 } },
{ start: { lat: 37.56, lng: 126.97 }, end: { lat: 1.35, lng: 103.82 } },
{ start: { lat: 41.01, lng: 28.97 }, end: { lat: 52.52, lng: 13.40 } },
{ start: { lat: -33.87, lng: 151.21 }, end: { lat: 31.23, lng: 121.47 } },
]
const TYPE_COLOR: Record<string, string> = { const TYPE_COLOR: Record<string, string> = {
conference: 'var(--ink)', exhibition: '#6b4a00', conference: 'var(--ink)', exhibition: '#6b4a00',
seminar: '#1a4a2a', international: 'var(--red)', seminar: '#1a4a2a', international: 'var(--red)',
@ -69,7 +53,7 @@ export default function MapEventsSection() {
<p style={{ fontSize: 13, color: 'var(--ink-4)', lineHeight: 1.7, maxWidth: 460 }}>{t.mapSub}</p> <p style={{ fontSize: 13, color: 'var(--ink-4)', lineHeight: 1.7, maxWidth: 460 }}>{t.mapSub}</p>
</div> </div>
<div style={{ padding: '8px 16px 32px' }}> <div style={{ padding: '8px 16px 32px' }}>
<WorldMap dots={ROUTES} lineColor="#9b1c1c" dotColor="#c8c2bc" /> <Globe size={460} />
</div> </div>
</div> </div>

View File

@ -1,55 +1,25 @@
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Telescope, Scale, Database, Users, Lightbulb, type LucideIcon } from 'lucide-react'
import { useLang } from '@/context/LangContext' import { useLang } from '@/context/LangContext'
import { RadarCard } from '@/pages/Radar/Radar' import { RadarCard } from '@/pages/Radar/Radar'
import { RADAR_ITEMS } from '@/content/radar' import { RADAR_ITEMS } from '@/content/radar'
/* ─── Tech data ──────────────────────────────────────── */ /* ─── رویکرد ما — the institute's approach (new content, not duplicated in the navbar) ─── */
const TECHS = { type Area = { Icon: LucideIcon; color: string; title: string; desc: string }
const RESEARCH_AREAS: Record<'fa' | 'en', Area[]> = {
fa: [ fa: [
{ { Icon: Telescope, color: '#c9a227', title: 'آینده‌نگاری علمی', desc: 'به‌جای پیش‌بینی تک‌نقطه‌ای، با سناریوپردازی و تحلیل روند، چند آیندهٔ محتمل صنعت فولاد را ترسیم می‌کنیم.' },
num: '۰۱', tag: 'انرژی پاک', { Icon: Database, color: '#2471a3', title: 'تحلیل داده‌محور', desc: 'هر بینش بر پایهٔ داده‌های معتبر جهانی و داخلی است، نه گمانه‌زنی؛ تصمیم‌سازی با شواهد قابل‌اتکا.' },
title: 'فولاد هیدروژنی (DRI-H₂)', { Icon: Scale, color: '#1e8449', title: 'بی‌طرفی و استقلال', desc: 'تحلیل‌های ما مستقل از منافع کوتاه‌مدت است؛ شفاف و بدون جانب‌داری از یک بازیگر خاص.' },
desc: 'جایگزینی کک با هیدروژن سبز در کوره‌های احیای مستقیم؛ اولین کارخانه تجاری اروپایی در ۲۰۲۴ وارد مرحله تولید شد. ظرفیت تولید ۲.۵ میلیون تن در سال.', { Icon: Users, color: '#7d3c98', title: 'هم‌اندیشی خبرگان', desc: 'شبکه‌ای از مدیران، پژوهشگران و فعالان صنعت که دانش پراکنده را به بینش جمعی تبدیل می‌کند.' },
img: '/news/photo-1613665813446-82a78c468a1d.jpg', { Icon: Lightbulb, color: '#c0392b', title: 'از تحلیل تا اقدام', desc: 'یافته‌ها را به راهبردهای عملی و قابل‌اجرا برای رهبران صنعت ترجمه می‌کنیم، نه گزارش‌های روی طاقچه.' },
horizon: '۲۰۲۷–۲۰۳۰', accentColor: '#0d6b3e',
},
{
num: '۰۲', tag: 'هوش مصنوعی',
title: 'دیجیتال‌سازی و AI در تولید فولاد',
desc: 'یادگیری ماشین در بهینه‌سازی فرآیند ذوب، کنترل کیفیت آنلاین و پیش‌بینی خرابی تجهیزات. کاهش ۱۵–۲۰٪ ضایعات و صرفه‌جویی ۸٪ در مصرف انرژی.',
img: '/news/photo-1485827404703-89b55fcc595e.jpg',
horizon: '۲۰۲۵–۲۰۲۸', accentColor: '#1a4a7a',
},
{
num: '۰۳', tag: 'فناوری نوین',
title: 'الکترولیز اکسید مذاب (MOE)',
desc: 'تولید فولاد از طریق الکترولیز مستقیم اکسید آهن با برق تجدیدپذیر — بدون انتشار CO₂. MIT این فناوری را «انقلاب صنعتی بعدی» نامیده است.',
img: '/news/photo-1451187580459-43490279c0fa.jpg',
horizon: '۲۰۳۰–۲۰۳۵', accentColor: '#6b21a8',
},
], ],
en: [ en: [
{ { Icon: Telescope, color: '#c9a227', title: 'Scientific Foresight', desc: 'Instead of single-point prediction, we map several plausible futures of the steel industry through scenario planning.' },
num: '01', tag: 'Clean Energy', { Icon: Database, color: '#2471a3', title: 'Data-Driven Analysis', desc: 'Every insight rests on credible global and domestic data — evidence, not guesswork.' },
title: 'Hydrogen Steel (DRI-H₂)', { Icon: Scale, color: '#1e8449', title: 'Independence & Neutrality', desc: 'Our analysis is independent of short-term interests — transparent and unbiased toward any single actor.' },
desc: "Replacing coke with green hydrogen in direct reduction furnaces; Europe's first commercial plant entered production in 2024 with 2.5 million ton annual capacity.", { Icon: Users, color: '#7d3c98', title: 'Expert Collaboration', desc: 'A network of executives, researchers and practitioners turning scattered knowledge into collective insight.' },
img: '/news/photo-1613665813446-82a78c468a1d.jpg', { Icon: Lightbulb, color: '#c0392b', title: 'From Analysis to Action', desc: 'We translate findings into practical strategies for industry leaders — not shelf-ware reports.' },
horizon: '20272030', accentColor: '#0d6b3e',
},
{
num: '02', tag: 'Artificial Intelligence',
title: 'Digitalization & AI in Steel Production',
desc: 'Machine learning for smelting optimization, online quality control, and equipment failure prediction. Cuts waste by 1520% and energy use by 8%.',
img: '/news/photo-1485827404703-89b55fcc595e.jpg',
horizon: '20252028', accentColor: '#1a4a7a',
},
{
num: '03', tag: 'Emerging Tech',
title: 'Molten Oxide Electrolysis (MOE)',
desc: 'Producing steel by direct electrolysis of iron oxide using renewable electricity — zero CO₂ emissions. MIT has called it "the next industrial revolution".',
img: '/news/photo-1451187580459-43490279c0fa.jpg',
horizon: '20302035', accentColor: '#6b21a8',
},
], ],
} }
@ -58,26 +28,21 @@ const T = {
radarOverline: 'FUTURE RADAR', radarHeading: 'رادار آینده', radarOverline: 'FUTURE RADAR', radarHeading: 'رادار آینده',
radarSub: 'سیگنال‌های جهانی با اثر مستقیم بر صنعت فولاد ایران', radarSub: 'سیگنال‌های جهانی با اثر مستقیم بر صنعت فولاد ایران',
radarAll: 'همه سیگنال‌ها ←', radarAll: 'همه سیگنال‌ها ←',
techOverline: 'TECH FRONTIER', techHeading: 'مرز فناوری', areasOverline: 'OUR APPROACH', areasHeading: 'رویکرد ما',
techSub: 'فناوری‌های نوظهور و افق کاربرد آن‌ها در صنعت فولاد', areasSub: 'اصول و روش کار اندیشکده فولاد آینده در تحلیل و آینده‌نگاری صنعت',
techAll: 'همه فناوری‌ها ←',
horizon: 'افق زمانی',
}, },
en: { en: {
radarOverline: 'FUTURE RADAR', radarHeading: 'Future Radar', radarOverline: 'FUTURE RADAR', radarHeading: 'Future Radar',
radarSub: 'Global signals with direct impact on Iran\'s steel industry', radarSub: 'Global signals with direct impact on Iran\'s steel industry',
radarAll: 'All Signals →', radarAll: 'All Signals →',
techOverline: 'TECH FRONTIER', techHeading: 'Tech Frontier', areasOverline: 'OUR APPROACH', areasHeading: 'Our Approach',
techSub: 'Emerging technologies and their application horizon in the steel industry', areasSub: 'How the Steel Futures Institute works — its principles for analysis and foresight',
techAll: 'All Technologies →',
horizon: 'Horizon',
}, },
} }
export default function RadarTechSection() { export default function RadarTechSection() {
const { lang } = useLang() const { lang } = useLang()
const t = T[lang] const t = T[lang]
const techs = TECHS[lang]
return ( return (
<section dir={lang === 'fa' ? 'rtl' : 'ltr'} style={{ background: 'var(--paper-2)' }}> <section dir={lang === 'fa' ? 'rtl' : 'ltr'} style={{ background: 'var(--paper-2)' }}>
@ -104,61 +69,43 @@ export default function RadarTechSection() {
{/* divider */} {/* divider */}
<div style={{ height: 1, background: 'var(--rule-thin)' }} /> <div style={{ height: 1, background: 'var(--rule-thin)' }} />
{/* ══ مرز فناوری ══ */} {/* ══ حوزه‌های پژوهشی ══ */}
<div style={{ paddingTop: 56, paddingBottom: 64 }}> <div style={{ paddingTop: 56, paddingBottom: 64 }}>
<div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', marginBottom: 32, flexWrap: 'wrap', gap: 12 }}> <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', marginBottom: 32, flexWrap: 'wrap', gap: 12 }}>
<div> <div>
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'var(--ink-4)', marginBottom: 6 }}>{t.techOverline}</div> <div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'var(--ink-4)', marginBottom: 6 }}>{t.areasOverline}</div>
<h2 style={{ fontSize: 'clamp(22px,2.5vw,32px)', fontWeight: 900, color: 'var(--ink)', letterSpacing: '-0.5px', marginBottom: 6 }}>{t.techHeading}</h2> <h2 style={{ fontSize: 'clamp(22px,2.5vw,32px)', fontWeight: 900, color: 'var(--ink)', letterSpacing: '-0.5px', marginBottom: 6 }}>{t.areasHeading}</h2>
<p style={{ fontSize: 13, color: 'var(--ink-4)' }}>{t.techSub}</p> <p style={{ fontSize: 13, color: 'var(--ink-4)' }}>{t.areasSub}</p>
</div> </div>
<Link to="/technology" style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)', textDecoration: 'none', whiteSpace: 'nowrap' }}>{t.techAll}</Link>
</div> </div>
<div className="grid grid-cols-3 max-md:grid-cols-1" style={{ gap: 16 }}> <div className="grid grid-cols-3 max-md:grid-cols-1 max-lg:grid-cols-2" style={{ gap: 16 }}>
{techs.map((tech, i) => ( {RESEARCH_AREAS[lang].map((a, i) => {
<div const Icon = a.Icon
key={i} return (
style={{ <div
background: '#fff', overflow: 'hidden', key={i}
boxShadow: '0 2px 12px rgba(0,0,0,0.07)', style={{
display: 'flex', flexDirection: 'column', background: '#fff', borderRadius: 16, padding: '26px 24px',
transition: 'transform 0.2s, box-shadow 0.2s', cursor: 'pointer', boxShadow: '0 2px 12px rgba(0,0,0,0.06)', display: 'flex', flexDirection: 'column', gap: 14,
}} border: '1px solid var(--rule-thin)', transition: 'transform 0.2s, box-shadow 0.2s',
onMouseEnter={e => { (e.currentTarget as HTMLDivElement).style.transform = 'translateY(-4px)'; (e.currentTarget as HTMLDivElement).style.boxShadow = '0 10px 30px rgba(0,0,0,0.12)' }} }}
onMouseLeave={e => { (e.currentTarget as HTMLDivElement).style.transform = 'translateY(0)'; (e.currentTarget as HTMLDivElement).style.boxShadow = '0 2px 12px rgba(0,0,0,0.07)' }} onMouseEnter={e => { const d = e.currentTarget as HTMLElement; d.style.transform = 'translateY(-4px)'; d.style.boxShadow = '0 14px 34px -18px rgba(7,29,73,0.25)' }}
> onMouseLeave={e => { const d = e.currentTarget as HTMLElement; d.style.transform = 'translateY(0)'; d.style.boxShadow = '0 2px 12px rgba(0,0,0,0.06)' }}
{/* Image */} >
<div style={{ position: 'relative', height: 160, overflow: 'hidden' }}>
<img src={tech.img} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', transition: 'transform 0.4s' }}
onMouseEnter={e => (e.currentTarget.style.transform = 'scale(1.06)')}
onMouseLeave={e => (e.currentTarget.style.transform = 'scale(1)')}
/>
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(0,0,0,0.5) 0%, transparent 60%)' }} />
<div style={{ <div style={{
position: 'absolute', bottom: 12, width: 52, height: 52, borderRadius: 14, flexShrink: 0,
right: lang === 'fa' ? 12 : 'auto', left: lang === 'en' ? 12 : 'auto', background: `${a.color}15`, display: 'flex', alignItems: 'center', justifyContent: 'center',
}}> }}>
<span style={{ background: tech.accentColor, color: '#fff', fontSize: 9, fontWeight: 700, padding: '2px 8px', letterSpacing: '1px', textTransform: 'uppercase' }}>{tech.tag}</span> <Icon size={26} color={a.color} strokeWidth={2} />
</div> </div>
<div style={{ <div>
position: 'absolute', top: 12, <h3 style={{ fontSize: 16, fontWeight: 900, color: 'var(--ink)', lineHeight: 1.4, margin: '0 0 8px' }}>{a.title}</h3>
right: lang === 'fa' ? 12 : 'auto', left: lang === 'en' ? 12 : 'auto', <p style={{ fontSize: 12.5, color: 'var(--ink-4)', lineHeight: 1.85, margin: 0 }}>{a.desc}</p>
fontSize: 36, fontWeight: 900, color: 'rgba(255,255,255,0.25)', direction: 'ltr', lineHeight: 1,
}}>{tech.num}</div>
</div>
{/* Content */}
<div style={{ padding: '20px', flex: 1, display: 'flex', flexDirection: 'column', gap: 10, borderTop: `3px solid ${tech.accentColor}` }}>
<h3 style={{ fontSize: 14, fontWeight: 800, color: 'var(--ink)', lineHeight: 1.5 }}>{tech.title}</h3>
<p style={{ fontSize: 12, color: 'var(--ink-4)', lineHeight: 1.75, flex: 1 }}>{tech.desc}</p>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 'auto' }}>
<span style={{ fontSize: 9, fontWeight: 700, color: 'var(--ink-5)', letterSpacing: '1px', textTransform: 'uppercase' }}>{t.horizon}:</span>
<span style={{ fontSize: 11, fontWeight: 700, color: tech.accentColor }}>{tech.horizon}</span>
</div> </div>
</div> </div>
</div> )
))} })}
</div> </div>
</div> </div>