Design system rebrand, image slider, contact form, new home sections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alireza 2026-05-31 17:00:22 +03:30
parent a72a3c61f9
commit c934ee4ab5
15 changed files with 1474 additions and 132 deletions

350
design-system.md Normal file
View File

@ -0,0 +1,350 @@
# Design System — اندیشکده فولاد آینده (Future Steel Think Tank)
> **Tagline (FA):** «بینشی نو برای توسعه صنعتی»
> **Tagline (EN):** Shaping the Future of Steel Industry Through Insight, Innovation and Impact
> **Pillars:** INSIGHT · FORESIGHT · IMPACT
> **Source:** Brand Book v1.0 · Khordad 1405
> **Brand personality:** Forward-looking · Smart · Professional · Calm & deep · Inspiring · Authoritative
This document is the single source of truth for the brand's visual and verbal identity. Tokens are written in English for implementation; brand names, taglines, and voice are preserved in Persian as defined in the brand book.
---
## 1. Design Principles
The whole system rests on five principles. Every screen, slide, report, and post should pass all five.
1. **Consistency (یکپارچگی)** — Hold one identity across every medium. Fixed elements never change.
2. **Smart simplicity (سادگی هوشمند)** — Clean on the surface, deep underneath. Empty space is intentional, not unfinished.
3. **Clarity (وضوح)** — The message lands fast. Comprehension over decoration.
4. **Analytical focus (تحلیل‌محوری)** — Everything points at insight and understanding, not at spectacle.
5. **Future-orientation (آینده‌محوری)** — The look should feel like technology and the future, never retro or generic.
**Litmus test for any artifact:** the audience should walk away feeling *«این مجموعه آینده صنعت را عمیق‌تر، حرفه‌ای‌تر و هوشمندانه‌تر از دیگران تحلیل می‌کند».*
---
## 2. Color
### 2.1 Palette
| Token | Name (FA) | Name (EN) | Hex | Role |
|---|---|---|---|---|
| `--color-navy` | سرمه‌ای عمیق | Deep Navy | `#071D49` | Primary brand color |
| `--color-blue` | آبی آینده | Future Blue | `#00AEEF` | Accent / emphasis |
| `--color-white` | سفید | White | `#FFFFFF` | Breathing space, simplicity |
| `--color-gray-light` | خاکستری روشن | Light Gray | `#E9EEF5` | Lines & secondary surfaces |
| `--color-gray-steel` | خاکستری صنعتی | Industrial Gray | `#7C8798` | Secondary text |
| `--color-orange` | نارنجی محدود | Limited Orange | `#FF8A00` | Warning / optional emphasis only |
### 2.2 Color meaning
- **Navy** → credibility, thought, professionalism (اعتبار، تفکر، حرفه‌ای‌گری)
- **Blue** → technology, innovation, the future (فناوری، نوآوری، آینده)
- **White** → transparency, simplicity (شفافیت و سادگی)
- **Gray** → analytical focus, balance (تحلیل‌محوری و تعادل)
- **Orange** → warning, energy, accent (هشدار، انرژی، تأکید)
### 2.3 Usage ratio
A balanced layout follows roughly this distribution:
| Navy | Blue | White | Gray | Orange |
|---|---|---|---|---|
| **60%** | **25%** | **10%** | **4%** | **1%** |
Orange is a 1% color — a single highlight, an alert, a KPI delta. It is never a fill or a theme color.
### 2.4 Rules
**Do**
- Keep generous white space.
- Use blue purely for emphasis and key data points.
- Maintain the balance between dark and light areas.
**Avoid**
- Overusing colors / multi-color clutter.
- Heavy gradients.
- Off-brand or advertising-style colors.
### 2.5 Tokens
```css
:root {
--color-navy: #071D49; /* primary */
--color-blue: #00AEEF; /* accent */
--color-white: #FFFFFF;
--color-gray-light: #E9EEF5; /* lines, surfaces */
--color-gray-steel: #7C8798; /* secondary text */
--color-orange: #FF8A00; /* warning / 1% accent */
/* Semantic aliases */
--bg-primary: var(--color-navy);
--bg-surface: var(--color-white);
--bg-muted: var(--color-gray-light);
--text-on-dark: var(--color-white);
--text-on-light: var(--color-navy);
--text-muted: var(--color-gray-steel);
--accent: var(--color-blue);
--alert: var(--color-orange);
}
```
---
## 3. Typography
### 3.1 Type stack
| Role | Font |
|---|---|
| Headings / titles | **IRANSansX Bold** |
| Body text | **IRANSansX Regular** |
| Reports & dashboards | **Peyda** / IRANSansX |
| English / Latin text | **Inter** / Montserrat |
### 3.2 Voice of the type
Typography must read as **modern · legible · professional · managerial** (مدرن، خوانا، حرفه‌ای، مدیریتی).
**Rules**
- Calm and highly legible (خلوت و خوانا).
- Clear visual hierarchy (سلسله‌مراتب بصری واضح).
- Carry a Think-Tank professional tone.
- Use **Bold** sparingly — for titles and true emphasis only.
- Precise alignment.
- Avoid text clutter; let space do the work.
### 3.3 Sample lock-ups
- **FA (IRANSansX Bold):** «آینده صنعتی با بینش و فناوری»
- **EN (Inter Bold):** STEEL FUTURE — *Data · Insight · Innovation*
### 3.4 Tokens
```css
:root {
--font-fa: "IRANSansX", "Peyda", sans-serif;
--font-en: "Inter", "Montserrat", sans-serif;
--font-report: "Peyda", "IRANSansX", sans-serif;
--weight-bold: 700; /* headings / emphasis only */
--weight-regular: 400; /* body */
}
```
---
## 4. Logo & Logotype System
### 4.1 What the mark stands for
Foresight · industrial development · technology · forward motion · the creation of insight.
**Three meaning layers:**
- **Upward movement** — symbol of progress & the future (حرکت رو به بالا).
- **Industrial structure** — symbol of development & industry (ساختار صنعتی).
- **Luminous star** — symbol of insight & innovation (ستاره نورانی).
**Logo message:** «آینده صنعتی کشور با بینش، فناوری و تفکر آینده‌محور ساخته می‌شود.»
### 4.2 Variants
| Variant | When to use |
|---|---|
| Primary logo (لوگوی اصلی) | Default — all official & primary media |
| Horizontal logo (لوگوی افقی) | Headers and websites |
| Vertical logo (لوگوی عمودی) | Documents and posters |
| Icon logo (آیکون لوگو) | Social avatars & favicon |
| Monochrome (نسخه تک‌رنگ) | Single-color print |
| Reverse (نسخه معکوس) | On dark backgrounds |
### 4.3 Clear space & minimum size
- **Clear space:** keep margin on all four sides equal to the height of the star symbol.
- **Minimum width:** `60px` (digital) · `25mm` (print).
- Primary logo is preferred, always shown with the full brand name, in official and management contexts.
### 4.4 Misuse — never do this
- Change the proportions / stretch or compress.
- Rotate the mark.
- Add effects (glow, shadow, etc.).
- Recolor the logo.
- Place it on busy / low-contrast backgrounds.
- Re-typeset or substitute the wordmark font.
---
## 5. Iconography
### 5.1 Style
Icons are **minimal · linear · smart · future-oriented** (مینیمال، خطی، هوشمند، آینده‌محور).
**Rules**
- Uniform, single stroke weight across the set.
- Simple, geometric forms.
- Read as complementary elements, not the main visual.
- Compatible with a technology/analysis aesthetic.
- Simple, legible, and reusable across every medium.
**Avoid:** 3D icons · cartoonish icons · mixed/inconsistent styles.
### 5.2 Core concept set
Industry · data analysis · AI & technology · foresight · energy · industrial development · sustainability · innovation · geoeconomy · network & connectivity.
---
## 6. Layout, Grid & White Space
- Use a defined column grid with consistent spacing between elements.
- Synchronize titles with their content blocks.
- Build deliberate breathing room around content.
- Keep titles at the top of the layout; keep body legible and scannable.
- **Avoid information density and visual clutter** above all.
**White-space standard:** layouts must stay clean, legible, and visually airy. Reject overcrowding, clutter, and excessive graphic elements.
---
## 7. Imagery
**Imagery must be:** industrial · future-oriented · technological · professional.
**Good image qualities**
- Professional photography, high quality.
- Modern industrial environments.
- A sense of technology and data.
- Color-graded to match the brand palette.
**Suggested subjects:** AI & analysis · data networks · future factories · advanced technologies · digital structures · energy & sustainability · industrial value chain.
**Avoid:** cliché stock images · advertising-style images · busy compositions · low-quality images.
---
## 8. Data Visualization & Charts
**Charts must be:** simple · managerial · legible · conclusion-driven.
**Rules**
- Limited color use (lean on navy + blue, orange for one delta).
- Focus on key data; strip everything non-essential.
- Fast readability.
- Reuse consistent iconography.
**Avoid:** overly complex charts · unnecessary effects · 3D graphics · busy pie charts (prefer clean bar/line charts).
---
## 9. Fixed Brand Elements
These elements stay constant across slides, reports, digital media, and social — they are what make the identity recognizable:
- Color palette
- Typography
- Icon style
- Chart style
- Grid & layout
- White-space discipline
- Imagery style
- Future-oriented visual tone
---
## 10. Voice & Tone
### 10.1 Brand tone
The voice should be **trustworthy · deep · analytical · future-oriented · professional** (قابل‌اعتماد، عمیق، تحلیلی، آینده‌محور، حرفه‌ای).
| The brand IS | The brand is NOT |
|---|---|
| مطمئن (confident) | تبلیغاتی (advertising) |
| هوشمند (smart) | شعاری (sloganeering) |
| آینده‌محور (future-oriented) | هیجان‌زده (hyped) |
| حرفه‌ای (professional) | پیچیده غیرضروری (needlessly complex) |
| آرام و عمیق (calm & deep) | بیش‌ازحد دانشگاهی (overly academic) |
| الهام‌بخش (inspiring) | — |
### 10.2 Brand language
**Clear · precise · managerial · forward-looking · data-driven** (روشن، دقیق، مدیریتی، آینده‌نگر، داده‌محور).
Writing should be: short but deep · analytical but readable · professional but understandable.
### 10.3 Content structure
A standard content/message flow:
1. Define the problem (مسئله را تعریف کند)
2. Analyze the trends (روندها را تحلیل کند)
3. Explain the implications (پیامدها را توضیح دهد)
4. Deliver the insight (بینش ارائه کند)
### 10.4 Core message & keywords
**Core message:** «توسعه صنعتی آینده، بدون تفکر آینده‌محور ممکن نیست.»
**Keyword set (واژگان کلیدی):** آینده‌نگری · توسعه صنعتی · تحول صنعتی · فناوری‌های پیشرفته · هوش مصنوعی · فولاد سبز · تاب‌آوری · زنجیره ارزش · ژئواکونومی · تحلیل راهبردی · توسعه آینده‌محور · بینش · تصمیم‌سازی · آینده صنعت
---
## 11. Application Quick Reference
| Surface | Key guidance |
|---|---|
| **Slides / presentations** | Minimal, light, data-driven; standard slide = key-message → context → analytical content → managerial takeaway. Limited bold; consistent icons. |
| **Reports / dashboards** | Use Peyda/IRANSansX; executive summary first; clean charts; visual hierarchy; standardized titles. |
| **Digital / web & app** | Responsive, fast, future-oriented; minimal UI; clear information architecture; content & analysis focus. |
| **Social media** | Short but deep, analytical but readable; consistent palette + icons; no hype, no pure-promotion posts. |
---
## 12. Token Summary (copy-paste)
```css
:root {
/* Color */
--color-navy: #071D49;
--color-blue: #00AEEF;
--color-white: #FFFFFF;
--color-gray-light: #E9EEF5;
--color-gray-steel: #7C8798;
--color-orange: #FF8A00;
/* Type */
--font-fa: "IRANSansX", "Peyda", sans-serif;
--font-en: "Inter", "Montserrat", sans-serif;
--font-report: "Peyda", "IRANSansX", sans-serif;
--weight-bold: 700;
--weight-regular: 400;
/* Logo */
--logo-min-digital: 60px;
--logo-min-print: 25mm;
}
```
```js
// tailwind.config.js (extend)
module.exports = {
theme: {
extend: {
colors: {
navy: "#071D49",
blue: "#00AEEF",
"gray-light": "#E9EEF5",
"gray-steel": "#7C8798",
orange: "#FF8A00",
},
fontFamily: {
fa: ["IRANSansX", "Peyda", "sans-serif"],
en: ["Inter", "Montserrat", "sans-serif"],
},
},
},
};
```

View File

@ -16,7 +16,8 @@
"express": "^4.21.0",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"nanoid": "^5.0.7"
"nanoid": "^5.0.7",
"nodemailer": "^8.0.10"
},
"engines": {
"node": ">=20"
@ -1210,6 +1211,15 @@
"node": ">=10"
}
},
"node_modules/nodemailer": {
"version": "8.0.10",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.10.tgz",
"integrity": "sha512-BLFuSth7QtHOkBzyqTehWWyub0NTRDuK2Q2SQfnGLsrJnzyU+Yeh4WpV1eZGuARFj1xQJHIdnTuJZLP+b9R1GQ==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",

View File

@ -19,7 +19,8 @@
"express": "^4.21.0",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"nanoid": "^5.0.7"
"nanoid": "^5.0.7",
"nodemailer": "^8.0.10"
},
"engines": {
"node": ">=20"

View File

@ -240,6 +240,35 @@ app.post('/api/prices/refresh', authRequired, async (_req, res) => {
app.get('/api/health', (_req, res) => res.json({ ok: true }));
/* ── Contact form → email ─────────────────────────────── */
import nodemailer from 'nodemailer';
app.post('/api/contact', async (req, res) => {
const { name, phone, email } = req.body || {};
if (!name || !phone) return res.status(400).json({ error: 'name and phone required' });
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: { user: process.env.MAIL_USER, pass: process.env.MAIL_PASS },
});
try {
await transporter.sendMail({
from: process.env.MAIL_USER,
to: 'alirezaameria2@gmail.com, mtdemne@gmail.com',
subject: `درخواست دسترسی نبض صنعت — ${name}`,
html: `<h2>درخواست دسترسی — نبض صنعت</h2>
<p><strong>نام:</strong> ${name}</p>
<p><strong>شماره همراه:</strong> ${phone}</p>
<p><strong>ایمیل:</strong> ${email || ''}</p>`,
});
res.json({ ok: true });
} catch (err) {
console.error('Mail error:', err.message);
res.status(500).json({ error: 'mail failed' });
}
});
app.listen(PORT, () => {
console.log(`Panel running at ${PUBLIC_ORIGIN}`);
const intervalMs = Number(process.env.SCRAPER_INTERVAL_MS) || 2 * 60 * 1000;

View File

@ -144,10 +144,10 @@ export default function Header() {
position: 'sticky',
top: 0,
zIndex: 50,
background: scrolled ? 'rgba(244,239,231,0.72)' : T.paper,
background: scrolled ? 'rgba(255,255,255,0.88)' : T.paper,
backdropFilter: scrolled ? 'blur(18px) saturate(160%)' : 'none',
WebkitBackdropFilter: scrolled ? 'blur(18px) saturate(160%)' : 'none',
borderBottom: scrolled ? '1px solid rgba(26,23,18,0.08)' : '1px solid transparent',
borderBottom: scrolled ? '1px solid rgba(7,29,73,0.10)' : '1px solid transparent',
direction: lang === 'fa' ? 'rtl' : 'ltr',
transition: 'background 220ms ease, backdrop-filter 220ms ease, border-color 220ms ease',
}}
@ -158,7 +158,7 @@ export default function Header() {
<div
style={{
background: T.ink,
color: 'rgba(244,239,231,0.7)',
color: 'rgba(255,255,255,0.7)',
height: scrolled ? 0 : 32,
opacity: scrolled ? 0 : 1,
overflow: 'hidden',
@ -189,22 +189,22 @@ export default function Header() {
href="#"
style={{
fontSize: 11,
color: 'rgba(244,239,231,0.7)',
color: 'rgba(255,255,255,0.7)',
textDecoration: 'none',
transition: 'color 120ms',
}}
onMouseEnter={e => (e.currentTarget.style.color = 'var(--paper)')}
onMouseLeave={e => (e.currentTarget.style.color = 'rgba(244,239,231,0.7)')}
onMouseLeave={e => (e.currentTarget.style.color = 'rgba(255,255,255,0.7)')}
>
{lang === 'fa' ? item.fa : item.en}
</a>
))}
<span style={{ color: 'rgba(244,239,231,0.2)' }}>·</span>
<span style={{ color: 'rgba(255,255,255,0.2)' }}>·</span>
<a
href="/login"
style={{
fontSize: 11,
color: 'rgba(244,239,231,0.85)',
color: 'rgba(255,255,255,0.9)',
textDecoration: 'none',
fontWeight: 600,
}}
@ -215,17 +215,17 @@ export default function Header() {
onClick={toggleLang}
style={{
fontSize: 11,
color: 'rgba(244,239,231,0.7)',
color: 'rgba(255,255,255,0.7)',
background: 'transparent',
border: '1px solid rgba(244,239,231,0.2)',
border: '1px solid rgba(255,255,255,0.2)',
padding: '2px 8px',
cursor: 'pointer',
fontFamily: 'inherit',
letterSpacing: '0.5px',
transition: 'color 120ms, border-color 120ms',
}}
onMouseEnter={e => { e.currentTarget.style.color = 'var(--paper)'; e.currentTarget.style.borderColor = 'rgba(244,239,231,0.5)' }}
onMouseLeave={e => { e.currentTarget.style.color = 'rgba(244,239,231,0.7)'; e.currentTarget.style.borderColor = 'rgba(244,239,231,0.2)' }}
onMouseEnter={e => { e.currentTarget.style.color = 'var(--paper)'; e.currentTarget.style.borderColor = 'rgba(255,255,255,0.5)' }}
onMouseLeave={e => { e.currentTarget.style.color = 'rgba(255,255,255,0.7)'; e.currentTarget.style.borderColor = 'rgba(255,255,255,0.2)' }}
>
{lang === 'fa' ? 'EN' : 'FA'}
</button>
@ -258,7 +258,7 @@ export default function Header() {
style={{
display: 'flex',
flexDirection: 'column',
gap: 2,
gap: 7,
textDecoration: 'none',
flexShrink: 0,
lineHeight: 1,
@ -420,7 +420,7 @@ export default function Header() {
style={{
position: 'fixed',
inset: 0,
background: 'rgba(26,23,18,0.55)',
background: 'rgba(7,29,73,0.55)',
zIndex: 100,
}}
/>

View File

@ -1,15 +1,16 @@
import { Outlet } from 'react-router-dom'
import { Outlet, useLocation } from 'react-router-dom'
import Header from './Header'
import { Footer } from './Footer'
import { MarketStrip } from './MarketStrip'
import { LangProvider } from '@/context/LangContext'
export default function RootLayout() {
const { pathname } = useLocation()
return (
<LangProvider>
<div style={{ minHeight: '100dvh', display: 'flex', flexDirection: 'column' }}>
<Header />
<MarketStrip />
{pathname === '/pulse' && <MarketStrip />}
<main style={{ flex: 1 }}>
<Outlet />
</main>

View File

@ -1,4 +1,5 @@
@import "tailwindcss";
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap');
/*
VAZIR FONT FACES
@ -40,23 +41,41 @@
}
/*
DESIGN TOKENS
BRAND PALETTE design-system.md v1.0
Navy 60% · Blue 25% · White 10% · Gray 4% · Orange 1%
*/
:root {
--paper: #fafbfd;
--paper-2: #f1f4f9;
--paper-3: #e4eaf2;
--ink: #0f172a;
--ink-2: #1e293b;
--ink-3: #334155;
--ink-4: #64748b;
--ink-5: #94a3b8;
--ink-6: #cbd5e1;
--rule-thin: rgba(15,23,42,0.10);
--red: #dc2626;
--red-2: #ef4444;
--amber: #d97706;
--green-ink: #059669;
/* Raw brand colors */
--color-navy: #071D49;
--color-blue: #00AEEF;
--color-white: #FFFFFF;
--color-gray-light: #E9EEF5;
--color-gray-steel: #7C8798;
--color-orange: #FF8A00;
/* Semantic surface tokens */
--paper: #FFFFFF; /* primary surface */
--paper-2: #E9EEF5; /* secondary surface */
--paper-3: #d5dce8; /* tertiary surface */
/* Semantic text tokens — all navy scale */
--ink: #071D49; /* primary text / headings */
--ink-2: #0d2d6b; /* strong secondary */
--ink-3: #1a3f7a; /* medium emphasis */
--ink-4: #7C8798; /* muted / secondary text */
--ink-5: #a0aab4; /* placeholder / disabled */
--ink-6: #c8d0dc; /* hairline / subtle */
/* Rules */
--rule-thin: rgba(7,29,73,0.10);
/* Accent — brand blue replaces red as the primary accent */
--red: #00AEEF; /* accent (named --red for compat) */
--red-2: #29bdf2; /* lighter accent */
/* Warning / limited use — 1% max */
--amber: #FF8A00;
--green-ink: #059669;
}
/*
@ -164,7 +183,7 @@
html { scroll-behavior: smooth; direction: rtl; }
body {
font-family: 'Vazir', system-ui, sans-serif;
font-family: 'Vazir', 'Inter', system-ui, sans-serif;
background: var(--paper);
color: var(--ink);
font-size: var(--fs-body);

View File

@ -1,9 +1,8 @@
import HeroSection from './sections/HeroSection'
import SnapshotBar from './sections/SnapshotBar'
import GlobalScannerSection from './sections/GlobalScannerSection'
import SteelNewsletterSection from './sections/SteelNewsletterSection'
import EventsSection from './sections/EventsSection'
import FactoryBentoSection from './sections/FactoryBentoSection'
import InternalNewsSection from './sections/InternalNewsSection'
import RadarTechSection from './sections/RadarTechSection'
import MapEventsSection from './sections/MapEventsSection'
import FactoryNewsletterLayout from './sections/FactoryNewsletterLayout'
import RiskSection from './sections/RiskSection'
import { useLang } from '@/context/LangContext'
@ -44,27 +43,18 @@ export default function Home() {
<div dir={lang === 'fa' ? 'rtl' : 'ltr'}>
{/* ── 00 · Hero ─────────────────────────────────── */}
<HeroSection />
<InternalNewsSection />
<RadarTechSection />
{/* ── Ticker ────────────────────────────────────── */}
<SnapshotBar />
{/* ── 01 · اسکنر جهانی ──────────────────────────── */}
{/* ── 01 · اسکنر جهانی + رویدادها ─────────────── */}
<SectionLabel n="۰۱" bg="paper" />
<GlobalScannerSection />
<MapEventsSection />
{/* ── 02 · خبرنامه فولاد ────────────────────────── */}
{/* ── 02 · ماهنامه + خبرنامه ──────────────────────── */}
<SectionLabel n="۰۲" bg="paper-2" />
<SteelNewsletterSection />
<FactoryNewsletterLayout />
{/* ── 03 · آخرین رویدادها ───────────────────────── */}
<SectionLabel n="۰۳" bg="paper-2" />
<EventsSection />
{/* ── 04 · گزارش کارخانه‌ها ─────────────────────── */}
<SectionLabel n="۰۴" bg="paper-2" />
<FactoryBentoSection />
{/* ── 05 · نقشه ریسک ────────────────────────────── */}
{/* ── 04 · نقشه ریسک ────────────────────────────── */}
<SectionLabel n="۰۵" bg="paper" />
<RiskSection />
</div>

View File

@ -0,0 +1,222 @@
import { useState } from 'react'
import { useLang } from '@/context/LangContext'
const REPORTS = {
fa: [
{
title: 'تحلیل عملکرد تولید و صادرات — بهمن ۱۴۰۳',
desc: 'بررسی آمار تولید ماهانه، مقایسه با بودجه سالانه و تحلیل روند صادرات فولاد تخت در بازارهای منطقه‌ای',
date: 'بهمن ۱۴۰۳',
img: 'https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=600&q=80',
},
{
title: 'وضعیت بازار و قیمت‌گذاری — دی ۱۴۰۳',
desc: 'تحلیل نوسانات قیمت فولاد تخت در بازار داخلی، بررسی اثر نرخ ارز و چشم‌انداز بازار در فصل زمستان',
date: 'دی ۱۴۰۳',
img: 'https://images.unsplash.com/photo-1587293852726-70cdb56c2866?w=600&q=80',
},
{
title: 'گزارش پایداری و محیط زیست — آذر ۱۴۰۳',
desc: 'شاخص‌های زیست‌محیطی ماهانه؛ مصرف انرژی، کاهش انتشار کربن و عملکرد سیستم بازیافت پساب صنعتی',
date: 'آذر ۱۴۰۳',
img: 'https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=600&q=80',
},
{
title: 'تحلیل نیروی انسانی و بهره‌وری — آبان ۱۴۰۳',
desc: 'بررسی شاخص‌های بهره‌وری نیروی کار، آموزش‌های ماهانه و آمار ایمنی شغلی',
date: 'آبان ۱۴۰۳',
img: 'https://images.unsplash.com/photo-1581092160607-ee22731c9b4e?w=600&q=80',
},
],
en: [
{
title: 'Production & Export Performance — Feb 2025',
desc: 'Monthly production figures, annual budget comparison, and flat steel export trend analysis across regional markets.',
date: 'Feb 2025',
img: 'https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=600&q=80',
},
{
title: 'Market Conditions & Pricing — Jan 2025',
desc: 'Flat steel price fluctuations in the domestic market, exchange rate impact analysis, and winter market outlook.',
date: 'Jan 2025',
img: 'https://images.unsplash.com/photo-1587293852726-70cdb56c2866?w=600&q=80',
},
{
title: 'Sustainability & Environment Report — Dec 2024',
desc: 'Monthly environmental KPIs: energy consumption, carbon emission reductions, and industrial wastewater recycling performance.',
date: 'Dec 2024',
img: 'https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=600&q=80',
},
{
title: 'Workforce & Productivity Analysis — Nov 2024',
desc: 'Workforce productivity indicators, monthly training hours, and occupational safety statistics.',
date: 'Nov 2024',
img: 'https://images.unsplash.com/photo-1581092160607-ee22731c9b4e?w=600&q=80',
},
],
}
const ISSUES = {
fa: [
{ num: '۱۲', title: 'فولاد و تحریم‌های جدید: راهکارهای صادراتی ۱۴۰۴', date: 'بهمن ۱۴۰۳' },
{ num: '۱۱', title: 'قیمت‌گذاری فولاد در بازار داخلی: چالش‌ها و فرصت‌ها', date: 'دی ۱۴۰۳' },
{ num: '۱۰', title: 'آمار تولید فولاد ایران در ۹ ماهه ۱۴۰۳', date: 'آذر ۱۴۰۳' },
],
en: [
{ num: '12', title: 'Steel & New Sanctions: Export Strategies for 1404', date: 'Feb 2025' },
{ num: '11', title: 'Steel Pricing in the Domestic Market: Challenges & Opportunities', date: 'Jan 2025' },
{ num: '10', title: 'Iran Steel Production Stats — First 9 Months of 1403', date: 'Dec 2024' },
],
}
const T = {
fa: {
factoryOverline: 'MOBARAKEH STEEL', factoryHeading: 'ماهنامه تحلیلی کارخانه', factoryAll: 'مشاهده همه ←',
readMore: 'دریافت گزارش ←',
nlOverline: 'STEEL NEWSLETTER', nlHeading: 'خبرنامه فولاد', nlArchive: 'آرشیو کامل ←',
placeholder: 'ایمیل خود را وارد کنید...', subscribe: 'عضویت', done: '✓ ثبت شد',
nlDesc: 'هر ماه تحلیل‌های تخصصی بازار فولاد، اخبار صنعت و گزارش‌های اختصاصی را در ایمیل خود دریافت کنید.',
},
en: {
factoryOverline: 'MOBARAKEH STEEL', factoryHeading: 'Monthly Factory Analysis', factoryAll: 'View All →',
readMore: 'Get Report →',
nlOverline: 'STEEL NEWSLETTER', nlHeading: 'Steel Newsletter', nlArchive: 'Full Archive →',
placeholder: 'Enter your email...', subscribe: 'Subscribe', done: '✓ Done',
nlDesc: 'Receive expert steel market analysis, industry news, and exclusive reports in your inbox every month.',
},
}
export default function FactoryNewsletterLayout() {
const { lang } = useLang()
const t = T[lang]
const [email, setEmail] = useState('')
const [done, setDone] = useState(false)
return (
<section dir={lang === 'fa' ? 'rtl' : 'ltr'} style={{ background: 'var(--paper-2)' }}>
<div className="max-w-7xl mx-auto px-12 max-md:px-5" style={{ paddingTop: 56, paddingBottom: 64 }}>
<div style={{ display: 'grid', gridTemplateColumns: '60% 1fr', gap: 40 }} className="max-md:grid-cols-1">
{/* ══ LEFT · ماهنامه ══ */}
<div>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 28, flexWrap: 'wrap', gap: 10 }}>
<div>
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'var(--ink-4)', marginBottom: 6 }}>{t.factoryOverline}</div>
<h2 style={{ fontSize: 'clamp(20px,2vw,28px)', fontWeight: 900, color: 'var(--ink)', letterSpacing: '-0.5px' }}>{t.factoryHeading}</h2>
</div>
<a href="#" style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)', textDecoration: 'none', whiteSpace: 'nowrap' }}>{t.factoryAll}</a>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }} className="max-sm:grid-cols-1">
{REPORTS[lang].map((r, i) => (
<div
key={i}
style={{
background: '#fff', overflow: 'hidden',
boxShadow: '0 2px 10px rgba(7,29,73,0.08)',
display: 'flex', flexDirection: 'column',
transition: 'transform 0.2s, box-shadow 0.2s', cursor: 'pointer',
}}
onMouseEnter={e => { const d = e.currentTarget as HTMLDivElement; d.style.transform = 'translateY(-3px)'; d.style.boxShadow = '0 8px 24px rgba(7,29,73,0.15)' }}
onMouseLeave={e => { const d = e.currentTarget as HTMLDivElement; d.style.transform = 'translateY(0)'; d.style.boxShadow = '0 2px 10px rgba(7,29,73,0.08)' }}
>
<div style={{ position: 'relative', height: 110, overflow: 'hidden' }}>
<img src={r.img} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', transition: 'transform 0.4s' }}
onMouseEnter={e => (e.currentTarget.style.transform = 'scale(1.07)')}
onMouseLeave={e => (e.currentTarget.style.transform = 'scale(1)')}
/>
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(7,29,73,0.88) 0%, rgba(7,29,73,0.15) 100%)' }} />
<div style={{
position: 'absolute', top: 8,
right: lang === 'fa' ? 8 : 'auto', left: lang === 'en' ? 8 : 'auto',
background: 'var(--color-navy)', color: '#fff',
fontSize: 8, fontWeight: 700, padding: '2px 7px', letterSpacing: '1.5px', textTransform: 'uppercase',
}}>
{lang === 'fa' ? 'فولاد مبارکه' : 'Mobarakeh Steel'}
</div>
<div style={{
position: 'absolute', bottom: 8,
right: lang === 'fa' ? 8 : 'auto', left: lang === 'en' ? 8 : 'auto',
background: 'var(--color-blue)', color: '#fff',
fontSize: 9, fontWeight: 700, padding: '2px 8px',
}}>
{r.date}
</div>
</div>
<div style={{ padding: '14px', flex: 1, display: 'flex', flexDirection: 'column', gap: 6, borderTop: '3px solid var(--color-navy)' }}>
<h3 style={{ fontSize: 12, fontWeight: 800, color: 'var(--ink)', lineHeight: 1.5, flex: 1 }}>{r.title}</h3>
<p style={{ fontSize: 11, color: 'var(--ink-4)', lineHeight: 1.65 }}>{r.desc}</p>
<a href="#" style={{ fontSize: 10, fontWeight: 700, color: 'var(--color-blue)', textDecoration: 'none', marginTop: 4 }}>{t.readMore}</a>
</div>
</div>
))}
</div>
</div>
{/* ══ RIGHT · خبرنامه ══ */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
<div>
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'var(--ink-4)', marginBottom: 6 }}>{t.nlOverline}</div>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 8 }}>
<h2 style={{ fontSize: 'clamp(20px,2vw,28px)', fontWeight: 900, color: 'var(--ink)', letterSpacing: '-0.5px' }}>{t.nlHeading}</h2>
<a href="#" style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)', textDecoration: 'none', whiteSpace: 'nowrap', flexShrink: 0 }}>{t.nlArchive}</a>
</div>
</div>
{/* Navy subscribe card */}
<div style={{ background: 'var(--color-navy)', padding: '24px 20px', display: 'flex', flexDirection: 'column', gap: 14 }}>
<p style={{ fontSize: 13, color: 'rgba(255,255,255,0.72)', lineHeight: 1.75 }}>{t.nlDesc}</p>
<div style={{ display: 'flex', border: '1px solid rgba(255,255,255,0.18)' }}>
<input
type="email" value={email} onChange={e => setEmail(e.target.value)}
placeholder={t.placeholder} disabled={done}
style={{
flex: 1, border: 'none', padding: '10px 12px', fontSize: 12,
fontFamily: 'inherit', background: 'rgba(255,255,255,0.07)',
color: '#fff', outline: 'none', direction: lang === 'fa' ? 'rtl' : 'ltr',
}}
/>
<button
onClick={() => { if (email) setDone(true) }} disabled={done}
style={{
background: done ? 'rgba(255,255,255,0.15)' : 'var(--color-blue)',
color: '#fff', border: 'none', padding: '10px 16px',
fontSize: 12, fontWeight: 700, fontFamily: 'inherit',
cursor: done ? 'default' : 'pointer', whiteSpace: 'nowrap', flexShrink: 0,
}}
>
{done ? t.done : t.subscribe}
</button>
</div>
</div>
{/* Recent issues — navy number badges */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 1, background: 'var(--rule-thin)' }}>
{ISSUES[lang].map((issue, i) => (
<div
key={i}
style={{ background: '#fff', padding: '14px 16px', display: 'flex', gap: 12, alignItems: 'center', cursor: 'pointer', transition: 'background 0.15s' }}
onMouseEnter={e => ((e.currentTarget as HTMLDivElement).style.background = 'var(--paper-2)')}
onMouseLeave={e => ((e.currentTarget as HTMLDivElement).style.background = '#fff')}
>
<div style={{
background: 'var(--color-navy)', color: '#fff',
fontSize: 12, fontWeight: 900, width: 34, height: 34,
display: 'flex', alignItems: 'center', justifyContent: 'center',
flexShrink: 0, direction: 'ltr',
}}>{issue.num}</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--ink)', lineHeight: 1.4, marginBottom: 2 }}>{issue.title}</div>
<div style={{ fontSize: 10, color: 'var(--ink-5)' }}>{issue.date}</div>
</div>
<div style={{ color: 'var(--color-blue)', fontSize: 14, flexShrink: 0 }}></div>
</div>
))}
</div>
</div>
</div>
</div>
</section>
)
}

