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:
parent
a72a3c61f9
commit
c934ee4ab5
|
|
@ -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"],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,22 +41,40 @@
|
|||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════
|
||||
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;
|
||||
/* 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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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)',
|
||||
<h1
|
||||
className="max-md:!whitespace-normal"
|
||||
style={{
|
||||
fontSize: 'clamp(18px, 2.2vw, 34px)',
|
||||
fontWeight: 900,
|
||||
lineHeight: 1.08,
|
||||
letterSpacing: '-2px',
|
||||
lineHeight: 1.2,
|
||||
letterSpacing: '-0.5px',
|
||||
color: 'var(--paper)',
|
||||
marginBottom: 24,
|
||||
}}>
|
||||
{t.h1a}
|
||||
<br />
|
||||
<span style={{ color: 'var(--red)' }}>{t.h1b}</span>
|
||||
marginBottom: 20,
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{t.h1}
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
<p style={{
|
||||
fontSize: 'clamp(14px, 1.2vw, 18px)',
|
||||
<p
|
||||
className="max-md:!border-0 max-md:!px-0"
|
||||
style={{
|
||||
fontSize: 15,
|
||||
lineHeight: 1.8,
|
||||
color: 'rgba(244,239,231,0.55)',
|
||||
maxWidth: 520,
|
||||
margin: '0 auto 36px',
|
||||
}}>
|
||||
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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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: '2027–2030', 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 15–20% and energy use by 8%.',
|
||||
img: 'https://images.unsplash.com/photo-1485827404703-89b55fcc595e?w=800&q=80',
|
||||
horizon: '2025–2028', 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: '2030–2035', 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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue