feat: phone+OTP auth, profile redesign, At-a-Glance Figma polish, radar/article fixes

- Auth: phone-or-email login, OTP login/signup (Kavenegar SFsignin/SFsignup),
  register-verify + password-otp endpoints, OTP-login session fix
- Profile page redesign (menu right / data left, RTL) + SMS password reset
- At a Glance: Figma-matched cards (gold pill badge, gray cards, meta), category
  rename, computed counts, animated news ticker, summit banner, 5-line gated preview
- About page: remove board banner, selectable dept tabs, contact section
- Radar: fix duplicated MiniCard excerpt; gate -> our own auth (login/register)
- Footer trust badges + flag-image language toggle; site-wide Persian digits
This commit is contained in:
alireza 2026-06-23 16:56:16 +03:30
parent dce09cbd23
commit b2e9bcdf40
152 changed files with 10331 additions and 3596 deletions

9
.gitignore vendored
View File

@ -35,3 +35,12 @@ secrets/
/[0-9]*.png /[0-9]*.png
brand_pages/ brand_pages/
/موکاپ*.jpg /موکاپ*.jpg
# Dev screenshots & scratch artifacts (website assets live in public/)
/*.jpeg
.playwright-mcp/
*.old
*.docx
# State file written by scripts/fetch-competitor-news.mjs
scripts/news-history.json

15
.liaraignore Normal file
View File

@ -0,0 +1,15 @@
node_modules
panel
dist
dist-ssr
.git
.github
.env
.env.*
*.log
*.pem
*.key
.vscode
.idea
.DS_Store
Thumbs.db

View File

@ -1,249 +0,0 @@
- generic [active] [ref=e1]:
- generic:
- img "Spread"
- generic: "100"
- navigation [ref=e2]:
- img "Spread" [ref=e4]
- list [ref=e5]:
- listitem [ref=e6]:
- link "How it works" [ref=e7] [cursor=pointer]:
- /url: "#how"
- listitem [ref=e8]:
- link "See a deal" [ref=e9] [cursor=pointer]:
- /url: "#deal"
- listitem [ref=e10]:
- link "Niches" [ref=e11] [cursor=pointer]:
- /url: "#niches"
- listitem [ref=e12]:
- link "Pricing" [ref=e13] [cursor=pointer]:
- /url: "#pricing"
- link "Get early access" [ref=e14] [cursor=pointer]:
- /url: /signup
- button "Get early access" [ref=e15]
- generic [ref=e26]:
- iframe [ref=e27]:
- region "Video Player" [ref=f2e2]:
- application [ref=f2e3]
- text:  
- generic [ref=e29]:
- heading "Find what's underpriced. Sell it for what it's worth." [level=1] [ref=e30]:
- generic [ref=e32]: Find
- generic [ref=e34]: what's
- generic [ref=e36]: underpriced.
- generic [ref=e38]: Sell
- generic [ref=e40]: it
- generic [ref=e42]: for
- generic [ref=e44]: what
- generic [ref=e46]: it's
- generic [ref=e48]: worth.
- paragraph [ref=e49]: AI that scans every marketplace, scores every deal, and helps you relist in one tap. Stop scrolling. Start flipping.
- generic [ref=e50]:
- link "Join the waitlist" [ref=e51] [cursor=pointer]:
- /url: /signup
- button "Join the waitlist" [ref=e52]:
- generic: Join the waitlist
- link "See it work →" [ref=e53] [cursor=pointer]:
- /url: "#deal"
- link "↗" [ref=e54] [cursor=pointer]:
- /url: /signup
- img [ref=e55]:
- generic [ref=e57]: CAPTURE THE SPREAD • FLIP SMARTER • CAPTURE THE SPREAD • FLIP SMARTER •
- generic:
- generic [ref=e59]:
- generic [ref=e60]: How it works
- heading "The AI finds deals you'd never see in time." [level=2] [ref=e61]
- paragraph [ref=e62]: Spread scans thousands of listings across Facebook Marketplace, Craigslist, OfferUp, eBay, and more — alerting you the second something surfaces 3060% below market value in your niches.
- generic [ref=e63]:
- strong [ref=e64]: 6 platforms.
- text: Scanned 24/7. Alerts in under 12 seconds.
- generic [ref=e67]:
- generic [ref=e68]: Score & relist
- heading "Know exactly what to offer. List it before you leave." [level=2] [ref=e69]
- paragraph [ref=e70]: Every deal gets an AI score — estimated resale value, projected margin, time-to-sell, and the best platform to relist on. One tap generates an optimized listing with AI-written descriptions.
- generic [ref=e71]:
- generic [ref=e74]: Spread AI · Deal Analysis
- generic [ref=e75]:
- generic [ref=e76]:
- text: Found a
- strong [ref=e77]: Breville Oracle Touch
- text: listed at $1,200
- generic [ref=e78]: "Market value: $1,800$2,100 on eBay"
- generic [ref=e79]: "Suggested offer: $750 · Projected profit: +$500"
- generic [ref=e80]: Generate opener message ↗
- generic [ref=e81]: "\"Hi — is this still available? I can pick up today. Would you take $750 cash?\""
- generic [ref=e82]: Opener copied · eBay listing drafted
- generic [ref=e86]:
- generic [ref=e87]:
- heading "Stop scrolling. Start flipping." [level=2] [ref=e88]
- paragraph [ref=e89]: Other flippers spend 20 hours a week refreshing marketplace feeds. You spend 20 minutes reviewing deals the AI already found.
- generic [ref=e90]:
- generic [ref=e91]:
- generic [ref=e92]: "0"
- generic [ref=e93]: Marketplaces scanned 24/7
- generic [ref=e94]:
- generic [ref=e95]: 0%
- generic [ref=e96]: Average margin per deal
- generic [ref=e97]:
- generic [ref=e98]: 0hrs
- generic [ref=e99]: Saved per week vs. manual
- generic [ref=e100]:
- generic [ref=e101]: 0s
- generic [ref=e102]: Avg. alert speed on new listings
- generic [ref=e104]:
- generic [ref=e105]: Live opportunities
- heading "This is what the AI surfaces every day." [level=2] [ref=e106]
- paragraph [ref=e107]: Real arbitrage opportunities — scored, analyzed, and ready for you to act on. This is what your feed looks like.
- generic [ref=e112]:
- generic:
- generic [ref=e113]:
- text: I used to spend 3 hours a day refreshing Marketplace. Now Spread sends me the good stuff and I just act on it. Made $1,200 my first month.
- generic [ref=e114]: The deal scoring is scary accurate. It told me to offer $380 on an Aeron listed at $520. I sold it for $710 on Apt Deco two days later.
- generic [ref=e115]: One-click cross-listing changed everything. I find it on Craigslist, hit relist, and the eBay listing writes itself. My volume tripled.
- generic [ref=e116]: Set up my espresso machine niche on a Sunday. Had three scored deals by Monday morning. Closed my first flip by Wednesday.
- generic [ref=e117]:
- text: Operator plan · Austin, TX
- generic [ref=e118]: Scout plan · Portland, OR
- generic [ref=e119]: Operator plan · Nashville, TN
- generic [ref=e120]: Scout plan · Denver, CO
- generic [ref=e121]:
- button "Sarah K." [ref=e122] [cursor=pointer]:
- generic [ref=e123]: Sarah K.
- button "Marcus T." [ref=e124] [cursor=pointer]:
- generic: Marcus T.
- button "David L." [ref=e125] [cursor=pointer]:
- generic: David L.
- button "Amy R." [ref=e126] [cursor=pointer]:
- generic: Amy R.
- generic [ref=e127]:
- generic [ref=e128]: Built for flippers
- generic [ref=e130]: Every marketplace has a pricing gap.
- generic [ref=e132]: The AI finds it. You capture it.
- generic [ref=e134]:
- generic [ref=e135]: Where we're headed
- heading "Your money. Your rules. Choose how hands-on you want to be." [level=2] [ref=e136]
- paragraph [ref=e137]: Today, you control every deal. Soon, the AI handles more — negotiation, auto-buy within your thresholds, and fully autonomous operation. You decide when to let go.
- generic [ref=e138]:
- generic [ref=e139]:
- button "Manual" [ref=e140] [cursor=pointer]
- button "Assisted SOON" [ref=e141] [cursor=pointer]
- button "Autopilot SOON" [ref=e142] [cursor=pointer]
- paragraph [ref=e144]: Available now. The AI finds deals, scores them, and drafts your relist. You decide what to buy, what to offer, and when to sell. Full control, full visibility, full profit tracking.
- generic [ref=e145]:
- generic [ref=e146]:
- generic [ref=e147]: "01"
- generic [ref=e148]: Deal found
- generic [ref=e149]: AI scans 6 platforms
- generic [ref=e150]:
- generic [ref=e151]: "02"
- generic [ref=e152]: Scored
- generic [ref=e153]: Profit + risk analysis
- generic [ref=e154]:
- generic [ref=e155]: "03"
- generic [ref=e156]: You act
- generic [ref=e157]: Message, offer, buy
- generic [ref=e158]:
- generic [ref=e159]: "04"
- generic [ref=e160]: Relist
- generic [ref=e161]: One-tap cross-listing
- generic [ref=e164]:
- generic [ref=e165]:
- generic [ref=e166]: Your niches
- heading "Pick the markets you know." [level=2] [ref=e167]
- paragraph [ref=e168]: Choose the categories you understand. The AI handles scanning, scoring, and listing across every marketplace.
- generic [ref=e169]:
- button "Espresso Machines $280" [ref=e170] [cursor=pointer]
- button "Camera Gear $320" [ref=e171] [cursor=pointer]
- button "Mid-Century Furniture $450" [ref=e172] [cursor=pointer]
- button "Power Tools $175" [ref=e173] [cursor=pointer]
- button "Commercial Kitchen $520" [ref=e174] [cursor=pointer]
- button "Audiophile Gear $210" [ref=e175] [cursor=pointer]
- button "Outdoor Gear $140" [ref=e176] [cursor=pointer]
- button "Vintage Watches $380" [ref=e177] [cursor=pointer]
- button "Sneakers $95" [ref=e178] [cursor=pointer]
- button "Vinyl Records $85" [ref=e179] [cursor=pointer]
- button "Mechanical Keyboards $120" [ref=e180] [cursor=pointer]
- button "Designer Handbags $340" [ref=e181] [cursor=pointer]
- generic [ref=e183]:
- paragraph [ref=e184]: Or search your own
- generic [ref=e185]:
- textbox "Search a niche — vintage watches, vinyl records..." [ref=e186]
- button [ref=e187] [cursor=pointer]:
- img [ref=e188]
- generic [ref=e193]:
- heading "Opportunities land while you sleep." [level=2] [ref=e194]
- paragraph [ref=e195]: The AI never stops scanning. Wake up to scored deals ready for your review.
- generic [ref=e196]:
- generic [ref=e197]:
- generic [ref=e198]:
- generic [ref=e199]: Pricing
- heading "One good flip pays for a year." [level=2] [ref=e200]
- generic [ref=e201]: No speed throttling, no alert delays. Every plan gets the same fast scanning. Upgrade for more niches and advanced tools.
- generic [ref=e202]:
- generic [ref=e203]:
- generic [ref=e204]: Starter
- generic [ref=e205]: Scout
- generic [ref=e206]: Find your first flips
- generic [ref=e207]:
- generic [ref=e208]: $29
- generic [ref=e209]: /mo
- generic [ref=e210]: Cancel anytime
- list [ref=e212]:
- listitem [ref=e213]: 2 active niches
- listitem [ref=e214]: Real-time deal alerts
- listitem [ref=e215]: AI deal scoring
- listitem [ref=e216]: Profit tracking dashboard
- link "Start free trial" [ref=e217] [cursor=pointer]:
- /url: /signup
- button "Start free trial" [ref=e218]
- generic [ref=e219]:
- generic [ref=e220]: Most popular
- generic [ref=e221]: Operator
- generic [ref=e222]: Scale your operation
- generic [ref=e223]:
- generic [ref=e224]: $49
- generic [ref=e225]: /mo
- generic [ref=e226]: Pays for itself in one flip
- list [ref=e228]:
- listitem [ref=e229]: 8 active niches
- listitem [ref=e230]: One-click cross-listing
- listitem [ref=e231]: AI opener messages
- listitem [ref=e232]: Niche trend intelligence
- link "Start free trial" [ref=e233] [cursor=pointer]:
- /url: /signup
- button "Start free trial" [ref=e234]
- generic [ref=e235]:
- generic [ref=e236]: Pro
- generic [ref=e237]: Syndicate
- generic [ref=e238]: For full-time flippers
- generic [ref=e239]:
- generic [ref=e240]: $99
- generic [ref=e241]: /mo
- generic [ref=e242]: Priority access to Autopilot
- list [ref=e244]:
- listitem [ref=e245]: Unlimited niches
- listitem [ref=e246]: Multi-platform analytics
- listitem [ref=e247]: Team seats + shared deals
- listitem [ref=e248]: "Early access: Assisted + Autopilot"
- link "Start free trial" [ref=e249] [cursor=pointer]:
- /url: /signup
- button "Start free trial" [ref=e250]
- generic [ref=e252]:
- heading "Built for the way you actually flip." [level=2] [ref=e253]
- paragraph [ref=e254]: Deals surface in the background. Your life stays in the foreground.
- generic [ref=e270]:
- heading "Every marketplace has a spread. Start capturing yours." [level=2] [ref=e271]:
- text: Every marketplace has a spread.
- text: Start capturing yours.
- paragraph [ref=e272]: Same item. Different platforms. Different prices. The AI finds the gap — you keep what's left.
- link "Start free trial" [ref=e273] [cursor=pointer]:
- /url: /signup
- button "Start free trial" [ref=e274]:
- generic: Start free trial
- contentinfo [ref=e275]:
- img "Spread" [ref=e277]
- generic [ref=e278]:
- link "Terms" [ref=e279] [cursor=pointer]:
- /url: /terms
- link "Privacy" [ref=e280] [cursor=pointer]:
- /url: /privacy
- generic [ref=e281]: © 2026 Spread

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 602 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 790 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 829 KiB

View File

@ -1,390 +0,0 @@
- generic [ref=e3]:
- banner [ref=e4]:
- generic [ref=e6]:
- link "اندیشکده فولاد آینده The Steel Futures Institute" [ref=e7] [cursor=pointer]:
- /url: /
- img [ref=e8]
- generic [ref=e9]:
- generic [ref=e10]: اندیشکده فولاد آینده
- generic [ref=e11]: The Steel Futures Institute
- navigation "ناوبری اصلی" [ref=e12]:
- button "رادار آینده" [ref=e14] [cursor=pointer]:
- text: رادار آینده
- img [ref=e15]
- link "آمار و داده" [ref=e17] [cursor=pointer]:
- /url: /pulse
- link "در یک نگاه" [ref=e18] [cursor=pointer]:
- /url: /at-a-glance
- button "رسانه و رویداد" [ref=e20] [cursor=pointer]:
- text: رسانه و رویداد
- img [ref=e21]
- link "شبکه خبرگان" [ref=e23] [cursor=pointer]:
- /url: /experts
- button "درباره ما" [ref=e25] [cursor=pointer]:
- text: درباره ما
- img [ref=e26]
- generic [ref=e28]:
- button "جستجو" [ref=e29] [cursor=pointer]:
- img [ref=e30]
- button "ورود / ثبت‌نام" [ref=e33] [cursor=pointer]
- button "EN" [ref=e34] [cursor=pointer]
- main [ref=e35]:
- generic [ref=e36]:
- generic [ref=e44]:
- img [ref=e47]
- heading "بینشی نو برای توسعه‌ی صنعتی" [level=2] [ref=e53]
- generic:
- paragraph
- link "آشنایی با چشم‌انداز ما" [ref=e55] [cursor=pointer]:
- /url: /about
- img [ref=e56]
- generic [ref=e58]: آشنایی با چشم‌انداز ما
- generic [ref=e65]:
- generic [ref=e66]:
- generic [ref=e67]:
- generic [ref=e68]: FUTURE RADAR
- heading "رادار آینده" [level=2] [ref=e69]
- paragraph [ref=e70]: تحلیل چندوجهی آینده صنعت فولاد
- link "همه سیگنال‌ها ←" [ref=e71] [cursor=pointer]:
- /url: /radar
- generic [ref=e72]:
- article [ref=e73] [cursor=pointer]:
- generic [ref=e75]:
- img [ref=e77]
- generic [ref=e80]:
- generic [ref=e81]: پیش‌بینی بازار با هوش مصنوعی
- generic [ref=e82]: ۱۴ اردیبهشت ۱۴۰۵
- article [ref=e83] [cursor=pointer]:
- generic [ref=e85]:
- img [ref=e87]
- generic [ref=e90]:
- generic [ref=e91]: پیش‌بینی بازار با هوش مصنوعی
- generic [ref=e92]: ۱۴ اردیبهشت ۱۴۰۵
- article [ref=e93] [cursor=pointer]:
- generic [ref=e95]:
- img [ref=e97]
- generic [ref=e99]:
- generic [ref=e100]: پیش‌بینی بازار با هوش مصنوعی
- generic [ref=e101]: ۱۴ اردیبهشت ۱۴۰۵
- article [ref=e102] [cursor=pointer]:
- generic [ref=e104]:
- img [ref=e106]
- generic [ref=e109]:
- generic [ref=e110]: پیش‌بینی بازار با هوش مصنوعی
- generic [ref=e111]: ۱۴ اردیبهشت ۱۴۰۵
- generic [ref=e113]:
- generic [ref=e114]:
- generic [ref=e117]:
- generic [ref=e118]: At a Glance
- heading "در یک نگاه" [level=2] [ref=e119]
- link "مشاهده همه" [ref=e120] [cursor=pointer]:
- /url: /at-a-glance
- img [ref=e122]
- generic [ref=e124]: مشاهده همه
- generic [ref=e130]:
- generic [ref=e131]:
- heading "شوک هیدروژنی به بازار جهانی؛ آغاز رسمی عصر فولادسازی بدون کربن" [level=3] [ref=e132]
- generic [ref=e133]:
- generic [ref=e134]: Financial Times
- generic [ref=e135]:
- img [ref=e136]
- text: اردیبهشت ۱۴۰۵
- paragraph [ref=e138]: پروژه عظیم «فولاد سبز H2» در سوئد با جذب بی‌سابقه ۶.۵ میلیارد یورو سرمایه، رسماً وارد فاز اجرایی شد تا نخستین کارخانه فولادسازی در مقیاس تجاری بر پایه هیدروژن سبز (بدون نیاز به زغال‌سنگ) در جهان متولد شود. این تأسیسات با هدف کاهش ۹۵ درصدی انتشار CO₂، قواعد رقابت در زنجیره ارزش اروپا را برای همیشه بازتعریف می‌کند.
- generic [ref=e139]:
- button "prev" [ref=e140] [cursor=pointer]:
- button "next" [ref=e141] [cursor=pointer]:
- generic [ref=e144]:
- generic [ref=e146]:
- generic [ref=e147]: OUR FRAMEWORK
- heading "چارچوب اندیشه ما" [level=2] [ref=e148]
- paragraph [ref=e149]: ترکیب داده، تحلیل، آینده‌نگاری و خرد جمعی برای فهم تحولات صنعت فولاد.
- generic [ref=e150]:
- generic [ref=e151]:
- img [ref=e153]
- generic [ref=e157]:
- heading "تحلیل داده‌محور" [level=3] [ref=e158]
- paragraph [ref=e159]: استفاده از داده‌های بازار، اقتصاد، فناوری و زنجیره ارزش فولاد برای استخراج بینش‌های قابل اتکا.
- generic [ref=e160]:
- img [ref=e162]
- generic [ref=e167]:
- heading "نگاه نظام‌مند" [level=3] [ref=e168]
- paragraph [ref=e169]: تحلیل صنعت فولاد در تعامل با اقتصاد، انرژی، فناوری، محیط زیست و سیاست‌های جهانی.
- generic [ref=e170]:
- img [ref=e172]
- generic [ref=e177]:
- heading "هم‌اندیشی خبرگان" [level=3] [ref=e178]
- paragraph [ref=e179]: بهره‌گیری مستمر از شبکه‌ای از خبرگان صنعت، دانشگاه و فناوری.
- generic [ref=e180]:
- img [ref=e182]
- generic [ref=e190]:
- heading "آینده‌نگاری علمی" [level=3] [ref=e191]
- paragraph [ref=e192]: تحلیل روندها، شناسایی عدم‌قطعیت‌ها و طراحی سناریوها.
- generic [ref=e193]:
- img [ref=e195]
- generic [ref=e197]:
- heading "از تحلیل تا اقدام" [level=3] [ref=e198]
- paragraph [ref=e199]: تبدیل تحلیل‌ها به توصیه‌ها و تجویزهای استراتژیک.
- generic [ref=e200]:
- img [ref=e202]
- generic [ref=e206]:
- heading "بی‌طرفی و استقلال" [level=3] [ref=e207]
- paragraph [ref=e208]: اندیشه‌های مستقل و متکی بر شواهد، داده‌ها و نگاه بلندمدت.
- generic [ref=e214]:
- generic [ref=e215]:
- generic [ref=e216]: ماهنامه شماره ۱۲ منتشر شد…
- heading "دیپلماسی معدنی در عصر انسدادهای ژئوپلیتیک" [level=2] [ref=e217]
- generic [ref=e218]: Mineral Diplomacy in the Era of Geopolitical Blockades
- button "دانلود گزارش" [ref=e220] [cursor=pointer]:
- img [ref=e221]
- text: دانلود گزارش
- img "دیپلماسی معدنی در عصر انسدادهای ژئوپلیتیک" [ref=e225]
- generic [ref=e228]:
- generic [ref=e230]:
- generic [ref=e231]: DATA SOURCES
- heading "نبض صنعت" [level=2] [ref=e232]
- paragraph [ref=e233]: پنجره‌ای واحد به جهان داده‌های فولاد؛ دسترسی به شاخص‌ها، قیمت‌ها، تجارت جهانی و داده‌های کلیدی از معدن تا محصول نهایی.
- link "مشاهده منابع داده" [ref=e234] [cursor=pointer]:
- /url: /pulse
- text: مشاهده منابع داده
- img [ref=e235]
- generic [ref=e238]:
- generic "London Metal Exchange" [ref=e244] [cursor=pointer]:
- img [ref=e246]
- generic "Shanghai Futures Exchange" [ref=e256] [cursor=pointer]:
- img [ref=e258]
- generic "S&P Global Platts" [ref=e262] [cursor=pointer]:
- img [ref=e264]
- generic "Reuters" [ref=e271] [cursor=pointer]:
- img [ref=e273]
- generic "Fastmarkets" [ref=e279] [cursor=pointer]:
- img [ref=e281]
- generic "World Steel Association" [ref=e287] [cursor=pointer]:
- img [ref=e289]
- generic "Mysteel" [ref=e296] [cursor=pointer]:
- img [ref=e298]
- generic "Dalian Commodity Exchange" [ref=e304] [cursor=pointer]:
- img [ref=e306]
- generic "CME Group" [ref=e311] [cursor=pointer]:
- img [ref=e313]
- generic [ref=e323]:
- generic [ref=e324]:
- heading "دیده‌بان رویداد‌ها" [level=2] [ref=e327]
- link "مشاهده همه" [ref=e328] [cursor=pointer]:
- /url: /events
- img [ref=e330]
- generic [ref=e332]: مشاهده همه
- generic [ref=e333]:
- generic [ref=e334]:
- button "اجلاس جهانی فولاد سبز | استکهلم، سوئد" [ref=e335] [cursor=pointer]:
- generic [ref=e336]: اجلاس جهانی فولاد سبز | استکهلم، سوئد
- img [ref=e337]
- paragraph [ref=e340]:
- text: بررسی آخرین تکنولوژی‌های احیای مستقیم بر پایه هیدروژن و جذب سرمایه برای پروژه‌های بدون کربن. حضور در این رویداد برای درک آینده مقررات زیست‌محیطی اروپا حیاتی است.
- generic [ref=e341]: "(زمان: مهر ۱۴۰۵)"
- generic [ref=e342]:
- button "نشست سالانه انجمن جهانی فولاد (worldsteel) | شانگهای، چین" [ref=e343] [cursor=pointer]:
- generic [ref=e344]: نشست سالانه انجمن جهانی فولاد (worldsteel) | شانگهای، چین
- img [ref=e345]
- paragraph [ref=e347]:
- text: "گردهمایی بزرگ‌ترین رهبران صنعت. محور اصلی امسال: پیش‌بینی تقاضای جهانی در سایه رکود بخش مسکن چین و تغییر مسیر زنجیره تامین به سمت هند و خاورمیانه."
- generic [ref=e348]: "(زمان: آبان ۱۴۰۵)"
- generic [ref=e349]:
- button "مجمع بین‌المللی هوش مصنوعی در تولید | برلین، آلمان" [ref=e350] [cursor=pointer]:
- generic [ref=e351]: مجمع بین‌المللی هوش مصنوعی در تولید | برلین، آلمان
- img [ref=e352]
- paragraph [ref=e354]:
- text: رونمایی از جدیدترین نسل دوقلوهای دیجیتال (Digital Twins) و الگوریتم‌های پیش‌بینی تعمیرات. فرصتی برای آشنایی با راهکارهای کاهش هزینه و افزایش بهره‌وری خطوط نورد.
- generic [ref=e355]: "(زمان: آذر ۱۴۰۵)"
- generic [ref=e362]:
- generic [ref=e363]:
- generic [ref=e364]:
- generic [ref=e365]: Monthly Industry Report
- heading "ماهنامه تحلیلی کارخانه" [level=2] [ref=e366]
- link "مشاهده همه" [ref=e367] [cursor=pointer]:
- /url: https://www.msc.ir/fa-IR/Portal/8029/page/%D9%85%D8%A7%D9%87%D9%86%D8%A7%D9%85%D9%87-%D8%AA%D8%AD%D9%84%DB%8C%D9%84%DB%8C-%D8%B1%D8%A7%D9%87%D8%A8%D8%B1%D8%AF%DB%8C-%DA%A9%D8%A7%D8%B1%D8%AE%D8%A7%D9%86%D9%87
- generic [ref=e368]:
- article [ref=e370] [cursor=pointer]:
- generic [ref=e373]: تولید و صادرات
- generic [ref=e374]:
- generic [ref=e375]: بهمن ۱۴۰۳
- heading "تحلیل عملکرد تولید و صادرات" [level=3] [ref=e376]
- paragraph [ref=e377]: بررسی آمار تولید ماهانه، مقایسه با بودجه سالانه و روند صادرات فولاد تخت در بازارهای منطقه‌ای.
- link "دریافت گزارش" [ref=e379]:
- /url: https://www.msc.ir/fa-IR/Portal/8029/page/%D9%85%D8%A7%D9%87%D9%86%D8%A7%D9%85%D9%87-%D8%AA%D8%AD%D9%84%DB%8C%D9%84%DB%8C-%D8%B1%D8%A7%D9%87%D8%A8%D8%B1%D8%AF%DB%8C-%DA%A9%D8%A7%D8%B1%D8%AE%D8%A7%D9%86%D9%87
- img [ref=e380]
- generic [ref=e383]: دریافت گزارش
- article [ref=e385] [cursor=pointer]:
- generic [ref=e388]: بازار و قیمت
- generic [ref=e389]:
- generic [ref=e390]: دی ۱۴۰۳
- heading "وضعیت بازار و قیمت‌گذاری" [level=3] [ref=e391]
- paragraph [ref=e392]: تحلیل نوسانات قیمت فولاد تخت در بازار داخلی و اثر نرخ ارز بر چشم‌انداز زمستان.
- link "دریافت گزارش" [ref=e394]:
- /url: https://www.msc.ir/fa-IR/Portal/8029/page/%D9%85%D8%A7%D9%87%D9%86%D8%A7%D9%85%D9%87-%D8%AA%D8%AD%D9%84%DB%8C%D9%84%DB%8C-%D8%B1%D8%A7%D9%87%D8%A8%D8%B1%D8%AF%DB%8C-%DA%A9%D8%A7%D8%B1%D8%AE%D8%A7%D9%86%D9%87
- img [ref=e395]
- generic [ref=e398]: دریافت گزارش
- article [ref=e400] [cursor=pointer]:
- generic [ref=e403]: فناوری
- generic [ref=e404]:
- generic [ref=e405]: آذر ۱۴۰۳
- heading "دیجیتال‌سازی خطوط تولید" [level=3] [ref=e406]
- paragraph [ref=e407]: گزارشی از پیاده‌سازی سامانه‌های کنترل هوشمند و اثر آن بر بهره‌وری خطوط نورد.
- link "دریافت گزارش" [ref=e409]:
- /url: https://www.msc.ir/fa-IR/Portal/8029/page/%D9%85%D8%A7%D9%87%D9%86%D8%A7%D9%85%D9%87-%D8%AA%D8%AD%D9%84%DB%8C%D9%84%DB%8C-%D8%B1%D8%A7%D9%87%D8%A8%D8%B1%D8%AF%DB%8C-%DA%A9%D8%A7%D8%B1%D8%AE%D8%A7%D9%86%D9%87
- img [ref=e410]
- generic [ref=e413]: دریافت گزارش
- article [ref=e415] [cursor=pointer]:
- generic [ref=e418]: نیروی انسانی
- generic [ref=e419]:
- generic [ref=e420]: آبان ۱۴۰۳
- heading "تحلیل نیروی انسانی و بهره‌وری" [level=3] [ref=e421]
- paragraph [ref=e422]: شاخص‌های بهره‌وری نیروی کار، آموزش‌های ماهانه و آمار ایمنی شغلی.
- link "دریافت گزارش" [ref=e424]:
- /url: https://www.msc.ir/fa-IR/Portal/8029/page/%D9%85%D8%A7%D9%87%D9%86%D8%A7%D9%85%D9%87-%D8%AA%D8%AD%D9%84%DB%8C%D9%84%DB%8C-%D8%B1%D8%A7%D9%87%D8%A8%D8%B1%D8%AF%DB%8C-%DA%A9%D8%A7%D8%B1%D8%AE%D8%A7%D9%86%D9%87
- img [ref=e425]
- generic [ref=e428]: دریافت گزارش
- generic [ref=e432]:
- generic [ref=e433]:
- heading "ویدیوکست" [level=2] [ref=e436]
- button "مشاهده همه" [ref=e437] [cursor=pointer]:
- img [ref=e439]
- generic [ref=e441]: مشاهده همه
- generic [ref=e442]:
- button "play" [ref=e444] [cursor=pointer]
- generic [ref=e450]:
- generic [ref=e451]:
- generic [ref=e452]:
- generic [ref=e453]:
- generic [ref=e454]: 🔊
- generic [ref=e455]: ۰۹:۲۷ / ۴۲ دقیقه
- generic [ref=e456]:
- generic [ref=e457]:
- generic [ref=e458]:
- generic [ref=e459]:
- generic [ref=e460]:
- generic [ref=e461]: 🎙
- heading "نگاهی به آن سوی مرزهای تولید" [level=3] [ref=e462]
- paragraph [ref=e463]: در ویدیوکست «Steel Horizon»، ما فراتر از داده‌های خام قیمت و تناژ، به سراغ مهندسی تغییر و معماری آینده صنعت می‌رویم. اینجا محل تلاقی استراتژی‌های کلان، تکنولوژی‌های برهم‌زننده و رهبرانی است که فولاد فردا را نه با کوره‌ها، که با «بینش» می‌سازند. ما طوفان‌های بازار را تحلیل می‌کنیم تا شما با اطمینان، مسیر آینده را ترسیم کنید. صنعت فولاد در حال ورود به پیچیده‌ترین چرخه حیات خود است؛ جایی که هوش مصنوعی، مقررات کربنی و ژئواکونومی، قواعد بازی را بازنویسی کرده‌اند. در «Steel Logic» با تحلیل‌گران ارشد و استراتژیست‌های تراز اول، به گفتگو می‌نشینیم تا لایه‌های پنهان این پیچیدگی‌ها را آشکار کنیم. اینجا خبری از هیجان‌زدگی نیست؛ فقط تحلیل‌های داده‌محور و تصمیم‌ساز.
- generic [ref=e468]:
- generic [ref=e469]:
- heading "پادکست‌ها" [level=2] [ref=e472]
- button "مشاهده همه" [ref=e473] [cursor=pointer]:
- generic [ref=e474]: مشاهده همه
- img [ref=e476]
- generic [ref=e478]:
- generic [ref=e480]:
- generic [ref=e481]: ★★★★★
- generic [ref=e482]: ۴/۵
- generic [ref=e483]:
- generic [ref=e484]:
- generic [ref=e485]:
- button [ref=e486] [cursor=pointer]:
- img [ref=e487]
- button [ref=e490] [cursor=pointer]:
- img [ref=e491]
- generic [ref=e493]: اپیزود ۷۷
- heading "کالبدشکافی عقلانی پیچیدگی‌های صنعتی" [level=3] [ref=e495]
- generic [ref=e497]:
- img [ref=e498]
- generic [ref=e500]: ۲۵ / خرداد / ۱۴۰۵
- generic [ref=e502]:
- button "play" [ref=e503] [cursor=pointer]:
- img [ref=e504]
- generic [ref=e506]:
- generic [ref=e507]: ۰۲:۳۱
- generic [ref=e589]: ۱۰:۵۶
- generic [ref=e590]:
- generic [ref=e591]:
- button "آزمایشگاه کوره ۱:۴۰:۱۸ «The Foundry Lab» محلی برای کالبدشکافی دقیق تحولات فناورانه و مدل‌های نوین فولادسازی است. ما در این پادکست با نگاهی موشکافانه، چالش‌های فنی را بررسی می‌کنیم." [ref=e592] [cursor=pointer]:
- generic [ref=e593]:
- generic [ref=e594]:
- generic [ref=e595]: آزمایشگاه کوره
- generic [ref=e596]:
- img [ref=e597]
- generic [ref=e600]: ۱:۴۰:۱۸
- generic [ref=e601]: «The Foundry Lab» محلی برای کالبدشکافی دقیق تحولات فناورانه و مدل‌های نوین فولادسازی است. ما در این پادکست با نگاهی موشکافانه، چالش‌های فنی را بررسی می‌کنیم.
- button "معمار فولاد ۱:۴۰:۱۸ در «Steel Architect»، ما به سراغ داستان ساختن فولاد فردا می‌رویم؛ روایتی که رهبرانی که از مدل‌های سنتی کسب‌وکار به چالش کشیده‌اند را به تصویر می‌کشد." [ref=e602] [cursor=pointer]:
- generic [ref=e603]:
- generic [ref=e604]:
- generic [ref=e605]: معمار فولاد
- generic [ref=e606]:
- img [ref=e607]
- generic [ref=e610]: ۱:۴۰:۱۸
- generic [ref=e611]: در «Steel Architect»، ما به سراغ داستان ساختن فولاد فردا می‌رویم؛ روایتی که رهبرانی که از مدل‌های سنتی کسب‌وکار به چالش کشیده‌اند را به تصویر می‌کشد.
- generic [ref=e612]:
- generic [ref=e613]:
- heading "کالبدشکافی عقلانی پیچیدگی‌های صنعتی" [level=3] [ref=e614]
- img [ref=e616]
- paragraph [ref=e621]: «صنعت فولاد در عصر حاضر، در پیچیده‌ترین چرخه حیات خود قرار گرفته است؛ جایی که همگرایی هوش مصنوعی، الزامات زیست‌محیطی و تنش‌های ژئواکونومیک، قواعد کلاسیک تولید و تجارت را به‌کلی بازنویسی کرده‌اند. در پادکست «نام انتخابی»، ما با عبور از هیاهوی رسانه‌ای و تحلیل‌های سطحی، به کالبدشکافی عقلانی این پیچیدگی‌ها می‌پردازیم. ما با دعوت از استراتژیست‌های تراز اول و خبرگان این حوزه، لایه‌های پنهان چالش‌های صنعتی را می‌شکافیم تا بینشی شفاف، داده‌محور و تصمیم‌ساز را در اختیار رهبری قرار دهیم که برای عبور از طوفان‌های تغییر، به قطب‌نمای دقیق‌تر از عرف‌های بازار نیاز دارند.»
- generic [ref=e622]: "مهمان برنامه: مهندس سیامک شجاعی"
- contentinfo [ref=e623]:
- generic [ref=e624]:
- img [ref=e625]
- button "بازگشت به بالا" [ref=e627] [cursor=pointer]:
- img [ref=e628]
- generic [ref=e633]:
- generic [ref=e634]:
- img "اندیشکده فولاد آینده" [ref=e635]
- generic [ref=e638]: اندیشکده فولاد آینده
- generic [ref=e640]: The Steel Futures Institute
- paragraph [ref=e641]: فضایی همدلانه برای اندیشیدن به فردای صنعت؛ جایی که دانش، آینده‌پژوهی و نوآوری به بینش‌هایی برای توسعه صنعتی تبدیل می‌شوند.
- generic [ref=e642]: ما را در شبکه‌های اجتماعی دنبال کنید.
- generic [ref=e643]:
- link "آپارات" [ref=e644] [cursor=pointer]:
- /url: "#"
- img [ref=e645]
- link "X" [ref=e647] [cursor=pointer]:
- /url: "#"
- img [ref=e648]
- link "لینکداین" [ref=e650] [cursor=pointer]:
- /url: "#"
- img [ref=e651]
- link "تلگرام" [ref=e653] [cursor=pointer]:
- /url: "#"
- img [ref=e654]
- navigation "پیوندهای پایین" [ref=e656]:
- link "رادار آینده" [ref=e657] [cursor=pointer]:
- /url: /radar
- text: رادار آینده
- link "آمار و داده" [ref=e659] [cursor=pointer]:
- /url: "#"
- text: آمار و داده
- link "رسانه و رویداد" [ref=e661] [cursor=pointer]:
- /url: "#"
- text: رسانه و رویداد
- link "در یک نگاه" [ref=e663] [cursor=pointer]:
- /url: /at-a-glance
- text: در یک نگاه
- link "شبکه خبرگان" [ref=e665] [cursor=pointer]:
- /url: "#"
- text: شبکه خبرگان
- link "درباره ما" [ref=e667] [cursor=pointer]:
- /url: "#"
- text: درباره ما
- generic [ref=e669]:
- generic [ref=e670]: اطلاعات تماس
- generic [ref=e671]:
- generic [ref=e672]:
- img [ref=e673]
- generic [ref=e676]: "نشانی:"
- generic [ref=e677]: اصفهان، خیابان سعادت‌آباد
- generic [ref=e678]:
- img [ref=e679]
- generic [ref=e681]: "شماره تماس:"
- generic [ref=e682]: ۰۳۱ - ۳۸۸۸۴۵۳۹
- generic [ref=e683]:
- img [ref=e684]
- generic [ref=e687]: "پست الکترونیک:"
- generic [ref=e688]: info@steelforesight.com
- generic [ref=e689]:
- generic [ref=e691]: نشان
- generic [ref=e693]: نشان
- generic [ref=e695]: نشان
- generic [ref=e696]:
- generic [ref=e697]: عضویت در خبرنامه اندیشکده
- generic [ref=e698]:
- textbox "ایمیل خود را وارد کنید..." [ref=e699]
- button "ثبت" [ref=e700] [cursor=pointer]
- generic [ref=e702]:
- generic [ref=e703]: تمامی حقوق مادی و معنوی این وب‌سایت متعلق به شرکت فولاد مبارکه می‌باشد.
- generic [ref=e704]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک

View File

@ -1,390 +0,0 @@
- generic [ref=e3]:
- banner [ref=e4]:
- generic [ref=e6]:
- link "اندیشکده فولاد آینده The Steel Futures Institute" [ref=e7] [cursor=pointer]:
- /url: /
- img [ref=e8]
- generic [ref=e9]:
- generic [ref=e10]: اندیشکده فولاد آینده
- generic [ref=e11]: The Steel Futures Institute
- navigation "ناوبری اصلی" [ref=e12]:
- button "رادار آینده" [ref=e14] [cursor=pointer]:
- text: رادار آینده
- img [ref=e15]
- link "آمار و داده" [ref=e17] [cursor=pointer]:
- /url: /pulse
- link "در یک نگاه" [ref=e18] [cursor=pointer]:
- /url: /at-a-glance
- button "رسانه و رویداد" [ref=e20] [cursor=pointer]:
- text: رسانه و رویداد
- img [ref=e21]
- link "شبکه خبرگان" [ref=e23] [cursor=pointer]:
- /url: /experts
- button "درباره ما" [ref=e25] [cursor=pointer]:
- text: درباره ما
- img [ref=e26]
- generic [ref=e28]:
- button "جستجو" [ref=e29] [cursor=pointer]:
- img [ref=e30]
- button "ورود / ثبت‌نام" [ref=e33] [cursor=pointer]
- button "EN" [ref=e34] [cursor=pointer]
- main [ref=e35]:
- generic [ref=e36]:
- generic [ref=e44]:
- img [ref=e47]
- heading "بینشی نو برای توسعه‌ی صنعتی" [level=2] [ref=e53]
- generic:
- paragraph
- link "آشنایی با چشم‌انداز ما" [ref=e55] [cursor=pointer]:
- /url: /about
- img [ref=e56]
- generic [ref=e58]: آشنایی با چشم‌انداز ما
- generic [ref=e65]:
- generic [ref=e66]:
- generic [ref=e67]:
- generic [ref=e68]: FUTURE RADAR
- heading "رادار آینده" [level=2] [ref=e69]
- paragraph [ref=e70]: تحلیل چندوجهی آینده صنعت فولاد
- link "همه سیگنال‌ها ←" [ref=e71] [cursor=pointer]:
- /url: /radar
- generic [ref=e72]:
- article [ref=e73] [cursor=pointer]:
- generic [ref=e75]:
- img [ref=e77]
- generic [ref=e80]:
- generic [ref=e81]: پیش‌بینی بازار با هوش مصنوعی
- generic [ref=e82]: ۱۴ اردیبهشت ۱۴۰۵
- article [ref=e83] [cursor=pointer]:
- generic [ref=e85]:
- img [ref=e87]
- generic [ref=e90]:
- generic [ref=e91]: پیش‌بینی بازار با هوش مصنوعی
- generic [ref=e92]: ۱۴ اردیبهشت ۱۴۰۵
- article [ref=e93] [cursor=pointer]:
- generic [ref=e95]:
- img [ref=e97]
- generic [ref=e99]:
- generic [ref=e100]: پیش‌بینی بازار با هوش مصنوعی
- generic [ref=e101]: ۱۴ اردیبهشت ۱۴۰۵
- article [ref=e102] [cursor=pointer]:
- generic [ref=e104]:
- img [ref=e106]
- generic [ref=e109]:
- generic [ref=e110]: پیش‌بینی بازار با هوش مصنوعی
- generic [ref=e111]: ۱۴ اردیبهشت ۱۴۰۵
- generic [ref=e113]:
- generic [ref=e114]:
- generic [ref=e117]:
- generic [ref=e118]: At a Glance
- heading "در یک نگاه" [level=2] [ref=e119]
- link "مشاهده همه" [ref=e120] [cursor=pointer]:
- /url: /at-a-glance
- img [ref=e122]
- generic [ref=e124]: مشاهده همه
- generic [ref=e130]:
- generic [ref=e131]:
- heading "شوک هیدروژنی به بازار جهانی؛ آغاز رسمی عصر فولادسازی بدون کربن" [level=3] [ref=e132]
- generic [ref=e133]:
- generic [ref=e134]: Financial Times
- generic [ref=e135]:
- img [ref=e136]
- text: اردیبهشت ۱۴۰۵
- paragraph [ref=e138]: پروژه عظیم «فولاد سبز H2» در سوئد با جذب بی‌سابقه ۶.۵ میلیارد یورو سرمایه، رسماً وارد فاز اجرایی شد تا نخستین کارخانه فولادسازی در مقیاس تجاری بر پایه هیدروژن سبز (بدون نیاز به زغال‌سنگ) در جهان متولد شود. این تأسیسات با هدف کاهش ۹۵ درصدی انتشار CO₂، قواعد رقابت در زنجیره ارزش اروپا را برای همیشه بازتعریف می‌کند.
- generic [ref=e139]:
- button "prev" [ref=e140] [cursor=pointer]:
- button "next" [ref=e141] [cursor=pointer]:
- generic [ref=e144]:
- generic [ref=e146]:
- generic [ref=e147]: OUR FRAMEWORK
- heading "چارچوب اندیشه ما" [level=2] [ref=e148]
- paragraph [ref=e149]: ترکیب داده، تحلیل، آینده‌نگاری و خرد جمعی برای فهم تحولات صنعت فولاد.
- generic [ref=e150]:
- generic [ref=e151]:
- img [ref=e153]
- generic [ref=e157]:
- heading "تحلیل داده‌محور" [level=3] [ref=e158]
- paragraph [ref=e159]: استفاده از داده‌های بازار، اقتصاد، فناوری و زنجیره ارزش فولاد برای استخراج بینش‌های قابل اتکا.
- generic [ref=e160]:
- img [ref=e162]
- generic [ref=e167]:
- heading "نگاه نظام‌مند" [level=3] [ref=e168]
- paragraph [ref=e169]: تحلیل صنعت فولاد در تعامل با اقتصاد، انرژی، فناوری، محیط زیست و سیاست‌های جهانی.
- generic [ref=e170]:
- img [ref=e172]
- generic [ref=e177]:
- heading "هم‌اندیشی خبرگان" [level=3] [ref=e178]
- paragraph [ref=e179]: بهره‌گیری مستمر از شبکه‌ای از خبرگان صنعت، دانشگاه و فناوری.
- generic [ref=e180]:
- img [ref=e182]
- generic [ref=e190]:
- heading "آینده‌نگاری علمی" [level=3] [ref=e191]
- paragraph [ref=e192]: تحلیل روندها، شناسایی عدم‌قطعیت‌ها و طراحی سناریوها.
- generic [ref=e193]:
- img [ref=e195]
- generic [ref=e197]:
- heading "از تحلیل تا اقدام" [level=3] [ref=e198]
- paragraph [ref=e199]: تبدیل تحلیل‌ها به توصیه‌ها و تجویزهای استراتژیک.
- generic [ref=e200]:
- img [ref=e202]
- generic [ref=e206]:
- heading "بی‌طرفی و استقلال" [level=3] [ref=e207]
- paragraph [ref=e208]: اندیشه‌های مستقل و متکی بر شواهد، داده‌ها و نگاه بلندمدت.
- generic [ref=e214]:
- generic [ref=e215]:
- generic [ref=e216]: ماهنامه شماره ۱۲ منتشر شد…
- heading "دیپلماسی معدنی در عصر انسدادهای ژئوپلیتیک" [level=2] [ref=e217]
- generic [ref=e218]: Mineral Diplomacy in the Era of Geopolitical Blockades
- button "دانلود گزارش" [ref=e220] [cursor=pointer]:
- img [ref=e221]
- text: دانلود گزارش
- img "دیپلماسی معدنی در عصر انسدادهای ژئوپلیتیک" [ref=e225]
- generic [ref=e228]:
- generic [ref=e230]:
- generic [ref=e231]: DATA SOURCES
- heading "نبض صنعت" [level=2] [ref=e232]
- paragraph [ref=e233]: پنجره‌ای واحد به جهان داده‌های فولاد؛ دسترسی به شاخص‌ها، قیمت‌ها، تجارت جهانی و داده‌های کلیدی از معدن تا محصول نهایی.
- link "مشاهده منابع داده" [ref=e234] [cursor=pointer]:
- /url: /pulse
- text: مشاهده منابع داده
- img [ref=e235]
- generic [ref=e238]:
- generic "London Metal Exchange" [ref=e244] [cursor=pointer]:
- img [ref=e246]
- generic "Shanghai Futures Exchange" [ref=e256] [cursor=pointer]:
- img [ref=e258]
- generic "S&P Global Platts" [ref=e262] [cursor=pointer]:
- img [ref=e264]
- generic "Reuters" [ref=e271] [cursor=pointer]:
- img [ref=e273]
- generic "Fastmarkets" [ref=e279] [cursor=pointer]:
- img [ref=e281]
- generic "World Steel Association" [ref=e287] [cursor=pointer]:
- img [ref=e289]
- generic "Mysteel" [ref=e296] [cursor=pointer]:
- img [ref=e298]
- generic "Dalian Commodity Exchange" [ref=e304] [cursor=pointer]:
- img [ref=e306]
- generic "CME Group" [ref=e311] [cursor=pointer]:
- img [ref=e313]
- generic [ref=e323]:
- generic [ref=e324]:
- heading "دیده‌بان رویداد‌ها" [level=2] [ref=e327]
- link "مشاهده همه" [ref=e328] [cursor=pointer]:
- /url: /events
- img [ref=e330]
- generic [ref=e332]: مشاهده همه
- generic [ref=e333]:
- generic [ref=e334]:
- button "اجلاس جهانی فولاد سبز | استکهلم، سوئد" [ref=e335] [cursor=pointer]:
- generic [ref=e336]: اجلاس جهانی فولاد سبز | استکهلم، سوئد
- img [ref=e337]
- paragraph [ref=e340]:
- text: بررسی آخرین تکنولوژی‌های احیای مستقیم بر پایه هیدروژن و جذب سرمایه برای پروژه‌های بدون کربن. حضور در این رویداد برای درک آینده مقررات زیست‌محیطی اروپا حیاتی است.
- generic [ref=e341]: "(زمان: مهر ۱۴۰۵)"
- generic [ref=e342]:
- button "نشست سالانه انجمن جهانی فولاد (worldsteel) | شانگهای، چین" [ref=e343] [cursor=pointer]:
- generic [ref=e344]: نشست سالانه انجمن جهانی فولاد (worldsteel) | شانگهای، چین
- img [ref=e345]
- paragraph [ref=e347]:
- text: "گردهمایی بزرگ‌ترین رهبران صنعت. محور اصلی امسال: پیش‌بینی تقاضای جهانی در سایه رکود بخش مسکن چین و تغییر مسیر زنجیره تامین به سمت هند و خاورمیانه."
- generic [ref=e348]: "(زمان: آبان ۱۴۰۵)"
- generic [ref=e349]:
- button "مجمع بین‌المللی هوش مصنوعی در تولید | برلین، آلمان" [ref=e350] [cursor=pointer]:
- generic [ref=e351]: مجمع بین‌المللی هوش مصنوعی در تولید | برلین، آلمان
- img [ref=e352]
- paragraph [ref=e354]:
- text: رونمایی از جدیدترین نسل دوقلوهای دیجیتال (Digital Twins) و الگوریتم‌های پیش‌بینی تعمیرات. فرصتی برای آشنایی با راهکارهای کاهش هزینه و افزایش بهره‌وری خطوط نورد.
- generic [ref=e355]: "(زمان: آذر ۱۴۰۵)"
- generic [ref=e362]:
- generic [ref=e363]:
- generic [ref=e364]:
- generic [ref=e365]: Monthly Industry Report
- heading "ماهنامه تحلیلی کارخانه" [level=2] [ref=e366]
- link "مشاهده همه" [ref=e367] [cursor=pointer]:
- /url: https://www.msc.ir/fa-IR/Portal/8029/page/%D9%85%D8%A7%D9%87%D9%86%D8%A7%D9%85%D9%87-%D8%AA%D8%AD%D9%84%DB%8C%D9%84%DB%8C-%D8%B1%D8%A7%D9%87%D8%A8%D8%B1%D8%AF%DB%8C-%DA%A9%D8%A7%D8%B1%D8%AE%D8%A7%D9%86%D9%87
- generic [ref=e368]:
- article [ref=e370] [cursor=pointer]:
- generic [ref=e373]: تولید و صادرات
- generic [ref=e374]:
- generic [ref=e375]: بهمن ۱۴۰۳
- heading "تحلیل عملکرد تولید و صادرات" [level=3] [ref=e376]
- paragraph [ref=e377]: بررسی آمار تولید ماهانه، مقایسه با بودجه سالانه و روند صادرات فولاد تخت در بازارهای منطقه‌ای.
- link "دریافت گزارش" [ref=e379]:
- /url: https://www.msc.ir/fa-IR/Portal/8029/page/%D9%85%D8%A7%D9%87%D9%86%D8%A7%D9%85%D9%87-%D8%AA%D8%AD%D9%84%DB%8C%D9%84%DB%8C-%D8%B1%D8%A7%D9%87%D8%A8%D8%B1%D8%AF%DB%8C-%DA%A9%D8%A7%D8%B1%D8%AE%D8%A7%D9%86%D9%87
- img [ref=e380]
- generic [ref=e383]: دریافت گزارش
- article [ref=e385] [cursor=pointer]:
- generic [ref=e388]: بازار و قیمت
- generic [ref=e389]:
- generic [ref=e390]: دی ۱۴۰۳
- heading "وضعیت بازار و قیمت‌گذاری" [level=3] [ref=e391]
- paragraph [ref=e392]: تحلیل نوسانات قیمت فولاد تخت در بازار داخلی و اثر نرخ ارز بر چشم‌انداز زمستان.
- link "دریافت گزارش" [ref=e394]:
- /url: https://www.msc.ir/fa-IR/Portal/8029/page/%D9%85%D8%A7%D9%87%D9%86%D8%A7%D9%85%D9%87-%D8%AA%D8%AD%D9%84%DB%8C%D9%84%DB%8C-%D8%B1%D8%A7%D9%87%D8%A8%D8%B1%D8%AF%DB%8C-%DA%A9%D8%A7%D8%B1%D8%AE%D8%A7%D9%86%D9%87
- img [ref=e395]
- generic [ref=e398]: دریافت گزارش
- article [ref=e400] [cursor=pointer]:
- generic [ref=e403]: فناوری
- generic [ref=e404]:
- generic [ref=e405]: آذر ۱۴۰۳
- heading "دیجیتال‌سازی خطوط تولید" [level=3] [ref=e406]
- paragraph [ref=e407]: گزارشی از پیاده‌سازی سامانه‌های کنترل هوشمند و اثر آن بر بهره‌وری خطوط نورد.
- link "دریافت گزارش" [ref=e409]:
- /url: https://www.msc.ir/fa-IR/Portal/8029/page/%D9%85%D8%A7%D9%87%D9%86%D8%A7%D9%85%D9%87-%D8%AA%D8%AD%D9%84%DB%8C%D9%84%DB%8C-%D8%B1%D8%A7%D9%87%D8%A8%D8%B1%D8%AF%DB%8C-%DA%A9%D8%A7%D8%B1%D8%AE%D8%A7%D9%86%D9%87
- img [ref=e410]
- generic [ref=e413]: دریافت گزارش
- article [ref=e415] [cursor=pointer]:
- generic [ref=e418]: نیروی انسانی
- generic [ref=e419]:
- generic [ref=e420]: آبان ۱۴۰۳
- heading "تحلیل نیروی انسانی و بهره‌وری" [level=3] [ref=e421]
- paragraph [ref=e422]: شاخص‌های بهره‌وری نیروی کار، آموزش‌های ماهانه و آمار ایمنی شغلی.
- link "دریافت گزارش" [ref=e424]:
- /url: https://www.msc.ir/fa-IR/Portal/8029/page/%D9%85%D8%A7%D9%87%D9%86%D8%A7%D9%85%D9%87-%D8%AA%D8%AD%D9%84%DB%8C%D9%84%DB%8C-%D8%B1%D8%A7%D9%87%D8%A8%D8%B1%D8%AF%DB%8C-%DA%A9%D8%A7%D8%B1%D8%AE%D8%A7%D9%86%D9%87
- img [ref=e425]
- generic [ref=e428]: دریافت گزارش
- generic [ref=e432]:
- generic [ref=e433]:
- heading "ویدیوکست" [level=2] [ref=e436]
- button "مشاهده همه" [ref=e437] [cursor=pointer]:
- img [ref=e439]
- generic [ref=e441]: مشاهده همه
- generic [ref=e442]:
- button "play" [ref=e444] [cursor=pointer]
- generic [ref=e450]:
- generic [ref=e451]:
- generic [ref=e452]:
- generic [ref=e453]:
- generic [ref=e454]: 🔊
- generic [ref=e455]: ۰۹:۲۷ / ۴۲ دقیقه
- generic [ref=e456]:
- generic [ref=e457]:
- generic [ref=e458]:
- generic [ref=e459]:
- generic [ref=e460]:
- generic [ref=e461]: 🎙
- heading "نگاهی به آن سوی مرزهای تولید" [level=3] [ref=e462]
- paragraph [ref=e463]: در ویدیوکست «Steel Horizon»، ما فراتر از داده‌های خام قیمت و تناژ، به سراغ مهندسی تغییر و معماری آینده صنعت می‌رویم. اینجا محل تلاقی استراتژی‌های کلان، تکنولوژی‌های برهم‌زننده و رهبرانی است که فولاد فردا را نه با کوره‌ها، که با «بینش» می‌سازند. ما طوفان‌های بازار را تحلیل می‌کنیم تا شما با اطمینان، مسیر آینده را ترسیم کنید. صنعت فولاد در حال ورود به پیچیده‌ترین چرخه حیات خود است؛ جایی که هوش مصنوعی، مقررات کربنی و ژئواکونومی، قواعد بازی را بازنویسی کرده‌اند. در «Steel Logic» با تحلیل‌گران ارشد و استراتژیست‌های تراز اول، به گفتگو می‌نشینیم تا لایه‌های پنهان این پیچیدگی‌ها را آشکار کنیم. اینجا خبری از هیجان‌زدگی نیست؛ فقط تحلیل‌های داده‌محور و تصمیم‌ساز.
- generic [ref=e468]:
- generic [ref=e469]:
- heading "پادکست‌ها" [level=2] [ref=e472]
- button "مشاهده همه" [ref=e473] [cursor=pointer]:
- generic [ref=e474]: مشاهده همه
- img [ref=e476]
- generic [ref=e478]:
- generic [ref=e480]:
- generic [ref=e481]: ★★★★★
- generic [ref=e482]: ۴/۵
- generic [ref=e483]:
- generic [ref=e484]:
- generic [ref=e485]:
- button [ref=e486] [cursor=pointer]:
- img [ref=e487]
- button [ref=e490] [cursor=pointer]:
- img [ref=e491]
- generic [ref=e493]: اپیزود ۷۷
- heading "کالبدشکافی عقلانی پیچیدگی‌های صنعتی" [level=3] [ref=e495]
- generic [ref=e497]:
- img [ref=e498]
- generic [ref=e500]: ۲۵ / خرداد / ۱۴۰۵
- generic [ref=e502]:
- button "play" [ref=e503] [cursor=pointer]:
- img [ref=e504]
- generic [ref=e506]:
- generic [ref=e507]: ۰۲:۳۱
- generic [ref=e589]: ۱۰:۵۶
- generic [ref=e590]:
- generic [ref=e591]:
- button "آزمایشگاه کوره ۱:۴۰:۱۸ «The Foundry Lab» محلی برای کالبدشکافی دقیق تحولات فناورانه و مدل‌های نوین فولادسازی است. ما در این پادکست با نگاهی موشکافانه، چالش‌های فنی را بررسی می‌کنیم." [ref=e592] [cursor=pointer]:
- generic [ref=e593]:
- generic [ref=e594]:
- generic [ref=e595]: آزمایشگاه کوره
- generic [ref=e596]:
- img [ref=e597]
- generic [ref=e600]: ۱:۴۰:۱۸
- generic [ref=e601]: «The Foundry Lab» محلی برای کالبدشکافی دقیق تحولات فناورانه و مدل‌های نوین فولادسازی است. ما در این پادکست با نگاهی موشکافانه، چالش‌های فنی را بررسی می‌کنیم.
- button "معمار فولاد ۱:۴۰:۱۸ در «Steel Architect»، ما به سراغ داستان ساختن فولاد فردا می‌رویم؛ روایتی که رهبرانی که از مدل‌های سنتی کسب‌وکار به چالش کشیده‌اند را به تصویر می‌کشد." [ref=e602] [cursor=pointer]:
- generic [ref=e603]:
- generic [ref=e604]:
- generic [ref=e605]: معمار فولاد
- generic [ref=e606]:
- img [ref=e607]
- generic [ref=e610]: ۱:۴۰:۱۸
- generic [ref=e611]: در «Steel Architect»، ما به سراغ داستان ساختن فولاد فردا می‌رویم؛ روایتی که رهبرانی که از مدل‌های سنتی کسب‌وکار به چالش کشیده‌اند را به تصویر می‌کشد.
- generic [ref=e612]:
- generic [ref=e613]:
- heading "کالبدشکافی عقلانی پیچیدگی‌های صنعتی" [level=3] [ref=e614]
- img [ref=e616]
- paragraph [ref=e621]: «صنعت فولاد در عصر حاضر، در پیچیده‌ترین چرخه حیات خود قرار گرفته است؛ جایی که همگرایی هوش مصنوعی، الزامات زیست‌محیطی و تنش‌های ژئواکونومیک، قواعد کلاسیک تولید و تجارت را به‌کلی بازنویسی کرده‌اند. در پادکست «نام انتخابی»، ما با عبور از هیاهوی رسانه‌ای و تحلیل‌های سطحی، به کالبدشکافی عقلانی این پیچیدگی‌ها می‌پردازیم. ما با دعوت از استراتژیست‌های تراز اول و خبرگان این حوزه، لایه‌های پنهان چالش‌های صنعتی را می‌شکافیم تا بینشی شفاف، داده‌محور و تصمیم‌ساز را در اختیار رهبری قرار دهیم که برای عبور از طوفان‌های تغییر، به قطب‌نمای دقیق‌تر از عرف‌های بازار نیاز دارند.»
- generic [ref=e622]: "مهمان برنامه: مهندس سیامک شجاعی"
- contentinfo [ref=e623]:
- generic [ref=e624]:
- img [ref=e625]
- button "بازگشت به بالا" [ref=e627] [cursor=pointer]:
- img [ref=e628]
- generic [ref=e633]:
- generic [ref=e634]:
- img "اندیشکده فولاد آینده" [ref=e635]
- generic [ref=e638]: اندیشکده فولاد آینده
- generic [ref=e640]: The Steel Futures Institute
- paragraph [ref=e641]: فضایی همدلانه برای اندیشیدن به فردای صنعت؛ جایی که دانش، آینده‌پژوهی و نوآوری به بینش‌هایی برای توسعه صنعتی تبدیل می‌شوند.
- generic [ref=e642]: ما را در شبکه‌های اجتماعی دنبال کنید.
- generic [ref=e643]:
- link "آپارات" [ref=e644] [cursor=pointer]:
- /url: "#"
- img [ref=e645]
- link "X" [ref=e647] [cursor=pointer]:
- /url: "#"
- img [ref=e648]
- link "لینکداین" [ref=e650] [cursor=pointer]:
- /url: "#"
- img [ref=e651]
- link "تلگرام" [ref=e653] [cursor=pointer]:
- /url: "#"
- img [ref=e654]
- navigation "پیوندهای پایین" [ref=e656]:
- link "رادار آینده" [ref=e657] [cursor=pointer]:
- /url: /radar
- text: رادار آینده
- link "آمار و داده" [ref=e659] [cursor=pointer]:
- /url: "#"
- text: آمار و داده
- link "رسانه و رویداد" [ref=e661] [cursor=pointer]:
- /url: "#"
- text: رسانه و رویداد
- link "در یک نگاه" [ref=e663] [cursor=pointer]:
- /url: /at-a-glance
- text: در یک نگاه
- link "شبکه خبرگان" [ref=e665] [cursor=pointer]:
- /url: "#"
- text: شبکه خبرگان
- link "درباره ما" [ref=e667] [cursor=pointer]:
- /url: "#"
- text: درباره ما
- generic [ref=e669]:
- generic [ref=e670]: اطلاعات تماس
- generic [ref=e671]:
- generic [ref=e672]:
- img [ref=e673]
- generic [ref=e676]: "نشانی:"
- generic [ref=e677]: اصفهان، خیابان سعادت‌آباد
- generic [ref=e678]:
- img [ref=e679]
- generic [ref=e681]: "شماره تماس:"
- generic [ref=e682]: ۰۳۱ - ۳۸۸۸۴۵۳۹
- generic [ref=e683]:
- img [ref=e684]
- generic [ref=e687]: "پست الکترونیک:"
- generic [ref=e688]: info@steelforesight.com
- generic [ref=e689]:
- generic [ref=e691]: نشان
- generic [ref=e693]: نشان
- generic [ref=e695]: نشان
- generic [ref=e696]:
- generic [ref=e697]: عضویت در خبرنامه اندیشکده
- generic [ref=e698]:
- textbox "ایمیل خود را وارد کنید..." [ref=e699]
- button "ثبت" [ref=e700] [cursor=pointer]
- generic [ref=e702]:
- generic [ref=e703]: تمامی حقوق مادی و معنوی این وب‌سایت متعلق به شرکت فولاد مبارکه می‌باشد.
- generic [ref=e704]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک

View File

@ -1,121 +0,0 @@
- generic [ref=e3]:
- banner [ref=e4]:
- generic [ref=e6]:
- link "اندیشکده فولاد آینده The Steel Futures Institute" [ref=e7] [cursor=pointer]:
- /url: /
- img [ref=e8]
- generic [ref=e9]:
- generic [ref=e10]: اندیشکده فولاد آینده
- generic [ref=e11]: The Steel Futures Institute
- navigation "ناوبری اصلی" [ref=e12]:
- button "رادار آینده" [ref=e14] [cursor=pointer]:
- text: رادار آینده
- img [ref=e15]
- link "آمار و داده" [ref=e17] [cursor=pointer]:
- /url: /pulse
- link "در یک نگاه" [ref=e18] [cursor=pointer]:
- /url: /at-a-glance
- button "رسانه و رویداد" [ref=e20] [cursor=pointer]:
- text: رسانه و رویداد
- img [ref=e21]
- link "شبکه خبرگان" [ref=e23] [cursor=pointer]:
- /url: /experts
- button "درباره ما" [ref=e25] [cursor=pointer]:
- text: درباره ما
- img [ref=e26]
- generic [ref=e28]:
- button "جستجو" [ref=e29] [cursor=pointer]:
- img [ref=e30]
- link "alire" [ref=e33] [cursor=pointer]:
- /url: /profile
- img [ref=e34]
- text: alire
- button "Switch to English" [ref=e37] [cursor=pointer]: 🇺🇸
- main [ref=e38]:
- generic [ref=e40]:
- generic [ref=e41]:
- img [ref=e43]
- heading "ورود به حساب" [level=1] [ref=e46]
- generic [ref=e47]:
- generic [ref=e48]:
- generic [ref=e49]: ایمیل
- textbox "example@email.com" [ref=e50]
- generic [ref=e51]:
- generic [ref=e52]: رمز عبور
- textbox [ref=e53]
- button "ورود" [ref=e54] [cursor=pointer]
- paragraph [ref=e55]:
- text: حساب ندارید؟
- link "ثبت‌نام کنید" [ref=e56] [cursor=pointer]:
- /url: /register
- contentinfo [ref=e57]:
- generic [ref=e58]:
- img [ref=e59]
- button "بازگشت به بالا" [ref=e61] [cursor=pointer]:
- img [ref=e62]
- generic [ref=e67]:
- generic [ref=e68]:
- img "اندیشکده فولاد آینده" [ref=e69]
- generic [ref=e72]: اندیشکده فولاد آینده
- generic [ref=e74]: The Steel Futures Institute
- paragraph [ref=e75]: فضایی همدلانه برای اندیشیدن به فردای صنعت؛ جایی که دانش، آینده‌پژوهی و نوآوری به بینش‌هایی برای توسعه صنعتی تبدیل می‌شوند.
- generic [ref=e76]: ما را در شبکه‌های اجتماعی دنبال کنید.
- generic [ref=e77]:
- link "آپارات" [ref=e78] [cursor=pointer]:
- /url: "#"
- img [ref=e79]
- link "X" [ref=e81] [cursor=pointer]:
- /url: "#"
- img [ref=e82]
- link "لینکداین" [ref=e84] [cursor=pointer]:
- /url: "#"
- img [ref=e85]
- link "تلگرام" [ref=e87] [cursor=pointer]:
- /url: "#"
- img [ref=e88]
- navigation "پیوندهای پایین" [ref=e90]:
- link "رادار آینده" [ref=e91] [cursor=pointer]:
- /url: /radar
- text: رادار آینده
- link "آمار و داده" [ref=e93] [cursor=pointer]:
- /url: "#"
- text: آمار و داده
- link "رسانه و رویداد" [ref=e95] [cursor=pointer]:
- /url: "#"
- text: رسانه و رویداد
- link "در یک نگاه" [ref=e97] [cursor=pointer]:
- /url: /at-a-glance
- text: در یک نگاه
- link "شبکه خبرگان" [ref=e99] [cursor=pointer]:
- /url: "#"
- text: شبکه خبرگان
- link "درباره ما" [ref=e101] [cursor=pointer]:
- /url: "#"
- text: درباره ما
- generic [ref=e103]:
- generic [ref=e104]: اطلاعات تماس
- generic [ref=e105]:
- generic [ref=e106]:
- img [ref=e107]
- generic [ref=e110]: "نشانی:"
- generic [ref=e111]: اصفهان، خیابان سعادت‌آباد
- generic [ref=e112]:
- img [ref=e113]
- generic [ref=e115]: "شماره تماس:"
- generic [ref=e116]: ۰۳۱ - ۳۸۸۸۴۵۳۹
- generic [ref=e117]:
- img [ref=e118]
- generic [ref=e121]: "پست الکترونیک:"
- generic [ref=e122]: info@steelforesight.com
- generic [ref=e123]:
- generic [ref=e125]: نشان
- generic [ref=e127]: نشان
- generic [ref=e129]: نشان
- generic [ref=e130]:
- generic [ref=e131]: عضویت در خبرنامه اندیشکده
- generic [ref=e132]:
- textbox "ایمیل خود را وارد کنید..." [ref=e133]
- button "ثبت" [ref=e134] [cursor=pointer]
- generic [ref=e136]:
- generic [ref=e137]: تمامی حقوق مادی و معنوی این وب‌سایت متعلق به شرکت فولاد مبارکه می‌باشد.
- generic [ref=e138]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

View File

@ -1,154 +0,0 @@
- generic [ref=e3]:
- banner [ref=e4]:
- generic [ref=e6]:
- link "اندیشکده فولاد آینده The Steel Futures Institute" [ref=e7] [cursor=pointer]:
- /url: /
- img [ref=e8]
- generic [ref=e9]:
- generic [ref=e10]: اندیشکده فولاد آینده
- generic [ref=e11]: The Steel Futures Institute
- navigation "ناوبری اصلی" [ref=e12]:
- button "رادار آینده" [ref=e14] [cursor=pointer]:
- text: رادار آینده
- img [ref=e15]
- link "آمار و داده" [ref=e17] [cursor=pointer]:
- /url: /pulse
- link "در یک نگاه" [ref=e18] [cursor=pointer]:
- /url: /at-a-glance
- button "رسانه و رویداد" [ref=e20] [cursor=pointer]:
- text: رسانه و رویداد
- img [ref=e21]
- link "شبکه خبرگان" [ref=e23] [cursor=pointer]:
- /url: /experts
- button "درباره ما" [ref=e25] [cursor=pointer]:
- text: درباره ما
- img [ref=e26]
- generic [ref=e28]:
- button "جستجو" [ref=e29] [cursor=pointer]:
- img [ref=e30]
- link "ورود / ثبت‌نام" [ref=e33] [cursor=pointer]:
- /url: /login
- button "Switch to English" [ref=e34] [cursor=pointer]: 🇺🇸
- main [ref=e35]:
- generic [ref=e36]:
- generic [ref=e37]:
- img [ref=e38]
- img [ref=e40]
- link "اندیشکده فولاد آینده The Steel Futures Institute" [ref=e48] [cursor=pointer]:
- /url: /
- generic [ref=e49]:
- generic [ref=e50]: اندیشکده فولاد آینده
- generic [ref=e51]: The Steel Futures Institute
- generic [ref=e52]:
- generic [ref=e53]: عضویت در اندیشکده
- heading "دسترسی به تحلیل‌های تخصصی صنعت فولاد ایران" [level=1] [ref=e54]:
- text: دسترسی به تحلیل‌های تخصصی
- text: صنعت فولاد ایران
- paragraph [ref=e55]: گزارش‌های بازار، رصد فناوری، و تحلیل ژئوپلیتیک — همه در یک پلتفرم
- generic [ref=e56]:
- generic [ref=e57]:
- generic [ref=e58]: +۲۰۰
- generic [ref=e59]: گزارش تخصصی
- generic [ref=e60]:
- generic [ref=e61]: ۵۰+
- generic [ref=e62]: کارشناس
- generic [ref=e63]:
- generic [ref=e64]: ۳ سال
- generic [ref=e65]: سابقه
- generic [ref=e67]:
- generic [ref=e68]:
- heading "خوش آمدید" [level=2] [ref=e69]
- paragraph [ref=e70]: برای ادامه وارد حساب خود شوید
- generic [ref=e71]:
- generic [ref=e72]:
- generic [ref=e73]: ایمیل
- generic [ref=e74]:
- textbox "example@email.com" [ref=e75]
- img [ref=e76]
- generic [ref=e79]:
- generic [ref=e80]: رمز عبور
- generic [ref=e81]:
- textbox [ref=e82]
- img [ref=e83]
- button [ref=e86] [cursor=pointer]:
- img [ref=e87]
- button "ورود به حساب" [ref=e90] [cursor=pointer]
- generic [ref=e91]:
- text: حساب ندارید؟
- link "ثبت‌نام کنید" [ref=e92] [cursor=pointer]:
- /url: /register
- link "بازگشت به سایت" [ref=e94] [cursor=pointer]:
- /url: /
- img [ref=e95]
- text: بازگشت به سایت
- contentinfo [ref=e97]:
- generic [ref=e98]:
- img [ref=e99]
- button "بازگشت به بالا" [ref=e101] [cursor=pointer]:
- img [ref=e102]
- generic [ref=e107]:
- generic [ref=e108]:
- img "اندیشکده فولاد آینده" [ref=e109]
- generic [ref=e112]: اندیشکده فولاد آینده
- generic [ref=e114]: The Steel Futures Institute
- paragraph [ref=e115]: فضایی همدلانه برای اندیشیدن به فردای صنعت؛ جایی که دانش، آینده‌پژوهی و نوآوری به بینش‌هایی برای توسعه صنعتی تبدیل می‌شوند.
- generic [ref=e116]: ما را در شبکه‌های اجتماعی دنبال کنید.
- generic [ref=e117]:
- link "آپارات" [ref=e118] [cursor=pointer]:
- /url: "#"
- img [ref=e119]
- link "X" [ref=e121] [cursor=pointer]:
- /url: "#"
- img [ref=e122]
- link "لینکداین" [ref=e124] [cursor=pointer]:
- /url: "#"
- img [ref=e125]
- link "تلگرام" [ref=e127] [cursor=pointer]:
- /url: "#"
- img [ref=e128]
- navigation "پیوندهای پایین" [ref=e130]:
- link "رادار آینده" [ref=e131] [cursor=pointer]:
- /url: /radar
- text: رادار آینده
- link "آمار و داده" [ref=e133] [cursor=pointer]:
- /url: "#"
- text: آمار و داده
- link "رسانه و رویداد" [ref=e135] [cursor=pointer]:
- /url: "#"
- text: رسانه و رویداد
- link "در یک نگاه" [ref=e137] [cursor=pointer]:
- /url: /at-a-glance
- text: در یک نگاه
- link "شبکه خبرگان" [ref=e139] [cursor=pointer]:
- /url: "#"
- text: شبکه خبرگان
- link "درباره ما" [ref=e141] [cursor=pointer]:
- /url: "#"
- text: درباره ما
- generic [ref=e143]:
- generic [ref=e144]: اطلاعات تماس
- generic [ref=e145]:
- generic [ref=e146]:
- img [ref=e147]
- generic [ref=e150]: "نشانی:"
- generic [ref=e151]: اصفهان، خیابان سعادت‌آباد
- generic [ref=e152]:
- img [ref=e153]
- generic [ref=e155]: "شماره تماس:"
- generic [ref=e156]: ۰۳۱ - ۳۸۸۸۴۵۳۹
- generic [ref=e157]:
- img [ref=e158]
- generic [ref=e161]: "پست الکترونیک:"
- generic [ref=e162]: info@steelforesight.com
- generic [ref=e163]:
- generic [ref=e165]: نشان
- generic [ref=e167]: نشان
- generic [ref=e169]: نشان
- generic [ref=e170]:
- generic [ref=e171]: عضویت در خبرنامه اندیشکده
- generic [ref=e172]:
- textbox "ایمیل خود را وارد کنید..." [ref=e173]
- button "ثبت" [ref=e174] [cursor=pointer]
- generic [ref=e176]:
- generic [ref=e177]: تمامی حقوق مادی و معنوی این وب‌سایت متعلق به شرکت فولاد مبارکه می‌باشد.
- generic [ref=e178]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک

View File

@ -1,119 +0,0 @@
- generic [ref=e3]:
- banner [ref=e4]:
- generic [ref=e6]:
- link "اندیشکده فولاد آینده The Steel Futures Institute" [ref=e7] [cursor=pointer]:
- /url: /
- img [ref=e8]
- generic [ref=e9]:
- generic [ref=e10]: اندیشکده فولاد آینده
- generic [ref=e11]: The Steel Futures Institute
- navigation "ناوبری اصلی" [ref=e12]:
- button "رادار آینده" [ref=e14] [cursor=pointer]:
- text: رادار آینده
- img [ref=e15]
- link "آمار و داده" [ref=e17] [cursor=pointer]:
- /url: /pulse
- link "در یک نگاه" [ref=e18] [cursor=pointer]:
- /url: /at-a-glance
- button "رسانه و رویداد" [ref=e20] [cursor=pointer]:
- text: رسانه و رویداد
- img [ref=e21]
- link "شبکه خبرگان" [ref=e23] [cursor=pointer]:
- /url: /experts
- button "درباره ما" [ref=e25] [cursor=pointer]:
- text: درباره ما
- img [ref=e26]
- generic [ref=e28]:
- button "جستجو" [ref=e29] [cursor=pointer]:
- img [ref=e30]
- link "ورود / ثبت‌نام" [ref=e33] [cursor=pointer]:
- /url: /login
- button "Switch to English" [ref=e34] [cursor=pointer]: 🇺🇸
- main [ref=e35]:
- generic [ref=e37]:
- generic [ref=e38]:
- img [ref=e40]
- heading "ورود به حساب" [level=1] [ref=e43]
- generic [ref=e44]:
- generic [ref=e45]:
- generic [ref=e46]: ایمیل
- textbox "example@email.com" [ref=e47]
- generic [ref=e48]:
- generic [ref=e49]: رمز عبور
- textbox [ref=e50]
- button "ورود" [ref=e51] [cursor=pointer]
- paragraph [ref=e52]:
- text: حساب ندارید؟
- link "ثبت‌نام کنید" [ref=e53] [cursor=pointer]:
- /url: /register
- contentinfo [ref=e54]:
- generic [ref=e55]:
- img [ref=e56]
- button "بازگشت به بالا" [ref=e58] [cursor=pointer]:
- img [ref=e59]
- generic [ref=e64]:
- generic [ref=e65]:
- img "اندیشکده فولاد آینده" [ref=e66]
- generic [ref=e69]: اندیشکده فولاد آینده
- generic [ref=e71]: The Steel Futures Institute
- paragraph [ref=e72]: فضایی همدلانه برای اندیشیدن به فردای صنعت؛ جایی که دانش، آینده‌پژوهی و نوآوری به بینش‌هایی برای توسعه صنعتی تبدیل می‌شوند.
- generic [ref=e73]: ما را در شبکه‌های اجتماعی دنبال کنید.
- generic [ref=e74]:
- link "آپارات" [ref=e75] [cursor=pointer]:
- /url: "#"
- img [ref=e76]
- link "X" [ref=e78] [cursor=pointer]:
- /url: "#"
- img [ref=e79]
- link "لینکداین" [ref=e81] [cursor=pointer]:
- /url: "#"
- img [ref=e82]
- link "تلگرام" [ref=e84] [cursor=pointer]:
- /url: "#"
- img [ref=e85]
- navigation "پیوندهای پایین" [ref=e87]:
- link "رادار آینده" [ref=e88] [cursor=pointer]:
- /url: /radar
- text: رادار آینده
- link "آمار و داده" [ref=e90] [cursor=pointer]:
- /url: "#"
- text: آمار و داده
- link "رسانه و رویداد" [ref=e92] [cursor=pointer]:
- /url: "#"
- text: رسانه و رویداد
- link "در یک نگاه" [ref=e94] [cursor=pointer]:
- /url: /at-a-glance
- text: در یک نگاه
- link "شبکه خبرگان" [ref=e96] [cursor=pointer]:
- /url: "#"
- text: شبکه خبرگان
- link "درباره ما" [ref=e98] [cursor=pointer]:
- /url: "#"
- text: درباره ما
- generic [ref=e100]:
- generic [ref=e101]: اطلاعات تماس
- generic [ref=e102]:
- generic [ref=e103]:
- img [ref=e104]
- generic [ref=e107]: "نشانی:"
- generic [ref=e108]: اصفهان، خیابان سعادت‌آباد
- generic [ref=e109]:
- img [ref=e110]
- generic [ref=e112]: "شماره تماس:"
- generic [ref=e113]: ۰۳۱ - ۳۸۸۸۴۵۳۹
- generic [ref=e114]:
- img [ref=e115]
- generic [ref=e118]: "پست الکترونیک:"
- generic [ref=e119]: info@steelforesight.com
- generic [ref=e120]:
- generic [ref=e122]: نشان
- generic [ref=e124]: نشان
- generic [ref=e126]: نشان
- generic [ref=e127]:
- generic [ref=e128]: عضویت در خبرنامه اندیشکده
- generic [ref=e129]:
- textbox "ایمیل خود را وارد کنید..." [ref=e130]
- button "ثبت" [ref=e131] [cursor=pointer]
- generic [ref=e133]:
- generic [ref=e134]: تمامی حقوق مادی و معنوی این وب‌سایت متعلق به شرکت فولاد مبارکه می‌باشد.
- generic [ref=e135]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک

View File

@ -1,121 +0,0 @@
- generic [ref=e3]:
- banner [ref=e4]:
- generic [ref=e6]:
- link "اندیشکده فولاد آینده The Steel Futures Institute" [ref=e7] [cursor=pointer]:
- /url: /
- img [ref=e8]
- generic [ref=e9]:
- generic [ref=e10]: اندیشکده فولاد آینده
- generic [ref=e11]: The Steel Futures Institute
- navigation "ناوبری اصلی" [ref=e12]:
- button "رادار آینده" [ref=e14] [cursor=pointer]:
- text: رادار آینده
- img [ref=e15]
- link "آمار و داده" [ref=e17] [cursor=pointer]:
- /url: /pulse
- link "در یک نگاه" [ref=e18] [cursor=pointer]:
- /url: /at-a-glance
- button "رسانه و رویداد" [ref=e20] [cursor=pointer]:
- text: رسانه و رویداد
- img [ref=e21]
- link "شبکه خبرگان" [ref=e23] [cursor=pointer]:
- /url: /experts
- button "درباره ما" [ref=e25] [cursor=pointer]:
- text: درباره ما
- img [ref=e26]
- generic [ref=e28]:
- button "جستجو" [ref=e29] [cursor=pointer]:
- img [ref=e30]
- link "علیرضا" [ref=e33] [cursor=pointer]:
- /url: /profile
- img [ref=e34]
- text: علیرضا
- button "Switch to English" [ref=e37] [cursor=pointer]: 🇺🇸
- main [ref=e38]:
- generic [ref=e40]:
- generic [ref=e41]:
- img [ref=e43]
- heading "ورود به حساب" [level=1] [ref=e46]
- generic [ref=e47]:
- generic [ref=e48]:
- generic [ref=e49]: ایمیل
- textbox "example@email.com" [ref=e50]
- generic [ref=e51]:
- generic [ref=e52]: رمز عبور
- textbox [ref=e53]
- button "ورود" [ref=e54] [cursor=pointer]
- paragraph [ref=e55]:
- text: حساب ندارید؟
- link "ثبت‌نام کنید" [ref=e56] [cursor=pointer]:
- /url: /register
- contentinfo [ref=e57]:
- generic [ref=e58]:
- img [ref=e59]
- button "بازگشت به بالا" [ref=e61] [cursor=pointer]:
- img [ref=e62]
- generic [ref=e67]:
- generic [ref=e68]:
- img "اندیشکده فولاد آینده" [ref=e69]
- generic [ref=e72]: اندیشکده فولاد آینده
- generic [ref=e74]: The Steel Futures Institute
- paragraph [ref=e75]: فضایی همدلانه برای اندیشیدن به فردای صنعت؛ جایی که دانش، آینده‌پژوهی و نوآوری به بینش‌هایی برای توسعه صنعتی تبدیل می‌شوند.
- generic [ref=e76]: ما را در شبکه‌های اجتماعی دنبال کنید.
- generic [ref=e77]:
- link "آپارات" [ref=e78] [cursor=pointer]:
- /url: "#"
- img [ref=e79]
- link "X" [ref=e81] [cursor=pointer]:
- /url: "#"
- img [ref=e82]
- link "لینکداین" [ref=e84] [cursor=pointer]:
- /url: "#"
- img [ref=e85]
- link "تلگرام" [ref=e87] [cursor=pointer]:
- /url: "#"
- img [ref=e88]
- navigation "پیوندهای پایین" [ref=e90]:
- link "رادار آینده" [ref=e91] [cursor=pointer]:
- /url: /radar
- text: رادار آینده
- link "آمار و داده" [ref=e93] [cursor=pointer]:
- /url: "#"
- text: آمار و داده
- link "رسانه و رویداد" [ref=e95] [cursor=pointer]:
- /url: "#"
- text: رسانه و رویداد
- link "در یک نگاه" [ref=e97] [cursor=pointer]:
- /url: /at-a-glance
- text: در یک نگاه
- link "شبکه خبرگان" [ref=e99] [cursor=pointer]:
- /url: "#"
- text: شبکه خبرگان
- link "درباره ما" [ref=e101] [cursor=pointer]:
- /url: "#"
- text: درباره ما
- generic [ref=e103]:
- generic [ref=e104]: اطلاعات تماس
- generic [ref=e105]:
- generic [ref=e106]:
- img [ref=e107]
- generic [ref=e110]: "نشانی:"
- generic [ref=e111]: اصفهان، خیابان سعادت‌آباد
- generic [ref=e112]:
- img [ref=e113]
- generic [ref=e115]: "شماره تماس:"
- generic [ref=e116]: ۰۳۱ - ۳۸۸۸۴۵۳۹
- generic [ref=e117]:
- img [ref=e118]
- generic [ref=e121]: "پست الکترونیک:"
- generic [ref=e122]: info@steelforesight.com
- generic [ref=e123]:
- generic [ref=e125]: نشان
- generic [ref=e127]: نشان
- generic [ref=e129]: نشان
- generic [ref=e130]:
- generic [ref=e131]: عضویت در خبرنامه اندیشکده
- generic [ref=e132]:
- textbox "ایمیل خود را وارد کنید..." [ref=e133]
- button "ثبت" [ref=e134] [cursor=pointer]
- generic [ref=e136]:
- generic [ref=e137]: تمامی حقوق مادی و معنوی این وب‌سایت متعلق به شرکت فولاد مبارکه می‌باشد.
- generic [ref=e138]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک

View File

@ -1,119 +0,0 @@
- generic [ref=e3]:
- banner [ref=e4]:
- generic [ref=e6]:
- link "اندیشکده فولاد آینده The Steel Futures Institute" [ref=e7] [cursor=pointer]:
- /url: /
- img [ref=e8]
- generic [ref=e9]:
- generic [ref=e10]: اندیشکده فولاد آینده
- generic [ref=e11]: The Steel Futures Institute
- navigation "ناوبری اصلی" [ref=e12]:
- button "رادار آینده" [ref=e14] [cursor=pointer]:
- text: رادار آینده
- img [ref=e15]
- link "آمار و داده" [ref=e17] [cursor=pointer]:
- /url: /pulse
- link "در یک نگاه" [ref=e18] [cursor=pointer]:
- /url: /at-a-glance
- button "رسانه و رویداد" [ref=e20] [cursor=pointer]:
- text: رسانه و رویداد
- img [ref=e21]
- link "شبکه خبرگان" [ref=e23] [cursor=pointer]:
- /url: /experts
- button "درباره ما" [ref=e25] [cursor=pointer]:
- text: درباره ما
- img [ref=e26]
- generic [ref=e28]:
- button "جستجو" [ref=e29] [cursor=pointer]:
- img [ref=e30]
- link "ورود / ثبت‌نام" [ref=e33] [cursor=pointer]:
- /url: /login
- button "Switch to English" [ref=e34] [cursor=pointer]: 🇺🇸
- main [ref=e35]:
- generic [ref=e37]:
- generic [ref=e38]:
- img [ref=e40]
- heading "ورود به حساب" [level=1] [ref=e43]
- generic [ref=e44]:
- generic [ref=e45]:
- generic [ref=e46]: ایمیل
- textbox "example@email.com" [ref=e47]
- generic [ref=e48]:
- generic [ref=e49]: رمز عبور
- textbox [ref=e50]
- button "ورود" [ref=e51] [cursor=pointer]
- paragraph [ref=e52]:
- text: حساب ندارید؟
- link "ثبت‌نام کنید" [ref=e53] [cursor=pointer]:
- /url: /register
- contentinfo [ref=e54]:
- generic [ref=e55]:
- img [ref=e56]
- button "بازگشت به بالا" [ref=e58] [cursor=pointer]:
- img [ref=e59]
- generic [ref=e64]:
- generic [ref=e65]:
- img "اندیشکده فولاد آینده" [ref=e66]
- generic [ref=e69]: اندیشکده فولاد آینده
- generic [ref=e71]: The Steel Futures Institute
- paragraph [ref=e72]: فضایی همدلانه برای اندیشیدن به فردای صنعت؛ جایی که دانش، آینده‌پژوهی و نوآوری به بینش‌هایی برای توسعه صنعتی تبدیل می‌شوند.
- generic [ref=e73]: ما را در شبکه‌های اجتماعی دنبال کنید.
- generic [ref=e74]:
- link "آپارات" [ref=e75] [cursor=pointer]:
- /url: "#"
- img [ref=e76]
- link "X" [ref=e78] [cursor=pointer]:
- /url: "#"
- img [ref=e79]
- link "لینکداین" [ref=e81] [cursor=pointer]:
- /url: "#"
- img [ref=e82]
- link "تلگرام" [ref=e84] [cursor=pointer]:
- /url: "#"
- img [ref=e85]
- navigation "پیوندهای پایین" [ref=e87]:
- link "رادار آینده" [ref=e88] [cursor=pointer]:
- /url: /radar
- text: رادار آینده
- link "آمار و داده" [ref=e90] [cursor=pointer]:
- /url: "#"
- text: آمار و داده
- link "رسانه و رویداد" [ref=e92] [cursor=pointer]:
- /url: "#"
- text: رسانه و رویداد
- link "در یک نگاه" [ref=e94] [cursor=pointer]:
- /url: /at-a-glance
- text: در یک نگاه
- link "شبکه خبرگان" [ref=e96] [cursor=pointer]:
- /url: "#"
- text: شبکه خبرگان
- link "درباره ما" [ref=e98] [cursor=pointer]:
- /url: "#"
- text: درباره ما
- generic [ref=e100]:
- generic [ref=e101]: اطلاعات تماس
- generic [ref=e102]:
- generic [ref=e103]:
- img [ref=e104]
- generic [ref=e107]: "نشانی:"
- generic [ref=e108]: اصفهان، خیابان سعادت‌آباد
- generic [ref=e109]:
- img [ref=e110]
- generic [ref=e112]: "شماره تماس:"
- generic [ref=e113]: ۰۳۱ - ۳۸۸۸۴۵۳۹
- generic [ref=e114]:
- img [ref=e115]
- generic [ref=e118]: "پست الکترونیک:"
- generic [ref=e119]: info@steelforesight.com
- generic [ref=e120]:
- generic [ref=e122]: نشان
- generic [ref=e124]: نشان
- generic [ref=e126]: نشان
- generic [ref=e127]:
- generic [ref=e128]: عضویت در خبرنامه اندیشکده
- generic [ref=e129]:
- textbox "ایمیل خود را وارد کنید..." [ref=e130]
- button "ثبت" [ref=e131] [cursor=pointer]
- generic [ref=e133]:
- generic [ref=e134]: تمامی حقوق مادی و معنوی این وب‌سایت متعلق به شرکت فولاد مبارکه می‌باشد.
- generic [ref=e135]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک

View File

@ -1,174 +0,0 @@
- generic [ref=e3]:
- banner [ref=e4]:
- generic [ref=e6]:
- link "اندیشکده فولاد آینده The Steel Futures Institute" [ref=e7] [cursor=pointer]:
- /url: /
- img [ref=e8]
- generic [ref=e9]:
- generic [ref=e10]: اندیشکده فولاد آینده
- generic [ref=e11]: The Steel Futures Institute
- navigation "ناوبری اصلی" [ref=e12]:
- button "رادار آینده" [ref=e14] [cursor=pointer]:
- text: رادار آینده
- img [ref=e15]
- link "آمار و داده" [ref=e17] [cursor=pointer]:
- /url: /pulse
- link "در یک نگاه" [ref=e18] [cursor=pointer]:
- /url: /at-a-glance
- button "رسانه و رویداد" [ref=e20] [cursor=pointer]:
- text: رسانه و رویداد
- img [ref=e21]
- link "شبکه خبرگان" [ref=e23] [cursor=pointer]:
- /url: /experts
- button "درباره ما" [ref=e25] [cursor=pointer]:
- text: درباره ما
- img [ref=e26]
- generic [ref=e28]:
- button "جستجو" [ref=e29] [cursor=pointer]:
- img [ref=e30]
- link "ورود / ثبت‌نام" [ref=e33] [cursor=pointer]:
- /url: /login
- button "Switch to English" [ref=e34] [cursor=pointer]: 🇺🇸
- main [ref=e35]:
- generic [ref=e36]:
- generic [ref=e37]:
- img [ref=e38]
- img
- link "اندیشکده فولاد آینده The Steel Futures Institute" [ref=e41] [cursor=pointer]:
- /url: /
- generic [ref=e42]:
- generic [ref=e43]: اندیشکده فولاد آینده
- generic [ref=e44]: The Steel Futures Institute
- generic [ref=e45]:
- generic [ref=e46]: عضویت رایگان
- heading "عضو اندیشکده شوید و به تحلیل‌ها دسترسی داشته باشید" [level=1] [ref=e47]:
- text: عضو اندیشکده شوید
- text: و به تحلیل‌ها دسترسی داشته باشید
- paragraph [ref=e48]: گزارش‌های بازار، رصد فناوری، و تحلیل ژئوپلیتیک — همه در یک پلتفرم
- generic [ref=e50]:
- generic [ref=e51]:
- img [ref=e53]
- text: دسترسی به گزارش‌های تخصصی
- generic [ref=e55]:
- img [ref=e57]
- text: رصد بازار و قیمت‌ها
- generic [ref=e59]:
- img [ref=e61]
- text: شبکه خبرگان صنعت
- generic [ref=e64]:
- generic [ref=e65]:
- button "ورود" [ref=e66] [cursor=pointer]
- button "ثبت‌نام" [ref=e67] [cursor=pointer]
- generic [ref=e68]:
- generic [ref=e69]:
- heading "ثبت‌نام" [level=2] [ref=e70]
- paragraph [ref=e71]: یک حساب کاربری جدید بسازید
- generic [ref=e72]:
- generic [ref=e73]: نام و نام خانوادگی
- generic [ref=e74]:
- textbox "علی محمدی" [ref=e75]
- generic:
- img
- generic [ref=e76]:
- generic [ref=e77]: ایمیل
- generic [ref=e78]:
- textbox "example@email.com" [ref=e79]
- generic:
- img
- generic [ref=e80]:
- generic [ref=e81]: شماره تماس*
- generic [ref=e82]:
- textbox "09123456789" [ref=e83]
- generic:
- img
- generic [ref=e84]:
- generic [ref=e85]: نام سازمان / شرکت (اختیاری)
- generic [ref=e86]:
- textbox "شرکت فولاد..." [ref=e87]
- generic:
- img
- generic [ref=e88]:
- generic [ref=e89]: رمز عبور
- generic [ref=e90]:
- textbox [ref=e91]
- generic:
- img
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- paragraph [ref=e97]: حداقل ۶ کاراکتر
- button "ساخت حساب کاربری" [ref=e98] [cursor=pointer]
- link "بازگشت به سایت" [ref=e100] [cursor=pointer]:
- /url: /
- img [ref=e101]
- text: بازگشت به سایت
- contentinfo [ref=e103]:
- generic [ref=e104]:
- img [ref=e105]
- button "بازگشت به بالا" [ref=e107] [cursor=pointer]:
- img [ref=e108]
- generic [ref=e113]:
- generic [ref=e114]:
- img "اندیشکده فولاد آینده" [ref=e115]
- generic [ref=e118]: اندیشکده فولاد آینده
- generic [ref=e120]: The Steel Futures Institute
- paragraph [ref=e121]: فضایی همدلانه برای اندیشیدن به فردای صنعت؛ جایی که دانش، آینده‌پژوهی و نوآوری به بینش‌هایی برای توسعه صنعتی تبدیل می‌شوند.
- generic [ref=e122]: ما را در شبکه‌های اجتماعی دنبال کنید.
- generic [ref=e123]:
- link "آپارات" [ref=e124] [cursor=pointer]:
- /url: "#"
- img [ref=e125]
- link "X" [ref=e127] [cursor=pointer]:
- /url: "#"
- img [ref=e128]
- link "لینکداین" [ref=e130] [cursor=pointer]:
- /url: "#"
- img [ref=e131]
- link "تلگرام" [ref=e133] [cursor=pointer]:
- /url: "#"
- img [ref=e134]
- navigation "پیوندهای پایین" [ref=e136]:
- link "رادار آینده" [ref=e137] [cursor=pointer]:
- /url: /radar
- text: رادار آینده
- link "آمار و داده" [ref=e139] [cursor=pointer]:
- /url: "#"
- text: آمار و داده
- link "رسانه و رویداد" [ref=e141] [cursor=pointer]:
- /url: "#"
- text: رسانه و رویداد
- link "در یک نگاه" [ref=e143] [cursor=pointer]:
- /url: /at-a-glance
- text: در یک نگاه
- link "شبکه خبرگان" [ref=e145] [cursor=pointer]:
- /url: "#"
- text: شبکه خبرگان
- link "درباره ما" [ref=e147] [cursor=pointer]:
- /url: "#"
- text: درباره ما
- generic [ref=e149]:
- generic [ref=e150]: اطلاعات تماس
- generic [ref=e151]:
- generic [ref=e152]:
- img [ref=e153]
- generic [ref=e156]: "نشانی:"
- generic [ref=e157]: اصفهان، خیابان سعادت‌آباد
- generic [ref=e158]:
- img [ref=e159]
- generic [ref=e161]: "شماره تماس:"
- generic [ref=e162]: ۰۳۱ - ۳۸۸۸۴۵۳۹
- generic [ref=e163]:
- img [ref=e164]
- generic [ref=e167]: "پست الکترونیک:"
- generic [ref=e168]: info@steelforesight.com
- generic [ref=e169]:
- generic [ref=e171]: نشان
- generic [ref=e173]: نشان
- generic [ref=e175]: نشان
- generic [ref=e176]:
- generic [ref=e177]: عضویت در خبرنامه اندیشکده
- generic [ref=e178]:
- textbox "ایمیل خود را وارد کنید..." [ref=e179]
- button "ثبت" [ref=e180] [cursor=pointer]
- generic [ref=e182]:
- generic [ref=e183]: تمامی حقوق مادی و معنوی این وب‌سایت متعلق به شرکت فولاد مبارکه می‌باشد.
- generic [ref=e184]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

View File

@ -1,135 +0,0 @@
- generic [ref=e3]:
- banner [ref=e4]:
- generic [ref=e6]:
- link "اندیشکده فولاد آینده" [ref=e7] [cursor=pointer]:
- /url: /
- img [ref=e8]
- generic [ref=e10]: اندیشکده فولاد آینده
- generic [ref=e11]:
- button "جستجو" [ref=e12] [cursor=pointer]:
- img [ref=e13]
- button "باز کردن منو" [ref=e16] [cursor=pointer]:
- img [ref=e17]
- main:
- generic [ref=e19]:
- generic [ref=e20]:
- link "اندیشکده فولاد آینده" [ref=e21] [cursor=pointer]:
- /url: /
- generic [ref=e22]: اندیشکده فولاد آینده
- link "بازگشت" [ref=e23] [cursor=pointer]:
- /url: /
- img [ref=e24]
- text: بازگشت
- generic [ref=e26]:
- generic [ref=e27]:
- button "ورود" [ref=e28] [cursor=pointer]
- button "ثبت‌نام" [ref=e29] [cursor=pointer]
- generic [ref=e30]:
- generic [ref=e31]:
- heading "ثبت‌نام" [level=2] [ref=e32]
- paragraph [ref=e33]: یک حساب کاربری جدید بسازید
- generic [ref=e34]:
- generic [ref=e35]: نام و نام خانوادگی
- generic [ref=e36]:
- textbox "علی محمدی" [ref=e37]
- generic:
- img
- generic [ref=e38]:
- generic [ref=e39]: ایمیل
- generic [ref=e40]:
- textbox "example@email.com" [ref=e41]
- generic:
- img
- generic [ref=e42]:
- generic [ref=e43]: شماره تماس*
- generic [ref=e44]:
- textbox "09123456789" [ref=e45]
- generic:
- img
- generic [ref=e46]:
- generic [ref=e47]: نام سازمان / شرکت (اختیاری)
- generic [ref=e48]:
- textbox "شرکت فولاد..." [ref=e49]
- generic:
- img
- generic [ref=e50]:
- generic [ref=e51]: رمز عبور
- generic [ref=e52]:
- textbox [ref=e53]
- generic:
- img
- button [ref=e55] [cursor=pointer]:
- img [ref=e56]
- paragraph [ref=e59]: حداقل ۶ کاراکتر
- button "ساخت حساب کاربری" [ref=e60] [cursor=pointer]
- contentinfo [ref=e61]:
- generic [ref=e62]:
- img [ref=e63]
- button "بازگشت به بالا" [ref=e65] [cursor=pointer]:
- img [ref=e66]
- generic [ref=e71]:
- generic [ref=e72]:
- img "اندیشکده فولاد آینده" [ref=e73]
- generic [ref=e76]: اندیشکده فولاد آینده
- generic [ref=e78]: The Steel Futures Institute
- paragraph [ref=e79]: فضایی همدلانه برای اندیشیدن به فردای صنعت؛ جایی که دانش، آینده‌پژوهی و نوآوری به بینش‌هایی برای توسعه صنعتی تبدیل می‌شوند.
- generic [ref=e80]: ما را در شبکه‌های اجتماعی دنبال کنید.
- generic [ref=e81]:
- link "آپارات" [ref=e82] [cursor=pointer]:
- /url: "#"
- img [ref=e83]
- link "X" [ref=e85] [cursor=pointer]:
- /url: "#"
- img [ref=e86]
- link "لینکداین" [ref=e88] [cursor=pointer]:
- /url: "#"
- img [ref=e89]
- link "تلگرام" [ref=e91] [cursor=pointer]:
- /url: "#"
- img [ref=e92]
- navigation "پیوندهای پایین" [ref=e94]:
- link "رادار آینده" [ref=e95] [cursor=pointer]:
- /url: /radar
- text: رادار آینده
- link "آمار و داده" [ref=e97] [cursor=pointer]:
- /url: "#"
- text: آمار و داده
- link "رسانه و رویداد" [ref=e99] [cursor=pointer]:
- /url: "#"
- text: رسانه و رویداد
- link "در یک نگاه" [ref=e101] [cursor=pointer]:
- /url: /at-a-glance
- text: در یک نگاه
- link "شبکه خبرگان" [ref=e103] [cursor=pointer]:
- /url: "#"
- text: شبکه خبرگان
- link "درباره ما" [ref=e105] [cursor=pointer]:
- /url: "#"
- text: درباره ما
- generic [ref=e107]:
- generic [ref=e108]: اطلاعات تماس
- generic [ref=e109]:
- generic [ref=e110]:
- img [ref=e111]
- generic [ref=e114]: "نشانی:"
- generic [ref=e115]: اصفهان، خیابان سعادت‌آباد
- generic [ref=e116]:
- img [ref=e117]
- generic [ref=e119]: "شماره تماس:"
- generic [ref=e120]: ۰۳۱ - ۳۸۸۸۴۵۳۹
- generic [ref=e121]:
- img [ref=e122]
- generic [ref=e125]: "پست الکترونیک:"
- generic [ref=e126]: info@steelforesight.com
- generic [ref=e127]:
- generic [ref=e129]: نشان
- generic [ref=e131]: نشان
- generic [ref=e133]: نشان
- generic [ref=e134]:
- generic [ref=e135]: عضویت در خبرنامه اندیشکده
- generic [ref=e136]:
- textbox "ایمیل خود را وارد کنید..." [ref=e137]
- button "ثبت" [ref=e138] [cursor=pointer]
- generic [ref=e140]:
- generic [ref=e141]: تمامی حقوق مادی و معنوی این وب‌سایت متعلق به شرکت فولاد مبارکه می‌باشد.
- generic [ref=e142]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@ -1,10 +0,0 @@
- generic [ref=e4]:
- img [ref=e5]:
- generic:
- generic: "403"
- generic [ref=e11]: شما به این محتوا دسترسی ندارید!
- link [ref=e12] [cursor=pointer]:
- /url: https://www.aspaco.org
- img [ref=e13]
- link "تماس با پشتیبان" [ref=e15] [cursor=pointer]:
- /url: https://www.aspaco.org/?_=/fa#contact

View File

@ -1,14 +0,0 @@
- iframe [ref=e2]:
- generic [active] [ref=f3e1]:
- generic [ref=f3e5]: "502"
- generic [ref=f3e6]:
- list [ref=f3e7]:
- listitem [ref=f3e8]: شاید پورتی که برنامه‌ی شما به آن listen می‌کند را اشتباه وارد کرده‌اید.
- listitem [ref=f3e9]: لازم است که برنامه‌ی شما حتما روی آدرس 0.0.0.0 اجرا شود.
- listitem [ref=f3e10]: از صفحه‌ی برنامک‌ها، وضعیت آخرین استقرار خود را بررسی کنید.
- listitem [ref=f3e11]: و یا ممکن است هیچ مشکلی وجود نداشته باشد و صرفا باید کمی صبر کنید تا سرویس‌تان آماده شود.
- link "مشاهده‌ی لاگ‌ها" [ref=f3e13] [cursor=pointer]:
- /url: https://console.liara.ir/apps
- contentinfo [ref=f3e14]:
- link [ref=f3e15] [cursor=pointer]:
- /url: https://liara.ir

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 KiB

View File

@ -11,7 +11,11 @@ WORKDIR /app
# --legacy-peer-deps avoids ERESOLVE failures on bleeding-edge peer ranges # --legacy-peer-deps avoids ERESOLVE failures on bleeding-edge peer ranges
# (React 19 / Vite 8 / TS 6 / ESLint 10). # (React 19 / Vite 8 / TS 6 / ESLint 10).
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN npm ci --no-audit --no-fund --legacy-peer-deps # The Liara build env has flaky access to registry.npmjs.org (EAI_AGAIN / dropped
# sockets → npm's "Exit handler never called!"). Use Liara's npm mirror, which is
# reliable from inside Iran, and lower --maxsockets to reduce the peak load.
RUN npm config set registry https://package-mirror.liara.ir/repository/npm/ \
&& npm ci --no-audit --no-fund --legacy-peer-deps --maxsockets 2
# Build the static bundle. # Build the static bundle.
# VITE_PANEL_API is inlined into the bundle at build time (Vite reads it during # VITE_PANEL_API is inlined into the bundle at build time (Vite reads it during

249
README_FA.md Normal file
View File

@ -0,0 +1,249 @@
# راهنمای جامع پروژه اندیشکده فولاد آینده (Andishkade Foolad)
این سند راهنمای فنی و معماری پروژه **اندیشکده فولاد آینده** است که شامل جزئیات ساختار فرانت‌اند، پنل مدیریت بک‌اند، پایگاه داده، پشته فناوری‌ها و نحوه راه‌اندازی و توسعه پروژه می‌باشد.
---
## فهرست مطالب
- [۱. ساختار کلی پروژه](#۱-ساختار-کلی-پروژه)
- [۲. فرانت‌اند (Frontend)](#۲-فرانت‌اند-frontend)
- [پشته فناوری فرانت‌اند](#پشته-فناوری-فرانت‌اند)
- [ویژگی‌ها و قابلیت‌های بصری فرانت‌اند](#ویژگی‌ها-و-قابلیت‌های-بصری-فرانت‌اند)
- [دستورات بخش فرانت‌اند](#دستورات-بخش-فرانت‌اند)
- [۳. پنل مدیریت (Backend / Admin Panel)](#۳-پنل-مدیریت-backend--admin-panel)
- [پشته فناوری بک‌اند](#پشته-فناوری-بک‌اند)
- [ساختار و معماری پایگاه داده](#ساختار-و-معماری-پایگاه-داده)
- [سرویس‌های ویژه پنل مدیریت](#سرویس‌های-ویژه-پنل-مدیریت)
- [دستورات بخش پنل مدیریت](#دستورات-بخش-پنل-مدیریت)
- [لیست APIهای پنل مدیریت](#لیست-apiهای-پنل-مدیریت)
- [۴. نحوه اتصال فرانت‌اند به پنل مدیریت (Wiring)](#۴-نحوه-اتصال-فرانت‌اند-به-پنل-مدیریت-wiring)
- [۵. استقرار و دگرگونی‌ها (Deployment)](#۵-استقرار-و-دگرگونی‌ها-deployment)
---
## ۱. ساختار کلی پروژه
پروژه به صورت یک ساختار تک‌مخزنی (Monorepo) طراحی شده است که شامل دو اپلیکیشن مستقل و بدون وابستگی اشتراکی در زمان ساخت (Build) است:
1. **فرانت‌اند (Frontend - ریشه پروژه)**: یک وب‌سایت بازاریابی و محتوایی تعاملی، مدرن و با اولویت زبان فارسی (Persian-first) و دو زبانه (فارسی / انگلیسی) که با React 19 و Vite توسعه یافته است.
2. **پنل مدیریت (Admin Panel - پوشه `panel/`)**: یک وب‌اپلیکیشن مستقل SPA با معماری Node.js/Express و پایگاه داده PostgreSQL که محتوای پویا وب‌سایت را مدیریت می‌کند.
```
andishkade-foolad/
├── panel/ # پروژه بک‌اند و پنل مدیریت
│ ├── public/ # فایل‌های استاتیک و رابط کاربری پنل (Vanilla JS)
│ ├── uploads/ # فایل‌های آپلود شده محلی (در صورت عدم استفاده از S3)
│ ├── server.js # سرور اصلی Express
│ ├── db.js # پیکربندی پایگاه داده PostgreSQL و کوئری‌ها
│ └── package.json # وابستگی‌های بک‌اند
├── src/ # کدهای منبع فرانت‌اند (React 19)
│ ├── app/ # مسیریابی و قالب اصلی (Layout)
│ ├── components/ # کامپوننت‌های اشتراکی و UI
│ ├── context/ # کانتکست‌های ری‌اکت (مانند زبان سیستم)
│ ├── data/ # داده‌های اولیه و ماک (Mock Data)
│ └── pages/ # صفحات مختلف وب‌سایت
├── package.json # وابستگی‌های فرانت‌اند
├── Dockerfile # داکرفایل اجرای فرانت‌اند به عنوان سرور استاتیک Nginx
└── docker-compose.yml # تنظیمات داکر کامپوز برای اجرای لوکال فرانت‌اند
```
---
## ۲. فرانت‌اند (Frontend)
بخش کاربری وب‌سایت اندیشکده فولاد آینده یک پورتال محتوایی بسیار پیشرفته و پویا با انیمیشن‌های غنی است.
### پشته فناوری فرانت‌اند
* **هسته اصلی:** React 19 همراه با TypeScript برای ایمنی کدها و مدل‌سازی داده‌ها.
* **سیستم بیلد:** Vite جهت بارگذاری سریع در زمان توسعه (HMR) و خروجی بهینه در زمان پروداکشن.
* **استایل‌دهی:** Tailwind CSS v4 (با پیکربندی جدید مبتنی بر پلاگین Vite) به همراه متغیرهای سفارشی CSS در `src/index.css` برای رنگ‌بندی سازمانی طلایی (`#CD9E53`) و سورمه‌ای (`#032340`).
* **مسیریابی:** React Router DOM v7 با استفاده از ساختار شیءگرا (`createBrowserRouter`).
* **انیمیشن‌ها و اسکرول:**
* **GSAP (GreenSock) & ScrollTrigger:** برای پیاده‌سازی انیمیشن‌های پیچیده مبتنی بر اسکرول (مانند بخش Hero تعاملی).
* **Lenis Smooth Scroll:** موتور اسکرول بسیار نرم هماهنگ شده با تیکر GSAP (مخصوص دسکتاپ).
* **Framer Motion:** برای انیمیشن‌های خرد (Micro-animations)، پاپ‌آپ‌ها و ترنزیشن کامپوننت‌ها.
* **کامپوننت‌های سه‌بعدی:**
* **Three.js & React Three Fiber (R3F):** برای رندر کردن اشیاء سه‌بعدی در وب.
* **Three Globe & Cobe:** جهت نمایش کره زمین سه‌بعدی تعاملی و زیبا در بخش ژئوپلیتیک و ارتباطات بین‌الملل.
* **نمودارها:** React ApexCharts جهت نمایش نوسانات بازار فولاد و قیمت‌ها.
* **مدیریت وضعیت:** Zustand جهت مدیریت وضعیت‌های سراسری وب‌سایت.
### ویژگی‌ها و قابلیت‌های بصری فرانت‌اند
1. **بومی‌سازی و پشتیبانی دو زبانه (Localization):**
* پیاده‌سازی شده با استفاده از `useLang()` در `src/context/LangContext`.
* تغییر خودکار چیدمان صفحه به RTL برای زبان فارسی و LTR برای زبان انگلیسی با هدایت ویژگی `dir`.
* ترجمه درون‌برنامه‌ای کامپوننت‌ها با ساختار دیکشنری‌های محلی درون هر ماژول.
2. **تقویم رویدادهای جلالی بومی (`EventCalendar.tsx`):**
* طراحی شده به صورت کاملاً اختصاصی و بدون استفاده از کتابخانه‌های سنگین خارجی.
* استفاده از موتور بومی مرورگر با متد `Intl.DateTimeFormat('en-US-u-ca-persian')` برای ساخت گرید روزها و ماه‌های هجری شمسی.
3. **مدیریت عملکرد و اسکرول:**
* غیرفعال‌سازی Lenis در مرورگرهای موبایل و تبلت جهت جلوگیری از لگ یا تاخیر لمسی و سپردن اسکرول به سیستم‌عامل بومی دستگاه.
* بهینه‌سازی تصاویر با فرمت مدرن WebP.
### دستورات بخش فرانت‌اند
این دستورات را در پوشه ریشه (Root) پروژه اجرا کنید:
```bash
# نصب پکیج‌ها
npm install
# اجرای سرور توسعه محلی ری‌اکت
npm run dev
# بررسی خطاهای تایپ‌اسکریپت (سریع‌ترین ابزار اعتبارسنجی لوپ توسعه)
npx tsc -b --noEmit
# بررسی و رفع کدهای غیراستاندارد با ESLint
npm run lint
# ایجاد پکیج خروجی نهایی (Production Build)
npm run build
# پیش‌نمایش نسخه نهایی بیلد شده روی سیستم محلی
npm run preview
```
---
## ۳. پنل مدیریت (Backend / Admin Panel)
پنل مدیریت در پوشه `panel/` قرار دارد و وظیفه ذخیره‌سازی داده‌های ساختاریافته وب‌سایت و مدیریت کاربران ادمین را بر عهده دارد.
### پشته فناوری بک‌اند
* **فریم‌ورک سرور:** Express.js 4 مبتنی بر Node.js (نسخه ۲۰ به بالا).
* **پایگاه داده:** PostgreSQL (سیستم میزبانی ابری لیارا) به همراه ماژول `pg`. برای تسهیل مهاجرت از SQLite قبلی، یک نمای هماهنگ شبیه به متدهای `better-sqlite3` در فایل `db.js` ایجاد شده است.
* **امنیت و احراز هویت:**
* `bcryptjs` برای رمزنگاری امن گذرواژه‌ها.
* `jsonwebtoken` (JWT) برای احراز هویت کاربران ادمین و اعضا. کوکی‌های امن با ویژگی‌های `HttpOnly` و `Secure/SameSite` به مرورگر فرستاده می‌شوند تا از حملات XSS جلوگیری شود.
* `helmet` جهت اضافه کردن هدرهای امنیتی HTTP.
* `express-rate-limit` برای محدود کردن تعداد درخواست‌ها به اندپوینت‌های حساس نظیر فرم ورود و فرم تماس با ما.
* **پردازش تصاویر و فایل‌ها:**
* `multer` برای مدیریت آپلود فایل‌های چندرسانه‌ای به صورت بافر حافظه.
* `sharp` جهت پردازش خودکار تصاویر آپلود شده (فشرده‌سازی داینامیک، چرخش صحیح بر اساس EXIF، ریسایز تا سقف ۲۰۰۰ پیکسل و خروجی با فرمت فوق‌العاده بهینه WebP).
* `pdf-parse` برای خواندن و استخراج متون داخل اسناد PDF گزارش‌ها جهت تحلیل توسط هوش مصنوعی.
* **ذخیره‌سازی فایل (Storage):**
* پشتیبانی از پروتکل **S3-compatible Object Storage** (مانند فضای ذخیره‌سازی ابری لیارا). در صورت تنظیم متغیرهای محیطی S3، فایل‌ها مستقیماً آپلود شده و آدرس مستقیم CDN دریافت می‌شود. در غیر این صورت، پروژه به صورت خودکار به حالت آپلود روی دیسک محلی (`panel/uploads/`) تغییر مسیر می‌دهد.
* **یکپارچه‌سازی هوش مصنوعی (AI Integration):**
* استفاده از SDKهای رسمی **OpenAI** و **Anthropic (Claude)** برای ایجاد خلاصه‌ها، استخراج داده‌های کلیدی از اسناد آپلود شده و کمک به آماده‌سازی مطالب برای ادمین.
### ساختار و معماری پایگاه داده
پایگاه داده در زمان استارت آپ اپلیکیشن به صورت خودکار با جداول زیر مقداردهی اولیه (ایجاد ایندکس‌ها و جداول در صورت عدم وجود) می‌شود:
1. **`users`**: ذخیره ادمین‌ها با نقش‌های `admin` یا `owner` (صاحب پنل که دسترسی ویرایش سایر ادمین‌ها را دارد).
2. **`articles`**: مقالات و گزارش‌های تحلیلی. شامل فیلدهای عنوان، دسته‌بندی، نویسنده، مشخصات صفحات، قیمت، تگ‌ها و متن اصلی.
3. **`risk_signals`**: سیگنال‌های ریسک ژئوپلیتیک و صنعتی با سطوح بحرانی متفاوت (`critical`, `high`, `medium`, `low`, `opportunity`).
4. **`events`**: رویدادهای تقویم اندیشکده (سمینارها، همایش‌ها، نمایشگاه‌ها).
5. **`team_members`**: اعضای تیم پژوهشی، تخصص‌ها، راه‌های ارتباطی و شمارش گزارش‌های منتشر شده.
6. **`radar_items`**: گزارش‌های رادار بازار در ۵ دسته‌بندی اصلی: بازار (Market)، فناوری (Tech)، کالا (Commodity)، ژئوپلیتیک (Geo) و انرژی (Energy).
7. **`prices`**: قیمت‌های ثبت شده و شاخص‌های مرتبط.
8. **`members` & `purchases`**: مدیریت خرید اعضا و دسترسی به فایل‌های گزارشات غیر رایگان.
### سرویس‌های ویژه پنل مدیریت
* **واردکننده خودکار داده (Import API):**
امکان وارد کردن فایل‌های JSON تولید شده توسط هوش مصنوعی Gemini که شامل خلاصه‌سازی‌ها، تگ‌ها و آیتم‌های رادار است با قابلیت پاک‌سازی خودکار کاراکترهای اضافی ارجاع دهی هوش مصنوعی (`[cite: ...]`).
* **مبدل خودکار تصاویر به WebP:**
تبدیل و بهینه‌سازی حجم تصاویر آپلود شده به صورت بی‌درنگ جهت بهبود سرعت لود کلاینت.
### دستورات بخش پنل مدیریت
ابتدا به پوشه پنل بروید: `cd panel`
```bash
# نصب وابستگی‌های بک‌اند
npm install
# ساخت حساب کاربری ادمین اولیه با استفاده از مقادیر .env
npm run seed
# اجرای سرور در حالت پروداکشن
npm start
# اجرای سرور در حالت توسعه با قابلیت راه‌اندازی مجدد خودکار هنگام تغییر کدها (Watch mode)
npm run dev
# بازسازی ماژول پایگاه داده در صورت تغییر نسخه Node.js سیستم
npm rebuild better-sqlite3
```
### لیست APIهای پنل مدیریت
تمامی متدهای ویرایشی (POST/PUT/DELETE) نیازمند ارسال توکن JWT در کوکی مرورگر یا هدر درخواست (`Authorization: Bearer <token>`) هستند.
| متد | مسیر | نیاز به احراز هویت | توضیحات |
| :--- | :--- | :--- | :--- |
| **POST** | `/api/auth/login` | خیر | ورود ادمین و ثبت کوکی نشست امن |
| **POST** | `/api/auth/logout` | خیر | خروج از حساب کاربری و پاک کردن کوکی |
| **GET** | `/api/auth/me` | بله | دریافت مشخصات کاربر جاری |
| **GET** | `/api/articles` | خیر | لیست مقالات و گزارش‌ها (دارای فیلتر دسته‌بندی و نوع) |
| **POST** | `/api/articles` | بله (ادمین) | ایجاد مقاله جدید |
| **PUT** | `/api/articles/:id` | بله (ادمین) | ویرایش مقاله موجود |
| **DELETE** | `/api/articles/:id` | بله (ادمین) | حذف مقاله |
| **POST** | `/api/uploads` | bله (ادمین) | آپلود تصویر و تبدیل خودکار به WebP بهینه |
| **POST** | `/api/uploads/raw` | بله (ادمین) | آپلود ویدیو و اسناد PDF به فضای ابری S3 |
| **GET** | `/api/events` | خیر | لیست کل رویدادهای تقویم |
| **GET** | `/api/radar` | خیر | لیست تحلیل‌های رادار بازار |
| **POST** | `/api/admin/import` | بله (ادمین) | ایمپورت مستقیم داده‌های تحلیلی هوش مصنوعی |
| **GET** | `/api/health` | خیر | بررسی سلامت سرور |
---
## ۴. نحوه اتصال فرانت‌اند به پنل مدیریت (Wiring)
در حال حاضر بخش زیادی از اطلاعات فرانت‌اند به صورت ایستا (Static / Mock Data) از پوشه `src/data/` یا `src/content/` لود می‌شود تا فرانت‌بخش بدون وابستگی به سرور قابل نمایش باشد.
برای داینامیک کردن داده‌ها و اتصال فرانت‌اند به پنل مدیریت مراحل زیر پیشنهاد می‌شود:
1. در ریشه پروژه، متغیر محیطی آدرس بک‌اند را در فایل `.env.local` تعریف کنید:
```env
VITE_PANEL_API=http://localhost:3001
```
2. یک سرویس کلاینت (مثلا `src/lib/api.ts`) برای واکشی داده‌ها بسازید:
```typescript
const API_URL = import.meta.env.VITE_PANEL_API || 'http://localhost:3001';
export async function getArticles(category?: string) {
const url = category ? `${API_URL}/api/articles?category=${encodeURIComponent(category)}` : `${API_URL}/api/articles`;
const res = await fetch(url);
if (!res.ok) throw new Error('خطا در دریافت اطلاعات از سرور');
return res.json();
}
```
3. در کامپوننت‌های فرانت‌اند (مانند بخش نمایش گزارش‌های صفحه اصلی)، از React Query (که پکیج آن در پروژه نصب است) یا هوک `useEffect` برای دریافت پویای داده‌ها به شکل زیر استفاده کنید:
```typescript
import { useQuery } from '@tanstack/react-query';
import { getArticles } from '@/lib/api';
// در داخل کامپوننت ری‌اکت
const { data: reports, isLoading } = useQuery({
queryKey: ['articles', category],
queryFn: () => getArticles(category),
placeholderData: fallbackMockData // استفاده از داده‌های ماک قبلی در زمان لودینگ یا خطا
});
```
---
## ۵. استقرار و دگرگونی‌ها (Deployment)
### فرانت‌اند
فرانت‌اند پروژه به دلیل ایستا (Static) بودن خروجی نهایی، به راحتی به کمک **Dockerfile** موجود استقرار می‌یابد.
* داکرفایل از ساختار دو مرحله‌ای (Multi-stage build) استفاده می‌کند:
1. مرحله اول: استفاده از ایمیج Node جهت نصب پکیج‌ها و ایجاد خروجی نهایی بیلد در پوشه `dist`.
2. مرحله دوم: استفاده از سرور بسیار سبک Nginx جهت کپی و سرو کردن فایل‌های پوشه `dist` روی پورت `7860`.
* این پورت برای آپلود مستقیم در پلتفرم‌هایی مانند **Hugging Face Spaces** بهینه شده است (تنظیمات متا دیتا در بالای فایل `README.md` ریشه قید شده است).
### پنل مدیریت (بک‌اند)
* پنل مدیریت به پایگاه داده و در صورت تمایل سیستم فایل نیاز دارد.
* برای دیپلوی روی **لیارا (Liara)**، فایل‌های کانفیگ نظیر `liara.json` و فایل نادیده‌گیری `.liaraignore` تعبیه شده‌اند.
* حتما باید متغیرهای محیطی زیر در کنترل پنل هاست تعریف شوند:
* `PORT`: پورت سرور (به عنوان مثال ۳۰۰۱).
* `JWT_SECRET`: یک رشته هگزادسیمل تصادفی طولانی (حداقل ۳۲ کاراکتر).
* `DATABASE_URL`: آدرس اتصال به پایگاه داده PostgreSQL ابری شما.
* `ALLOWED_ORIGINS`: آدرس دامنه فرانت‌اند جهت عبور از گیت CORS.
* متغیرهای سرویس فایل ابری در صورت نیاز (`LIARA_ENDPOINT`, `LIARA_BUCKET`, `LIARA_ACCESS_KEY`, `LIARA_SECRET_KEY`).

BIN
fig-profile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

3
hero-bg.original.mp4 Normal file
View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3e22f4390856dd4995a930e22c5026008b333d6b5429d4201ae71d6b3a397c41
size 43690639

View File

@ -2,7 +2,11 @@
<html lang="fa" dir="rtl"> <html lang="fa" dir="rtl">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="اندیشکده فولاد آینده — مرکز مستقل تحلیل راهبردی و سیاست‌گذاری صنعت فولاد" /> <meta name="description" content="اندیشکده فولاد آینده — مرکز مستقل تحلیل راهبردی و سیاست‌گذاری صنعت فولاد" />

170
package-lock.json generated
View File

@ -8,11 +8,13 @@
"name": "andishkade-foolad", "name": "andishkade-foolad",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@radix-ui/react-slot": "^1.3.0",
"@react-three/drei": "^10.7.7", "@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.6.1", "@react-three/fiber": "^9.6.1",
"@tabler/icons-react": "^3.44.0", "@tabler/icons-react": "^3.44.0",
"@tanstack/react-query": "^5.100.14", "@tanstack/react-query": "^5.100.14",
"apexcharts": "^5.13.0", "apexcharts": "^5.13.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cobe": "^2.0.1", "cobe": "^2.0.1",
"dotted-map": "^3.1.0", "dotted-map": "^3.1.0",
@ -24,6 +26,7 @@
"react": "^19.2.6", "react": "^19.2.6",
"react-apexcharts": "^2.1.0", "react-apexcharts": "^2.1.0",
"react-dom": "^19.2.6", "react-dom": "^19.2.6",
"react-globe.gl": "^2.38.0",
"react-router-dom": "^7.15.1", "react-router-dom": "^7.15.1",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
"three": "^0.184.0", "three": "^0.184.0",
@ -629,6 +632,39 @@
"url": "https://github.com/sponsors/Boshen" "url": "https://github.com/sponsors/Boshen"
} }
}, },
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz",
"integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz",
"integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.3"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@react-three/drei": { "node_modules/@react-three/drei": {
"version": "10.7.7", "version": "10.7.7",
"resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz", "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz",
@ -1974,6 +2010,18 @@
], ],
"license": "CC-BY-4.0" "license": "CC-BY-4.0"
}, },
"node_modules/class-variance-authority": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
"license": "Apache-2.0",
"dependencies": {
"clsx": "^2.1.1"
},
"funding": {
"url": "https://polar.sh/cva"
}
},
"node_modules/clsx": { "node_modules/clsx": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@ -2163,6 +2211,15 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time": { "node_modules/d3-time": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
@ -2612,6 +2669,20 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/float-tooltip": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz",
"integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==",
"license": "MIT",
"dependencies": {
"d3-selection": "2 - 3",
"kapsule": "^1.16",
"preact": "10"
},
"engines": {
"node": ">=12"
}
},
"node_modules/frame-ticker": { "node_modules/frame-ticker": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/frame-ticker/-/frame-ticker-1.0.3.tgz", "resolved": "https://registry.npmjs.org/frame-ticker/-/frame-ticker-1.0.3.tgz",
@ -2699,6 +2770,23 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/globe.gl": {
"version": "2.46.1",
"resolved": "https://registry.npmjs.org/globe.gl/-/globe.gl-2.46.1.tgz",
"integrity": "sha512-h+OvX52EBIPLtM0/2JkM+JZ9gPAhPJ4y3+hxUwD5Ey/O0Zk2ockuTiJ71bZbnNBGmNiIZzA5Vr3TMT0b3d35IQ==",
"license": "MIT",
"dependencies": {
"@tweenjs/tween.js": "18 - 25",
"accessor-fn": "1",
"kapsule": "^1.16",
"three": ">=0.179 <1",
"three-globe": "^2.45",
"three-render-objects": "^1.41"
},
"engines": {
"node": ">=12"
}
},
"node_modules/glsl-noise": { "node_modules/glsl-noise": {
"version": "0.0.0", "version": "0.0.0",
"resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz", "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
@ -2869,6 +2957,15 @@
"integrity": "sha512-Jl/EwY84JwjW2wsWqeU4pNd22VNQ7EkjI36bDuLw31wH98WQW4fPjD0+mG7cdCK+Y8D6s9R3zLiQ3LaKu6bD8A==", "integrity": "sha512-Jl/EwY84JwjW2wsWqeU4pNd22VNQ7EkjI36bDuLw31wH98WQW4fPjD0+mG7cdCK+Y8D6s9R3zLiQ3LaKu6bD8A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/jerrypick": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/jerrypick/-/jerrypick-1.1.2.tgz",
"integrity": "sha512-YKnxXEekXKzhpf7CLYA0A+oDP8V0OhICNCr5lv96FvSsDEmrb0GKM776JgQvHTMjr7DTTPEVv/1Ciaw0uEWzBA==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/jiti": { "node_modules/jiti": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
@ -3544,6 +3641,18 @@
"robust-predicates": "^3.0.2" "robust-predicates": "^3.0.2"
} }
}, },
"node_modules/polished": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz",
"integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.17.8"
},
"engines": {
"node": ">=10"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.15", "version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
@ -3579,6 +3688,16 @@
"integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/preact": {
"version": "10.29.2",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.29.2.tgz",
"integrity": "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -3667,12 +3786,44 @@
"react": "^19.2.6" "react": "^19.2.6"
} }
}, },
"node_modules/react-globe.gl": {
"version": "2.38.0",
"resolved": "https://registry.npmjs.org/react-globe.gl/-/react-globe.gl-2.38.0.tgz",
"integrity": "sha512-NQkMy5BGD269lkdjm9BJ2PEK81++y9H82+j4/QuWVlfXPJPTVfzaxnkC3MvaiGmpus8P6czq58OfMeYvn/4Xqg==",
"license": "MIT",
"dependencies": {
"globe.gl": "^2.46",
"prop-types": "15",
"react-kapsule": "^2.5"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-kapsule": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/react-kapsule/-/react-kapsule-2.6.0.tgz",
"integrity": "sha512-HzLJoYb1n1kfwjXbqFFcRR0EA6oPsJ64tNdDmCSaL/bz2o9wUZRSb0cMe//grLFeF9EVoL4CD/e6ozLyzEv+PQ==",
"license": "MIT",
"dependencies": {
"jerrypick": "^1.1.2"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"react": ">=16.13.1"
}
},
"node_modules/react-router": { "node_modules/react-router": {
"version": "7.15.1", "version": "7.15.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz",
@ -3984,6 +4135,25 @@
"three": ">= 0.159.0" "three": ">= 0.159.0"
} }
}, },
"node_modules/three-render-objects": {
"version": "1.42.0",
"resolved": "https://registry.npmjs.org/three-render-objects/-/three-render-objects-1.42.0.tgz",
"integrity": "sha512-KYfkPrYGEbIK8ChFocWqOF1aAN80FBUBWVYB8mB2oBpVuVN+52FvvngVYB5ieFANQu7Rt21rPYZ/xKaAgVWWRQ==",
"license": "MIT",
"dependencies": {
"@tweenjs/tween.js": "18 - 25",
"accessor-fn": "1",
"float-tooltip": "^1.7",
"kapsule": "^1.16",
"polished": "4"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"three": ">=0.179"
}
},
"node_modules/three-slippy-map-globe": { "node_modules/three-slippy-map-globe": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/three-slippy-map-globe/-/three-slippy-map-globe-1.0.6.tgz", "resolved": "https://registry.npmjs.org/three-slippy-map-globe/-/three-slippy-map-globe-1.0.6.tgz",

View File

@ -10,11 +10,13 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-slot": "^1.3.0",
"@react-three/drei": "^10.7.7", "@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.6.1", "@react-three/fiber": "^9.6.1",
"@tabler/icons-react": "^3.44.0", "@tabler/icons-react": "^3.44.0",
"@tanstack/react-query": "^5.100.14", "@tanstack/react-query": "^5.100.14",
"apexcharts": "^5.13.0", "apexcharts": "^5.13.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cobe": "^2.0.1", "cobe": "^2.0.1",
"dotted-map": "^3.1.0", "dotted-map": "^3.1.0",
@ -26,6 +28,7 @@
"react": "^19.2.6", "react": "^19.2.6",
"react-apexcharts": "^2.1.0", "react-apexcharts": "^2.1.0",
"react-dom": "^19.2.6", "react-dom": "^19.2.6",
"react-globe.gl": "^2.38.0",
"react-router-dom": "^7.15.1", "react-router-dom": "^7.15.1",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
"three": "^0.184.0", "three": "^0.184.0",

View File

@ -413,6 +413,10 @@ for (const sql of [
'ALTER TABLE banners ADD COLUMN IF NOT EXISTS overline_fa TEXT', 'ALTER TABLE banners ADD COLUMN IF NOT EXISTS overline_fa TEXT',
'ALTER TABLE banners ADD COLUMN IF NOT EXISTS overline_en TEXT', 'ALTER TABLE banners ADD COLUMN IF NOT EXISTS overline_en TEXT',
"ALTER TABLE users ADD COLUMN IF NOT EXISTS role TEXT NOT NULL DEFAULT 'admin'", "ALTER TABLE users ADD COLUMN IF NOT EXISTS role TEXT NOT NULL DEFAULT 'admin'",
'ALTER TABLE articles ADD COLUMN IF NOT EXISTS blocks TEXT',
'ALTER TABLE articles ADD COLUMN IF NOT EXISTS sources TEXT',
'ALTER TABLE radar_items ADD COLUMN IF NOT EXISTS tags TEXT',
'ALTER TABLE radar_items ADD COLUMN IF NOT EXISTS source TEXT',
]) { ]) {
await pool.query(sql); await pool.query(sql);
} }
@ -671,6 +675,8 @@ export function rowToRadarItem(row) {
excerptFa: row.excerpt_fa, excerptFa: row.excerpt_fa,
bodyFa: row.body_fa || null, bodyFa: row.body_fa || null,
analysisFa: row.analysis_fa || null, analysisFa: row.analysis_fa || null,
tags: row.tags ? JSON.parse(row.tags) : [],
source: row.source || null,
titleEn: row.title_en, titleEn: row.title_en,
dateEn: row.date_en, dateEn: row.date_en,
excerptEn: row.excerpt_en, excerptEn: row.excerpt_en,
@ -748,6 +754,8 @@ export function rowToArticle(row) {
summary: row.summary, summary: row.summary,
body: row.body, body: row.body,
coverImage: row.cover_image, coverImage: row.cover_image,
blocks: row.blocks ? JSON.parse(row.blocks) : [],
sources: row.sources ? JSON.parse(row.sources) : [],
tags: row.tags ? JSON.parse(row.tags) : [], tags: row.tags ? JSON.parse(row.tags) : [],
featured: !!row.featured, featured: !!row.featured,
createdAt: row.created_at, createdAt: row.created_at,

View File

@ -2,18 +2,11 @@ const $ = (sel, root = document) => root.querySelector(sel);
const root = $('#app'); const root = $('#app');
const TAB_GROUPS = [ const TAB_GROUPS = [
{ label: 'محتوا', tabs: ['articles', 'events', 'radar', 'radarPages', 'factoryReports'] }, { label: 'محتوا', tabs: ['glance', 'radarFuture', 'media', 'events', 'banners'] },
{ label: 'داده', tabs: ['risks', 'mprices', 'stats', 'charts', 'integrations'] },
{ label: 'سایت', tabs: ['vision', 'advisory', 'team', 'experts', 'plans', 'banners'] },
{ label: 'ابزار', tabs: ['pdfAnalyze', 'import', 'users'] },
]; ];
const TAB_LABELS = { const TAB_LABELS = {
articles: 'مطالب', risks: 'ریسک‌ها', events: 'رویدادها', glance: 'در یک نگاه', radarFuture: 'رادار آینده', media: 'ویدیوکست و پادکست',
radar: 'رادار', radarPages: 'صفحات رادار', factoryReports: 'گزارش کارخانه', events: 'رویدادها', banners: 'بنرها',
integrations: 'منابع داده', stats: 'آمار', mprices: 'قیمت‌ها',
charts: 'نمودارها', vision: 'چشم‌انداز', advisory: 'شورای مشورتی',
team: 'تیم', experts: 'خبرگان', plans: 'پلان‌ها', banners: 'بنرها',
pdfAnalyze: '📄 PDF', import: '⬇️ ایمپورت', users: 'کاربران',
}; };
@ -39,14 +32,23 @@ const CONTENT_TYPES = [
{ key: 'video', label: 'ویدیوکست', icon: '🎬', desc: 'ویدیوی تحلیلی یا مصاحبه', defaultType: 'video', defaultCategory: '' }, { key: 'video', label: 'ویدیوکست', icon: '🎬', desc: 'ویدیوی تحلیلی یا مصاحبه', defaultType: 'video', defaultCategory: '' },
{ key: 'podcast', label: 'پادکست', icon: '🎙️', desc: 'اپیزود پادکست صوتی', defaultType: 'podcast', defaultCategory: '' }, { key: 'podcast', label: 'پادکست', icon: '🎙️', desc: 'اپیزود پادکست صوتی', defaultType: 'podcast', defaultCategory: '' },
{ key: 'factory', label: 'گزارش کارخانه', icon: '🏭', desc: 'گزارش تولید و عملیات کارخانه', defaultType: 'factoryReport', defaultCategory: 'گزارش کارخانه' }, { key: 'factory', label: 'گزارش کارخانه', icon: '🏭', desc: 'گزارش تولید و عملیات کارخانه', defaultType: 'factoryReport', defaultCategory: 'گزارش کارخانه' },
{ key: 'radar', label: 'رادار آینده', icon: '🔭', desc: 'از تب «رادار» در منوی بالا مدیریت می‌شود', defaultType: '', defaultCategory: '', externalTab: 'radar' }, { key: 'radarFuture', label: 'رادار آینده', icon: '🔭', desc: 'مقالهٔ بلوکی (متن/عکس) برای ۵ زیرمنوی رادار آینده', defaultType: 'radarfuture', defaultCategory: '' },
];
// the 5 رادار آینده sub-sections (block-article categories)
const RADAR_FUTURE_CATEGORIES = [
'آینده‌پژوهی و رصد هوشمند',
'پایداری و فولاد سبز',
'ژئوپلیتیک و اقتصاد جهانی',
'فناوری و نوآوری',
'بازار و زنجیره فولاد',
]; ];
function inferContentType(a) { function inferContentType(a) {
if (!a) return 'report'; if (!a) return 'report';
if (a.type === 'video') return 'video'; if (a.type === 'video') return 'video';
if (a.type === 'podcast') return 'podcast'; if (a.type === 'podcast') return 'podcast';
if (a.type === 'radar') return 'radar'; if (a.type === 'radarfuture') return 'radarFuture';
if (a.type === 'factoryReport' || a.category === 'گزارش کارخانه') return 'factory'; if (a.type === 'factoryReport' || a.category === 'گزارش کارخانه') return 'factory';
if (a.type === 'news') return 'news'; if (a.type === 'news') return 'news';
return 'report'; return 'report';
@ -60,11 +62,11 @@ const EVENT_TYPES = [
]; ];
const RADAR_CATEGORIES = [ const RADAR_CATEGORIES = [
{ value: 'market', label: 'بازار' }, { value: 'market', label: 'بازارهای جهانی و اقتصادِ کلان' },
{ value: 'tech', label: 'فناوری' }, { value: 'tech', label: 'نوآوری و فناوری‌هایِ تحول‌آفرین' },
{ value: 'commodity', label: 'مواد اولیه' }, { value: 'commodity', label: 'مواد معدنیِ حیاتی و زنجیره‌ی تامین' },
{ value: 'geo', label: 'ژئوپلیتیک' }, { value: 'geo', label: 'ژئوپلیتیک و امنیتِ اقتصادی' },
{ value: 'energy', label: 'انرژی' }, { value: 'energy', label: 'تجارت و کسب‌وکارِ بین‌الملل' },
]; ];
const SCROLL_DIRS = [ const SCROLL_DIRS = [
@ -145,7 +147,7 @@ function renderLogin(errorMsg = '') {
method: 'POST', method: 'POST',
body: { username: fd.get('username'), password: fd.get('password') }, body: { username: fd.get('username'), password: fd.get('password') },
}); });
renderList(); renderRadarList();
} catch { } catch {
renderLogin('نام کاربری یا رمز عبور اشتباه است'); renderLogin('نام کاربری یا رمز عبور اشتباه است');
} }
@ -178,25 +180,11 @@ function wireTabs() {
document.querySelectorAll('.tabs button').forEach(btn => { document.querySelectorAll('.tabs button').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
const t = btn.dataset.tab; const t = btn.dataset.tab;
if (t === 'articles') renderList(); if (t === 'glance') renderRadarList();
else if (t === 'risks') renderRisksList(); else if (t === 'radarFuture') renderRadarFutureList();
else if (t === 'events') renderEventsList(); else if (t === 'media') renderMediaList();
else if (t === 'radar') renderRadarList(); else if (t === 'events') renderEventsList();
else if (t === 'radarPages') renderRadarPagesList(); else if (t === 'banners') renderBannersList();
else if (t === 'factoryReports') renderFactoryReportsList();
else if (t === 'integrations') renderIntegrationsList();
else if (t === 'stats') renderStatsList();
else if (t === 'mprices') renderMarketPricesList();
else if (t === 'charts') renderChartPointsList();
else if (t === 'vision') renderVisionList();
else if (t === 'advisory') renderAdvisoryList();
else if (t === 'team') renderTeamList(false);
else if (t === 'experts') renderTeamList(true);
else if (t === 'plans') renderPlansList();
else if (t === 'banners') renderBannersList();
else if (t === 'pdfAnalyze') renderPdfAnalyze();
else if (t === 'import') renderImport();
else if (t === 'users') renderUsersList();
}); });
}); });
$('#logoutBtn').addEventListener('click', async () => { $('#logoutBtn').addEventListener('click', async () => {
@ -287,6 +275,86 @@ async function renderList() {
} }
} }
async function renderRadarFutureList() {
root.innerHTML = `
${topbarHtml('radarFuture', '+ رادار آینده جدید')}
<main class="page">
<h2>رادار آینده</h2>
<p class="muted" style="margin:-4px 0 12px">محتوای صفحات «رادار آینده» سایت (/radar/*) مقالهٔ بلوکی به تفکیک ۵ زیرمنو.</p>
<div id="cat-bar" class="cat-bar">
<button class="cat-btn active" data-cat="">همه</button>
${RADAR_FUTURE_CATEGORIES.map(c => `<button class="cat-btn" data-cat="${escapeAttr(c)}">${escapeHtml(c)}</button>`).join('')}
</div>
<div id="list" class="list"><p class="muted">در حال بارگذاری</p></div>
</main>`;
wireTabs();
$('#newBtn').addEventListener('click', () => renderEditor(null, { contentType: 'radarFuture', type: 'radarfuture', category: '' }));
let allItems = [], activeCat = '';
function renderItems() {
const filtered = activeCat ? allItems.filter(a => a.category === activeCat) : allItems;
if (!filtered.length) { $('#list').innerHTML = `<p class="muted">مطلبی در این بخش یافت نشد.</p>`; return; }
$('#list').innerHTML = filtered.map(a => `
<article class="row" data-id="${escapeAttr(a.id)}">
<div class="row-cover" style="background-image:url('${escapeAttr(a.coverImage||'')}')"></div>
<div class="row-main">
<div class="row-meta">${a.category?`<span class="pill">${escapeHtml(a.category)}</span>`:''}${a.publishDate?`<span class="muted">${escapeHtml(a.publishDate)}</span>`:''}</div>
<h3>${escapeHtml(a.title)}</h3>
<p class="muted clamp-2">${escapeHtml(a.summary||'')}</p>
</div>
<div class="row-actions"><button data-action="edit">ویرایش</button><button data-action="delete" class="danger">حذف</button></div>
</article>`).join('');
}
$('#cat-bar').addEventListener('click', e => { const b = e.target.closest('.cat-btn'); if (!b) return; activeCat = b.dataset.cat; document.querySelectorAll('.cat-btn').forEach(x => x.classList.toggle('active', x === b)); renderItems(); });
$('#list').addEventListener('click', async e => {
const btn = e.target.closest('button[data-action]'); if (!btn) return;
const id = btn.closest('.row').dataset.id;
if (btn.dataset.action === 'edit') renderEditor(await api(`/api/articles/${id}`));
else if (btn.dataset.action === 'delete') { if (!confirm('این مطلب حذف شود؟')) return; await api(`/api/articles/${id}`, { method: 'DELETE' }); renderRadarFutureList(); }
});
try {
const all = await api('/api/articles?limit=500');
allItems = all.filter(a => a.type === 'radarfuture');
if (!allItems.length) { $('#list').innerHTML = `<p class="muted">هنوز مطلبی ثبت نشده — با «+ رادار آینده جدید» شروع کن.</p>`; return; }
renderItems();
} catch (err) { $('#list').innerHTML = `<p class="error">خطا در بارگذاری.</p>`; }
}
async function renderMediaList() {
root.innerHTML = `
${topbarHtml('media', '+ ویدیوکست جدید')}
<main class="page">
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap">
<h2>ویدیوکست و پادکست</h2>
<button id="newPod" class="primary">+ پادکست جدید</button>
</div>
<div id="list" class="list"><p class="muted">در حال بارگذاری</p></div>
</main>`;
wireTabs();
$('#newBtn').addEventListener('click', () => renderEditor(null, { contentType: 'video', type: 'video', category: '' }));
$('#newPod').addEventListener('click', () => renderEditor(null, { contentType: 'podcast', type: 'podcast', category: '' }));
$('#list').addEventListener('click', async e => {
const btn = e.target.closest('button[data-action]'); if (!btn) return;
const id = btn.closest('.row').dataset.id;
if (btn.dataset.action === 'edit') renderEditor(await api(`/api/articles/${id}`));
else if (btn.dataset.action === 'delete') { if (!confirm('این مورد حذف شود؟')) return; await api(`/api/articles/${id}`, { method: 'DELETE' }); renderMediaList(); }
});
try {
const all = await api('/api/articles?limit=500');
const items = all.filter(a => a.type === 'video' || a.type === 'podcast');
if (!items.length) { $('#list').innerHTML = `<p class="muted">هنوز موردی ثبت نشده.</p>`; return; }
$('#list').innerHTML = items.map(a => `
<article class="row" data-id="${escapeAttr(a.id)}">
<div class="row-cover" style="background-image:url('${escapeAttr(a.coverImage||'')}')"></div>
<div class="row-main">
<div class="row-meta"><span class="pill">${a.type==='video'?'ویدیوکست':'پادکست'}</span>${a.publishDate?`<span class="muted">${escapeHtml(a.publishDate)}</span>`:''}</div>
<h3>${escapeHtml(a.title)}</h3>
<p class="muted clamp-2">${escapeHtml(a.summary||'')}</p>
</div>
<div class="row-actions"><button data-action="edit">ویرایش</button><button data-action="delete" class="danger">حذف</button></div>
</article>`).join('');
} catch (err) { $('#list').innerHTML = `<p class="error">خطا در بارگذاری.</p>`; }
}
function renderTypeSelector() { function renderTypeSelector() {
root.innerHTML = ` root.innerHTML = `
<header class="topbar"> <header class="topbar">
@ -339,15 +407,23 @@ function renderEditor(article, draft = {}) {
summary: '', body: '', coverImage: '', tags: [], featured: false, summary: '', body: '', coverImage: '', tags: [], featured: false,
}; };
// content blocks (interleaved text + chart/image) and citation sources
let blocks = (Array.isArray(a.blocks) && a.blocks.length)
? a.blocks.map(b => ({ ...b }))
: (a.body ? [{ type: 'text', value: a.body }] : []);
let sources = Array.isArray(a.sources) ? a.sources.map(s => ({ ...s })) : [];
const REPORT_TYPES = TYPES.filter(t => ['quarterly','flash','special','risk','free'].includes(t.value)); const REPORT_TYPES = TYPES.filter(t => ['quarterly','flash','special','risk','free'].includes(t.value));
const hasCover = ['report','news','video','podcast','factory'].includes(contentType); const isRadarFuture = contentType === 'radarFuture';
const hasCategory = ['report','news'].includes(contentType); const hasCover = ['report','news','video','podcast','factory','radarFuture'].includes(contentType);
const hasCategory = ['report','news','radarFuture'].includes(contentType);
const hasAuthor = ['report','video','podcast'].includes(contentType); const hasAuthor = ['report','video','podcast'].includes(contentType);
const hasPages = ['report','factory'].includes(contentType); const hasPages = ['report','factory'].includes(contentType);
const hasPrice = contentType === 'report'; const hasPrice = contentType === 'report';
const hasBody = ['report','news','factory'].includes(contentType); const hasBody = ['report','news','factory','radarFuture'].includes(contentType);
const hasMediaLink = ['video','podcast'].includes(contentType); const hasMediaLink = ['video','podcast'].includes(contentType);
const hasReportType = contentType === 'report'; const hasReportType = contentType === 'report';
const categoryOptions = isRadarFuture ? RADAR_FUTURE_CATEGORIES : CATEGORIES;
root.innerHTML = ` root.innerHTML = `
<header class="topbar"> <header class="topbar">
@ -370,10 +446,10 @@ function renderEditor(article, draft = {}) {
</div> </div>
${hasCategory ? ` ${hasCategory ? `
<label>دستهبندی <label>${isRadarFuture ? 'بخش رادار آینده' : 'دسته‌بندی'}
<select name="category"> <select name="category">
<option value="">انتخاب کنید</option> <option value="">انتخاب کنید</option>
${CATEGORIES.map(c => `<option value="${c}" ${c===a.category?'selected':''}>${c}</option>`).join('')} ${categoryOptions.map(c => `<option value="${escapeAttr(c)}" ${c===a.category?'selected':''}>${escapeHtml(c)}</option>`).join('')}
</select> </select>
</label>` : `<input type="hidden" name="category" value="${escapeAttr(a.category)}" />`} </label>` : `<input type="hidden" name="category" value="${escapeAttr(a.category)}" />`}
@ -432,9 +508,17 @@ function renderEditor(article, draft = {}) {
${hasBody ? ` ${hasBody ? `
<div class="col span-2"> <div class="col span-2">
<label>متن کامل <label style="display:block;margin-bottom:6px">متن کامل (متن + نمودار / تصویر، به ترتیب دلخواه)</label>
<textarea name="body" rows="12">${escapeHtml(a.body)}</textarea> <div id="blocksEditor" class="blocks-editor"></div>
</label> <div style="display:flex;gap:8px;margin-top:10px">
<button type="button" id="addTextBlock" class="ghost"> پاراگراف متن</button>
<button type="button" id="addImageBlock" class="ghost">🖼 تصویر / نمودار</button>
</div>
</div>
<div class="col span-2">
<label style="display:block;margin-bottom:6px">منابع و ارجاعات</label>
<div id="sourcesEditor"></div>
<button type="button" id="addSource" class="ghost" style="margin-top:8px"> افزودن منبع</button>
</div>` : ``} </div>` : ``}
${hasMediaLink ? ` ${hasMediaLink ? `
@ -467,7 +551,12 @@ function renderEditor(article, draft = {}) {
</main> </main>
`; `;
$('#backBtn').addEventListener('click', () => renderList()); const backToList = () => {
if (contentType === 'radarFuture') return renderRadarFutureList();
if (contentType === 'video' || contentType === 'podcast') return renderMediaList();
return renderRadarFutureList();
};
$('#backBtn').addEventListener('click', backToList);
document.getElementById('changeTypeBtn')?.addEventListener('click', () => renderTypeSelector()); document.getElementById('changeTypeBtn')?.addEventListener('click', () => renderTypeSelector());
const coverFile = document.getElementById('coverFile'); const coverFile = document.getElementById('coverFile');
@ -478,10 +567,12 @@ function renderEditor(article, draft = {}) {
const fd = new FormData(); const fd = new FormData();
fd.append('file', file); fd.append('file', file);
try { try {
const { url } = await api('/api/uploads', { method: 'POST', body: fd, isForm: true }); const data = await api('/api/uploads', { method: 'POST', body: fd, isForm: true });
$('input[name="coverImage"]').value = url; const url = data.url;
const input = $('input[name="coverImage"]');
if (input) input.value = url;
const prev = document.getElementById('coverPreview'); const prev = document.getElementById('coverPreview');
prev.src = url; prev.style.display = ''; if (prev) { prev.src = url; prev.style.display = ''; }
} catch (err) { alert('آپلود ناموفق بود: ' + err.message); } } catch (err) { alert('آپلود ناموفق بود: ' + err.message); }
}); });
} }
@ -507,6 +598,73 @@ function renderEditor(article, draft = {}) {
}); });
} }
// ── content blocks (text + chart/image, reorderable) ──────────────────
function renderBlocks() {
const el = document.getElementById('blocksEditor');
if (!el) return;
if (!blocks.length) {
el.innerHTML = `<div class="muted" style="padding:10px 0">هنوز محتوایی اضافه نشده — یک پاراگراف یا تصویر/نمودار اضافه کنید.</div>`;
return;
}
el.innerHTML = blocks.map((b, i) => {
const ctrls = `<div style="position:absolute;top:6px;left:6px;display:flex;gap:4px">
<button type="button" data-act="up" data-i="${i}" class="ghost" style="padding:2px 8px" title="بالا"></button>
<button type="button" data-act="down" data-i="${i}" class="ghost" style="padding:2px 8px" title="پایین"></button>
<button type="button" data-act="del" data-i="${i}" class="ghost" style="padding:2px 8px" title="حذف"></button>
</div>`;
if (b.type === 'image') {
return `<div style="position:relative;border:1px solid #e2e8f0;border-radius:8px;padding:12px;margin-bottom:10px;background:#fafafa">
${ctrls}
<div style="display:flex;align-items:center;gap:12px;margin:6px 0 8px">
${b.url ? `<img src="${escapeAttr(b.url)}" style="max-width:220px;max-height:150px;border-radius:6px;border:1px solid #e2e8f0" />` : `<div style="width:140px;height:90px;border:1px dashed #cbd5e1;border-radius:6px;display:flex;align-items:center;justify-content:center;color:#94a3b8;font-size:12px">تصویر / نمودار</div>`}
<input type="file" accept="image/*" data-imgfile="${i}" />
</div>
<input type="text" data-cap="${i}" value="${escapeAttr(b.caption || '')}" placeholder="توضیح تصویر / نمودار (اختیاری)" style="width:100%" />
</div>`;
}
return `<div style="position:relative;border:1px solid #e2e8f0;border-radius:8px;padding:12px;margin-bottom:10px">
${ctrls}
<textarea data-txt="${i}" rows="5" placeholder="متن پاراگراف…" style="width:100%;margin-top:18px">${escapeHtml(b.value || '')}</textarea>
</div>`;
}).join('');
el.querySelectorAll('[data-txt]').forEach(t => t.addEventListener('input', e => { blocks[+e.target.dataset.txt].value = e.target.value; }));
el.querySelectorAll('[data-cap]').forEach(t => t.addEventListener('input', e => { blocks[+e.target.dataset.cap].caption = e.target.value; }));
el.querySelectorAll('[data-imgfile]').forEach(f => f.addEventListener('change', async e => {
const file = e.target.files?.[0]; if (!file) return;
const i = +e.target.dataset.imgfile;
const fd2 = new FormData(); fd2.append('file', file);
try { const { url } = await api('/api/uploads', { method: 'POST', body: fd2, isForm: true }); blocks[i].url = url; renderBlocks(); }
catch (err) { alert('آپلود ناموفق بود: ' + err.message); }
}));
el.querySelectorAll('button[data-act]').forEach(btn => btn.addEventListener('click', e => {
const i = +e.currentTarget.dataset.i, act = e.currentTarget.dataset.act;
if (act === 'del') blocks.splice(i, 1);
else if (act === 'up' && i > 0) { [blocks[i - 1], blocks[i]] = [blocks[i], blocks[i - 1]]; }
else if (act === 'down' && i < blocks.length - 1) { [blocks[i + 1], blocks[i]] = [blocks[i], blocks[i + 1]]; }
renderBlocks();
}));
}
document.getElementById('addTextBlock')?.addEventListener('click', () => { blocks.push({ type: 'text', value: '' }); renderBlocks(); });
document.getElementById('addImageBlock')?.addEventListener('click', () => { blocks.push({ type: 'image', url: '', caption: '' }); renderBlocks(); });
renderBlocks();
// ── sources / citations ───────────────────────────────────────────────
function renderSources() {
const el = document.getElementById('sourcesEditor');
if (!el) return;
el.innerHTML = sources.length ? sources.map((s, i) => `
<div style="display:flex;gap:8px;margin-bottom:6px">
<input type="text" data-stitle="${i}" value="${escapeAttr(s.title || '')}" placeholder="عنوان منبع" style="flex:1" />
<input type="text" data-surl="${i}" value="${escapeAttr(s.url || '')}" placeholder="https://… (اختیاری)" style="flex:1" dir="ltr" />
<button type="button" data-sdel="${i}" class="ghost" style="padding:2px 10px"></button>
</div>`).join('') : `<div class="muted" style="padding:6px 0">منبعی اضافه نشده.</div>`;
el.querySelectorAll('[data-stitle]').forEach(t => t.addEventListener('input', e => { sources[+e.target.dataset.stitle].title = e.target.value; }));
el.querySelectorAll('[data-surl]').forEach(t => t.addEventListener('input', e => { sources[+e.target.dataset.surl].url = e.target.value; }));
el.querySelectorAll('[data-sdel]').forEach(b => b.addEventListener('click', e => { sources.splice(+e.currentTarget.dataset.sdel, 1); renderSources(); }));
}
document.getElementById('addSource')?.addEventListener('click', () => { sources.push({ title: '', url: '' }); renderSources(); });
renderSources();
$('#editorForm').addEventListener('submit', async (e) => { $('#editorForm').addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
const fd = new FormData(e.target); const fd = new FormData(e.target);
@ -525,8 +683,12 @@ function renderEditor(article, draft = {}) {
isFree: isFreeVal === 'on' || isFreeVal === 'true', isFree: isFreeVal === 'on' || isFreeVal === 'true',
featured: fd.get('featured') === 'on', featured: fd.get('featured') === 'on',
summary: fd.get('summary') || null, summary: fd.get('summary') || null,
body: fd.get('body') || null, body: hasBody
? blocks.filter(b => b.type === 'text' && b.value && b.value.trim()).map(b => b.value.trim()).join('\n\n')
: (fd.get('body') || null),
coverImage: fd.get('coverImage') || null, coverImage: fd.get('coverImage') || null,
blocks: hasBody ? blocks.filter(b => (b.type === 'text' && b.value && b.value.trim()) || (b.type === 'image' && b.url)) : [],
sources: sources.filter(s => (s.title && s.title.trim()) || (s.url && s.url.trim())),
tags: tagsRaw, tags: tagsRaw,
}; };
try { try {
@ -535,7 +697,7 @@ function renderEditor(article, draft = {}) {
} else { } else {
await api('/api/articles', { method: 'POST', body: payload }); await api('/api/articles', { method: 'POST', body: payload });
} }
renderList(); backToList();
} catch (err) { alert('ذخیره ناموفق بود: ' + err.message); } } catch (err) { alert('ذخیره ناموفق بود: ' + err.message); }
}); });
} }
@ -1116,12 +1278,13 @@ function sectionLabelsForCategory(cat) {
async function renderRadarList() { async function renderRadarList() {
root.innerHTML = ` root.innerHTML = `
${topbarHtml('radar', '+ مطلب رادار جدید')} ${topbarHtml('glance', '+ در یک نگاه جدید')}
<main class="page"> <main class="page">
<h2>رادار آینده</h2> <h2>در یک نگاه</h2>
<p class="muted" style="margin:-4px 0 12px">محتوای صفحهٔ «در یک نگاه» سایت (/at-a-glance) به تفکیک دسته.</p>
<div id="section-bar" class="cat-bar"> <div id="section-bar" class="cat-bar">
<button class="cat-btn active" data-slug="">همه بخشها</button> <button class="cat-btn active" data-cat="">همه</button>
${RADAR_PAGE_SLUGS.map(s => `<button class="cat-btn" data-slug="${s.slug}">${s.label}</button>`).join('')} ${RADAR_CATEGORIES.map(c => `<button class="cat-btn" data-cat="${c.value}">${c.label}</button>`).join('')}
</div> </div>
<div id="list" class="list"><p class="muted">در حال بارگذاری</p></div> <div id="list" class="list"><p class="muted">در حال بارگذاری</p></div>
</main>`; </main>`;
@ -1129,22 +1292,21 @@ async function renderRadarList() {
$('#newBtn').addEventListener('click', () => renderRadarEditor(null)); $('#newBtn').addEventListener('click', () => renderRadarEditor(null));
let allItems = []; let allItems = [];
let activeSlug = ''; let activeCat = '';
const safeImgUrl = (u) => /^(\/uploads\/[a-zA-Z0-9._-]+|\/[a-zA-Z0-9._\/-]+)$/.test(String(u || '')) ? u : ''; const safeImgUrl = (u) => /^(\/uploads\/[a-zA-Z0-9._-]+|\/[a-zA-Z0-9._\/-]+)$/.test(String(u || '')) ? u : '';
const catLabel = (v) => (RADAR_CATEGORIES.find(c => c.value === v) || {}).label || v;
function renderItems() { function renderItems() {
const activePage = RADAR_PAGE_SLUGS.find(s => s.slug === activeSlug); const filtered = activeCat ? allItems.filter(r => r.category === activeCat) : allItems;
const filtered = activePage ? allItems.filter(r => activePage.cats.includes(r.category)) : allItems; if (!filtered.length) { $('#list').innerHTML = `<p class="muted">مطلبی در این دسته یافت نشد.</p>`; return; }
if (!filtered.length) { $('#list').innerHTML = `<p class="muted">مطلبی در این بخش یافت نشد.</p>`; return; }
$('#list').innerHTML = filtered.map(r => { $('#list').innerHTML = filtered.map(r => {
const sections = sectionLabelsForCategory(r.category);
return ` return `
<article class="row" data-id="${escapeAttr(r.id)}"> <article class="row" data-id="${escapeAttr(r.id)}">
<div class="row-cover" style="background-image:url('${escapeAttr(safeImgUrl(r.img))}')"></div> <div class="row-cover" style="background-image:url('${escapeAttr(safeImgUrl(r.img))}')"></div>
<div class="row-main"> <div class="row-main">
<div class="row-meta"> <div class="row-meta">
${sections.map(s => `<span class="pill">${escapeHtml(s)}</span>`).join('')} ${r.category ? `<span class="pill">${escapeHtml(catLabel(r.category))}</span>` : ''}
${r.dateFa ? `<span class="muted">${escapeHtml(r.dateFa)}</span>` : ''} ${r.dateFa ? `<span class="muted">${escapeHtml(r.dateFa)}</span>` : ''}
</div> </div>
<h3>${escapeHtml(r.titleFa||'')}</h3> <h3>${escapeHtml(r.titleFa||'')}</h3>
@ -1160,7 +1322,7 @@ async function renderRadarList() {
$('#section-bar').addEventListener('click', e => { $('#section-bar').addEventListener('click', e => {
const btn = e.target.closest('.cat-btn'); if (!btn) return; const btn = e.target.closest('.cat-btn'); if (!btn) return;
activeSlug = btn.dataset.slug; activeCat = btn.dataset.cat;
document.querySelectorAll('#section-bar .cat-btn').forEach(b => b.classList.toggle('active', b === btn)); document.querySelectorAll('#section-bar .cat-btn').forEach(b => b.classList.toggle('active', b === btn));
renderItems(); renderItems();
}); });
@ -1186,7 +1348,7 @@ async function renderRadarList() {
function renderRadarEditor(item) { function renderRadarEditor(item) {
const r = item || { category:'', img:'', authorName:'', authorAvatar:'', const r = item || { category:'', img:'', authorName:'', authorAvatar:'',
titleFa:'', dateFa:'', excerptFa:'', bodyFa:'', analysisFa:'', titleFa:'', dateFa:'', excerptFa:'', bodyFa:'', analysisFa:'',
titleEn:'', dateEn:'', excerptEn:'', bodyEn:'', analysisEn:'', sortOrder:0 }; titleEn:'', dateEn:'', excerptEn:'', bodyEn:'', analysisEn:'', tags:[], source:'', sortOrder:0 };
const isEdit = !!item; const isEdit = !!item;
function sectionBadges(cat) { function sectionBadges(cat) {
@ -1198,7 +1360,7 @@ function renderRadarEditor(item) {
root.innerHTML = ` root.innerHTML = `
<header class="topbar"> <header class="topbar">
<div class="topbar-row"> <div class="topbar-row">
<div class="brand">🔭 ${isEdit?'ویرایش':'مطلب جدید'} رادار آینده</div> <div class="brand">📌 ${isEdit?'ویرایش':'مطلب جدید'} در یک نگاه</div>
<nav class="groups"></nav> <nav class="groups"></nav>
<div class="actions"><button id="backBtn" class="ghost"> بازگشت</button></div> <div class="actions"><button id="backBtn" class="ghost"> بازگشت</button></div>
</div> </div>
@ -1239,6 +1401,10 @@ function renderRadarEditor(item) {
</div> </div>
<label>تاریخ (فارسی) <input name="dateFa" value="${escapeAttr(r.dateFa||'')}" placeholder="۱۴ خرداد ۱۴۰۴" /></label> <label>تاریخ (فارسی) <input name="dateFa" value="${escapeAttr(r.dateFa||'')}" placeholder="۱۴ خرداد ۱۴۰۴" /></label>
<label>نام نویسنده <input name="authorName" value="${escapeAttr(r.authorName||'')}" /></label> <label>نام نویسنده <input name="authorName" value="${escapeAttr(r.authorName||'')}" /></label>
<label>منبع <input name="source" value="${escapeAttr(r.source||'')}" placeholder="مثلاً Financial Times" /></label>
<div class="col span-2">
<label>هشتگها (با ویرگول جدا کنید) <input name="tags" value="${escapeAttr((r.tags||[]).join('، '))}" placeholder="فولاد سبز، هیدروژن، اروپا" /></label>
</div>
<div class="col span-2"> <div class="col span-2">
<label>چکیده (فارسی) <textarea name="excerptFa" rows="3">${escapeHtml(r.excerptFa||'')}</textarea></label> <label>چکیده (فارسی) <textarea name="excerptFa" rows="3">${escapeHtml(r.excerptFa||'')}</textarea></label>
</div> </div>
@ -1308,6 +1474,8 @@ function renderRadarEditor(item) {
excerptFa: fd.get('excerptFa')||null, excerptEn: fd.get('excerptEn')||null, excerptFa: fd.get('excerptFa')||null, excerptEn: fd.get('excerptEn')||null,
bodyFa: fd.get('bodyFa')||null, bodyEn: fd.get('bodyEn')||null, bodyFa: fd.get('bodyFa')||null, bodyEn: fd.get('bodyEn')||null,
analysisFa: fd.get('analysisFa')||null, analysisEn: fd.get('analysisEn')||null, analysisFa: fd.get('analysisFa')||null, analysisEn: fd.get('analysisEn')||null,
source: fd.get('source')||null,
tags: String(fd.get('tags')||'').split(/[,،]/).map(s=>s.trim()).filter(Boolean),
sortOrder: Number(fd.get('sortOrder')||0), sortOrder: Number(fd.get('sortOrder')||0),
}; };
try { try {
@ -2036,10 +2204,12 @@ function renderAdvisoryEditor(member) {
// BANNERS // BANNERS
// ═══════════════════════════════════════════════ // ═══════════════════════════════════════════════
const POSITIONS = [ const POSITIONS = [
{ value: 'featured', label: 'گزارش ویژه (صفحه اصلی)' }, { value: 'featured', label: 'بنر صفحه اصلی' },
{ value: 'hero', label: 'هیرو (بالای صفحه اصلی)' }, { value: 'radar-foresight', label: 'صفحه رادار: آینده‌پژوهی و رصد هوشمند' },
{ value: 'announcement', label: 'اطلاع‌رسانی (نوار بالا)' }, { value: 'radar-sustainability', label: 'صفحه رادار: پایداری و فولاد سبز' },
{ value: 'promo', label: 'تبلیغاتی (درون صفحه)' }, { value: 'radar-geopolitics', label: 'صفحه رادار: ژئوپلیتیک و اقتصاد جهانی' },
{ value: 'radar-technology', label: 'صفحه رادار: فناوری و نوآوری' },
{ value: 'radar-market', label: 'صفحه رادار: بازار و زنجیره فولاد' },
]; ];
async function renderBannersList() { async function renderBannersList() {
@ -2145,7 +2315,7 @@ function renderBannerEditor(banner) {
ctaLabelEn: fd.get('ctaLabelEn') || null, ctaLabelEn: fd.get('ctaLabelEn') || null,
ctaUrl: fd.get('ctaUrl') || null, ctaUrl: fd.get('ctaUrl') || null,
image: fd.get('imageUrl') || null, image: fd.get('imageUrl') || null,
position: fd.get('position') || 'hero', position: fd.get('position') || 'featured',
active: fd.get('active') === 'on', active: fd.get('active') === 'on',
sortOrder: Number(fd.get('sortOrder') || 0), sortOrder: Number(fd.get('sortOrder') || 0),
}; };
@ -2482,4 +2652,4 @@ function renderPdfAnalyze() {
} }
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
api('/api/auth/me').then(() => renderList()).catch(() => renderLogin()); api('/api/auth/me').then(() => renderRadarList()).catch(() => renderLogin());

View File

@ -0,0 +1,120 @@
// ─────────────────────────────────────────────────────────────────────────────
// One-off content migration:
// 1) move every existing radar_item → articles(type='radarfuture') as a BLOCK
// article (رادار آینده), mapping its category to one of the 5 sub-menus.
// 2) clear radar_items, then seed it with the 10 «در یک نگاه» (/at-a-glance) cards.
//
// Safe to run once. If radarfuture articles already exist it assumes the
// migration ran and exits without doing anything.
//
// cd panel && node scripts/migrate-content.mjs
// ─────────────────────────────────────────────────────────────────────────────
import 'dotenv/config';
import { nanoid } from 'nanoid';
import { db } from '../db.js';
// radar_item.category → رادار آینده sub-menu
const CAT_TO_SUBMENU = {
market: 'بازار و زنجیره فولاد',
commodity: 'بازار و زنجیره فولاد',
tech: 'فناوری و نوآوری',
geo: 'ژئوپلیتیک و اقتصاد جهانی',
energy: 'پایداری و فولاد سبز',
};
// the 10 «در یک نگاه» cards (mirrors src/pages/AtAGlance/AtAGlance.tsx)
const GLANCE = [
{ cat:'market', img:'/news/photo-1504307651254-35680f356dfd.jpg', title:'شوک هیدروژنی به بازار جهانی فولاد',
body:['پروژه عظیم «فولاد سبز H2» در سوئد با جذب بی‌سابقه ۶.۵ میلیارد یورو سرمایه، رسماً وارد فاز اجرایی شد.','این تأسیسات با هدف کاهش ۹۵ درصدی انتشار CO₂، قواعد رقابت در زنجیره ارزش اروپا را برای همیشه بازتعریف می‌کند.'],
analysis:'تحول فولاد سبز، فرصت صادراتی ایران را در بازارهای سنتی تهدید می‌کند.', tags:['فولاد سبز','هیدروژن','اروپا'], source:'Financial Times', date:'۱۴ اردیبهشت ۱۴۰۵' },
{ cat:'market', img:'/news/photo-1581092160607-ee22731c9b4e.jpg', title:'CBAM اروپا وارد اجرای کامل شد',
body:['با اجرای کامل مکانیسم تنظیم مرزهای کربنی، صادرکنندگان فولاد به اروپا موظف به پرداخت هزینه کامل کربن شدند.','این تحول فشار بی‌سابقه‌ای بر تولیدکنندگان مبتنی بر کوره بلند وارد کرده است.'],
analysis:'ایران باید فوری‌ترین اولویت خود را کربن‌زدایی از فرآیند تولید قرار دهد.', tags:['CBAM','کربن','صادرات'], source:'Reuters', date:'۱۲ اردیبهشت ۱۴۰۵' },
{ cat:'tech', img:'/news/photo-1518770660439-4636190af475.jpg', title:'هوش مصنوعی تقاضای فولاد مراکز داده را بازتعریف می‌کند',
body:['موج ساخت مراکز داده هوش مصنوعی، تقاضای جدیدی برای مقاطع ساختمانی و فولاد ویژه ایجاد کرده است.','تحلیلگران پیش‌بینی می‌کنند این روند تا پایان دهه یکی از پیشران‌های اصلی رشد مصرف فولاد باشد.'],
analysis:'فولادسازان باید ظرفیت تولید مقاطع ساختمانی پیشرفته را افزایش دهند.', tags:['هوش مصنوعی','مراکز داده','تقاضا'], source:'Bloomberg', date:'۱۰ اردیبهشت ۱۴۰۵' },
{ cat:'tech', img:'/news/photo-1567789884554-0b844b597180.jpg', title:'اتوماسیون کوره‌های القایی در ایران',
body:['چندین کارخانه بزرگ فولاد ایران سرمایه‌گذاری در سیستم‌های کنترل هوشمند کوره را آغاز کرده‌اند.','این سیستم‌ها توانایی کاهش مصرف انرژی تا ۱۵ درصد را دارند.'],
analysis:'گام مثبتی است اما سرعت پیاده‌سازی باید افزایش یابد.', tags:['اتوماسیون','کوره','انرژی'], source:'فولاد نیوز', date:'۸ اردیبهشت ۱۴۰۵' },
{ cat:'geo', img:'/news/photo-1540575467063-178a50c2df87.jpg', title:'تنش‌های خاورمیانه و تجارت فولاد',
body:['ناآرامی‌های منطقه‌ای مسیرهای حمل‌ونقل دریایی را مختل کرده و هزینه‌های لجستیک را افزایش داده است.','صادرکنندگان ایرانی با چالش‌های اضافی در انتقال محصولات روبرو هستند.'],
analysis:'تنوع‌بخشی در مسیرهای صادراتی اکنون یک ضرورت استراتژیک است.', tags:['ژئوپلیتیک','حمل دریایی','صادرات'], source:'Al Jazeera', date:'۶ اردیبهشت ۱۴۰۵' },
{ cat:'geo', img:'/news/photo-1570168007204-dfb528c6958f.jpg', title:'رقابت چین و آمریکا بر سر فلزات استراتژیک',
body:['تنش‌های ژئوپلیتیک میان قدرت‌های بزرگ، زنجیره تأمین فلزات حیاتی را بی‌ثبات کرده است.','این رقابت فرصت‌هایی برای تأمین‌کنندگان جایگزین ایجاد می‌کند.'],
analysis:'ایران می‌تواند نقش تأمین‌کننده جایگزین برخی فلزات را ایفا کند.', tags:['چین','آمریکا','فلزات'], source:'Reuters', date:'۴ اردیبهشت ۱۴۰۵' },
{ cat:'energy', img:'/news/photo-1466611653911-95081537e5b7.jpg', title:'بحران گاز و صنعت فولاد ایران',
body:['کمبود گاز در فصل سرما چندین واحد فولادسازی را مجبور به تعلیق موقت تولید کرد.','این چالش ساختاری نیاز به سرمایه‌گذاری جدی در زیرساخت انرژی دارد.'],
analysis:'بدون تأمین پایدار انرژی، اهداف تولید فولاد محقق نمی‌شود.', tags:['گاز','انرژی','تولید'], source:'ایسنا', date:'۲ اردیبهشت ۱۴۰۵' },
{ cat:'energy', img:'/news/photo-1611273426858-450d8e3c9fce.jpg', title:'انرژی خورشیدی برای فولادسازی پایدار',
body:['پتانسیل عظیم انرژی خورشیدی ایران می‌تواند پایه‌ای برای فولادسازی کم‌کربن باشد.','اولین پروژه‌های آزمایشی ترکیب پنل خورشیدی با کوره‌های الکتریکی در حال بررسی است.'],
analysis:'سرمایه‌گذاری در این حوزه می‌تواند چند مشکل را هم‌زمان حل کند.', tags:['خورشیدی','فولاد سبز','EAF'], source:'PV Magazine', date:'۳۰ فروردین ۱۴۰۵' },
{ cat:'commodity', img:'/news/photo-1540575467063-178a50c2df87.jpg', title:'قیمت سنگ‌آهن در مسیر کاهش',
body:['کاهش تقاضای چین در بخش ساختمان قیمت سنگ‌آهن را به پایین‌ترین سطح دو سال گذشته رساند.','این کاهش برای تولیدکنندگان فولاد مبتنی بر کوره بلند مزیت هزینه‌ای ایجاد می‌کند.'],
analysis:'فرصتی برای افزایش ذخیره‌سازی سنگ‌آهن با قیمت پایین وجود دارد.', tags:['سنگ‌آهن','چین','قیمت'], source:'Bloomberg', date:'۲۸ فروردین ۱۴۰۵' },
{ cat:'commodity', img:'/news/photo-1504307651254-35680f356dfd.jpg', title:'نوسان قیمت زغال‌سنگ کک‌شو',
body:['محدودیت‌های صادرات استرالیا و بارندگی در مناطق استخراجی، قیمت زغال کک‌شو را نوسانی کرده است.','این بی‌ثباتی برنامه‌ریزی تولید را برای کارخانه‌های کوره بلند دشوار می‌کند.'],
analysis:'تنوع در منابع تأمین زغال‌سنگ یک اولویت استراتژیک است.', tags:['زغال‌سنگ','استرالیا','تولید'], source:'Metal Bulletin', date:'۲۶ فروردین ۱۴۰۵' },
];
const done = await db.prepare("SELECT COUNT(*)::int AS n FROM articles WHERE type = 'radarfuture'").get();
if (done.n > 0) {
console.log(`${done.n} radarfuture article(s) already exist — migration already ran, exiting.`);
process.exit(0);
}
// 1) radar_items → articles(radarfuture) as block articles
const radars = await db.prepare('SELECT * FROM radar_items ORDER BY sort_order, created_at').all();
let migrated = 0;
for (const r of radars) {
const blocks = [];
if (r.img) blocks.push({ type: 'image', url: r.img, caption: '' });
if (r.body_fa) blocks.push({ type: 'text', value: r.body_fa });
if (r.analysis_fa) blocks.push({ type: 'text', value: 'تحلیل راهبردی: ' + r.analysis_fa });
if (!blocks.length) blocks.push({ type: 'text', value: r.excerpt_fa || r.title_fa || '' });
const tags = r.tags ? JSON.parse(r.tags) : [];
const sources = r.source ? [{ title: r.source, url: '' }] : [];
await db.prepare(`INSERT INTO articles
(id,title,category,type,publish_date,summary,body,cover_image,blocks,sources,tags,featured)
VALUES (@id,@title,@category,'radarfuture',@publish_date,@summary,@body,@cover_image,@blocks,@sources,@tags,0)`)
.run({
id: nanoid(14),
title: r.title_fa || 'بدون عنوان',
category: CAT_TO_SUBMENU[r.category] || 'آینده‌پژوهی و رصد هوشمند',
publish_date: r.date_fa || null,
summary: r.excerpt_fa || null,
body: r.body_fa || null,
cover_image: r.img || null,
blocks: JSON.stringify(blocks),
sources: JSON.stringify(sources),
tags: JSON.stringify(tags),
});
migrated++;
}
console.log(`✓ migrated ${migrated} radar_item(s) → رادار آینده (articles/radarfuture)`);
// 2) clear radar_items, seed with the 10 «در یک نگاه» cards
await db.prepare('DELETE FROM radar_items').run();
let seeded = 0;
for (let i = 0; i < GLANCE.length; i++) {
const c = GLANCE[i];
await db.prepare(`INSERT INTO radar_items
(id,category,img,title_fa,date_fa,excerpt_fa,body_fa,analysis_fa,tags,source,sort_order)
VALUES (@id,@category,@img,@title_fa,@date_fa,@excerpt_fa,@body_fa,@analysis_fa,@tags,@source,@sort_order)`)
.run({
id: nanoid(14),
category: c.cat,
img: c.img,
title_fa: c.title,
date_fa: c.date,
excerpt_fa: c.body[0] || '',
body_fa: c.body.join('\n\n'),
analysis_fa: c.analysis,
tags: JSON.stringify(c.tags),
source: c.source,
sort_order: i,
});
seeded++;
}
console.log(`✓ seeded ${seeded} «در یک نگاه» card(s) → radar_items`);
console.log('🎉 migration complete.');
process.exit(0);

View File

@ -0,0 +1,202 @@
// Seed mock «در یک نگاه» items (radar_items) across the 5 categories.
// Does NOT touch «رادار آینده» (radarFuture / radar_pages).
// Run from panel/: node scripts/seed-glance-mock.mjs
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import pg from 'pg';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// minimal .env loader (panel/.env)
const envPath = path.join(__dirname, '..', '.env');
if (fs.existsSync(envPath)) {
for (const line of fs.readFileSync(envPath, 'utf8').split(/\r?\n/)) {
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
if (m && !process.env[m[1]]) process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
}
}
if (!process.env.DATABASE_URL) { console.error('DATABASE_URL not set'); process.exit(1); }
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
ssl: process.env.DATABASE_SSL === 'disable' ? false : { rejectUnauthorized: false },
});
const IMGS = [
'/news/photo-1611974789855-9c2a0a7236a3.jpg',
'/news/photo-1518770660439-4636190af475.jpg',
'/news/photo-1581092160607-ee22731c9b4e.jpg',
'/news/photo-1466611653911-95081537e5b7.jpg',
'/news/photo-1504307651254-35680f356dfd.jpg',
];
const DATES_FA = ['فروردین ۱۴۰۵', 'اردیبهشت ۱۴۰۵', 'خرداد ۱۴۰۵', 'تیر ۱۴۰۵'];
const DATES_EN = ['Mar 2026', 'Apr 2026', 'May 2026', 'Jun 2026'];
const AUTHOR = 'تحریریه اندیشکده فولاد آینده';
// 4 topics per category: [titleFa, excerptFa, bodyFa(2 paras), analysisFa, titleEn, excerptEn, bodyEn, analysisEn]
const DATA = {
market: [
['روند قیمت جهانی فولاد در نیمه دوم ۱۴۰۵', 'بررسی عوامل کلان مؤثر بر قیمت فولاد در بازارهای جهانی.',
['قیمت جهانی فولاد در ماه‌های اخیر تحت تأثیر تقاضای چین، سیاست‌های پولی بانک‌های مرکزی و نوسان هزینه انرژی قرار گرفته است.', 'تحلیل‌گران معتقدند تثبیت تقاضای ساخت‌وساز و کاهش نرخ بهره می‌تواند کف قیمتی جدیدی برای بازار رقم بزند.'],
'برای فولادسازان ایرانی، پایش هفتگی شاخص‌های قیمت و پوشش ریسک ارزی، کلید حفظ حاشیه سود در این دوره نوسانی است.',
'Global Steel Price Trend in H2 2026', 'Macro drivers shaping steel prices in world markets.',
['Global steel prices have been driven by Chinese demand, central-bank policy and volatile energy costs.'], 'Iranian mills should hedge FX exposure and track weekly price indices to protect margins.'],
['تأثیر تورم جهانی بر تقاضای فولاد', 'چگونه تورم و سیاست انقباضی، مصرف فولاد را بازتعریف می‌کند.',
['تورم پایدار در اقتصادهای بزرگ، هزینه تأمین مالی پروژه‌های زیرساختی را افزایش داده و بخشی از تقاضای فولاد را به تعویق انداخته است.', 'با این حال، طرح‌های توسعه زیرساخت در آسیا و خاورمیانه همچنان موتور محرک تقاضا باقی مانده‌اند.'],
'تمرکز بر بازارهای منطقه‌ای با تقاضای ساختاری بالا، ریسک کاهش مصرف ناشی از تورم را تا حد زیادی خنثی می‌کند.',
'Global Inflation and Steel Demand', 'How inflation reshapes steel consumption.',
['Persistent inflation has raised infrastructure financing costs and deferred part of steel demand.'], 'Focusing on regional markets with structural demand offsets inflation-driven consumption risk.'],
['چشم‌انداز بازار سنگ‌آهن و قراضه', 'تعادل عرضه و تقاضا در زنجیره مواد اولیه فولاد.',
['بازار سنگ‌آهن با افزایش عرضه استرالیا و برزیل و همزمان رشد تقاضای کوره‌های قوس الکتریکی برای قراضه، وارد فاز جدیدی شده است.', 'قیمت قراضه به‌عنوان شاخص پیشرو، نقش مهمی در پیش‌بینی مسیر قیمت محصولات نهایی دارد.'],
'تنوع‌بخشی به منابع تأمین سنگ‌آهن و توسعه بازار قراضه داخلی، مزیت رقابتی پایداری ایجاد می‌کند.',
'Iron Ore and Scrap Market Outlook', 'Supply-demand balance in steel raw materials.',
['Iron ore supply from Australia and Brazil rose while EAF scrap demand climbed.'], 'Diversifying ore sources and growing the domestic scrap market builds durable advantage.'],
['نوسانات نرخ ارز و صادرات فولاد ایران', 'اثر بی‌ثباتی ارزی بر رقابت‌پذیری صادرات.',
['نوسان نرخ ارز هم فرصت و هم تهدید برای صادرکنندگان فولاد است؛ تضعیف پول ملی رقابت قیمتی را بهبود می‌دهد اما هزینه واردات تجهیزات را بالا می‌برد.', 'مدیریت ریسک ارزی از طریق ابزارهای پوشش و قراردادهای بلندمدت اهمیت یافته است.'],
'صادرکنندگانی که سبد ارزی متنوع و قراردادهای ارزی پوشش‌دار دارند، در برابر شوک‌های نرخ ارز تاب‌آورترند.',
'FX Volatility and Iran Steel Exports', 'How currency swings affect export competitiveness.',
['Currency volatility is both an opportunity and a threat for steel exporters.'], 'Exporters with diversified currency baskets and hedged contracts are more resilient.'],
],
tech: [
['هوش مصنوعی در بهینه‌سازی کوره‌های قوس الکتریکی', 'کاهش مصرف انرژی با مدل‌های یادگیری ماشین.',
['الگوریتم‌های یادگیری ماشین با تحلیل هزاران پارامتر فرآیندی، نقطه بهینه مصرف انرژی و افزودنی‌ها را در کوره‌های قوس الکتریکی پیش‌بینی می‌کنند.', 'کارخانه‌های پیشرو تا ۱۲ درصد کاهش مصرف برق و بهبود کیفیت مذاب را گزارش کرده‌اند.'],
'سرمایه‌گذاری در زیرساخت داده و حسگرها پیش‌نیاز بهره‌برداری واقعی از هوش مصنوعی در فولادسازی است.',
'AI for Electric Arc Furnace Optimization', 'Cutting energy use with machine learning.',
['ML models predict the optimal energy and additive set-point in EAFs.'], 'Investing in data infrastructure and sensors is a prerequisite for real AI gains.'],
['دوقلوی دیجیتال در خطوط نورد', 'شبیه‌سازی بی‌درنگ برای کنترل کیفیت.',
['دوقلوی دیجیتال نسخه‌ای مجازی از خط نورد می‌سازد که امکان شبیه‌سازی سناریوها و پیش‌بینی عیوب پیش از وقوع را فراهم می‌کند.', 'این فناوری زمان توقف برنامه‌ریزی‌نشده را به‌طور محسوس کاهش می‌دهد.'],
'استقرار تدریجی دوقلوی دیجیتال از واحدهای پرریسک، مسیر کم‌هزینه‌تری برای تحول دیجیتال است.',
'Digital Twins in Rolling Mills', 'Real-time simulation for quality control.',
['A digital twin mirrors the rolling line to simulate scenarios and predict defects.'], 'Rolling out digital twins from high-risk units first is the lower-cost path.'],
['چاپ سه‌بعدی فلزی و آینده تولید قطعات', 'ساخت افزایشی در صنعت فولاد.',
['چاپ سه‌بعدی فلزی امکان تولید قطعات یدکی پیچیده و کم‌تیراژ را بدون نیاز به قالب فراهم می‌کند و زنجیره تأمین را کوتاه می‌سازد.', 'کاربرد آن در ساخت قطعات تعمیراتی کوره‌ها رو به گسترش است.'],
'ساخت افزایشی برای قطعات بحرانی و کمیاب، ریسک توقف تولید ناشی از کمبود قطعه را کاهش می‌دهد.',
'Metal 3D Printing and the Future of Parts', 'Additive manufacturing in steel.',
['Metal 3D printing enables complex, low-volume spare parts without tooling.'], 'Additive manufacturing for critical, scarce parts reduces downtime risk.'],
['اینترنت اشیای صنعتی در فولادسازی', 'پایش بی‌درنگ تجهیزات و نگهداری پیش‌بینانه.',
['با اتصال هزاران حسگر به پلتفرم‌های ابری، وضعیت سلامت تجهیزات به‌صورت لحظه‌ای پایش و خرابی‌ها پیش‌بینی می‌شود.', 'نگهداری پیش‌بینانه نرخ خرابی ناگهانی را به‌طور چشمگیری کاهش داده است.'],
'یکپارچگی داده میان واحدها، شرط تبدیل داده‌های حسگر به تصمیم‌های عملیاتی ارزشمند است.',
'Industrial IoT in Steelmaking', 'Real-time monitoring and predictive maintenance.',
['Thousands of cloud-connected sensors monitor equipment health in real time.'], 'Cross-unit data integration turns sensor data into valuable operational decisions.'],
],
commodity: [
['زنجیره تأمین عناصر نادر خاکی', 'وابستگی صنعت به مواد معدنی حیاتی.',
['عناصر نادر خاکی و فلزات حیاتی، نقش راهبردی در فناوری‌های نوین و آلیاژهای پیشرفته فولاد دارند و تمرکز عرضه آن‌ها در چند کشور، ریسک ژئواکونومیک ایجاد می‌کند.', 'توسعه ذخایر داخلی و بازیافت، اهرم کاهش این وابستگی است.'],
'نقشه‌برداری از ریسک تأمین مواد معدنی حیاتی باید بخشی از برنامه‌ریزی راهبردی صنعت باشد.',
'Rare Earth Supply Chain', 'Industry dependence on critical minerals.',
['Rare earths play a strategic role in advanced steel alloys and new tech.'], 'Mapping critical-mineral supply risk must be part of strategic planning.'],
['امنیت تأمین سنگ‌آهن داخلی', 'پایداری زنجیره مواد اولیه فولاد کشور.',
['با رشد ظرفیت فولادسازی، تقاضای سنگ‌آهن از عرضه داخلی پیشی گرفته و مدیریت ذخایر و کنسانتره اهمیت راهبردی یافته است.', 'سرمایه‌گذاری در اکتشاف و فرآوری، تضمین‌کننده تأمین بلندمدت است.'],
'تعادل میان صادرات سنگ‌آهن خام و تأمین خوراک داخلی، تصمیمی کلیدی برای سیاست‌گذار است.',
'Domestic Iron Ore Security', 'Stability of the national raw-material chain.',
['Steelmaking growth has outpaced domestic iron ore supply.'], 'Balancing raw ore exports with domestic feed is a key policy decision.'],
['بازار جهانی زغال‌سنگ کک‌شو', 'نقش زغال کک در مسیر تولید فولاد.',
['زغال‌سنگ کک‌شو نهاده کلیدی کوره‌بلند است و نوسان قیمت و محدودیت عرضه آن مستقیماً بر بهای تمام‌شده فولاد اثر می‌گذارد.', 'گذار به فناوری‌های کم‌کربن، تقاضای آتی این ماده را تحت‌تأثیر قرار می‌دهد.'],
'تنوع منابع تأمین زغال کک و قراردادهای بلندمدت، نوسان بهای تمام‌شده را مهار می‌کند.',
'Global Coking Coal Market', 'Cokes role in the steel route.',
['Coking coal is a key blast-furnace input; its price swings hit steel costs.'], 'Diversified coking-coal sources and long-term contracts curb cost volatility.'],
['تاب‌آوری زنجیره تأمین مواد اولیه', 'مدیریت ریسک اختلال در تأمین.',
['رویدادهای اقلیمی، تنش‌های تجاری و اعتصابات می‌توانند زنجیره تأمین مواد اولیه را مختل کنند؛ از این‌رو تاب‌آوری به اولویت راهبردی بدل شده است.', 'موجودی ایمنی و چندمنبعی‌سازی، دو ابزار اصلی کاهش این ریسک‌اند.'],
'طراحی زنجیره تأمین چندمنبعی با موجودی ایمنی هوشمند، تداوم تولید را در شوک‌ها تضمین می‌کند.',
'Raw-Material Supply Resilience', 'Managing disruption risk.',
['Climate events, trade tensions and strikes can disrupt raw-material chains.'], 'Multi-sourcing with smart safety stock ensures production continuity in shocks.'],
],
geo: [
['ژئوپلیتیک تعرفه‌های فولاد و امنیت اقتصادی', 'بازآرایی تجارت جهانی فولاد در سایه تعرفه‌ها.',
['موج جدید تعرفه‌ها و اقدامات حمایتی، مسیرهای صادراتی فولاد را بازآرایی کرده و امنیت اقتصادی تولیدکنندگان را به مسئله‌ای راهبردی تبدیل کرده است.', 'کشورهای صادرکننده ناچار به یافتن بازارهای جایگزین و ائتلاف‌های تجاری جدیدند.'],
'برای ایران، تمرکز بر بازارهای جنوب جهانی و کریدورهای منطقه‌ای، سپر ژئوپلیتیک تعرفه‌هاست.',
'Geopolitics of Steel Tariffs', 'Reshaping global trade under tariffs.',
['A new wave of tariffs is realigning steel export routes.'], 'For Iran, focusing on the Global South and regional corridors is the tariff shield.'],
['تحریم‌ها و مسیرهای جایگزین صادرات', 'راهبردهای دور زدن محدودیت‌های تجاری.',
['تحریم‌ها هزینه و پیچیدگی صادرات را افزایش می‌دهند، اما توسعه مسیرهای جایگزین و شرکای منطقه‌ای، فضای تنفس ایجاد می‌کند.', 'لجستیک منعطف و تنوع مقصد، کلید تداوم صادرات است.'],
'سرمایه‌گذاری در زیرساخت لجستیک منطقه‌ای، اثر تحریم بر جریان صادرات را کاهش می‌دهد.',
'Sanctions and Alternative Export Routes', 'Strategies around trade restrictions.',
['Sanctions raise export cost and complexity; alternative routes create breathing room.'], 'Investing in regional logistics infrastructure dampens sanctions impact on exports.'],
['رقابت چین و آمریکا در بازار فولاد', 'اثر رقابت دو ابرقدرت بر بازارهای هدف.',
['رقابت راهبردی چین و آمریکا بر زنجیره ارزش فولاد، از فناوری تا تجارت، سایه افکنده و بازارهای ثالث را به میدان رقابت تبدیل کرده است.', 'مازاد عرضه چین فشار قیمتی مستمری بر بازارهای آسیا و خاورمیانه وارد می‌کند.'],
'پایش سیاست‌های تجاری دو ابرقدرت، پیش‌نیاز تصمیم‌گیری صادراتی هوشمندانه است.',
'US-China Rivalry in Steel', 'Superpower competition in target markets.',
['US-China rivalry shadows the steel value chain from tech to trade.'], 'Monitoring both powers trade policy is a prerequisite for smart export decisions.'],
['کریدورهای ترانزیتی و امنیت تجارت', 'نقش کریدورهای منطقه‌ای در پایداری صادرات.',
['کریدورهای ترانزیتی شمال-جنوب و شرق-غرب، فرصت کاهش هزینه و زمان حمل فولاد به بازارهای منطقه‌ای را فراهم می‌کنند.', 'امنیت و پایداری این مسیرها مستقیماً بر رقابت‌پذیری صادرات اثر می‌گذارد.'],
'مشارکت فعال در توسعه کریدورهای منطقه‌ای، مزیت لجستیکی پایدار ایجاد می‌کند.',
'Transit Corridors and Trade Security', 'Regional corridors for export stability.',
['North-South and East-West corridors cut steel shipping cost and time.'], 'Active participation in regional corridors builds durable logistics advantage.'],
],
energy: [
['توافق‌نامه‌های تجاری منطقه‌ای و فولاد', 'فرصت‌های ناشی از پیمان‌های تجاری.',
['پیمان‌های تجاری منطقه‌ای با کاهش تعرفه و تسهیل گمرکی، دسترسی فولاد ایران به بازارهای همسایه را آسان‌تر می‌کنند.', 'بهره‌گیری از این ظرفیت نیازمند انطباق با استانداردها و گواهی‌های مقصد است.'],
'تطبیق محصولات با استانداردهای بازار هدف، پیش‌شرط بهره‌برداری از مزایای پیمان‌های تجاری است.',
'Regional Trade Pacts and Steel', 'Opportunities from trade agreements.',
['Regional trade pacts ease Iranian steel access to neighboring markets.'], 'Meeting destination standards is a precondition for reaping pact benefits.'],
['فرصت‌های صادرات به آفریقا و آسیای جنوب شرقی', 'بازارهای نوظهور با تقاضای رو به رشد.',
['رشد جمعیت و شهرنشینی در آفریقا و آسیای جنوب شرقی، تقاضای پایداری برای مقاطع ساختمانی فولاد ایجاد کرده است.', 'این بازارها کمتر در معرض فشارهای ژئوپلیتیک غرب قرار دارند.'],
'حضور زودهنگام و ساخت شبکه توزیع محلی، سهم بازار بلندمدت در این مقاصد را تثبیت می‌کند.',
'Export Opportunities in Africa and SE Asia', 'Emerging markets with rising demand.',
['Population growth and urbanization drive steady steel demand in Africa and SE Asia.'], 'Early entry and local distribution networks lock in long-term market share.'],
['تجارت مرزی و بازارهای همسایه', 'ظرفیت بازارهای کم‌فاصله برای صادرات.',
['بازارهای همسایه با هزینه حمل پایین و نیاز ساختاری به فولاد، مقصد طبیعی صادرات کوتاه‌زنجیره‌اند.', 'تسهیل تجارت مرزی و بازارچه‌ها، جریان صادرات خرد را تقویت می‌کند.'],
'تمرکز بر بازارهای همسایه با لجستیک کوتاه، حاشیه سود صادرات را به‌دلیل هزینه حمل پایین بهبود می‌دهد.',
'Border Trade and Neighboring Markets', 'Short-haul export potential.',
['Neighboring markets with low freight cost are natural short-chain export targets.'], 'Focusing on nearby markets improves export margins via low freight cost.'],
['کسب‌وکارهای صادرات‌محورِ فولاد', 'مدل‌های نوین تجارت بین‌المللی.',
['شرکت‌های صادرات‌محور با تخصصی‌سازی در بازارهای هدف و خدمات پس از فروش، ارزش افزوده بالاتری نسبت به صادرات خام ایجاد می‌کنند.', 'برندسازی و کیفیت پایدار، مزیت رقابتی این مدل است.'],
'گذار از صادرات خام به مدل صادرات‌محورِ برنددار، تاب‌آوری درآمد ارزی را افزایش می‌دهد.',
'Export-Oriented Steel Businesses', 'New international trade models.',
['Export-focused firms add more value than raw exports via specialization and after-sales.'], 'Shifting from raw exports to branded export models boosts FX revenue resilience.'],
],
};
const rows = [];
let order = 0;
const cats = Object.keys(DATA);
cats.forEach((cat, ci) => {
DATA[cat].forEach((t, i) => {
rows.push({
id: `glance-${cat}-${i + 1}`,
category: cat,
img: IMGS[(ci + i) % IMGS.length],
author_name: AUTHOR,
author_avatar: '',
title_fa: t[0], date_fa: DATES_FA[i % DATES_FA.length], excerpt_fa: t[1],
body_fa: t[2].join('\n\n'), analysis_fa: t[3],
title_en: t[4], date_en: DATES_EN[i % DATES_EN.length], excerpt_en: t[5],
body_en: t[6].join('\n\n'), analysis_en: t[7],
sort_order: order++,
});
});
});
const SQL = `
INSERT INTO radar_items
(id, category, img, author_name, author_avatar, title_fa, date_fa, excerpt_fa, body_fa, analysis_fa,
title_en, date_en, excerpt_en, body_en, analysis_en, sort_order)
VALUES
($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
ON CONFLICT (id) DO UPDATE SET
category=EXCLUDED.category, img=EXCLUDED.img, author_name=EXCLUDED.author_name,
title_fa=EXCLUDED.title_fa, date_fa=EXCLUDED.date_fa, excerpt_fa=EXCLUDED.excerpt_fa,
body_fa=EXCLUDED.body_fa, analysis_fa=EXCLUDED.analysis_fa,
title_en=EXCLUDED.title_en, date_en=EXCLUDED.date_en, excerpt_en=EXCLUDED.excerpt_en,
body_en=EXCLUDED.body_en, analysis_en=EXCLUDED.analysis_en, sort_order=EXCLUDED.sort_order,
updated_at=now();
`;
const main = async () => {
let n = 0;
for (const r of rows) {
await pool.query(SQL, [
r.id, r.category, r.img, r.author_name, r.author_avatar, r.title_fa, r.date_fa, r.excerpt_fa, r.body_fa, r.analysis_fa,
r.title_en, r.date_en, r.excerpt_en, r.body_en, r.analysis_en, r.sort_order,
]);
n++;
}
const counts = await pool.query(
"SELECT category, count(*)::int AS c FROM radar_items GROUP BY category ORDER BY category"
);
console.log(`seeded ${n} mock «در یک نگاه» items.`);
console.table(counts.rows);
await pool.end();
};
main().catch((e) => { console.error(e); process.exit(1); });

View File

@ -6,7 +6,7 @@ import cookieParser from 'cookie-parser';
import rateLimit from 'express-rate-limit'; import rateLimit from 'express-rate-limit';
import helmet from 'helmet'; import helmet from 'helmet';
import multer from 'multer'; import multer from 'multer';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
import sharp from 'sharp'; import sharp from 'sharp';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
@ -25,6 +25,9 @@ if (!JWT_SECRET || JWT_SECRET.length < 32) {
process.exit(1); process.exit(1);
} }
const PUBLIC_ORIGIN = process.env.PUBLIC_ORIGIN || `http://localhost:${PORT}`; const PUBLIC_ORIGIN = process.env.PUBLIC_ORIGIN || `http://localhost:${PORT}`;
// Canonical base for media URLs. The object-storage host blocks browser User-Agents,
// so we never hand browsers a direct storage URL — they go through this app's /media proxy.
const MEDIA_BASE = (process.env.MEDIA_BASE || PUBLIC_ORIGIN).replace(/\/+$/, '');
// On Liara, set UPLOADS_DIR to a mounted Disk path (e.g. /var/lib/uploads) so // On Liara, set UPLOADS_DIR to a mounted Disk path (e.g. /var/lib/uploads) so
// uploaded images survive redeploys. Defaults to a local folder for dev. // uploaded images survive redeploys. Defaults to a local folder for dev.
@ -86,7 +89,7 @@ async function putObject(key, body, contentType) {
Body: body, Body: body,
ContentType: contentType, ContentType: contentType,
})); }));
return `https://${process.env.LIARA_BUCKET}.${s3Host}/${key}`; return `${MEDIA_BASE}/media/${key}`;
} }
const app = express(); const app = express();
@ -110,7 +113,7 @@ app.use(helmet({
defaultSrc: ["'self'"], defaultSrc: ["'self'"],
scriptSrc: ["'self'"], scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "blob:"], imgSrc: ["'self'", "data:", "blob:", ...(s3Host && process.env.LIARA_BUCKET ? [`https://${process.env.LIARA_BUCKET}.${s3Host}`] : [])],
connectSrc: ["'self'"], connectSrc: ["'self'"],
fontSrc: ["'self'"], fontSrc: ["'self'"],
objectSrc: ["'none'"], objectSrc: ["'none'"],
@ -120,6 +123,28 @@ app.use(helmet({
frameguard: { action: 'deny' }, frameguard: { action: 'deny' },
})); }));
app.use(express.json({ limit: '50mb' })); app.use(express.json({ limit: '50mb' }));
// Public media proxy: streams an object from storage server-side and relays it from
// our own domain. Browsers are blocked from the storage host by a User-Agent filter,
// so this is the only way an <img>/<video> can load uploaded media in a browser.
app.get('/media/:key', async (req, res) => {
if (!s3) return res.status(404).end();
try {
const range = req.headers.range;
const obj = await s3.send(new GetObjectCommand({
Bucket: process.env.LIARA_BUCKET, Key: req.params.key, Range: range || undefined,
}));
if (obj.ContentType) res.setHeader('Content-Type', obj.ContentType);
res.setHeader('Accept-Ranges', 'bytes');
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
// allow the site (different origin) to embed this media — overrides helmet's same-origin CORP
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
if (obj.ContentRange) { res.status(206); res.setHeader('Content-Range', obj.ContentRange); }
if (obj.ContentLength != null) res.setHeader('Content-Length', String(obj.ContentLength));
obj.Body.pipe(res);
} catch {
res.status(404).end();
}
});
app.use('/uploads', express.static(uploadsDir, { maxAge: '7d' })); app.use('/uploads', express.static(uploadsDir, { maxAge: '7d' }));
app.use('/', express.static(path.join(__dirname, 'public'))); app.use('/', express.static(path.join(__dirname, 'public')));
@ -200,12 +225,12 @@ app.post('/api/auth/login', loginLimiter, async (req, res) => {
if (!row || !bcrypt.compareSync(password, row.password_hash)) { if (!row || !bcrypt.compareSync(password, row.password_hash)) {
return res.status(401).json({ error: 'bad_credentials' }); return res.status(401).json({ error: 'bad_credentials' });
} }
const token = jwt.sign({ sub: row.id, username: row.username, role: row.role }, JWT_SECRET, { expiresIn: '24h' }); const token = jwt.sign({ sub: row.id, username: row.username, role: row.role }, JWT_SECRET, { expiresIn: '7d' });
res.cookie('session', token, { res.cookie('session', token, {
httpOnly: true, httpOnly: true,
secure: cookieSecure(req), secure: cookieSecure(req),
sameSite: 'strict', sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000, maxAge: 7 * 24 * 60 * 60 * 1000,
path: '/', path: '/',
}); });
// token is delivered only via the HttpOnly cookie — never in the body (XSS can't read it) // token is delivered only via the HttpOnly cookie — never in the body (XSS can't read it)
@ -224,7 +249,7 @@ app.get('/api/auth/me', authRequired, (req, res) => {
const PUBLIC_FIELDS = ` const PUBLIC_FIELDS = `
id, title, category, type, author, author_role, author_initial, id, title, category, type, author, author_role, author_initial,
publish_date, pages, price, is_free, summary, body, cover_image, publish_date, pages, price, is_free, summary, body, cover_image,
tags, featured, created_at, updated_at blocks, sources, tags, featured, created_at, updated_at
`; `;
app.get('/api/articles', async (req, res) => { app.get('/api/articles', async (req, res) => {
@ -327,6 +352,8 @@ function normalizeArticleBody(b) {
summary: b.summary || null, summary: b.summary || null,
body: b.body || null, body: b.body || null,
cover_image: b.coverImage || null, cover_image: b.coverImage || null,
blocks: JSON.stringify(Array.isArray(b.blocks) ? b.blocks : []),
sources: JSON.stringify(Array.isArray(b.sources) ? b.sources : []),
tags: JSON.stringify(Array.isArray(b.tags) ? b.tags : []), tags: JSON.stringify(Array.isArray(b.tags) ? b.tags : []),
featured: b.featured ? 1 : 0, featured: b.featured ? 1 : 0,
}; };
@ -340,11 +367,11 @@ app.post('/api/articles', authRequired, async (req, res) => {
INSERT INTO articles ( INSERT INTO articles (
id, title, category, type, author, author_role, author_initial, id, title, category, type, author, author_role, author_initial,
publish_date, pages, price, is_free, summary, body, cover_image, publish_date, pages, price, is_free, summary, body, cover_image,
tags, featured blocks, sources, tags, featured
) VALUES ( ) VALUES (
@id, @title, @category, @type, @author, @author_role, @author_initial, @id, @title, @category, @type, @author, @author_role, @author_initial,
@publish_date, @pages, @price, @is_free, @summary, @body, @cover_image, @publish_date, @pages, @price, @is_free, @summary, @body, @cover_image,
@tags, @featured @blocks, @sources, @tags, @featured
) )
`).run({ id, ...a }); `).run({ id, ...a });
const row = await db.prepare(`SELECT ${PUBLIC_FIELDS} FROM articles WHERE id = ?`).get(id); const row = await db.prepare(`SELECT ${PUBLIC_FIELDS} FROM articles WHERE id = ?`).get(id);
@ -362,6 +389,7 @@ app.put('/api/articles/:id', authRequired, async (req, res) => {
author = @author, author_role = @author_role, author_initial = @author_initial, author = @author, author_role = @author_role, author_initial = @author_initial,
publish_date = @publish_date, pages = @pages, price = @price, is_free = @is_free, publish_date = @publish_date, pages = @pages, price = @price, is_free = @is_free,
summary = @summary, body = @body, cover_image = @cover_image, summary = @summary, body = @body, cover_image = @cover_image,
blocks = @blocks, sources = @sources,
tags = @tags, featured = @featured, tags = @tags, featured = @featured,
updated_at = now() updated_at = now()
WHERE id = @id WHERE id = @id
@ -540,7 +568,7 @@ app.delete('/api/events/:id', authRequired, async (req, res) => {
const RADAR_FIELDS = `id, category, img, author_name, author_avatar, const RADAR_FIELDS = `id, category, img, author_name, author_avatar,
title_fa, date_fa, excerpt_fa, body_fa, analysis_fa, title_fa, date_fa, excerpt_fa, body_fa, analysis_fa,
title_en, date_en, excerpt_en, body_en, analysis_en, title_en, date_en, excerpt_en, body_en, analysis_en,
sort_order, created_at, updated_at`; tags, source, sort_order, created_at, updated_at`;
app.get('/api/radar', async (req, res) => { app.get('/api/radar', async (req, res) => {
const limit = Math.min(Number(req.query.limit) || 50, 500); const limit = Math.min(Number(req.query.limit) || 50, 500);
@ -570,6 +598,8 @@ function normalizeRadarBody(b) {
excerpt_en: b.excerptEn || null, excerpt_en: b.excerptEn || null,
body_en: b.bodyEn || null, body_en: b.bodyEn || null,
analysis_en: b.analysisEn || null, analysis_en: b.analysisEn || null,
tags: JSON.stringify(Array.isArray(b.tags) ? b.tags : []),
source: b.source || null,
sort_order: Number(b.sortOrder) || 0, sort_order: Number(b.sortOrder) || 0,
}; };
} }
@ -580,10 +610,10 @@ app.post('/api/radar', authRequired, async (req, res) => {
const id = nanoid(14); const id = nanoid(14);
await db.prepare(`INSERT INTO radar_items (id,category,img,author_name,author_avatar, await db.prepare(`INSERT INTO radar_items (id,category,img,author_name,author_avatar,
title_fa,date_fa,excerpt_fa,body_fa,analysis_fa, title_fa,date_fa,excerpt_fa,body_fa,analysis_fa,
title_en,date_en,excerpt_en,body_en,analysis_en,sort_order) title_en,date_en,excerpt_en,body_en,analysis_en,tags,source,sort_order)
VALUES (@id,@category,@img,@author_name,@author_avatar, VALUES (@id,@category,@img,@author_name,@author_avatar,
@title_fa,@date_fa,@excerpt_fa,@body_fa,@analysis_fa, @title_fa,@date_fa,@excerpt_fa,@body_fa,@analysis_fa,
@title_en,@date_en,@excerpt_en,@body_en,@analysis_en,@sort_order) @title_en,@date_en,@excerpt_en,@body_en,@analysis_en,@tags,@source,@sort_order)
`).run({ id, ...r }); `).run({ id, ...r });
res.status(201).json(rowToRadarItem(await db.prepare(`SELECT ${RADAR_FIELDS} FROM radar_items WHERE id = ?`).get(id))); res.status(201).json(rowToRadarItem(await db.prepare(`SELECT ${RADAR_FIELDS} FROM radar_items WHERE id = ?`).get(id)));
}); });
@ -597,6 +627,7 @@ app.put('/api/radar/:id', authRequired, async (req, res) => {
body_fa=@body_fa,analysis_fa=@analysis_fa, body_fa=@body_fa,analysis_fa=@analysis_fa,
title_en=@title_en,date_en=@date_en,excerpt_en=@excerpt_en, title_en=@title_en,date_en=@date_en,excerpt_en=@excerpt_en,
body_en=@body_en,analysis_en=@analysis_en, body_en=@body_en,analysis_en=@analysis_en,
tags=@tags,source=@source,
sort_order=@sort_order,updated_at=now() WHERE id=@id sort_order=@sort_order,updated_at=now() WHERE id=@id
`).run({ id: req.params.id, ...r }); `).run({ id: req.params.id, ...r });
res.json(rowToRadarItem(await db.prepare(`SELECT ${RADAR_FIELDS} FROM radar_items WHERE id = ?`).get(req.params.id))); res.json(rowToRadarItem(await db.prepare(`SELECT ${RADAR_FIELDS} FROM radar_items WHERE id = ?`).get(req.params.id)));
@ -1546,24 +1577,26 @@ ${text.slice(0, 6000)}
// ────────────────────────────────────────── // ──────────────────────────────────────────
app.post('/api/members/register', loginLimiter, async (req, res) => { app.post('/api/members/register', loginLimiter, async (req, res) => {
const { name, email, password } = req.body || {}; const { name, email, password, phone } = req.body || {};
if (!name || !email || !password) return res.status(400).json({ error: 'همه فیلدها الزامی است' }); if (!name || !email || !password) return res.status(400).json({ error: 'همه فیلدها الزامی است' });
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return res.status(400).json({ error: 'ایمیل نامعتبر است' }); if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return res.status(400).json({ error: 'ایمیل نامعتبر است' });
if (password.length < 6) return res.status(400).json({ error: 'رمز عبور باید حداقل ۶ کاراکتر باشد' }); if (password.length < 6) return res.status(400).json({ error: 'رمز عبور باید حداقل ۶ کاراکتر باشد' });
const exists = await db.prepare('SELECT 1 FROM members WHERE email = ?').get(email); const exists = await db.prepare('SELECT 1 FROM members WHERE email = ?').get(email);
if (exists) return res.status(409).json({ error: 'این ایمیل قبلاً ثبت شده است' }); if (exists) return res.status(409).json({ error: 'این ایمیل قبلاً ثبت شده است' });
const hash = bcrypt.hashSync(password, 10); const hash = bcrypt.hashSync(password, 10);
const info = await db.prepare('INSERT INTO members (name, email, password_hash) VALUES (?, ?, ?) RETURNING id').run(name.trim(), email.toLowerCase().trim(), hash); const info = await db.prepare('INSERT INTO members (name, email, password_hash, phone) VALUES (?, ?, ?, ?) RETURNING id').run(name.trim(), email.toLowerCase().trim(), hash, phone ? String(phone).trim() : null);
const token = jwt.sign({ sub: info.lastInsertRowid, email, name: name.trim(), role: 'member' }, JWT_SECRET, { expiresIn: '30d' }); const token = jwt.sign({ sub: info.lastInsertRowid, email, name: name.trim(), role: 'member' }, JWT_SECRET, { expiresIn: '30d' });
res.cookie('member_session', token, memberCookieOpts(req)); res.cookie('member_session', token, memberCookieOpts(req));
res.status(201).json({ user: { id: info.lastInsertRowid, name: name.trim(), email } }); res.status(201).json({ user: { id: info.lastInsertRowid, name: name.trim(), email } });
}); });
app.post('/api/members/login', loginLimiter, async (req, res) => { app.post('/api/members/login', loginLimiter, async (req, res) => {
const { email, password } = req.body || {}; const { email, phone, password } = req.body || {};
if (!email || !password) return res.status(400).json({ error: 'ایمیل و رمز عبور الزامی است' }); const identifier = String(email || phone || '').trim();
const row = await db.prepare('SELECT id, name, email, password_hash, is_active FROM members WHERE email = ?').get(email.toLowerCase().trim()); if (!identifier || !password) return res.status(400).json({ error: 'شماره موبایل یا ایمیل و رمز عبور الزامی است' });
if (!row || !bcrypt.compareSync(password, row.password_hash)) return res.status(401).json({ error: 'ایمیل یا رمز عبور اشتباه است' }); // accept either an email or a phone number as the login identifier
const row = await db.prepare('SELECT id, name, email, password_hash, is_active FROM members WHERE email = ? OR phone = ?').get(identifier.toLowerCase(), identifier);
if (!row || !bcrypt.compareSync(password, row.password_hash)) return res.status(401).json({ error: 'شماره موبایل/ایمیل یا رمز عبور اشتباه است' });
if (!row.is_active) return res.status(403).json({ error: 'حساب کاربری غیرفعال است' }); if (!row.is_active) return res.status(403).json({ error: 'حساب کاربری غیرفعال است' });
const token = jwt.sign({ sub: row.id, email: row.email, name: row.name, role: 'member' }, JWT_SECRET, { expiresIn: '30d' }); const token = jwt.sign({ sub: row.id, email: row.email, name: row.name, role: 'member' }, JWT_SECRET, { expiresIn: '30d' });
res.cookie('member_session', token, memberCookieOpts(req)); res.cookie('member_session', token, memberCookieOpts(req));
@ -1575,6 +1608,95 @@ app.post('/api/members/logout', (req, res) => {
res.json({ ok: true }); res.json({ ok: true });
}); });
/* OTP login/signup via Kavenegar (verify/lookup)
SFsignin existing numbers, SFsignup new numbers (auto-created on verify). */
const otpStore = new Map(); // phone -> { code, expires }
async function sendOtpSms(phone, code, template) {
const key = process.env.KAVENEGAR_API_KEY;
if (!key) throw new Error('otp_not_configured');
const url = `https://api.kavenegar.com/v1/${key}/verify/lookup.json?receptor=${encodeURIComponent(phone)}&token=${encodeURIComponent(code)}&template=${encodeURIComponent(template)}`;
const r = await fetch(url);
const j = await r.json().catch(() => null);
if (!j || j.return?.status !== 200) throw new Error('sms_failed');
}
app.post('/api/members/otp/send', loginLimiter, async (req, res) => {
const phone = String(req.body?.phone || '').trim();
if (!/^09\d{9}$/.test(phone)) return res.status(400).json({ error: 'شماره موبایل معتبر وارد کنید' });
const existing = await db.prepare('SELECT id FROM members WHERE phone = ?').get(phone);
const code = String(Math.floor(100000 + Math.random() * 900000));
otpStore.set(phone, { code, expires: Date.now() + 5 * 60 * 1000 });
try {
await sendOtpSms(phone, code, existing ? 'SFsignin' : 'SFsignup');
res.json({ ok: true, isNew: !existing });
} catch (err) {
console.error('[otp] send failed:', err.message);
res.status(502).json({ error: 'ارسال پیامک ناموفق بود' });
}
});
app.post('/api/members/otp/verify', loginLimiter, async (req, res) => {
const phone = String(req.body?.phone || '').trim();
const code = String(req.body?.code || '').trim();
const rec = otpStore.get(phone);
if (!rec || rec.expires < Date.now() || rec.code !== code) return res.status(401).json({ error: 'کد نامعتبر یا منقضی است' });
otpStore.delete(phone);
let member = await db.prepare('SELECT id, name, email, is_active FROM members WHERE phone = ?').get(phone);
if (!member) {
// auto-signup: create a member keyed by phone (random password, placeholder email)
const hash = bcrypt.hashSync(nanoid(16), 10);
const info = await db.prepare('INSERT INTO members (name, email, password_hash, phone) VALUES (?, ?, ?, ?) RETURNING id')
.run('کاربر جدید', `${phone}@otp.fstt.ir`, hash, phone);
member = { id: info.lastInsertRowid, name: 'کاربر جدید', email: `${phone}@otp.fstt.ir`, is_active: true };
}
if (!member.is_active) return res.status(403).json({ error: 'حساب کاربری غیرفعال است' });
const token = jwt.sign({ sub: member.id, email: member.email, name: member.name, role: 'member' }, JWT_SECRET, { expiresIn: '30d' });
res.cookie('member_session', token, memberCookieOpts(req));
res.json({ user: { id: member.id, name: member.name, email: member.email } });
});
/* verify the SFsignup OTP code, then create the account with the real form data */
app.post('/api/members/register-verify', loginLimiter, async (req, res) => {
const { name, email, password, code } = req.body || {};
const phone = String(req.body?.phone || '').trim();
if (!name || !email || !password || !phone) return res.status(400).json({ error: 'همه فیلدها الزامی است' });
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return res.status(400).json({ error: 'ایمیل نامعتبر است' });
if (password.length < 6) return res.status(400).json({ error: 'رمز عبور باید حداقل ۶ کاراکتر باشد' });
const rec = otpStore.get(phone);
if (!rec || rec.expires < Date.now() || rec.code !== String(code || '').trim()) return res.status(401).json({ error: 'کد تأیید نامعتبر یا منقضی است' });
if (await db.prepare('SELECT 1 FROM members WHERE email = ?').get(email.toLowerCase().trim())) return res.status(409).json({ error: 'این ایمیل قبلاً ثبت شده است' });
if (await db.prepare('SELECT 1 FROM members WHERE phone = ?').get(phone)) return res.status(409).json({ error: 'این شماره موبایل قبلاً ثبت شده است' });
otpStore.delete(phone);
const hash = bcrypt.hashSync(password, 10);
const info = await db.prepare('INSERT INTO members (name, email, password_hash, phone) VALUES (?, ?, ?, ?) RETURNING id')
.run(name.trim(), email.toLowerCase().trim(), hash, phone);
const token = jwt.sign({ sub: info.lastInsertRowid, email, name: name.trim(), role: 'member' }, JWT_SECRET, { expiresIn: '30d' });
res.cookie('member_session', token, memberCookieOpts(req));
res.status(201).json({ user: { id: info.lastInsertRowid, name: name.trim(), email } });
});
/* set password via SMS code — for members who don't know their current password (e.g. OTP signups) */
app.post('/api/members/me/password-otp/send', memberRequired, async (req, res) => {
const row = await db.prepare('SELECT phone FROM members WHERE id = ?').get(req.member.sub);
if (!row || !row.phone) return res.status(400).json({ error: 'برای این حساب شماره موبایلی ثبت نشده است' });
const code = String(Math.floor(100000 + Math.random() * 900000));
otpStore.set(row.phone, { code, expires: Date.now() + 5 * 60 * 1000 });
try { await sendOtpSms(row.phone, code, 'SFsignin'); res.json({ ok: true }); }
catch (err) { console.error('[otp:pw] send failed:', err.message); res.status(502).json({ error: 'ارسال پیامک ناموفق بود' }); }
});
app.post('/api/members/me/password-otp/set', memberRequired, async (req, res) => {
const { code, newPassword } = req.body || {};
if (!newPassword || String(newPassword).length < 6) return res.status(400).json({ error: 'رمز جدید باید حداقل ۶ کاراکتر باشد' });
const row = await db.prepare('SELECT phone FROM members WHERE id = ?').get(req.member.sub);
const rec = row?.phone ? otpStore.get(row.phone) : null;
if (!rec || rec.expires < Date.now() || rec.code !== String(code || '').trim()) return res.status(401).json({ error: 'کد نامعتبر یا منقضی است' });
otpStore.delete(row.phone);
const hash = bcrypt.hashSync(String(newPassword), 10);
await db.prepare("UPDATE members SET password_hash = ?, updated_at = now() WHERE id = ?").run(hash, req.member.sub);
res.json({ ok: true });
});
app.get('/api/members/me', memberRequired, async (req, res) => { app.get('/api/members/me', memberRequired, async (req, res) => {
const row = await db.prepare('SELECT id, name, email, phone, avatar, created_at FROM members WHERE id = ?').get(req.member.sub); const row = await db.prepare('SELECT id, name, email, phone, avatar, created_at FROM members WHERE id = ?').get(req.member.sub);
if (!row) return res.status(404).json({ error: 'not_found' }); if (!row) return res.status(404).json({ error: 'not_found' });

BIN
public/about/analyst-a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

BIN
public/about/analyst-b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

BIN
public/about/analyst-c.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

BIN
public/about/analyst-d.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

BIN
public/about/council-a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

BIN
public/about/council-b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

BIN
public/about/council-c.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

BIN
public/about/council-d.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

BIN
public/about/team-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

BIN
public/about/team-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

BIN
public/about/team-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

BIN
public/about/team-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

BIN
public/about/vision-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

BIN
public/about/vision-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

BIN
public/about/vision-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

BIN
public/about/vision-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

View File

@ -0,0 +1,89 @@
{
"updatedAt": "2026-06-21T07:41:10.481Z",
"items": {
"Foulad Mobarakeh": {
"newsFa": "ثبات در عصر طوفان های صنعتی: داستان تعالی پایدار فولاد مبارکه",
"source": "Tehran Times",
"link": "https://news.google.com/rss/articles/CBMipwFBVV95cUxQWktyNUxWQ19od2tBRmROcWdRckgxVm50NWhuaVZYR3RibjFBR1VoMklMa2thdFhSTERaakx5SjdodW9lMTNQSWFudWFSQ1VCN0ZhTmg1cFFhWDFGTm5TVHRodS1mQWNIZFZWV3lJMkRSZVkxWC1LYXhCeldMUFRTeTJDQ3ZyenpQMGtwbzFHaE1Eb01TNUcwRlNDc2V6SWZ5QVE3T0pMRQ?oc=5",
"date": "۲۷ خرداد ۱۴۰۵"
},
"China Baowu": {
"newsFa": "وزارت بازرگانی چین اعلام کرد که چین در حال مذاکره با اتحادیه اروپا بر سر اقدامات تجاری فولاد خود است",
"source": "Reuters",
"link": "https://news.google.com/rss/articles/CBMiwAFBVV95cUxNVEpteDN2M2hRVWd6OUVXdHhVeHB4NWhpNExjWnJ2djJTWlhLSDVTZDl6LXhxdlkzTF9LX19nMlYtSmpSM0pQMUVDUk9XcVk0S0sxbGJkckYzTzVyUngxVkduUmxEand1XzJUZEYxLXAwajJGRlYzdWhXRmNzZlRFX3RxNTE4b3B2Y2FRdGFuZ3ZhOElCMUlXbUFvQURIZTQzZGQtcXRNRDRnamg1aXloei10VTBqTlBhV0RqTnJjOEk?oc=5",
"date": "۷ خرداد ۱۴۰۵"
},
"ArcelorMittal": {
"newsFa": "ArcelorMittal Building Solutions مقر آمریکای شمالی را به Macon-Bibb می آورد",
"source": "Georgia Economic Development",
"link": "https://news.google.com/rss/articles/CBMirAFBVV95cUxONTkxTms5QlBuU0Vsbi1JbURjS0owZ2FkSFU0SlNyX18yTW1jZXdkUi03TV9zUXdpLVZZU0pfMC1jUEI1QjZnYTR1bnV5VHk2dDRfNGlLczJWbW5Kc2FQbm1vT3U1djJabjFPUWpyQW9wdmd6UjJFM1g5bk9uZ3hyenhPQ0x2Q2dMRlpXSzhEVkxaWFMxU1B1d3ItYmhPUy1JUkVrdW9VTjF5LTlz?oc=5",
"date": "۱۱ خرداد ۱۴۰۵"
},
"Nippon Steel": {
"newsFa": "یک سال بعد، خرید فولاد ایالات متحده توسط نیپون بسیار تغییر کرده است - اما نه Mon Valley",
"source": "Pittsburgh's Public Source",
"link": "https://news.google.com/rss/articles/CBMibEFVX3lxTE1CcVlnUEpOUEhmU3hESXViR0h6Z2UySU5xMEtGdlNLRk1OSk8wczZwVzhwR0tYcFUtbHVJZHNvTUVFM1Z6TVcyNFZxNXkyOUxJejFMMGNqaEdQeUtVN09oMmJGRlBoNTNkTUR0Mw?oc=5",
"date": "۱۹ خرداد ۱۴۰۵"
},
"HBIS Group": {
"newsFa": "رئیس جمهور صربستان از توسعه فولاد سبز در HBIS حمایت می کند",
"source": "EUROMETAL",
"link": "https://news.google.com/rss/articles/CBMiiAFBVV95cUxOMFV1dTl2X2l0T1F3Umo3VVFpNlA5SlZxSnlfRkV0cjUxRTMwazJjT3kteUpaOXVDVS15RTkxOTFfQ2x2NU41dUJMaDE4UHhYZ0ZMX3BVZnRCdWd0QTJ5RF9Zb1d1T1hSREtmWXJpSk5yM2pCMWVNUXNMWmN4WGlYa2tHWGR4V2Ez?oc=5",
"date": "۱۲ خرداد ۱۴۰۵"
},
"POSCO Holdings": {
"newsFa": "ReElement و Posco جوهر تولید خاکهای کمیاب و آهنربا در ایالات متحده",
"source": "Manufacturing Dive",
"link": "https://news.google.com/rss/articles/CBMiwgFBVV95cUxQMXFaTGtKWUs0eXExRnZVeEZEU1B6UjhoeEN2N3E0akp1SWVTTm10TVEzQUY3dXRGc2Faand4T1A0VkxBWWdBd2pUT3R2aXdiaWRXcDFrZWk3ZkN6VlpCdDJnRGxTTVZQYjl0YjdrSkh3V0NzQWQ1YXRMdE5mV2ZVLTd5eXdmZVNxLVU0RzVwQkM0UkZlTWM1aXNzV1BZdzExUG55aXJZUEM1SWpLaEhGVWRIRmtSUy0wd2J2amE2QmFyUQ?oc=5",
"date": "۶ خرداد ۱۴۰۵"
},
"Tata Steel": {
"newsFa": "فعال سابق محیط زیست هلندی توسط تاتا استیل به دلیل ارتباط با راست افراطی اخراج شد",
"source": "Euronews.com",
"link": "https://news.google.com/rss/articles/CBMi0gFBVV95cUxPbURpS2hqQUtSRTE2bUVZNlFIUFpuWGNLaUZoNW5kV2F4eUpfRUthWG1YMDUyWThnRUtaeVJmckFoOFNYS1VrTVdRUzZ6THJMRFk2MnBCTl9SNWNwNGJxUlc3TXFNTWlncGt5QkM1alRMZW5wdjJJd0dBVzV2WGpDTzNaRE9XbUFqWTZhR1RsTDVZY28yOEotakZxNzFmYTBBWEtGb1lnSXJFSmFKMHU1Sl9vcDZWRTZJc1FqU2xGUGloaHFOTlJJdXdub3JTMWhYQ2c?oc=5",
"date": "۱۳ خرداد ۱۴۰۵"
},
"JSW Steel": {
"newsFa": "JSW Steel ساخت یک کارخانه فولاد جدید در اودیشا را آغاز کرد",
"source": "GMK Center",
"link": "https://news.google.com/rss/articles/CBMikgFBVV95cUxNTHlfQU9sSV9EalZfNUFwR0JFcUM0ckx4d2hnMUZOdnpFbXhROUNFR0NoamJxblRUdHRqYk1wSTNCQU8zdDRUZmxzdTY4MkV6emJmeXNsc3BOQ2REVGo4dWYzWHlfWXpUNFNXZEx2bHRURnhSS3NWZGtyOG5PUFVwT1BCb3AzdnZ2bGxKcXBDNUk4UdIBlwFBVV95cUxQZ1R0ZlJwVjN1bHZheDNkM2c3b3lONDFWWFV4RU5aM0ZGd3ViVzNjel9jVFNwaVFjZkoxRlY3T2cyaDFyVE4yNzhEVGU3TVJMcnh6UmMxWFBxbm5FM2lmcUZJVHBhWnRSekwxWlYwQURMakIyOEllXzVBckx2S3BNMHByOV9HZHNIRWN4aXd2cnA3REZ0TW9B?oc=5",
"date": "۷ خرداد ۱۴۰۵"
},
"Nucor": {
"newsFa": "Nucor دویست و سیزدهمین سود نقدی متوالی را اعلام کرد",
"source": "PR Newswire",
"link": "https://news.google.com/rss/articles/CBMipgFBVV95cUxOMkk0Qno2emJzT3lSc3kwZWpuc19BTnBtaEFyOVBjNktwVHFFTzdmVlA5NXpoNmxMYWU5cW56U1JMdWtCVk5OSUhsa3dnLXZCM1diZnFPbFZvbllYakdha3pBRmxrZktPdFBnQjd6VVl3amFTSEdCY1Fja2g0MFJUcm5zb2w2V1B5SUFXSzZKX2ZLMkVsTjZ2Z0tFc2VJbFV1Rl9NMmRB?oc=5",
"date": "۱۹ خرداد ۱۴۰۵"
},
"Khouzestan Steel": {
"newsFa": "آهن کاهش یافته مستقیم [DRI] اندازه بازار | گزارش جهانی، 2034",
"source": "Fortune Business Insights",
"link": "https://news.google.com/rss/articles/CBMifkFVX3lxTE84N3NGOENXQUs1U3FITFBDZ240NXpSSG9hUld2dElNQmNjdkhxa0VpZmtabGM0aTZZaW02Q2NSbnA0NHo2RHZKVHNjVXBQbWRsTTREQ2pUdUFCYlV1MVlDQlg3UFh2VEZURzgySjVCMndIUlA0VGF5YTFDNE1idw?oc=5",
"date": "۴ خرداد ۱۴۰۵"
},
"Erdemir": {
"newsFa": "اردمیر رهبر فولاد ترکیه 61 سال برتری خود را جشن گرفت",
"source": "SteelRadar",
"link": "https://news.google.com/rss/articles/CBMioAFBVV95cUxNMUEzN05OZm56VkJudzRJWUJlNEdydkFPSU9tNmlSUXpwaVFqSVl5d3ppZENEZW5aTF9UZGtIR3Jad0djWnlubVRwVURSTHBXbEtsaFN2ZkFTNUtsSVFiZHJXY3dWWWo4Sm1XelY2Q21fdDdzYnEtRU4tcUhGMWd5dlhyQkZrT1J5T3JzQWZMaUZGZVFaemo4dXp2cUd2b1po?oc=5",
"date": "۲۹ خرداد ۱۴۰۵"
},
"Tosyalı Holding": {
"newsFa": "عملکرد تولیدکنندگان پیشرو جهانی فولاد در سال 25 چگونه بود؟",
"source": "BigMint",
"link": "https://news.google.com/rss/articles/CBMinwFBVV95cUxOQmhmanIwZ2Y0eGY3SWYzWkpQc2tYbm55RkdyMEhXMGFUUFJHTUZYc2NMeVhFVG9kMkJNbnk2YnZjdmQwMXlHZ1YzMWdKRUQyOVRrNk1Ja3ZjTzNIOEFsWEsyUnVObzdDbzl4QnpVanBjMXBqOXlpYjRyRzRLOUNYSjVmcTNuQWZJWDJTak40SThCdFdxa0w2cFh5cVlBYU0?oc=5",
"date": "۲۲ خرداد ۱۴۰۵"
},
"EMSTEEL": {
"newsFa": "شرکت های فولادی امارات مرکز تولید فولاد را در ام القوین راه اندازی کردند",
"source": "SteelOrbis",
"link": "https://news.google.com/rss/articles/CBMiyAFBVV95cUxOaU9vWkw0NlFDbnpfSEhhX1V6Vk40TWEwSUFzNmpLSEZfLTdVZGJBZ2d5VlQ5UkpBWnpidVM4bEwzcGNGVVB0Q2RMbzAwblVKZGxzcFk5TzBHUlFDaUFNb2U0cmdYQ3JfWHBadjZ2SVBqbnRTTi1sRTBkLUotUHBzMnlQdUkyTmc5SzctaDIzdGV0bkZqSkVUMG5FdGdWUEhFOWh2UXVqSHRKZjVGRnB3VFdFeFdHanllYkljcTFwTWpFbjdXUWxJbA?oc=5",
"date": "۵ خرداد ۱۴۰۵"
},
"Ezz Steel": {
"newsFa": "قمار 1 میلیارد دلاری که احمد ابو هاشیما را به یک تجارت سنگین مصری تبدیل کرد",
"source": "Business Insider Africa",
"link": "https://news.google.com/rss/articles/CBMi0gFBVV95cUxNNmEzcm9nRFRheWg1dHJVTWFqWXJtOTFycllwdjJkTWpQUk9VUk1sZ296QmZLbnp0QWlhLVFFR2NHdUZtRzFXMnpySWNUZmtSaE1fSnloYkd1S0FtZ3JuUmtHbmUwcmpKY1VLbnk5WEFHYjU0Ny1UNzVqeXhuNGk2bE1TSUhSemdZc1FsRVNhT2dQaEsyVlhjdGphUzltcDRSMTZadXZjamxDWWY5ZEFRNkFNX05HWlFrTTNXNXhVVHZ0YWxpU19JX3c1NVE1c1kxTFE?oc=5",
"date": "۲۴ خرداد ۱۴۰۵"
}
}
}

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:400daccc315388c24c972cb62de1902d922bcabb8062c353283a0323f468209d
size 37935185

BIN
public/factory/cover-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
public/factory/cover-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

BIN
public/factory/cover-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

BIN
public/factory/cover-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

BIN
public/factory/cover-5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/flag-iran.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
public/flag-usa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
public/hero-bg.mp4 (Stored with Git LFS)

Binary file not shown.

1
public/site.webmanifest Normal file
View File

@ -0,0 +1 @@
{"name":"اندیشکده فولاد آینده","short_name":"فولاد آینده","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#032340","background_color":"#faf7f4","display":"standalone"}

1
public/steel-plants.json Normal file

File diff suppressed because one or more lines are too long

BIN
public/summit-banner.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

BIN
public/trust-enamad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/trust-etehadie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
public/trust-samandehi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -0,0 +1,173 @@
// ─────────────────────────────────────────────────────────────────────────────
// Competitor news fetcher → Persian, with source + link
//
// For each steel company it pulls the latest headline from Google News RSS,
// translates it to Persian, and writes public/competitor-news.json. The
// dashboard ("آخرین خبر و اقدام" / رصد رقبا) reads that file at runtime.
//
// No API key required (uses Google News RSS + Google translate gtx endpoint).
//
// Usage:
// node scripts/fetch-competitor-news.mjs # run once now
// node scripts/fetch-competitor-news.mjs --daemon # run now, then daily 16:00 (Asia/Tehran)
//
// Schedule daily at 16:00 without the daemon:
// • Linux/macOS cron: 30 12 * * * cd /path/to/repo && node scripts/fetch-competitor-news.mjs (12:30 UTC = 16:00 Tehran)
// • Windows Task Scheduler: daily trigger 16:00 → program "node", args "scripts/fetch-competitor-news.mjs", start-in = repo root
//
// Requires Node 18+ (global fetch).
// ─────────────────────────────────────────────────────────────────────────────
import { readFile, writeFile, mkdir } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const OUT = resolve(__dirname, '..', 'public', 'competitor-news.json')
const HISTORY = resolve(__dirname, 'news-history.json') // remembers topics already shown so they never repeat
// keys MUST match competitor.name in src/pages/SteelDashboard/sections/IndustrySection.tsx
const COMPANIES = [
{ name: 'Foulad Mobarakeh', q: 'Mobarakeh Steel Company' },
{ name: 'China Baowu', q: 'China Baowu steel' },
{ name: 'ArcelorMittal', q: 'ArcelorMittal' },
{ name: 'Ansteel Group', q: 'Ansteel Group steel' },
{ name: 'Nippon Steel', q: 'Nippon Steel' },
{ name: 'HBIS Group', q: 'HBIS Group steel' },
{ name: 'POSCO Holdings', q: 'POSCO steel' },
{ name: 'Tata Steel', q: 'Tata Steel' },
{ name: 'JSW Steel', q: 'JSW Steel' },
{ name: 'Nucor', q: 'Nucor steel' },
{ name: 'Khouzestan Steel', q: 'Khouzestan Steel Company' },
{ name: 'Esfahan Steel (Zob Ahan)', q: 'Esfahan Steel Zob Ahan' },
{ name: 'Erdemir', q: 'Erdemir steel Turkey' },
{ name: 'Tosyalı Holding', q: 'Tosyali steel' },
{ name: 'EMSTEEL', q: 'EMSTEEL Emirates Steel' },
{ name: 'Ezz Steel', q: 'Ezz Steel Egypt' },
]
const UA = { 'User-Agent': 'Mozilla/5.0 (compatible; SteelForesightNewsBot/1.0)' }
async function fetchT(url, ms = 9000) {
const ctrl = new AbortController()
const id = setTimeout(() => ctrl.abort(), ms)
try { return await fetch(url, { headers: UA, signal: ctrl.signal }) }
finally { clearTimeout(id) }
}
function decode(s = '') {
return s
.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1')
.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
.replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&apos;/g, "'")
.trim()
}
async function fetchItems(query) {
// last 30 days only; pull several so we can skip topics that are already taken
const url = `https://news.google.com/rss/search?q=${encodeURIComponent(query + ' when:30d')}&hl=en-US&gl=US&ceid=US:en`
const res = await fetchT(url)
if (!res.ok) throw new Error(`news HTTP ${res.status}`)
const xml = await res.text()
const blocks = [...xml.matchAll(/<item>([\s\S]*?)<\/item>/g)].map((m) => m[1])
return blocks
.map((block) => {
const rawTitle = decode(block.match(/<title>([\s\S]*?)<\/title>/)?.[1] || '')
const link = decode(block.match(/<link>([\s\S]*?)<\/link>/)?.[1] || '')
const pubDate = decode(block.match(/<pubDate>([\s\S]*?)<\/pubDate>/)?.[1] || '')
const source = decode(block.match(/<source[^>]*>([\s\S]*?)<\/source>/)?.[1] || '')
// Google News titles end with " - Source" — strip it for a clean headline
const title = source && rawTitle.endsWith(` - ${source}`) ? rawTitle.slice(0, -(source.length + 3)) : rawTitle
return { title, link, pubDate, source }
})
.filter((it) => it.title)
}
// normalised "topic" so near-identical headlines collapse to one key (for de-duping)
function topicKey(title) {
return title
.toLowerCase()
.replace(/[^a-z0-9؀-ۿ\s]/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.split(' ')
.slice(0, 8)
.join(' ')
}
async function loadHistory() {
try { return new Set(JSON.parse(await readFile(HISTORY, 'utf8'))) } catch { return new Set() }
}
async function saveHistory(seen) {
try { await writeFile(HISTORY, JSON.stringify([...seen].slice(-600)), 'utf8') } catch { /* ignore */ }
}
async function toPersian(text) {
if (!text) return ''
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=fa&dt=t&q=${encodeURIComponent(text)}`
const res = await fetchT(url)
if (!res.ok) throw new Error(`translate HTTP ${res.status}`)
const data = await res.json()
return (data?.[0] || []).map((seg) => seg[0]).join('')
}
function faDate(iso) {
try {
return new Intl.DateTimeFormat('fa-IR', { year: 'numeric', month: 'long', day: 'numeric' }).format(new Date(iso))
} catch {
return ''
}
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
async function runOnce() {
const history = await loadHistory() // topics shown on previous days
const usedThisRun = new Set() // topics already taken in this run
const items = {}
for (const c of COMPANIES) {
try {
const list = await fetchItems(c.q)
// first headline whose topic is new — not taken this run AND not shown before
let pick = list.find((it) => { const k = topicKey(it.title); return k && !usedThisRun.has(k) && !history.has(k) })
// if everything is a repeat, at least don't clash with another company in this same run
if (!pick) pick = list.find((it) => !usedThisRun.has(topicKey(it.title)))
if (!pick) { console.warn(`· no fresh news: ${c.name}`); continue }
const key = topicKey(pick.title)
usedThisRun.add(key); history.add(key)
const newsFa = await toPersian(pick.title)
items[c.name] = { newsFa, source: pick.source, link: pick.link, date: faDate(pick.pubDate) }
console.log(`${c.name}${newsFa.slice(0, 64)}`)
} catch (e) {
console.warn(`${c.name}: ${e.message}`)
}
await sleep(1200) // stay polite to the free endpoints
}
await saveHistory(history)
await mkdir(dirname(OUT), { recursive: true })
await writeFile(OUT, JSON.stringify({ updatedAt: new Date().toISOString(), items }, null, 2), 'utf8')
console.log(`\n📰 wrote ${Object.keys(items).length}/${COMPANIES.length} items (no repeated topics) → ${OUT}`)
}
// 16:00 Asia/Tehran = 12:30 UTC (Iran dropped DST in 2022, fixed at UTC+3:30)
function msUntilNextRun() {
const now = new Date()
const next = new Date(now)
next.setUTCHours(12, 30, 0, 0)
if (next <= now) next.setUTCDate(next.getUTCDate() + 1)
return next.getTime() - now.getTime()
}
async function daemon() {
console.log('🕓 news daemon up — runs now, then every day at 16:00 Asia/Tehran')
await runOnce()
const schedule = () => {
const wait = msUntilNextRun()
console.log(`next run in ${(wait / 3_600_000).toFixed(2)}h`)
setTimeout(async () => { await runOnce(); schedule() }, wait)
}
schedule()
}
if (process.argv.includes('--daemon')) daemon()
else runOnce()

Some files were not shown because too many files have changed in this diff Show More