View File

@ -8,14 +8,12 @@ gsap.registerPlugin(ScrollTrigger)
const T = {
fa: {
tag: 'اندیشکده‌ فولاد آینده',
h1a: 'جایی که آینده',
h1b: 'به تصمیم تبدیل می‌شود',
h1: 'جایی که آینده به تصمیم تبدیل می‌شود',
sub: 'تحلیل روندها، فناوری‌ها و سناریوهای آینده صنعت فولاد ایران و جهان',
},
en: {
tag: 'Steel Policy Institute',
h1a: 'Where the Future',
h1b: 'Becomes Decision',
h1: 'Where the Future Becomes Decision',
sub: 'Analyzing trends, technologies and future scenarios of the global steel industry',
},
}
@ -84,57 +82,58 @@ export default function HeroSection() {
src="/hero-bg.mp4#t=2"
/>
{/* Dark overlay */}
<div ref={overlayRef} aria-hidden="true" style={{ position: 'absolute', inset: 0, background: 'rgba(16,13,10,0.55)', zIndex: 1 }} />
<div ref={overlayRef} aria-hidden="true" style={{ position: 'absolute', inset: 0, background: 'rgba(7,29,73,0.60)', zIndex: 1 }} />
{/* Vignette */}
<div aria-hidden="true" style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse 90% 80% at 50% 50%, transparent 40%, rgba(16,13,10,0.7) 100%)', zIndex: 2 }} />
<div aria-hidden="true" style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse 90% 80% at 50% 50%, transparent 40%, rgba(7,29,73,0.75) 100%)', zIndex: 2 }} />
{/* Centered content */}
{/* Content block */}
<div
ref={textRef}
className="max-md:!left-5 max-md:!right-5 max-md:!max-w-full max-md:text-center"
style={{
position: 'relative',
position: 'absolute',
top: '28%',
right: lang === 'fa' ? 48 : 'auto',
left: lang === 'en' ? 48 : 'auto',
transform: 'translateY(-50%)',
zIndex: 3,
textAlign: 'center',
direction: lang === 'fa' ? 'rtl' : 'ltr',
maxWidth: 520,
padding: '0 24px',
maxWidth: 760,
width: '100%',
}}
>
{/* Tag */}
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 10, marginBottom: 28 }}>
<div style={{ width: 28, height: 1.5, background: 'var(--red)' }} />
<span style={{ fontSize: 11, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'rgba(244,239,231,0.5)' }}>
{t.tag}
</span>
<div style={{ width: 28, height: 1.5, background: 'var(--red)' }} />
</div>
{/* Headline */}
<h1 style={{
fontSize: 'clamp(36px, 6vw, 88px)',
fontWeight: 900,
lineHeight: 1.08,
letterSpacing: '-2px',
color: 'var(--paper)',
marginBottom: 24,
}}>
{t.h1a}
<br />
<span style={{ color: 'var(--red)' }}>{t.h1b}</span>
<h1
className="max-md:!whitespace-normal"
style={{
fontSize: 'clamp(18px, 2.2vw, 34px)',
fontWeight: 900,
lineHeight: 1.2,
letterSpacing: '-0.5px',
color: 'var(--paper)',
marginBottom: 20,
whiteSpace: 'nowrap',
}}
>
{t.h1}
</h1>
{/* Subtitle */}
<p style={{
fontSize: 'clamp(14px, 1.2vw, 18px)',
lineHeight: 1.8,
color: 'rgba(244,239,231,0.55)',
maxWidth: 520,
margin: '0 auto 36px',
}}>
<p
className="max-md:!border-0 max-md:!px-0"
style={{
fontSize: 15,
lineHeight: 1.8,
color: 'rgba(255,255,255,0.65)',
borderRight: lang === 'fa' ? '2px solid var(--red)' : 'none',
borderLeft: lang === 'en' ? '2px solid var(--red)' : 'none',
paddingRight: lang === 'fa' ? 14 : 0,
paddingLeft: lang === 'en' ? 14 : 0,
}}
>
{t.sub}
</p>
</div>
</section>
)

View File

@ -0,0 +1,177 @@
import { useLang } from '@/context/LangContext'
type NewsItem = {
tag: string; tagEn: string
title: string; titleEn: string
desc?: string; descEn?: string
source: string
accent: string
img: string
date: string; dateEn: string
featured?: boolean
}
const NEWS: NewsItem[] = [
{
tag: 'سرمایه‌گذاری', tagEn: 'Investment',
title: 'فولاد مبارکه به پیشران نوآوری پایدار در صنعت فولاد کشور و منطقه تبدیل می‌شود',
titleEn: 'Mobarakeh Steel to Become the Driver of Sustainable Innovation in the Steel Industry',
desc: 'مدیرعامل صندوق سرمایه‌گذاری خطرپذیر فولاد مبارکه: این گروه باید به پیشران نوآوری پایدار در صنعت فولاد کشور و منطقه تبدیل شود.',
descEn: 'The CEO of Mobarakeh Steel Venture Fund: this group must become the driver of sustainable innovation in the country and region\'s steel industry.',
source: 'MSTID', accent: '#c0392b',
img: 'https://images.unsplash.com/photo-1504307651254-35680f356dfd?w=1200&q=80',
date: '۱۴ اردیبهشت ۱۴۰۴', dateEn: 'May 4, 2025',
featured: true,
},
{
tag: 'نوآوری', tagEn: 'Innovation',
title: 'از بومی‌سازی تا نوآفرینی',
titleEn: 'From Localization to Innovation',
desc: 'گزارشی از مسیر پنج‌ساله بومی‌سازی قطعات حیاتی در فولاد مبارکه.',
descEn: 'A report on the five-year path of localizing critical components at Mobarakeh Steel.',
source: 'MSTID', accent: '#2471a3',
img: 'https://images.unsplash.com/photo-1581092160607-ee22731c9b4e?w=800&q=80',
date: '۱۰ اردیبهشت ۱۴۰۴', dateEn: 'Apr 30, 2025',
},
{
tag: 'فناوری', tagEn: 'Technology',
title: 'حمایت معاون علمی رئیس‌جمهور از تکمیل «پارک تخصصی فناوری و نوآوری فولاد»',
titleEn: 'Presidential Science Advisor Backs Completion of Steel Technology & Innovation Park',
source: 'MSTID', accent: '#1e8449',
img: 'https://images.unsplash.com/photo-1518770660439-4636190af475?w=800&q=80',
date: '۸ اردیبهشت ۱۴۰۴', dateEn: 'Apr 28, 2025',
},
{
tag: 'رویداد', tagEn: 'Event',
title: 'بیست و ششمین رویداد «پورتفوکنوتور» برگزار شد',
titleEn: '26th PortfoConnotor Event Held',
desc: 'اتحاد MSTID و شرکت صناپ فولاد برای انرژی پاک.',
descEn: 'MSTID and Sanap Foolad alliance for clean energy.',
source: 'MSTID', accent: '#b7770d',
img: 'https://images.unsplash.com/photo-1540575467063-178a50c2df87?w=800&q=80',
date: '۵ اردیبهشت ۱۴۰۴', dateEn: 'Apr 25, 2025',
},
]
const T = {
fa: { overline: 'اخبار داخلی', heading: 'تازه‌ترین اخبار صنعت', all: 'همه اخبار ←' },
en: { overline: 'INTERNAL NEWS', heading: 'Latest Industry News', all: 'All News →' },
}
function Tag({ label, accent }: { label: string; accent: string }) {
return (
<span style={{
display: 'inline-block',
background: accent, color: '#fff',
fontSize: 10, fontWeight: 700, letterSpacing: '1px',
padding: '3px 10px', textTransform: 'uppercase',
}}>{label}</span>
)
}
export default function InternalNewsSection() {
const { lang } = useLang()
const t = T[lang]
const [featured, ...rest] = NEWS
return (
<section dir={lang === 'fa' ? 'rtl' : 'ltr'} style={{ background: 'var(--paper-2)' }}>
<div className="max-w-7xl mx-auto px-12 max-md:px-5" style={{ paddingTop: 56, paddingBottom: 64 }}>
{/* Header */}
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 32, flexWrap: 'wrap', gap: 12 }}>
<div>
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'var(--ink-4)', marginBottom: 6 }}>{t.overline}</div>
<h2 style={{ fontSize: 'clamp(22px,2.5vw,32px)', fontWeight: 900, color: 'var(--ink)', letterSpacing: '-0.5px' }}>{t.heading}</h2>
</div>
<a href="#" style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)', textDecoration: 'none' }}>{t.all}</a>
</div>
{/* Featured card — horizontal */}
<div style={{
display: 'grid', gridTemplateColumns: '1fr 1fr',
background: '#fff', overflow: 'hidden',
boxShadow: '0 2px 16px rgba(0,0,0,0.07)',
marginBottom: 20,
}} className="max-md:grid-cols-1">
<div style={{ position: 'relative', minHeight: 280, overflow: 'hidden' }}>
<img
src={featured.img}
alt=""
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', transition: 'transform 0.4s ease' }}
onMouseEnter={e => (e.currentTarget.style.transform = 'scale(1.04)')}
onMouseLeave={e => (e.currentTarget.style.transform = 'scale(1)')}
/>
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(0,0,0,0.35) 0%, transparent 50%)' }} />
<div style={{ position: 'absolute', top: 16, right: lang === 'fa' ? 16 : 'auto', left: lang === 'en' ? 16 : 'auto' }}>
<Tag label={lang === 'fa' ? featured.tag : featured.tagEn} accent={featured.accent} />
</div>
</div>
<div style={{ padding: '36px 32px', display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 14 }}>
<div style={{ fontSize: 10, color: 'var(--ink-5)', fontWeight: 600, letterSpacing: '1px' }}>{featured.source} · {lang === 'fa' ? featured.date : featured.dateEn}</div>
<h3 style={{ fontSize: 'clamp(16px,1.8vw,22px)', fontWeight: 900, color: 'var(--ink)', lineHeight: 1.45, letterSpacing: '-0.3px' }}>
{lang === 'fa' ? featured.title : featured.titleEn}
</h3>
<p style={{ fontSize: 13, color: 'var(--ink-3)', lineHeight: 1.8 }}>
{lang === 'fa' ? featured.desc : featured.descEn}
</p>
<a href="#" style={{ fontSize: 12, fontWeight: 700, color: featured.accent, textDecoration: 'none', marginTop: 4 }}>
{lang === 'fa' ? 'ادامه مطلب ←' : 'Read more →'}
</a>
</div>
</div>
{/* Regular cards grid */}
<div className="grid grid-cols-3 max-md:grid-cols-1" style={{ gap: 16 }}>
{rest.map((item, i) => (
<div
key={i}
style={{
background: '#fff',
overflow: 'hidden',
boxShadow: '0 2px 12px rgba(0,0,0,0.06)',
display: 'flex', flexDirection: 'column',
cursor: 'pointer',
transition: 'box-shadow 0.2s ease, transform 0.2s ease',
}}
onMouseEnter={e => { (e.currentTarget as HTMLDivElement).style.boxShadow = '0 8px 28px rgba(0,0,0,0.12)'; (e.currentTarget as HTMLDivElement).style.transform = 'translateY(-3px)' }}
onMouseLeave={e => { (e.currentTarget as HTMLDivElement).style.boxShadow = '0 2px 12px rgba(0,0,0,0.06)'; (e.currentTarget as HTMLDivElement).style.transform = 'translateY(0)' }}
>
{/* Image */}
<div style={{ position: 'relative', height: 180, overflow: 'hidden' }}>
<img
src={item.img}
alt=""
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', transition: 'transform 0.4s ease' }}
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.3) 0%, transparent 60%)' }} />
<div style={{ position: 'absolute', bottom: 12, right: lang === 'fa' ? 12 : 'auto', left: lang === 'en' ? 12 : 'auto' }}>
<Tag label={lang === 'fa' ? item.tag : item.tagEn} accent={item.accent} />
</div>
</div>
{/* Content */}
<div style={{ padding: '20px', flex: 1, display: 'flex', flexDirection: 'column', gap: 10 }}>
<div style={{ fontSize: 10, color: 'var(--ink-5)', fontWeight: 600 }}>{item.source} · {lang === 'fa' ? item.date : item.dateEn}</div>
<h3 style={{ fontSize: 14, fontWeight: 800, color: 'var(--ink)', lineHeight: 1.5, flex: 1 }}>
{lang === 'fa' ? item.title : item.titleEn}
</h3>
{(lang === 'fa' ? item.desc : item.descEn) && (
<p style={{ fontSize: 12, color: 'var(--ink-4)', lineHeight: 1.7 }}>
{lang === 'fa' ? item.desc : item.descEn}
</p>
)}
<a href="#" style={{ fontSize: 11, fontWeight: 700, color: item.accent, textDecoration: 'none', marginTop: 'auto' }}>
{lang === 'fa' ? 'ادامه مطلب ←' : 'Read more →'}
</a>
</div>
</div>
))}
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,135 @@
import { Link } from 'react-router-dom'
import { motion } from 'framer-motion'
import WorldMap from '@/components/ui/world-map'
import { events } from '@/data/events'
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> = {
conference: 'var(--ink)', exhibition: '#6b4a00',
seminar: '#1a4a2a', international: 'var(--red)',
}
const TYPE_LABEL = {
fa: { conference: 'کنگره', exhibition: 'نمایشگاه', seminar: 'سمینار', international: 'جهانی' },
en: { conference: 'Conference', exhibition: 'Exhibition', seminar: 'Seminar', international: 'International' },
}
const T = {
fa: {
mapOverline: 'اسکنر جهانی',
mapHeading: 'تمام رویدادهای فولادی دنیا را رصد می‌کنیم',
mapSub: 'از بورس دالیان چین تا گمرک بصره عراق — هر سیگنالی که بر رقابت‌پذیری ایران اثر می‌گذارد.',
eventsOverline: 'تقویم رویداد', eventsHeading: 'رویدادهای پیش‌رو',
eventsAll: 'تقویم کامل ←', register: 'ثبت‌نام ←', soon: 'به‌زودی',
},
en: {
mapOverline: 'GLOBAL SCANNER',
mapHeading: 'Every Global Steel Event, Monitored',
mapSub: 'From the Dalian Commodity Exchange to the Basra customs gate — every signal affecting Iran\'s competitiveness.',
eventsOverline: 'EVENT CALENDAR', eventsHeading: 'Upcoming Events',
eventsAll: 'Full Calendar →', register: 'Register →', soon: 'Coming Soon',
},
}
export default function MapEventsSection() {
const { lang } = useLang()
const t = T[lang]
const typeLabel = TYPE_LABEL[lang]
const displayed = events.slice(0, 5)
return (
<section dir={lang === 'fa' ? 'rtl' : 'ltr'} style={{ background: 'var(--paper)', borderBottom: '1px solid var(--rule-thin)', overflow: 'hidden' }}>
<div className="max-w-7xl mx-auto px-12 max-md:px-5">
<div style={{ display: 'flex', alignItems: 'stretch', minHeight: 520 }} className="max-md:flex-col">
{/* ── 70% · Map ── */}
<div style={{
flex: '7 7 0', minWidth: 0, overflow: 'hidden',
borderLeft: lang === 'fa' ? '1px solid var(--rule-thin)' : 'none',
borderRight: lang === 'en' ? '1px solid var(--rule-thin)' : 'none',
}}>
<div className="px-12 max-md:px-5" style={{ paddingTop: 48, paddingBottom: 8 }}>
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'var(--ink-4)', marginBottom: 8 }}>{t.mapOverline}</div>
<h2 style={{ fontSize: 'clamp(18px,2vw,28px)', fontWeight: 900, color: 'var(--ink)', letterSpacing: '-0.5px', lineHeight: 1.2, marginBottom: 10 }}>{t.mapHeading}</h2>
<p style={{ fontSize: 13, color: 'var(--ink-4)', lineHeight: 1.7, maxWidth: 460 }}>{t.mapSub}</p>
</div>
<div style={{ padding: '8px 16px 32px' }}>
<WorldMap dots={ROUTES} lineColor="#9b1c1c" dotColor="#c8c2bc" />
</div>
</div>
{/* ── 30% · Events ── */}
<div style={{ flex: '3 3 0', minWidth: 0, background: 'var(--paper-2)', display: 'flex', flexDirection: 'column' }}>
<div style={{ padding: '48px 20px 16px', borderBottom: '1px solid var(--rule-thin)' }}>
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'var(--ink-4)', marginBottom: 6 }}>{t.eventsOverline}</div>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 8 }}>
<h2 style={{ fontSize: 'clamp(15px,1.4vw,20px)', fontWeight: 900, color: 'var(--ink)', letterSpacing: '-0.3px' }}>{t.eventsHeading}</h2>
<Link to="/events" style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)', textDecoration: 'none', whiteSpace: 'nowrap', flexShrink: 0 }}>{t.eventsAll}</Link>
</div>
</div>
<div style={{ flex: 1 }}>
{displayed.map((ev, i) => (
<motion.div
key={ev.id}
initial={{ opacity: 0, x: 8 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: i * 0.06 }}
style={{
padding: '16px 20px',
borderBottom: '1px solid var(--rule-thin)',
display: 'flex', gap: 14, alignItems: 'flex-start',
}}
>
{/* Date */}
<div style={{ textAlign: 'center', flexShrink: 0, minWidth: 40 }}>
<div style={{ fontSize: 26, fontWeight: 900, lineHeight: 1, color: 'var(--ink)' }}>
{ev.date.toLocaleString('fa-IR')}
</div>
<div style={{ fontSize: 9, color: 'var(--ink-4)', marginTop: 3 }}>{ev.month}</div>
</div>
{/* Content */}
<div style={{ flex: 1, minWidth: 0 }}>
<span style={{
display: 'inline-block', marginBottom: 5,
border: `1px solid ${TYPE_COLOR[ev.type]}`,
color: TYPE_COLOR[ev.type],
fontSize: 8, fontWeight: 700, textTransform: 'uppercase',
padding: '2px 5px', letterSpacing: '1px',
}}>
{typeLabel[ev.type]}
</span>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--ink)', lineHeight: 1.45, marginBottom: 3 }}>{ev.title}</div>
<div style={{ fontSize: 10, color: 'var(--ink-5)', marginBottom: 4 }}>{ev.city}</div>
{ev.registrationOpen
? <Link to="#" style={{ fontSize: 10, fontWeight: 700, color: 'var(--red)', textDecoration: 'none' }}>{t.register}</Link>
: <span style={{ fontSize: 10, color: 'var(--ink-5)', fontWeight: 600 }}>{t.soon}</span>
}
</div>
</motion.div>
))}
</div>
</div>
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,252 @@
import { Link } from 'react-router-dom'
import { useLang } from '@/context/LangContext'
/* ─── Radar data ─────────────────────────────────────── */
const SIGNALS = {
fa: [
{
tag: 'چین', level: 'critical', levelLabel: 'بحرانی',
title: 'صادرات فولاد چین به بالاترین رکورد ۸ ساله رسید',
desc: 'در بهمن ۱۴۰۳ صادرات فولاد چین به ۱۰.۷ میلیون تن رسید — بالاترین رقم از ۲۰۱۶ — که فشار قیمتی شدیدی بر بازارهای منطقه‌ای از جمله ایران وارد کرد.',
date: 'بهمن ۱۴۰۳', img: 'https://images.unsplash.com/photo-1611273426858-450d8e3c9fce?w=800&q=80',
accentColor: '#7f1d1d', levelColor: '#7f1d1d',
},
{
tag: 'اروپا', level: 'high', levelLabel: 'بالا',
title: 'CBAM اروپا از فروردین ۱۴۰۴ اجرایی می‌شود',
desc: 'مکانیزم تعدیل مرزی کربن اتحادیه اروپا برای فولاد اجرایی شد. صادرکنندگان ایرانی باید الزامات جدید گزارش‌دهی کربن را رعایت کنند.',
date: 'دی ۱۴۰۳', img: 'https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=800&q=80',
accentColor: '#dc2626', levelColor: '#dc2626',
},
{
tag: 'هند', level: 'medium', levelLabel: 'متوسط',
title: 'هند ظرفیت تولید فولاد خود را تا ۲۰۳۰ دو برابر می‌کند',
desc: 'با سرمایه‌گذاری ۱۴۲ میلیارد دلاری دولت هند، این کشور تا ۲۰۳۰ به ظرفیت ۳۰۰ میلیون تن می‌رسد و رقیب جدی ایران در بازارهای صادراتی می‌شود.',
date: 'آذر ۱۴۰۳', img: 'https://images.unsplash.com/photo-1566873535350-6586b0b7a1f2?w=800&q=80',
accentColor: '#b45309', levelColor: '#b45309',
},
],
en: [
{
tag: 'China', level: 'critical', levelLabel: 'Critical',
title: "China's Steel Exports Hit an 8-Year Record High",
desc: "In Feb 2025, China's steel exports reached 10.7 million tons — the highest since 2016 — putting severe price pressure on regional markets including Iran.",
date: 'Feb 2025', img: 'https://images.unsplash.com/photo-1611273426858-450d8e3c9fce?w=800&q=80',
accentColor: '#7f1d1d', levelColor: '#7f1d1d',
},
{
tag: 'Europe', level: 'high', levelLabel: 'High',
title: "Europe's CBAM Mechanism Takes Effect from Spring 2025",
desc: "The EU's Carbon Border Adjustment Mechanism for steel is now in force. Iranian exporters must comply with new carbon reporting requirements.",
date: 'Jan 2025', img: 'https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=800&q=80',
accentColor: '#dc2626', levelColor: '#dc2626',
},
{
tag: 'India', level: 'medium', levelLabel: 'Medium',
title: 'India to Double Steel Production Capacity by 2030',
desc: 'With a $142 billion government investment, India targets 300 million tons capacity by 2030, becoming a serious competitor in Iran\'s export markets.',
date: 'Dec 2024', img: 'https://images.unsplash.com/photo-1566873535350-6586b0b7a1f2?w=800&q=80',
accentColor: '#b45309', levelColor: '#b45309',
},
],
}
/* ─── Tech data ──────────────────────────────────────── */
const TECHS = {
fa: [
{
num: '۰۱', tag: 'انرژی پاک',
title: 'فولاد هیدروژنی (DRI-H₂)',
desc: 'جایگزینی کک با هیدروژن سبز در کوره‌های احیای مستقیم؛ اولین کارخانه تجاری اروپایی در ۲۰۲۴ وارد مرحله تولید شد. ظرفیت تولید ۲.۵ میلیون تن در سال.',
img: 'https://images.unsplash.com/photo-1613665813446-82a78c468a1d?w=800&q=80',
horizon: '۲۰۲۷–۲۰۳۰', accentColor: '#0d6b3e',
},
{
num: '۰۲', tag: 'هوش مصنوعی',
title: 'دیجیتال‌سازی و AI در تولید فولاد',
desc: 'یادگیری ماشین در بهینه‌سازی فرآیند ذوب، کنترل کیفیت آنلاین و پیش‌بینی خرابی تجهیزات. کاهش ۱۵–۲۰٪ ضایعات و صرفه‌جویی ۸٪ در مصرف انرژی.',
img: 'https://images.unsplash.com/photo-1485827404703-89b55fcc595e?w=800&q=80',
horizon: '۲۰۲۵–۲۰۲۸', accentColor: '#1a4a7a',
},
{
num: '۰۳', tag: 'فناوری نوین',
title: 'الکترولیز اکسید مذاب (MOE)',
desc: 'تولید فولاد از طریق الکترولیز مستقیم اکسید آهن با برق تجدیدپذیر — بدون انتشار CO₂. MIT این فناوری را «انقلاب صنعتی بعدی» نامیده است.',
img: 'https://images.unsplash.com/photo-1451187580459-43490279c0fa?w=800&q=80',
horizon: '۲۰۳۰–۲۰۳۵', accentColor: '#6b21a8',
},
],
en: [
{
num: '01', tag: 'Clean Energy',
title: 'Hydrogen Steel (DRI-H₂)',
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.",
img: 'https://images.unsplash.com/photo-1613665813446-82a78c468a1d?w=800&q=80',
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: 'https://images.unsplash.com/photo-1485827404703-89b55fcc595e?w=800&q=80',
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: 'https://images.unsplash.com/photo-1451187580459-43490279c0fa?w=800&q=80',
horizon: '20302035', accentColor: '#6b21a8',
},
],
}
const T = {
fa: {
radarOverline: 'FUTURE RADAR', radarHeading: 'رادار آینده',
radarSub: 'سیگنال‌های جهانی با اثر مستقیم بر صنعت فولاد ایران',
radarAll: 'همه سیگنال‌ها ←',
techOverline: 'TECH FRONTIER', techHeading: 'مرز فناوری',
techSub: 'فناوری‌های نوظهور و افق کاربرد آن‌ها در صنعت فولاد',
techAll: 'همه فناوری‌ها ←',
horizon: 'افق زمانی',
},
en: {
radarOverline: 'FUTURE RADAR', radarHeading: 'Future Radar',
radarSub: 'Global signals with direct impact on Iran\'s steel industry',
radarAll: 'All Signals →',
techOverline: 'TECH FRONTIER', techHeading: 'Tech Frontier',
techSub: 'Emerging technologies and their application horizon in the steel industry',
techAll: 'All Technologies →',
horizon: 'Horizon',
},
}
export default function RadarTechSection() {
const { lang } = useLang()
const t = T[lang]
const signals = SIGNALS[lang]
const techs = TECHS[lang]
return (
<section dir={lang === 'fa' ? 'rtl' : 'ltr'} style={{ background: 'var(--paper-2)' }}>
<div className="max-w-7xl mx-auto px-12 max-md:px-5">
{/* ══ رادار آینده ══ */}
<div style={{ paddingTop: 64, paddingBottom: 56 }}>
<div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', marginBottom: 32, flexWrap: 'wrap', gap: 12 }}>
<div>
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'var(--ink-4)', marginBottom: 6 }}>{t.radarOverline}</div>
<h2 style={{ fontSize: 'clamp(22px,2.5vw,32px)', fontWeight: 900, color: 'var(--ink)', letterSpacing: '-0.5px', marginBottom: 6 }}>{t.radarHeading}</h2>
<p style={{ fontSize: 13, color: 'var(--ink-4)' }}>{t.radarSub}</p>
</div>
<Link to="/radar" style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)', textDecoration: 'none', whiteSpace: 'nowrap' }}>{t.radarAll}</Link>
</div>
<div className="grid grid-cols-3 max-md:grid-cols-1" style={{ gap: 16 }}>
{signals.map((s, i) => (
<div
key={i}
style={{
background: '#fff', overflow: 'hidden',
boxShadow: '0 2px 12px rgba(0,0,0,0.07)',
display: 'flex', flexDirection: 'column',
transition: 'transform 0.2s, box-shadow 0.2s', cursor: 'pointer',
}}
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)' }}
>
{/* Image */}
<div style={{ position: 'relative', height: 160, overflow: 'hidden' }}>
<img src={s.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.45) 0%, transparent 60%)' }} />
{/* Level badge */}
<div style={{
position: 'absolute', bottom: 12,
right: lang === 'fa' ? 12 : 'auto', left: lang === 'en' ? 12 : 'auto',
display: 'flex', gap: 6, alignItems: 'center',
}}>
<span style={{ background: s.levelColor, color: '#fff', fontSize: 9, fontWeight: 700, padding: '2px 8px', letterSpacing: '1px' }}>{s.levelLabel}</span>
<span style={{ background: 'rgba(0,0,0,0.6)', color: '#fff', fontSize: 9, fontWeight: 700, padding: '2px 8px' }}>{s.tag}</span>
</div>
</div>
{/* Content */}
<div style={{ padding: '20px', flex: 1, display: 'flex', flexDirection: 'column', gap: 10, borderTop: `3px solid ${s.accentColor}` }}>
<h3 style={{ fontSize: 14, fontWeight: 800, color: 'var(--ink)', lineHeight: 1.5 }}>{s.title}</h3>
<p style={{ fontSize: 12, color: 'var(--ink-4)', lineHeight: 1.75, flex: 1 }}>{s.desc}</p>
<div style={{ fontSize: 10, color: 'var(--ink-5)', fontWeight: 600 }}>{s.date}</div>
</div>
</div>
))}
</div>
</div>
{/* divider */}
<div style={{ height: 1, background: 'var(--rule-thin)' }} />
{/* ══ مرز فناوری ══ */}
<div style={{ paddingTop: 56, paddingBottom: 64 }}>
<div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', marginBottom: 32, flexWrap: 'wrap', gap: 12 }}>
<div>
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'var(--ink-4)', marginBottom: 6 }}>{t.techOverline}</div>
<h2 style={{ fontSize: 'clamp(22px,2.5vw,32px)', fontWeight: 900, color: 'var(--ink)', letterSpacing: '-0.5px', marginBottom: 6 }}>{t.techHeading}</h2>
<p style={{ fontSize: 13, color: 'var(--ink-4)' }}>{t.techSub}</p>
</div>
<Link to="/technology" style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)', textDecoration: 'none', whiteSpace: 'nowrap' }}>{t.techAll}</Link>
</div>
<div className="grid grid-cols-3 max-md:grid-cols-1" style={{ gap: 16 }}>
{techs.map((tech, i) => (
<div
key={i}
style={{
background: '#fff', overflow: 'hidden',
boxShadow: '0 2px 12px rgba(0,0,0,0.07)',
display: 'flex', flexDirection: 'column',
transition: 'transform 0.2s, box-shadow 0.2s', cursor: 'pointer',
}}
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)' }}
>
{/* 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={{
position: 'absolute', bottom: 12,
right: lang === 'fa' ? 12 : 'auto', left: lang === 'en' ? 12 : 'auto',
}}>
<span style={{ background: tech.accentColor, color: '#fff', fontSize: 9, fontWeight: 700, padding: '2px 8px', letterSpacing: '1px', textTransform: 'uppercase' }}>{tech.tag}</span>
</div>
<div style={{
position: 'absolute', top: 12,
right: lang === 'fa' ? 12 : 'auto', left: lang === 'en' ? 12 : 'auto',
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>
</section>
)
}

View File

@ -109,32 +109,6 @@ export default function SteelNewsletterSection() {
</div>
</div>
{/* Major plants */}
<div>
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '3px', textTransform: 'uppercase', color: 'var(--ink-4)', marginBottom: 6 }}>{t.overlinePlants}</div>
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 24, flexWrap: 'wrap', gap: 12 }}>
<h2 style={{ fontSize: 'clamp(20px,2.5vw,28px)', fontWeight: 900, color: 'var(--ink)', letterSpacing: '-0.5px' }}>{t.headingPlants}</h2>
<a href="#" style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)', textDecoration: 'none' }}>{t.allPlants}</a>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(210px,1fr))', gap: 1, background: 'var(--rule-thin)' }}>
{FACTORIES[lang].map((f, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 8 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.35, delay: i * 0.06 }}
style={{ background: 'var(--paper)', padding: '20px 18px', position: 'relative' }}
>
<div style={{ position: 'absolute', top: 0, right: 0, left: 0, height: 2, background: 'var(--ink)' }} />
<div style={{ fontSize: 14, fontWeight: 800, color: 'var(--ink)', marginBottom: 8, lineHeight: 1.3 }}>{f.name}</div>
<div style={{ fontSize: 11, color: 'var(--ink-4)', marginBottom: 2 }}>{f.location} · {f.type}</div>
<div style={{ fontSize: 20, fontWeight: 900, color: 'var(--red)', letterSpacing: '-0.5px', marginTop: 10 }}>{f.capacity}</div>
<div style={{ fontSize: 9, color: 'var(--ink-5)', fontWeight: 600, marginTop: 2 }}>{t.capacity}</div>
</motion.div>
))}
</div>
</div>
</div>
</section>
)

View File

@ -1,6 +1,7 @@
import { useState } from 'react'
import { useState, useRef, useEffect } from 'react'
import { motion } from 'framer-motion'
import { useLang } from '@/context/LangContext'
import SnapshotBar from '@/pages/Home/sections/SnapshotBar'
const STATS = {
fa: [
@ -52,6 +53,195 @@ const T = {
},
}
const SLIDES = Array.from({ length: 13 }, (_, i) => `/${i + 1}.jpg`)
function ImageSlider({ lang }: { lang: 'fa' | 'en' }) {
const [current, setCurrent] = useState(0)
useEffect(() => {
const id = setInterval(() => setCurrent(c => (c + 1) % SLIDES.length), 3500)
return () => clearInterval(id)
}, [])
return (
<div style={{ position: 'relative', marginBottom: 48, overflow: 'hidden', border: '1px solid var(--rule-thin)', background: '#000' }}>
{/* 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 ContactPanel({ lang }: { lang: 'fa' | 'en' }) {
const [sent, setSent] = useState(false)
const nameRef = useRef<HTMLInputElement>(null)
const phoneRef = useRef<HTMLInputElement>(null)
const emailRef = useRef<HTMLInputElement>(null)
const label = lang === 'fa'
? { title: 'برای استفاده لطفاً تماس بگیرید', name: 'نام و نام‌خانوادگی', phone: 'شماره تماس', email: 'ایمیل', btn: 'ارسال', done: 'پیام شما دریافت شد. به‌زودی تماس می‌گیریم.' }
: { title: 'Please contact us to get access', name: 'Full Name', phone: 'Phone Number', email: 'Email', btn: 'Send', done: 'Message received. We\'ll be in touch soon.' }
if (sent) return (
<div style={{ background: 'rgba(15,23,42,0.85)', backdropFilter: 'blur(12px)', border: '1px solid rgba(255,255,255,0.12)', padding: '28px 32px', color: '#fff', fontSize: 14, fontWeight: 600, textAlign: 'center', maxWidth: 340 }}>
{label.done}
</div>
)
return (
<div style={{
background: 'rgba(15,23,42,0.82)', backdropFilter: 'blur(14px)',
border: '1px solid rgba(255,255,255,0.12)',
padding: '28px 32px', display: 'flex', flexDirection: 'column', gap: 14,
minWidth: 300, maxWidth: 360, direction: lang === 'fa' ? 'rtl' : 'ltr',
}}>
<div style={{ fontSize: 14, fontWeight: 700, color: '#fff', marginBottom: 4 }}>{label.title}</div>
{[
{ ref: nameRef, placeholder: label.name, type: 'text' },
{ ref: phoneRef, placeholder: label.phone, type: 'tel' },
{ ref: emailRef, placeholder: label.email, type: 'email'},
].map(({ ref, placeholder, type }) => (
<input
key={placeholder}
ref={ref}
type={type}
placeholder={placeholder}
style={{
background: 'rgba(255,255,255,0.08)', border: '1px solid rgba(255,255,255,0.15)',
color: '#fff', padding: '10px 14px', fontSize: 13, fontFamily: 'inherit',
outline: 'none', width: '100%',
}}
/>
))}
<button
onClick={() => { if (nameRef.current?.value && phoneRef.current?.value) setSent(true) }}
style={{
background: 'var(--red)', color: '#fff', border: 'none',
padding: '11px 0', fontSize: 13, fontWeight: 700,
fontFamily: 'inherit', cursor: 'pointer', width: '100%',
}}
>
{label.btn}
</button>
</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')
try {
const res = await fetch('http://localhost:3001/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>
)
}
export default function Pulse() {
const { lang } = useLang()
const t = T[lang]
@ -60,6 +250,7 @@ export default function Pulse() {
return (
<div dir={lang === 'fa' ? 'rtl' : 'ltr'} style={{ background: 'var(--paper)', minHeight: '80vh' }}>
<SnapshotBar />
<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 }}>
@ -88,12 +279,15 @@ export default function Pulse() {
</div>
</div>
{/* Auto-sliding image carousel — radial blur paywall */}
<ImageSlider lang={lang} />
<motion.div
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
style={{ border: '2px solid var(--ink)', padding: '40px 36px', maxWidth: 600, position: 'relative' }}
style={{ border: '2px solid var(--ink)', padding: '40px 36px', position: 'relative' }}
>
<div style={{ position: 'absolute', top: 0, right: 0, left: 0, height: 4, background: 'var(--red)' }} />
<div style={{ fontSize: 9, fontWeight: 700, letterSpacing: '2.5px', textTransform: 'uppercase', color: 'var(--red)', marginBottom: 16 }}>{t.gateAccess}</div>
@ -107,18 +301,7 @@ export default function Pulse() {
</li>
))}
</ul>
{!requested ? (
<button
onClick={() => setRequested(true)}
style={{ background: 'var(--ink)', color: 'var(--paper)', border: 'none', padding: '13px 32px', fontSize: 13, fontWeight: 700, fontFamily: 'Vazir, sans-serif', cursor: 'pointer', letterSpacing: '0.3px', transition: 'background 150ms' }}
>
{t.cta}
</button>
) : (
<div style={{ background: 'rgba(26,23,18,0.05)', padding: '14px 20px', fontSize: 13, color: 'var(--ink)', fontWeight: 600 }}>
{t.ctaDone}
</div>
)}
<MembershipForm lang={lang} />
</motion.div>
</div>
</div>