diff --git a/.playwright-mcp/page-2026-06-13T05-35-07-683Z.yml b/.playwright-mcp/page-2026-06-13T05-35-07-683Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-13T05-35-10-421Z.png b/.playwright-mcp/page-2026-06-13T05-35-10-421Z.png new file mode 100644 index 0000000..8d96ed0 Binary files /dev/null and b/.playwright-mcp/page-2026-06-13T05-35-10-421Z.png differ diff --git a/.playwright-mcp/page-2026-06-13T06-44-17-636Z.yml b/.playwright-mcp/page-2026-06-13T06-44-17-636Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-13T06-44-21-687Z.png b/.playwright-mcp/page-2026-06-13T06-44-21-687Z.png new file mode 100644 index 0000000..e59db3d Binary files /dev/null and b/.playwright-mcp/page-2026-06-13T06-44-21-687Z.png differ diff --git a/.playwright-mcp/page-2026-06-13T06-49-59-240Z.yml b/.playwright-mcp/page-2026-06-13T06-49-59-240Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-13T06-50-23-700Z.yml b/.playwright-mcp/page-2026-06-13T06-50-23-700Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-13T06-50-29-064Z.yml b/.playwright-mcp/page-2026-06-13T06-50-29-064Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-13T06-50-53-440Z.yml b/.playwright-mcp/page-2026-06-13T06-50-53-440Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-13T06-50-58-800Z.png b/.playwright-mcp/page-2026-06-13T06-50-58-800Z.png new file mode 100644 index 0000000..760aaab Binary files /dev/null and b/.playwright-mcp/page-2026-06-13T06-50-58-800Z.png differ diff --git a/.playwright-mcp/page-2026-06-13T06-51-06-462Z.yml b/.playwright-mcp/page-2026-06-13T06-51-06-462Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-13T06-51-11-956Z.png b/.playwright-mcp/page-2026-06-13T06-51-11-956Z.png new file mode 100644 index 0000000..f21df44 Binary files /dev/null and b/.playwright-mcp/page-2026-06-13T06-51-11-956Z.png differ diff --git a/.playwright-mcp/page-2026-06-13T06-55-02-146Z.yml b/.playwright-mcp/page-2026-06-13T06-55-02-146Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-13T06-55-08-279Z.png b/.playwright-mcp/page-2026-06-13T06-55-08-279Z.png new file mode 100644 index 0000000..28ca555 Binary files /dev/null and b/.playwright-mcp/page-2026-06-13T06-55-08-279Z.png differ diff --git a/.playwright-mcp/page-2026-06-13T06-56-24-525Z.yml b/.playwright-mcp/page-2026-06-13T06-56-24-525Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-13T06-56-35-515Z.png b/.playwright-mcp/page-2026-06-13T06-56-35-515Z.png new file mode 100644 index 0000000..8ee4e6b Binary files /dev/null and b/.playwright-mcp/page-2026-06-13T06-56-35-515Z.png differ diff --git a/.playwright-mcp/page-2026-06-13T13-31-50-514Z.yml b/.playwright-mcp/page-2026-06-13T13-31-50-514Z.yml new file mode 100644 index 0000000..7577671 --- /dev/null +++ b/.playwright-mcp/page-2026-06-13T13-31-50-514Z.yml @@ -0,0 +1,390 @@ +- 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]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک \ No newline at end of file diff --git a/.playwright-mcp/page-2026-06-13T13-34-48-550Z.yml b/.playwright-mcp/page-2026-06-13T13-34-48-550Z.yml new file mode 100644 index 0000000..7577671 --- /dev/null +++ b/.playwright-mcp/page-2026-06-13T13-34-48-550Z.yml @@ -0,0 +1,390 @@ +- 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]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک \ No newline at end of file diff --git a/.playwright-mcp/page-2026-06-14T05-12-09-456Z.yml b/.playwright-mcp/page-2026-06-14T05-12-09-456Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-14T05-13-22-496Z.yml b/.playwright-mcp/page-2026-06-14T05-13-22-496Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-14T05-13-48-058Z.yml b/.playwright-mcp/page-2026-06-14T05-13-48-058Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-14T05-14-24-103Z.yml b/.playwright-mcp/page-2026-06-14T05-14-24-103Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-14T05-15-36-745Z.yml b/.playwright-mcp/page-2026-06-14T05-15-36-745Z.yml new file mode 100644 index 0000000..e69de29 diff --git a/.playwright-mcp/page-2026-06-15T06-13-53-790Z.yml b/.playwright-mcp/page-2026-06-15T06-13-53-790Z.yml new file mode 100644 index 0000000..9fe6dde --- /dev/null +++ b/.playwright-mcp/page-2026-06-15T06-13-53-790Z.yml @@ -0,0 +1,121 @@ +- 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]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک \ No newline at end of file diff --git a/.playwright-mcp/page-2026-06-15T06-13-57-002Z.png b/.playwright-mcp/page-2026-06-15T06-13-57-002Z.png new file mode 100644 index 0000000..f034287 Binary files /dev/null and b/.playwright-mcp/page-2026-06-15T06-13-57-002Z.png differ diff --git a/.playwright-mcp/page-2026-06-15T06-16-32-391Z.yml b/.playwright-mcp/page-2026-06-15T06-16-32-391Z.yml new file mode 100644 index 0000000..fce8be6 --- /dev/null +++ b/.playwright-mcp/page-2026-06-15T06-16-32-391Z.yml @@ -0,0 +1,154 @@ +- 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]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک \ No newline at end of file diff --git a/.playwright-mcp/page-2026-06-15T06-17-14-936Z.yml b/.playwright-mcp/page-2026-06-15T06-17-14-936Z.yml new file mode 100644 index 0000000..e584ae4 --- /dev/null +++ b/.playwright-mcp/page-2026-06-15T06-17-14-936Z.yml @@ -0,0 +1,119 @@ +- 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]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک \ No newline at end of file diff --git a/.playwright-mcp/page-2026-06-15T06-19-19-729Z.yml b/.playwright-mcp/page-2026-06-15T06-19-19-729Z.yml new file mode 100644 index 0000000..67718d7 --- /dev/null +++ b/.playwright-mcp/page-2026-06-15T06-19-19-729Z.yml @@ -0,0 +1,121 @@ +- 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]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک \ No newline at end of file diff --git a/.playwright-mcp/page-2026-06-15T06-19-38-287Z.yml b/.playwright-mcp/page-2026-06-15T06-19-38-287Z.yml new file mode 100644 index 0000000..e584ae4 --- /dev/null +++ b/.playwright-mcp/page-2026-06-15T06-19-38-287Z.yml @@ -0,0 +1,119 @@ +- 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]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک \ No newline at end of file diff --git a/.playwright-mcp/page-2026-06-15T06-22-47-646Z.yml b/.playwright-mcp/page-2026-06-15T06-22-47-646Z.yml new file mode 100644 index 0000000..d04c531 --- /dev/null +++ b/.playwright-mcp/page-2026-06-15T06-22-47-646Z.yml @@ -0,0 +1,174 @@ +- 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]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک \ No newline at end of file diff --git a/.playwright-mcp/page-2026-06-15T06-22-53-335Z.png b/.playwright-mcp/page-2026-06-15T06-22-53-335Z.png new file mode 100644 index 0000000..6018ca9 Binary files /dev/null and b/.playwright-mcp/page-2026-06-15T06-22-53-335Z.png differ diff --git a/.playwright-mcp/page-2026-06-15T06-24-00-379Z.png b/.playwright-mcp/page-2026-06-15T06-24-00-379Z.png new file mode 100644 index 0000000..9d15d98 Binary files /dev/null and b/.playwright-mcp/page-2026-06-15T06-24-00-379Z.png differ diff --git a/.playwright-mcp/page-2026-06-15T06-25-16-228Z.yml b/.playwright-mcp/page-2026-06-15T06-25-16-228Z.yml new file mode 100644 index 0000000..96662eb --- /dev/null +++ b/.playwright-mcp/page-2026-06-15T06-25-16-228Z.yml @@ -0,0 +1,135 @@ +- 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]: طراح و پشتیبان؛ شرکت دانش‌بنیان نواندیشان آتی نگار فرتاک \ No newline at end of file diff --git a/.playwright-mcp/page-2026-06-15T06-25-23-244Z.png b/.playwright-mcp/page-2026-06-15T06-25-23-244Z.png new file mode 100644 index 0000000..6105917 Binary files /dev/null and b/.playwright-mcp/page-2026-06-15T06-25-23-244Z.png differ diff --git a/.playwright-mcp/page-2026-06-15T06-25-43-852Z.png b/.playwright-mcp/page-2026-06-15T06-25-43-852Z.png new file mode 100644 index 0000000..9850bdc Binary files /dev/null and b/.playwright-mcp/page-2026-06-15T06-25-43-852Z.png differ diff --git a/.playwright-mcp/page-2026-06-15T08-21-48-656Z.yml b/.playwright-mcp/page-2026-06-15T08-21-48-656Z.yml new file mode 100644 index 0000000..49a267e --- /dev/null +++ b/.playwright-mcp/page-2026-06-15T08-21-48-656Z.yml @@ -0,0 +1,10 @@ +- 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 \ No newline at end of file diff --git a/.playwright-mcp/radar-lower.png b/.playwright-mcp/radar-lower.png new file mode 100644 index 0000000..2c473ba Binary files /dev/null and b/.playwright-mcp/radar-lower.png differ diff --git a/panel/db.js b/panel/db.js index 101809b..57d3bdf 100644 --- a/panel/db.js +++ b/panel/db.js @@ -9,6 +9,12 @@ export const db = new Database(dbPath); db.pragma('journal_mode = WAL'); db.pragma('foreign_keys = ON'); +// safe migrations — ignore if column already exists +for (const sql of [ + "ALTER TABLE articles ADD COLUMN status TEXT DEFAULT 'published'", + "ALTER TABLE articles ADD COLUMN key_data TEXT", +]) { try { db.exec(sql) } catch {} } + db.exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -290,6 +296,29 @@ db.exec(` source_url TEXT, fetched_at TEXT NOT NULL DEFAULT (datetime('now')) ); + + CREATE TABLE IF NOT EXISTS members ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + phone TEXT, + avatar TEXT, + is_active INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + CREATE INDEX IF NOT EXISTS idx_members_email ON members(email); + + CREATE TABLE IF NOT EXISTS purchases ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + member_id INTEGER NOT NULL REFERENCES members(id) ON DELETE CASCADE, + article_id TEXT NOT NULL REFERENCES articles(id) ON DELETE CASCADE, + amount INTEGER NOT NULL DEFAULT 0, + purchased_at TEXT NOT NULL DEFAULT (datetime('now')), + UNIQUE(member_id, article_id) + ); + CREATE INDEX IF NOT EXISTS idx_purchases_member ON purchases(member_id, purchased_at DESC); `); /* idempotent column migrations for already-created tables */ diff --git a/panel/imports-data.json b/panel/imports-data.json new file mode 100644 index 0000000..58019f9 --- /dev/null +++ b/panel/imports-data.json @@ -0,0 +1,194 @@ +[ + { + "report": { + "id": "green-premia-trends", + "coverImage": "/news/photo-1473341304170-971dccb5ac1e.jpg", + "title": "روندها و چشم‌انداز پاداش سبز مواد تا سال ۲۰۳۰", + "category": "انرژی و ESG", + "type": "free", + "author": "کریستین هافمن", + "authorRole": "شریک ارشد", + "authorInitial": "ه", + "publishDate": "شهریور ۱۴۰۳", + "pages": 9, + "price": 0, + "isFree": true, + "summary": "خریداران و تامین‌کنندگان در حال ارزیابی مجدد تقاضا برای مواد کم‌کربن در مواجهه با چالش‌های اقتصاد کلان هستند. پیش‌بینی می‌شود که تا انتهای دهه جاری میزان تقاضا برای این کالاها تا چندین برابر افزایش یابد. تولیدکنندگان برای بهره‌گیری از این فرصت ملزم به افزایش شفافیت زنجیره تامین خود می‌باشند.", + "tags": ["مواد سبز", "کربن‌زدایی", "پاداش سبز", "توسعه پایدار"], + "featured": false + }, + "fa_summary_long": "بازار کالاها و مواد پایدار به تازگی با موانع اقتصادی متعددی روبه‌رو شده است که پرسش‌های مهمی را درباره آینده پاداش‌های پرداختی برای این محصولات ایجاد می‌کند. با وجود هزینه‌های بالای انرژی و کاهش قیمت کربن که موجب نگرانی‌هایی درباره کند شدن پروژه‌های زیست‌محیطی شده است تقاضا همچنان قدرتمند باقی مانده است. خریداران در صنایع مختلف اراده جدی خود را برای ادامه مسیر کاهش آلایندگی‌ها نشان می‌دهند.\n\nنظرسنجی‌ها نشان می‌دهد که بیش از ۹۰ درصد پاسخ‌دهندگان شاهد ثبات یا رشد تقاضا برای کالاهای سبز طی یک سال گذشته بوده‌اند. این رشد تا حد زیادی با تعهدات شرکت‌ها برای کاهش انتشار در دامنه ۳ (Scope 3) هدایت می‌شود. در واقع انتظار می‌رود تا سال ۲۰۳۰ سهم این مواد در خریدهای کلی صنایع از یک چهارم برای مس تا بیش از ۴۰ درصد برای فولاد و پلاستیک متغیر باشد.\n\nنکته جالب توجه در این گزارش تفاوت‌های چشمگیر در میزان تمایل به پرداخت این پاداش‌ها میان بخش‌ها و مناطق مختلف است. بازارهای اروپایی به طور مداوم هزینه‌های بیشتری را نسبت به همتایان خود در آمریکای شمالی برای این مواد پرداخت می‌کنند. با این وجود حدود ۶۰ درصد از پاسخ‌دهندگان اظهار داشته‌اند که در صورت کمبود عرضه تا سال ۲۰۳۰ آماده پرداخت مبالغ اضافی برای تضمین تامین کالاهای مورد نیاز خود هستند.\n\nدر کنار رشد تقاضا تعاریف و استانداردهای مربوط به محصولات سبز نیز به شدت سخت‌گیرانه‌تر خواهند شد. به عنوان نمونه نیمی از خریداران فولاد انتظار دارند که معیار فولاد سبز تا سال ۲۰۳۰ به کمتر از ۰.۳ تن دی‌اکسید کربن به ازای هر تن محصول محدود شود. این تغییرات نیازمند ردیابی دقیق و تایید انتشار گازهای گلخانه‌ای در تمامی مراحل زنجیره تامین است.\n\nبرای تامین‌کنندگان این شرایط فرصت‌های بی‌نظیری را برای کسب درآمد بیشتر رقم می‌زند اما موفقیت در آن نیازمند استراتژی‌های هدفمند است. اولویت‌بندی بخش‌های بازار با بالاترین تقاضای بی‌پاسخ و ایمن‌سازی زودهنگام مواد خام پایدار از جمله اقداماتی است که به شرکت‌ها برتری رقابتی می‌دهد. در نهایت همکاری نزدیک در سراسر زنجیره ارزش برای تضمین پایداری اقتصادی و زیست‌محیطی ضروری خواهد بود.", + "radar_items": [ + { + "id": "green-demand-growth", + "title": "رشد تقاضای مواد پایدار", + "summary": "پیش‌بینی افزایش ۴.۵ برابری تقاضا برای کالاهای پایدار تا سال ۲۰۳۰ توسط خریداران ثبت شده است.", + "category": "market", + "date": "شهریور ۱۴۰۳" + }, + { + "id": "stricter-green-definitions", + "title": "سخت‌گیرانه‌تر شدن تعاریف", + "summary": "استانداردهای تشخیص کالای پاک تا انتهای دهه جاری با تمرکز بر دامنه ۳ تغییرات جدی خواهند کرد.", + "category": "tech", + "date": "شهریور ۱۴۰۳" + }, + { + "id": "premium-willingness", + "title": "تمایل به پرداخت حق بیمه", + "summary": "نزدیک به ۶۰ درصد صنایع آماده پرداخت مبالغ اضافی برای تضمین تامین مواد اولیه پاک در آینده هستند.", + "category": "energy", + "date": "شهریور ۱۴۰۳" + } + ], + "key_data": { + "سهم فولاد سبز در ۲۰۳۰": "بیش از ۴۰ درصد", + "رشد تقاضای مواد سبز": "تا ۴.۵ برابر", + "استاندارد انتشار فولاد پاک ۲۰۳۰": "کمتر از ۰.۳ تن کربن" + } + }, + { + "report": { + "id": "commodity-trading-scurve", + "coverImage": "/news/photo-1611974789855-9c2a0a7236a3.jpg", + "title": "چگونه منحنی اس بعدی را در تجارت کالا تسخیر کنیم", + "category": "بازار جهانی", + "type": "free", + "author": "یوشا شابرام", + "authorRole": "شریک ارشد", + "authorInitial": "ش", + "publishDate": "بهمن ۱۴۰۳", + "pages": 16, + "price": 0, + "isFree": true, + "summary": "سودآوری استثنایی سال‌های گذشته در بازارهای تجاری کالا در حال عادی شدن است و حاشیه سودها را کاهش می‌دهد. تجارت کالاها با وجود این فشارهای رقابتی همچنان مسیری رو به رشد تا انتهای این دهه خواهد داشت. معامله‌گران برای بقا و موفقیت باید مدل‌های عملیاتی خود را بازنگری کرده ابزارهای دیجیتال را یکپارچه سازند.", + "tags": ["تجارت کالا", "بهینه‌سازی زنجیره ارزش", "دیجیتال‌سازی", "بازارهای نوظهور"], + "featured": false + }, + "fa_summary_long": "تجارت کالاهای پایه پس از گذراندن یک دوره رونق و سودآوری بی‌سابقه اکنون در حال ورود به فاز جدیدی از عادی‌سازی بازار است. در سال‌های ۲۰۲۲ و ۲۰۲۳ نوسانات شدید باعث افزایش چشمگیر حاشیه سود در این صنعت شد اما سال ۲۰۲۴ با فشرده شدن این حاشیه‌ها و افزایش رقابت همراه بود. با این وجود تحقیقات نشان می‌دهد که استخرهای ارزش در این حوزه با رشد سالانه ۱۰ درصد تا پایان دهه جاری به گسترش خود ادامه می‌دهند.\n\nیکی از تحولات مهم در این بازار تغییر مرکز ثقل سودآوری از کالاهای نفتی به سمت برق و گاز طبیعی مایع (LNG) است. افزایش استفاده از انرژی‌های تجدیدپذیر به همراه رشد چشمگیر تقاضا از سوی مراکز داده (Data Centers) باعث ایجاد نوسانات و فرصت‌های تجاری جدید در بخش برق شده است. این در حالی است که حاشیه سود فرآورده‌های نفتی در مسیر کاهشی قرار دارد.\n\nبرای پاسخ به این محیط تجاری جدید شرکت‌ها نیازمند بهینه‌سازی جامع زنجیره ارزش خود هستند. اجرای راهکارهای پیشرفته دیجیتال و شکستن سیلوهای سازمانی می‌تواند ارزش قابل توجهی را به دارایی‌های موجود اضافه کند. شرکت‌هایی که بتوانند این یکپارچگی را ایجاد کنند درآمد مازاد چشمگیری را نسبت به رقبای سنتی خود تجربه خواهند کرد.\n\nاستفاده از فناوری‌ها و قابلیت‌های تحلیلی جدید به عنوان یک عامل متمایزکننده کلیدی در آینده این صنعت نقش ایفا می‌کند. پیاده‌سازی هوش مصنوعی و مدل‌های پردازش داده پیشرفته به معامله‌گران اجازه می‌دهد تا با سرعت و دقت بیشتری در بازارهای پیچیده تصمیم‌گیری کنند. ایجاد فرهنگی که نوآوری دیجیتال را تشویق کند برای رقابت در این فضای جدید الزامی است.\n\nدر نهایت معامله‌گران موفق باید افق دید خود را به فراتر از بازارهای سنتی گسترش دهند و وارد بازارهای نوظهور مرتبط با گذار انرژی شوند. بازارهای کربن داوطلبانه فلزات سبز و گواهی‌های مبدا (GOOs) از جمله این فرصت‌های جدید هستند که پتانسیل رشد چهار برابری را در یک دهه آینده نشان می‌دهند.", + "radar_items": [ + { + "id": "trading-value-growth", + "title": "رشد استخرهای ارزش تجاری", + "summary": "ارزش کلی تجارت کالاها تا سال ۲۰۳۰ با نرخ رشد سالانه ۱۰ درصد افزایش خواهد یافت.", + "category": "market", + "date": "بهمن ۱۴۰۳" + }, + { + "id": "power-demand-surge", + "title": "افزایش تقاضای انرژی پایگاه‌های داده", + "summary": "مصرف برق مراکز داده و هوش مصنوعی با رشد سالانه ۱۵ درصدی در بازارهای پیشرو روبرو خواهد بود.", + "category": "energy", + "date": "بهمن ۱۴۰۳" + }, + { + "id": "digital-trading-edge", + "title": "برتری تجارت داده‌محور", + "summary": "توسعه زیرساخت‌های دیجیتال بالغ بر ۵ میلیارد دلار صرفه‌جویی در هزینه‌های صنعت به همراه دارد.", + "category": "tech", + "date": "بهمن ۱۴۰۳" + } + ], + "key_data": { + "پتانسیل افزایش سود با بهینه‌سازی": "بیش از ۳۰ میلیارد دلار سالانه", + "رشد بازارهای دارایی گذار انرژی": "۴ برابر در دهه آینده", + "صرفه‌جویی ناشی از دیجیتال‌سازی": "بیش از ۵ میلیارد دلار" + } + }, + { + "report": { + "id": "green-steel-hubs-pathway", + "coverImage": "/news/photo-1581092160607-ee22731c9b4e.jpg", + "title": "هاب‌های فولاد سبز: مسیری برای کربن‌زدایی صنعت فولاد", + "category": "انرژی و ESG", + "type": "free", + "author": "کریستین هافمن", + "authorRole": "شریک ارشد", + "authorInitial": "ه", + "publishDate": "آذر ۱۴۰۳", + "pages": 9, + "price": 0, + "isFree": true, + "summary": "صنعت فولاد برای دستیابی به اهداف زیست‌محیطی خود نیازمند بازنگری در مکان تولید محصولات در سراسر جهان است. ایجاد هاب‌های جدید آهن سبز در مناطقی با دسترسی ارزان به انرژی پاک می‌تواند این روند را تسریع کند. این مراکز با تولید آهن اسفنجی بریکت‌شده (HBI) امکان انتقال کارآمد خوراک به کوره‌های قوس الکتریکی را فراهم می‌آورند.", + "tags": ["فولاد سبز", "هاب تولید", "کربن‌زدایی", "آهن اسفنجی"], + "featured": false + }, + "fa_summary_long": "کربن‌زدایی از زنجیره تامین فولاد یکی از چالش‌برانگیزترین اقداماتی است که جهان امروز با آن روبه‌رو است و نیازمند تغییراتی در مقیاس وسیع می‌باشد. این صنعت به تنهایی ۷ درصد از کل گازهای گلخانه‌ای جهان را تولید می‌کند و اصلاح آن نیازمند بازسازی بیش از ۱۶۵۰ کارخانه در سطح جهانی است. یکی از راهکارهای نوین برای پاسخ به این بحران تغییر مکان و ساختار فیزیکی زنجیره تولید می‌باشد.\n\nمفهوم هاب‌های فولاد سبز به معنای جداسازی فرآیند تولید آهن از تولید نهایی فولاد است که اجازه می‌دهد هر بخش در مکان بهینه خود مستقر شود. در این مدل تولید آهن اسفنجی مستقیماً در مناطقی احداث می‌گردد که دسترسی فراوان به منابع گاز طبیعی یا هیدروژن ارزان و سنگ آهن دارند. محصولات این مراکز سپس به صورت آهن اسفنجی بریکت‌شده در مسافت‌های طولانی به کارخانه‌های نهایی منتقل می‌شوند.\n\nنیروی محرکه اصلی در این تغییر ساختار هزینه‌های تامین برق است که به زودی جایگزین زغال‌سنگ به عنوان عامل تعیین‌کننده رقابت‌پذیری خواهد شد. تولید فولاد سبز وابستگی شدیدی به انرژی الکتریکی به ویژه در فرآیند تولید هیدروژن دارد. تحلیل‌ها مشخص می‌کند که هزینه برق به تنهایی حدود ۴۰ تا ۵۰ درصد از مخارج تولید این نوع فولاد را شامل می‌گردد.\n\nتوسعه چنین مراکزی مزایای استراتژیک متعددی به همراه دارد و می‌تواند سرعت کربن‌زدایی را به شکل معناداری افزایش دهد. با ایزوله کردن بخش آهن‌سازی شرکت‌ها قادر خواهند بود جذب سرمایه را برای پروژه‌های کاملاً پاک و بدون آلایندگی تسهیل کنند. مناطقی نظیر خاورمیانه استرالیا و آمریکای جنوبی پتانسیل بالایی برای میزبانی از این هاب‌ها دارند.\n\nبا این حال راه‌اندازی این شبکه‌های تولیدی جدید به ایجاد اکوسیستمی هماهنگ از ذینفعان گوناگون وابسته است. شرکت‌های معدنی تامین‌کنندگان انرژی دولت‌ها و بخش‌های لجستیک باید برای تضمین موفقیت این زیرساخت‌ها با یکدیگر قراردادهای بلندمدت منعقد کنند. تنها در این صورت هاب‌های فولاد سبز می‌توانند ابزاری تحول‌آفرین برای آینده این صنعت باشند.", + "radar_items": [ + { + "id": "electricity-cost-driver", + "title": "برق عامل کلیدی رقابت‌پذیری", + "summary": "نزدیک به نیمی از هزینه‌های تولید فولاد پایدار مستقیماً به تامین برق وابسته خواهد بود.", + "category": "energy", + "date": "آذر ۱۴۰۳" + }, + { + "id": "hbi-decoupling", + "title": "جداسازی تولید آهن و فولاد", + "summary": "تجزیه زنجیره ارزش از طریق انتقال تولید HBI به مناطق دارای انرژی ارزان امکان‌پذیر می‌گردد.", + "category": "tech", + "date": "آذر ۱۴۰۳" + }, + { + "id": "massive-asset-reconfiguration", + "title": "بزرگترین تغییر ساختار دارایی‌ها", + "summary": "بیش از ۱۶۵۰ کارخانه فعال نیازمند تحول اساسی برای همگامی با اهداف اقلیمی هستند.", + "category": "market", + "date": "آذر ۱۴۰۳" + } + ], + "key_data": { + "سهم فولاد از انتشار جهانی": "۷ درصد", + "هزینه برق در فولاد سبز": "۴۰ تا ۵۰ درصد کل هزینه‌ها", + "انرژی برای یک تن آهن اسفنجی": "۲۷۰۰ کیلووات ساعت" + } + }, + { + "report": { + "id": "india-steel-growth-resilience", + "coverImage": "/news/photo-1567789884554-0b844b597180.jpg", + "title": "تقویت آینده: فولاد برای رشد و تاب‌آوری", + "category": "بازار جهانی", + "type": "special", + "author": "پرابهاو شارما", + "authorRole": "شریک ارشد", + "authorInitial": "ش", + "publishDate": "آذر ۱۴۰۴", + "pages": 40, + "price": 500000, + "isFree": false, + "summary": "صنعت جهانی با چالش‌های سه‌گانه تغییر مراکز تقاضا نیاز به کربن‌زدایی و جریان‌های تجاری پرنوسان دست و پنجه نرم می‌کند. هند به عنوان یکی از بازارهای اصلی با قابلیت دو برابر کردن تقاضای خود تا سال ۲۰۳۵ در حال ظهور است. این کشور برای دستیابی به رشد پایدار نیازمند تمرکز بر بخش‌های با پتانسیل بالا و تامین امن مواد اولیه می‌باشد.", + "tags": ["رشد تقاضا", "هند", "کربن‌زدایی", "زنجیره تامین"], + "featured": true + }, + "fa_summary_long": "چشم‌انداز جهانی صنعت فولاد در حال تجربه یک تغییر مسیر ساختاری است که در آن مراکز سنتی تقاضا با بلوغ روبه‌رو شده و بازارهای نوظهور رهبری را به دست می‌گیرند. تقاضای چین به دلیل رکود در بخش ساخت‌وساز در مسیر نزولی قرار گرفته و این موضوع پویایی صادرات در بازارهای بین‌المللی را به شدت تحت تاثیر قرار داده است. در مقابل هند و جنوب شرقی آسیا به موتورهای اصلی رشد تبدیل گشته‌اند.\n\nکشور هند با اجرای پروژه‌های زیربنایی عظیم و سرمایه‌گذاری ۱.۴ تریلیون دلاری در خط لوله زیرساخت ملی (NIP) نقشی محوری در آینده این کالا ایفا می‌کند. مصرف در این کشور با نرخ رشد سالانه ۵ تا ۶ درصد پیش می‌رود و انتظار می‌رود تا سال ۲۰۳۵ به ارقامی بین ۲۴۰ تا ۲۶۰ میلیون تن برسد. این تقاضای فزاینده به ویژه در بخش‌های با ارزش افزوده بالا مانند خودرو انرژی‌های تجدیدپذیر و دفاعی دیده می‌شود.\n\nبرای پاسخگویی به این نیاز فزاینده تولیدکنندگان هندی در حال برنامه‌ریزی برای افزودن حدود ۷۰ میلیون تن ظرفیت جدید طی ده سال آینده هستند. مشکل اساسی اینجاست که بخش اعظم این توسعه بر پایه روش سنتی کوره بلند (BF-BOF) بنا شده که ریسک وابستگی به دارایی‌های پرکربن را افزایش می‌دهد. این تصمیم‌گیری‌ها فشار مضاعفی را در مسیر پایبندی به اهداف اقلیمی ایجاد خواهد کرد.\n\nعلاوه بر چالش‌های زیست‌محیطی تامین مواد اولیه نیز یک گلوگاه حیاتی برای این کشور محسوب می‌گردد. در حالی که تقاضا برای زغال‌سنگ متالورژیک در سطح جهانی رو به کاهش است هند با افزایش نیاز مواجه خواهد بود و مجبور به تامین بخش قابل توجهی از آن از طریق واردات است. همچنین کمبود منابع داخلی قراضه با کیفیت رقابت برای به دست آوردن این نهاده ارزشمند را به شدت تشدید می‌کند.\n\nدر نهایت بازیگران این صنعت برای حفظ رقابت‌پذیری در عرصه جهانی باید استراتژی‌های خود را اصلاح کنند. استفاده از مسیرهای جایگزین نظیر آهن اسفنجی مبتنی بر هیدروژن و توسعه زیرساخت‌های انرژی پاک در کنار هم‌افزایی با برنامه‌های حمایتی دولت از ملزومات این گذار تاریخی هستند. استقرار سیستم‌های هوش مصنوعی برای بهینه‌سازی فرآیندها نیز نقشی اساسی در بهبود راندمان ایفا می‌کند.", + "radar_items": [ + { + "id": "india-demand-doubling", + "title": "دو برابر شدن تقاضای هند", + "summary": "پیش‌بینی می‌گردد مصرف داخلی این کشور تا سال ۲۰۳۵ به ۲۴۰ تا ۲۶۰ میلیون تن بالغ شود.", + "category": "market", + "date": "آذر ۱۴۰۴" + }, + { + "id": "scrap-shortage-crisis", + "title": "بحران تامین قراضه", + "summary": "کمبود ۱۰ تا ۱۵ میلیون تنی قراضه در بازار هند تا سال ۲۰۳۰ چالش‌های جدی برای تولیدات پاک ایجاد می‌کند.", + "category": "commodity", + "date": "آذر ۱۴۰۴" + }, + { + "id": "china-demand-decline", + "title": "کاهش ساختاری تقاضای چین", + "summary": "مصرف فولاد در چین با افت سالانه ۰.۸ درصدی به دلیل رکود بخش ساخت‌وساز مواجه خواهد بود.", + "category": "geo", + "date": "آذر ۱۴۰۴" + } + ], + "key_data": { + "تقاضای جهانی تا ۲۰۳۵": "۱۸۵۰ تا ۱۹۰۰ میلیون تن", + "سرمایه‌گذاری زیرساختی هند": "۱.۴ تریلیون دلار (NIP)", + "ظرفیت جدید در هند": "حدود ۷۰ میلیون تن در دهه آینده" + } + } +] diff --git a/panel/insert-events.mjs b/panel/insert-events.mjs new file mode 100644 index 0000000..ec13092 --- /dev/null +++ b/panel/insert-events.mjs @@ -0,0 +1,108 @@ +import Database from 'better-sqlite3'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const db = new Database(path.join(__dirname, 'data.db')); + +// Gregorian ISO → Jalali parts using Intl (same engine the site's calendar uses) +function jalali(iso) { + const d = new Date(iso + 'T00:00:00'); + const en = new Intl.DateTimeFormat('en-US-u-ca-persian', { day: 'numeric', month: 'numeric', year: 'numeric' }).formatToParts(d); + const get = (t) => en.find((p) => p.type === t).value; + const day = Number(get('day')); + const year = Number(get('year')); + const monthFa = new Intl.DateTimeFormat('fa-IR-u-ca-persian', { month: 'long' }).format(d); + const monthEn = new Intl.DateTimeFormat('en-US-u-ca-persian', { month: 'long' }).format(d); + return { day, year, monthFa, monthEn }; +} + +// 9 real, verified, upcoming steel-industry events (all after 2026-06-15) +const events = [ + { id: 'gulf-steel-expo-2026', iso: '2026-09-20', type: 'exhibition', + titleFa: 'نمایشگاه فولاد خلیج ۲۰۲۶', titleEn: 'Gulf Steel Expo 2026', + cityFa: 'دمام', cityEn: 'Dammam', countryFa: 'عربستان سعودی', + descFa: 'نمایشگاه و کنفرانس عربستان سعودی با پوشش کامل زنجیره ارزش صنعت فولاد.', + descEn: "Saudi Arabia's exhibition and conference covering the entire steel value chain." }, + + { id: 'arab-steel-summit-2026', iso: '2026-11-03', type: 'conference', + titleFa: 'نوزدهمین اجلاس فولاد عرب ۲۰۲۶', titleEn: '19th Arab Steel Summit 2026', + cityFa: 'دوحه', cityEn: 'Doha', countryFa: 'قطر', + descFa: 'اجلاس پرچم‌دار اتحادیه آهن و فولاد عرب همراه با نمایشگاه بین‌المللی آهن و فولاد.', + descEn: "The Arab Iron and Steel Union's flagship summit and international iron & steel exhibition." }, + + { id: 'meis-2026', iso: '2026-11-16', type: 'conference', + titleFa: 'کنفرانس آهن و فولاد خاورمیانه ۲۰۲۶', titleEn: 'Middle East Iron & Steel 2026', + cityFa: 'دبی', cityEn: 'Dubai', countryFa: 'امارات متحده عربی', + descFa: 'مهم‌ترین کنفرانس تجارت آهن و فولاد خاورمیانه با حضور بیش از ۱۴۵۰ مدیر ارشد صنعت.', + descEn: "Fastmarkets' premier Middle East iron and steel trading conference with 1,450+ senior executives." }, + + { id: 'iran-metafo-2026', iso: '2026-11-20', type: 'exhibition', + titleFa: 'نمایشگاه بین‌المللی متالورژی ایران (متافو) ۲۰۲۶', titleEn: 'Iran Metafo 2026', + cityFa: 'تهران', cityEn: 'Tehran', countryFa: 'ایران', + descFa: 'بزرگ‌ترین نمایشگاه بین‌المللی متالورژی، فولاد، معدن و ریخته‌گری ایران در محل دائمی نمایشگاه‌های بین‌المللی تهران.', + descEn: "Iran's largest international metallurgy, steel, mining and foundry exhibition in Tehran." }, + + { id: 'made-in-steel-2027', iso: '2027-05-11', type: 'exhibition', + titleFa: 'نمایشگاه و کنفرانس مید این استیل ۲۰۲۷', titleEn: 'Made in Steel 2027', + cityFa: 'میلان', cityEn: 'Milan', countryFa: 'ایتالیا', + descFa: 'بزرگ‌ترین نمایشگاه و کنفرانس دوسالانه صنعت فولاد ایتالیا در مرکز نمایشگاهی میلان.', + descEn: "Italy's leading biennial steel-industry conference and exhibition at Fiera Milano Rho." }, + + { id: 'seaisi-2027', iso: '2027-05-17', type: 'conference', + titleFa: 'کنفرانس و نمایشگاه فولاد جنوب شرق آسیا (SEAISI) ۲۰۲۷', titleEn: 'SEAISI Conference & Exhibition 2027', + cityFa: 'کوالالامپور', cityEn: 'Kuala Lumpur', countryFa: 'مالزی', + descFa: 'کنفرانس سالانه مؤسسه آهن و فولاد جنوب شرق آسیا برای صنعت فولاد منطقه آ‌سه‌آن.', + descEn: "The South East Asia Iron and Steel Institute's annual conference for the ASEAN steel industry." }, + + { id: 'future-steel-forum-2027', iso: '2027-06-09', type: 'conference', + titleFa: 'فروم آینده فولاد ۲۰۲۷', titleEn: 'Future Steel Forum 2027', + cityFa: 'بولونیا', cityEn: 'Bologna', countryFa: 'ایتالیا', + descFa: 'کنفرانس شاخص جهانی در حوزه تحول دیجیتال، تولید هوشمند و کربن‌زدایی صنعت فولاد.', + descEn: 'A flagship conference on digital transformation, smart manufacturing and decarbonisation in steel.' }, + + { id: 'gifa-metec-2027', iso: '2027-06-21', type: 'exhibition', + titleFa: 'نمایشگاه متالورژی و ریخته‌گری METEC / GIFA ۲۰۲۷', titleEn: 'GIFA / METEC 2027', + cityFa: 'دوسلدورف', cityEn: 'Düsseldorf', countryFa: 'آلمان', + descFa: 'مجموعه نمایشگاه‌های پیشروی جهان در حوزه ریخته‌گری و متالورژی، شامل متک برای فناوری فولادسازی.', + descEn: "The world's leading combined foundry and metallurgy trade-fairs, including METEC for steelmaking technology." }, + + { id: 'stainless-steel-world-2027', iso: '2027-11-30', type: 'exhibition', + titleFa: 'کنفرانس و نمایشگاه جهانی فولاد ضدزنگ ۲۰۲۷', titleEn: 'Stainless Steel World 2027', + cityFa: 'ماستریخت', cityEn: 'Maastricht', countryFa: 'هلند', + descFa: 'کنفرانس و نمایشگاه پیشروی جهانی در حوزه فولاد ضدزنگ و آلیاژهای مقاوم به خوردگی.', + descEn: 'The leading global conference and exhibition dedicated to stainless steel and corrosion-resistant alloys.' }, +]; + +// replace the old mock events with the real future ones +db.prepare('DELETE FROM events').run(); + +const stmt = db.prepare(` + INSERT INTO events + (id, title_fa, title_en, date_fa, date_en, month_fa, month_en, year, type, + location_fa, location_en, city_fa, city_en, description_fa, description_en, + registration_open, sort_order) + VALUES + (@id, @title_fa, @title_en, @date_fa, @date_en, @month_fa, @month_en, @year, @type, + @location_fa, @location_en, @city_fa, @city_en, @description_fa, @description_en, + @registration_open, @sort_order) +`); + +events.forEach((e, i) => { + const j = jalali(e.iso); + stmt.run({ + id: e.id, + title_fa: e.titleFa, title_en: e.titleEn, + date_fa: j.day, date_en: j.day, + month_fa: j.monthFa, month_en: j.monthEn, + year: j.year, + type: e.type, + location_fa: `${e.cityFa}، ${e.countryFa}`, location_en: `${e.cityEn}, ${e.countryFa}`, + city_fa: e.cityFa, city_en: e.cityEn, + description_fa: e.descFa, description_en: e.descEn, + registration_open: 1, sort_order: i, + }); + console.log(`✓ ${e.id} → ${j.day} ${j.monthFa} ${j.year}`); +}); + +console.log(`\nDone: ${events.length} events inserted`); diff --git a/panel/insert-factory.mjs b/panel/insert-factory.mjs new file mode 100644 index 0000000..7d4d6c4 --- /dev/null +++ b/panel/insert-factory.mjs @@ -0,0 +1,60 @@ +import Database from 'better-sqlite3'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const db = new Database(path.join(__dirname, 'data.db')); + +// Real issues of Mobarakeh Steel's "ماهنامه تحلیلی کارخانه" (titles/descriptions/images +// taken from msc.ir). The listing page itself is WAF-gated, so only the latest 3 issues +// were retrievable automatically; images hotlink fine from msc.ir. +const MSC = 'https://www.msc.ir/_DouranPortal/Album/Articles'; +const reports = [ + { + id: 'msc-55', dir: 'left', + img: `${MSC}/%DB%B5%DB%B5_14050314_083241.jpg`, + titleFa: 'ماهنامه تحلیلی کارخانه — شماره ۵۵', titleEn: 'Factory Analysis Monthly — No. 55', + dateFa: 'خرداد ۱۴۰۵', dateEn: 'May 2026', + excerptFa: 'در این شماره می‌خوانید: راهبرد یکپارچه چگونه انسجام راهبردی و مزیت رقابتی پایدار فولاد مبارکه را تضمین می‌کند؟ هم‌افزایی شش محور استراتژیک، تبدیل ثروت معدنی به قدرت صنعتی، و دوام مزیت‌های نسبی فولاد ایران زیر فشار ناترازی انرژی.', + excerptEn: 'Integrated strategy, synergy of six strategic axes, turning mineral wealth into industrial power, and the resilience of Iran’s steel advantages under energy imbalance.', + }, + { + id: 'msc-54', dir: 'right', + img: `${MSC}/;hvohki%2054_14050323_095959.jpg`, + titleFa: 'ماهنامه تحلیلی کارخانه — شماره ۵۴', titleEn: 'Factory Analysis Monthly — No. 54', + dateFa: 'مهر ۱۴۰۴', dateEn: 'Oct 2025', + excerptFa: 'شماره ۵۴ ماهنامه تحلیلی راهبردی کارخانه فولاد مبارکه.', + excerptEn: 'Issue No. 54 of Mobarakeh Steel’s strategic factory analysis monthly.', + }, + { + id: 'msc-53', dir: 'up', + img: `${MSC}/01_20250921_103454.jpg`, + titleFa: 'ماهنامه تحلیلی کارخانه — شماره ۵۳', titleEn: 'Factory Analysis Monthly — No. 53', + dateFa: 'شهریور ۱۴۰۴', dateEn: 'Sep 2025', + excerptFa: 'عبور از ملاحظات؛ آیا زمان جراحی نهادی برای تحقق توسعه پایدار صنایع معدنی فرا رسیده است؟', + excerptEn: 'Beyond considerations: has the time come for institutional surgery to achieve sustainable development of the mining industries?', + }, +]; + +db.prepare('DELETE FROM factory_reports').run(); + +const stmt = db.prepare(` + INSERT INTO factory_reports + (id, img, tag_fa, tag_en, title_fa, title_en, date_fa, date_en, excerpt_fa, excerpt_en, scroll_dir, sort_order) + VALUES + (@id, @img, @tag_fa, @tag_en, @title_fa, @title_en, @date_fa, @date_en, @excerpt_fa, @excerpt_en, @scroll_dir, @sort_order) +`); + +reports.forEach((r, i) => { + stmt.run({ + id: r.id, img: r.img, + tag_fa: 'ماهنامه تحلیلی کارخانه', tag_en: 'Factory Analysis', + title_fa: r.titleFa, title_en: r.titleEn, + date_fa: r.dateFa, date_en: r.dateEn, + excerpt_fa: r.excerptFa, excerpt_en: r.excerptEn, + scroll_dir: r.dir, sort_order: i, + }); + console.log(`✓ ${r.id} → ${r.titleFa}`); +}); + +console.log(`\nDone: ${reports.length} factory reports inserted`); diff --git a/panel/package-lock.json b/panel/package-lock.json index 20c3b32..572d41b 100644 --- a/panel/package-lock.json +++ b/panel/package-lock.json @@ -8,6 +8,7 @@ "name": "andishkade-foolad-panel", "version": "1.0.0", "dependencies": { + "@anthropic-ai/sdk": "^0.104.1", "bcryptjs": "^2.4.3", "better-sqlite3": "^11.3.0", "cheerio": "^1.2.0", @@ -21,12 +22,44 @@ "multer": "^1.4.5-lts.1", "nanoid": "^5.0.7", "nodemailer": "^8.0.10", + "openai": "^6.42.0", + "pdf-parse": "^2.4.5", "sharp": "^0.35.1" }, "engines": { "node": ">=20" } }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.104.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.104.1.tgz", + "integrity": "sha512-gGACa/+IaiXzRRmF96aOhamoBgapKRBiFWbmmTFP8aMkpaEcuStF+Q61bjo4vPxBM7gqWJNZqsngslRdnLHv0Q==", + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1", + "standardwebhooks": "^1.0.0" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", @@ -537,6 +570,196 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz", + "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==", + "license": "MIT", + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.80", + "@napi-rs/canvas-darwin-arm64": "0.1.80", + "@napi-rs/canvas-darwin-x64": "0.1.80", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.80", + "@napi-rs/canvas-linux-arm64-musl": "0.1.80", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-musl": "0.1.80", + "@napi-rs/canvas-win32-x64-msvc": "0.1.80" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz", + "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz", + "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz", + "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz", + "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz", + "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz", + "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz", + "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz", + "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz", + "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz", + "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1238,6 +1461,12 @@ "express": ">= 4.11" } }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -1496,6 +1725,19 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", @@ -1828,6 +2070,24 @@ "wrappy": "1" } }, + "node_modules/openai": { + "version": "6.42.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.42.0.tgz", + "integrity": "sha512-1WFEt/uXMXOLhYRNkgJWo08Y2YNvNwpVU72K7ibrWgWpNOXd4VojXLbe6SQ4bLiUQ3Y8jz4IiyVkylJCL1DtZg==", + "license": "Apache-2.0", + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -1892,6 +2152,38 @@ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, + "node_modules/pdf-parse": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-2.4.5.tgz", + "integrity": "sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==", + "license": "Apache-2.0", + "dependencies": { + "@napi-rs/canvas": "0.1.80", + "pdfjs-dist": "5.4.296" + }, + "bin": { + "pdf-parse": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.16.0 <21 || >=22.3.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/mehmet-kozan" + } + }, + "node_modules/pdfjs-dist": { + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.80" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -2285,6 +2577,16 @@ "simple-concat": "^1.0.0" } }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -2377,6 +2679,12 @@ "node": ">=0.6" } }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/panel/package.json b/panel/package.json index 2ccf25e..889b46d 100644 --- a/panel/package.json +++ b/panel/package.json @@ -11,6 +11,7 @@ "seed": "node seed.js" }, "dependencies": { + "@anthropic-ai/sdk": "^0.104.1", "bcryptjs": "^2.4.3", "better-sqlite3": "^11.3.0", "cheerio": "^1.2.0", @@ -24,6 +25,8 @@ "multer": "^1.4.5-lts.1", "nanoid": "^5.0.7", "nodemailer": "^8.0.10", + "openai": "^6.42.0", + "pdf-parse": "^2.4.5", "sharp": "^0.35.1" }, "engines": { diff --git a/panel/public/app.js b/panel/public/app.js index b15c39c..d6611b8 100644 --- a/panel/public/app.js +++ b/panel/public/app.js @@ -111,6 +111,7 @@ function renderLogin(errorMsg = '') { method: 'POST', body: { username: fd.get('username'), password: fd.get('password') }, }); + if (data.token) sessionStorage.setItem('sf_token', data.token); renderList(); } catch { renderLogin('نام کاربری یا رمز عبور اشتباه است'); @@ -139,6 +140,8 @@ function topbarHtml(active, primaryLabel) { + +
@@ -166,9 +169,11 @@ function wireTabs() { 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 === 'users') renderUsersList(); + 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 () => { @@ -234,10 +239,12 @@ async function renderList() { } } -function renderEditor(article) { +function renderEditor(article, draft = {}) { const a = article || { - title: '', category: '', type: '', author: '', authorRole: '', authorInitial: '', - publishDate: '', pages: 0, price: 0, isFree: true, summary: '', body: '', + title: draft.title || '', category: draft.category || '', type: draft.type || '', + author: '', authorRole: '', authorInitial: '', + publishDate: '', pages: 0, price: 0, isFree: true, + summary: draft.summary || '', body: '', coverImage: '', tags: [], featured: false, }; const isEdit = !!article; @@ -1889,5 +1896,331 @@ function renderBannerEditor(banner) { }); } +// ───────────────────────────────────────────── +function renderImport() { + root.innerHTML = ` + ${topbarHtml('import', '')} +
+

⬇️ ایمپورت از Gemini

+

+ خروجی JSON که Gemini داد رو اینجا paste کن و دکمه ایمپورت رو بزن. + گزارش توی مطالب و آیتم‌های رادار توی رادار ذخیره می‌شن. +

+ +
+ + + +
+ + + +
+ +
+
+ `; + + setupTopbar(); + + const jsonTA = document.getElementById('import-json'); + const statusEl = document.getElementById('import-status'); + const resultEl = document.getElementById('import-result'); + const countEl = document.getElementById('import-count'); + + jsonTA.addEventListener('input', () => { + try { + const d = JSON.parse(jsonTA.value); + const ra = d.radar_items?.length || 0; + countEl.textContent = `✓ JSON معتبر — گزارش: ${d.report?.title ? '1' : '0'} | رادار: ${ra}`; + countEl.style.color = '#22c55e'; + } catch { + countEl.textContent = 'JSON نامعتبر'; + countEl.style.color = '#ef4444'; + } + }); + + document.getElementById('import-clear').onclick = () => { + jsonTA.value = ''; countEl.textContent = ''; resultEl.innerHTML = ''; + }; + + document.getElementById('import-btn').onclick = async () => { + let data; + try { data = JSON.parse(jsonTA.value); } + catch { statusEl.innerHTML = '
JSON نامعتبر است
'; return; } + + const btn = document.getElementById('import-btn'); + btn.disabled = true; btn.textContent = 'در حال ایمپورت...'; + statusEl.innerHTML = ''; + + try { + const res = await apiFetch('/api/admin/import', { method:'POST', body: JSON.stringify(data) }); + if (!res.ok) { const e = await res.json(); throw new Error(e.error || res.status); } + const result = await res.json(); + + resultEl.innerHTML = ` +
+
✓ ایمپورت موفق
+
+
📄 گزارش: ${result.article}
+ ${result.radar.length ? `
🔭 آیتم‌های رادار: ${result.radar.length} مورد اضافه شد
` : ''} +
+
+ مشاهده در مطالب ← + ${result.radar.length ? `مشاهده در رادار ←` : ''} +
+
+ `; + jsonTA.value = ''; countEl.textContent = ''; + } catch (err) { + statusEl.innerHTML = `
خطا: ${err.message}
`; + } finally { + btn.disabled = false; btn.textContent = 'ایمپورت'; + } + }; +} + +// ───────────────────────────────────────────── +function renderPdfAnalyze() { + root.innerHTML = ` + ${topbarHtml('pdfAnalyze', '')} +
+

آنالیز و دسته‌بندی PDF

+

فایل PDF گزارش را آپلود کنید — سیستم متن را استخراج کرده و پیشنهاد بخش می‌دهد.

+ +
+
+ + +
+
+ +
+
+ `; + wireTabs(); + $('#newBtn') && ($('#newBtn').style.display = 'none'); + + $('#pdfForm').addEventListener('submit', async (e) => { + e.preventDefault(); + const file = e.target.pdf.files[0]; + if (!file) return; + const result = $('#pdfResult'); + result.innerHTML = `

در حال پردازش PDF… (${(file.size/1024).toFixed(0)} KB)

`; + try { + const base64 = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result.split(',')[1]); + reader.onerror = reject; + reader.readAsDataURL(file); + }); + const _token = sessionStorage.getItem('sf_token') || ''; + const _res = await fetch('/api/pdf-analyze', { + method: 'POST', credentials: 'include', + headers: { 'Content-Type': 'application/json', ...(_token ? { 'Authorization': 'Bearer ' + _token } : {}) }, + body: JSON.stringify({ base64 }), + }); + if (!_res.ok) { + const errText = await _res.text(); + result.innerHTML = `
خطای سرور ${_res.status}:
${escapeHtml(errText)}
`; + return; + } + const data = await _res.json(); + const SECTION_LABELS = { + market: 'بازار و زنجیره فولاد', technology: 'فناوری صنعتی', + geopolitics: 'ژئواکونومی', sustainability: 'پایداری محیطی', foresight: 'آینده‌نگاری', + }; + + if (data.is_garbled) { + // PDF has non-standard font encoding — offer manual text input + result.innerHTML = ` +
+
+ ⚠️ + متن PDF قابل استخراج نیست +
+

+ این PDF از رمزگذاری فونت غیراستاندارد استفاده می‌کند (رایج در نشریات فارسی). + متن به‌درستی خوانده نمی‌شود و نیاز به OCR دارد.
+ می‌توانید متن رو دستی paste کنید تا بخش پیشنهادی و خلاصه ساخته بشه. +

+

${data.page_count} صفحه · ${(data.text_length/1000).toFixed(1)}k کاراکتر (ناخوانا)

+
+ +
+

عنوان گزارش

+ + +

متن / خلاصه (paste کنید)

+ + +

بخش مناسب

+ + +
+ + +
+
+
+ `; + + async function runGenerate(textSrc, titleSrc, catKey) { + const gr = $('#generateResult'); + gr.innerHTML = '

در حال تولید محتوا... ⏳

'; + const _token = sessionStorage.getItem('sf_token') || ''; + try { + const r = await fetch('/api/pdf-generate', { + method: 'POST', credentials: 'include', + headers: { 'Content-Type': 'application/json', ...(_token ? { 'Authorization': 'Bearer ' + _token } : {}) }, + body: JSON.stringify({ text: textSrc, category: SECTION_LABELS[catKey] || catKey }), + }); + const d = await r.json(); + if (!r.ok) { gr.innerHTML = `
${escapeHtml(d.error || 'خطا')}
`; return; } + const generated = d.generated || ''; + gr.innerHTML = ` +
+

محتوای تولیدشده

+
${escapeHtml(generated)}
+
+ + `; + $('#useGeneratedBtn').addEventListener('click', () => { + const lines = generated.split('\n').filter(l => l.trim()); + const autoTitle = titleSrc || lines[0]?.replace(/^#+\s*|\*\*/g,'').slice(0,100) || ''; + window._pdfDraft = { title: autoTitle, summary: generated.slice(0, 600), category: SECTION_LABELS[catKey] || catKey, type: 'quarterly' }; + renderEditor(null, window._pdfDraft); + }); + } catch(e) { gr.innerHTML = `
${escapeHtml(e.message)}
`; } + } + + $('#generateContentBtn').addEventListener('click', () => { + const t = $('#manualText').value.trim(); + if (!t) { alert('لطفاً ابتدا متن را paste کنید'); return; } + runGenerate(t, $('#manualTitle').value.trim(), $('#manualCategory').value); + }); + + $('#createArticleBtn').addEventListener('click', () => { + const manualTitle = $('#manualTitle').value.trim(); + const manualText = $('#manualText').value.trim(); + const catKey = $('#manualCategory').value; + window._pdfDraft = { + title: manualTitle, + summary: manualText.slice(0, 300), + category: SECTION_LABELS[catKey] || catKey, + type: 'quarterly', + }; + renderEditor(null, window._pdfDraft); + }); + return; + } + + const c = data.classification; + const allRows = Object.entries(c.allScores) + .sort((a,b) => b[1].score - a[1].score) + .map(([cat, d]) => ` + + ${cat === c.category ? '✅' : ''} ${SECTION_LABELS[cat] || cat} + +
+
+ ${d.score} +
+ + `).join(''); + + result.innerHTML = ` +
+
+ ${escapeHtml(c.label)} + اطمینان: ${c.confidence}٪ + ${data.page_count} صفحه · ${(data.text_length/1000).toFixed(1)}k کاراکتر +
+ +

عنوان پیشنهادی

+

${escapeHtml(data.title)}

+ +

کلیدواژه‌های شناسایی‌شده

+
+ ${c.keywords.map(kw => `${escapeHtml(kw)}`).join('')} +
+ +

امتیاز بخش‌ها

+ + ${allRows} +
+
+ +
+

متن استخراج‌شده (۸۰۰ کاراکتر اول)

+
${escapeHtml(data.excerpt)}
+
+ +
+

ایجاد مقاله از این PDF

+
+ + +
+
+
+ `; + + $('#createArticleBtn').addEventListener('click', () => { + window._pdfDraft = { + title: data.title, + summary: data.excerpt.slice(0, 300), + category: c.label, + type: 'quarterly', + }; + renderEditor(null, window._pdfDraft); + }); + + $('#generateContentBtn').addEventListener('click', async () => { + const gr = $('#generateResult'); + gr.innerHTML = '

در حال تولید محتوا... ⏳

'; + const _token = sessionStorage.getItem('sf_token') || ''; + try { + const r = await fetch('/api/pdf-generate', { + method: 'POST', credentials: 'include', + headers: { 'Content-Type': 'application/json', ...(_token ? { 'Authorization': 'Bearer ' + _token } : {}) }, + body: JSON.stringify({ text: data.excerpt + '\n\n' + (data.text || '').slice(0, 5000), category: c.label }), + }); + const d = await r.json(); + if (!r.ok) { gr.innerHTML = `
${escapeHtml(d.error || 'خطا')}
`; return; } + const generated = d.generated || ''; + gr.innerHTML = ` +
+

محتوای تولیدشده

+
${escapeHtml(generated)}
+
+ + `; + $('#useGeneratedBtn').addEventListener('click', () => { + const lines = generated.split('\n').filter(l => l.trim()); + const autoTitle = data.title || lines[0]?.replace(/^#+\s*|\*\*/g,'').slice(0,100) || ''; + window._pdfDraft = { title: autoTitle, summary: generated.slice(0, 600), category: c.label, type: 'quarterly' }; + renderEditor(null, window._pdfDraft); + }); + } catch(e) { gr.innerHTML = `
${escapeHtml(e.message)}
`; } + }); + + } catch (err) { + result.innerHTML = `
خطا: ${escapeHtml(err.message)}
`; + } + }); +} + // ───────────────────────────────────────────── api('/api/auth/me').then(() => renderList()).catch(() => renderLogin()); diff --git a/panel/run-import.mjs b/panel/run-import.mjs new file mode 100644 index 0000000..dd1aac7 --- /dev/null +++ b/panel/run-import.mjs @@ -0,0 +1,42 @@ +import fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import dotenv from 'dotenv'; + +dotenv.config({ path: fileURLToPath(new URL('./.env', import.meta.url)) }); + +const BASE = `http://localhost:${process.env.PORT || 3001}`; +const USER = process.env.ADMIN_USERNAME || 'admin'; +const PASS = process.env.ADMIN_PASSWORD; + +const payloads = JSON.parse(fs.readFileSync(new URL('./imports-data.json', import.meta.url))); + +// 1) login +const loginRes = await fetch(`${BASE}/api/auth/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: USER, password: PASS }), +}); +if (!loginRes.ok) { + console.error('LOGIN FAILED', loginRes.status, await loginRes.text()); + process.exit(1); +} +const { token } = await loginRes.json(); +console.log('✓ logged in\n'); + +// 2) import each +let ok = 0; +for (const p of payloads) { + const res = await fetch(`${BASE}/api/admin/import`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, + body: JSON.stringify(p), + }); + const data = await res.json(); + if (res.ok) { + ok++; + console.log(`✓ ${p.report.id} → article + ${data.radar.length} radar items`); + } else { + console.error(`✗ ${p.report.id} → ${res.status}`, data); + } +} +console.log(`\nDone: ${ok}/${payloads.length} imported`); diff --git a/panel/server.js b/panel/server.js index f1891db..f663a31 100644 --- a/panel/server.js +++ b/panel/server.js @@ -63,7 +63,7 @@ app.use(helmet({ }, frameguard: { action: 'deny' }, })); -app.use(express.json({ limit: '2mb' })); +app.use(express.json({ limit: '50mb' })); app.use('/uploads', express.static(uploadsDir, { maxAge: '7d' })); app.use('/', express.static(path.join(__dirname, 'public'))); @@ -84,7 +84,11 @@ const contactLimiter = rateLimit({ }); function authRequired(req, res, next) { - const token = req.cookies?.session; + let token = req.cookies?.session; + if (!token) { + const auth = req.headers['authorization']; + if (auth?.startsWith('Bearer ')) token = auth.slice(7); + } if (!token) return res.status(401).json({ error: 'unauthorized' }); try { req.user = jwt.verify(token, JWT_SECRET); @@ -94,6 +98,20 @@ function authRequired(req, res, next) { } } +function memberRequired(req, res, next) { + const auth = req.headers['authorization']; + const token = (auth?.startsWith('Bearer ') ? auth.slice(7) : null) ?? req.query.token ?? null; + if (!token) return res.status(401).json({ error: 'unauthorized' }); + try { + const payload = jwt.verify(token, JWT_SECRET); + if (payload.role !== 'member') return res.status(403).json({ error: 'forbidden' }); + req.member = payload; + next(); + } catch { + return res.status(401).json({ error: 'invalid_token' }); + } +} + app.post('/api/auth/login', loginLimiter, (req, res) => { const { username, password } = req.body || {}; if (!username || !password) return res.status(400).json({ error: 'username_password_required' }); @@ -110,7 +128,7 @@ app.post('/api/auth/login', loginLimiter, (req, res) => { maxAge: 24 * 60 * 60 * 1000, path: '/', }); - res.json({ user: { id: row.id, username: row.username } }); + res.json({ user: { id: row.id, username: row.username }, token }); }); app.post('/api/auth/logout', (_req, res) => { @@ -255,6 +273,91 @@ app.delete('/api/articles/:id', authRequired, (req, res) => { res.status(204).end(); }); +// ---------- bulk import (Gemini JSON) ---------- +// Gemini grounding leaves [cite: 1921] / [cite: 1921, 1931] markers in the text — strip them +// (and the whitespace artifacts they leave behind) before anything hits the DB. +function stripCites(s) { + return s + .replace(/\[cite:[^\]]*\]/g, '') + .replace(/[ \t]+([.،؛:!؟])/g, '$1') // space before punctuation + .replace(/[ \t]{2,}/g, ' ') // runs of spaces + .replace(/[ \t]+\n/g, '\n') // trailing space before newline (keeps \n\n) + .trim(); +} +function deepSanitize(v) { + if (typeof v === 'string') return stripCites(v); + if (Array.isArray(v)) return v.map(deepSanitize); + if (v && typeof v === 'object') { + const o = {}; + for (const k of Object.keys(v)) o[k] = deepSanitize(v[k]); + return o; + } + return v; +} + +app.post('/api/admin/import', authRequired, (req, res) => { + const { report, radar_items = [], fa_summary_long, key_data } = deepSanitize(req.body || {}); + if (!report?.title) return res.status(400).json({ error: 'report.title required' }); + + const results = { article: null, radar: [] }; + + // upsert article + const articleId = report.id || nanoid(14); + const body = fa_summary_long + ? (key_data ? fa_summary_long + '\n\n---KEY_DATA---\n' + JSON.stringify(key_data, null, 2) : fa_summary_long) + : (key_data ? JSON.stringify(key_data, null, 2) : null); + + db.prepare(` + INSERT OR REPLACE INTO articles + (id, title, category, type, author, author_role, author_initial, + publish_date, pages, price, is_free, summary, body, tags, cover_image, featured, status) + VALUES + (@id, @title, @category, @type, @author, @author_role, @author_initial, + @publish_date, @pages, @price, @is_free, @summary, @body, @tags, @cover_image, @featured, 'published') + `).run({ + id: articleId, + title: report.title, + category: report.category || null, + type: report.type || 'special', + author: report.author || null, + author_role: report.authorRole || report.author_role || null, + author_initial: report.authorInitial || report.author_initial || null, + publish_date: report.publishDate || report.publish_date || null, + pages: Number(report.pages) || 0, + price: Number(report.price) || 0, + is_free: (report.isFree ?? report.is_free ?? false) ? 1 : 0, + summary: report.summary || null, + body, + tags: JSON.stringify(Array.isArray(report.tags) ? report.tags : []), + cover_image: report.coverImage || report.cover_image || null, + featured: report.featured ? 1 : 0, + }); + results.article = articleId; + + // upsert radar items + const insertRadar = db.prepare(` + INSERT OR REPLACE INTO radar_items + (id, category, img, title_fa, excerpt_fa, date_fa, author_name, sort_order) + VALUES (@id, @category, @img, @title_fa, @excerpt_fa, @date_fa, @author_name, @sort_order) + `); + radar_items.forEach((item, i) => { + const rid = item.id || nanoid(12); + insertRadar.run({ + id: rid, + category: item.category || 'market', + img: item.img || item.image || report.coverImage || '', + title_fa: item.title || '', + excerpt_fa: item.summary || item.excerpt_fa || '', + date_fa: item.date || '', + author_name: report.author || '', + sort_order: i, + }); + results.radar.push(rid); + }); + + res.json({ ok: true, ...results }); +}); + // ---------- events ---------- const EVENT_FIELDS = `id, title_fa, title_en, date_fa, date_en, month_fa, month_en, year, type, location_fa, location_en, city_fa, city_en, description_fa, description_en, @@ -1180,6 +1283,225 @@ app.post('/api/contact', contactLimiter, async (req, res) => { } }); +// ---------- PDF analyser ---------- + +const SECTION_KEYWORDS = { + market: { + words: ['بازار','قیمت','صادرات','واردات','معامله','تجارت','فولاد','آهن','سنگ آهن','بلوم','تختال','کویل','عرضه','تقاضا','تولید','اقتصاد','market','price','trade','export','import','steel','iron ore','billet','slab','coil','supply','demand','production'], + label: 'بازار و زنجیره فولاد', + }, + technology: { + words: ['فناوری','هوش مصنوعی','دیجیتال','اتوماسیون','نرم‌افزار','ربات','هوشمند','نوآوری','تکنولوژی','داده','technology','ai','digital','automation','software','robot','smart','innovation','iot','machine learning'], + label: 'فناوری صنعتی', + }, + geopolitics: { + words: ['ژئوپلیتیک','تحریم','سیاست','دیپلماسی','جنگ','تنش','بین‌الملل','ژئواکونومی','قدرت','geopolitics','sanction','policy','diplomacy','war','tension','international','brics','conflict'], + label: 'ژئواکونومی', + }, + sustainability: { + words: ['محیط زیست','کربن','پایداری','سبز','آلودگی','اقلیم','انرژی پاک','انتشار','تجدیدپذیر','esg','carbon','sustainability','environment','green','climate','emission','renewable','clean energy'], + label: 'پایداری محیطی', + }, + foresight: { + words: ['آینده','سناریو','پیش‌بینی','روند','چشم‌انداز','آینده‌نگاری','استراتژی','رصد','تحلیل','future','scenario','forecast','trend','outlook','foresight','strategy','horizon','intelligence'], + label: 'آینده‌نگاری', + }, +}; + +function classifyPdfText(text) { + const lower = text.toLowerCase(); + const scores = {}; + for (const [cat, data] of Object.entries(SECTION_KEYWORDS)) { + let score = 0; const matched = []; + for (const kw of data.words) { + const hits = (lower.match(new RegExp(kw.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length; + if (hits > 0) { score += hits; matched.push(kw); } + } + scores[cat] = { score, matched, label: data.label }; + } + const sorted = Object.entries(scores).sort((a, b) => b[1].score - a[1].score); + const [topCat, topData] = sorted[0]; + const secondScore = sorted[1]?.[1].score || 0; + const confidence = topData.score > 0 ? Math.min(100, Math.round(((topData.score - secondScore) / (topData.score + 1)) * 100 + 40)) : 0; + return { + category: topCat, label: topData.label, score: topData.score, confidence, + keywords: [...new Set(topData.matched)].slice(0, 8), + allScores: Object.fromEntries(sorted.map(([c, d]) => [c, { score: d.score, label: d.label }])), + }; +} + +function extractPdfTitle(text) { + const lines = text.split('\n').map(l => l.trim()).filter(l => l.length > 8 && l.length < 180); + return lines[0] || 'بدون عنوان'; +} + +app.post('/api/pdf-analyze', authRequired, async (req, res) => { + try { + const { base64 } = req.body || {}; + if (!base64) return res.status(400).json({ error: 'فایل PDF ارسال نشده' }); + const buf = Buffer.from(base64, 'base64'); + const { PDFParse } = await import('pdf-parse'); + const parser = new PDFParse({ data: buf }); + const result = await parser.getText(); + const text = result.text || ''; + const pageCount = result.total || result.pages?.length || 0; + await parser.destroy(); + // detect garbled text: check ratio of Persian/Arabic Unicode chars + const persianChars = (text.match(/[؀-ۿ]/g) || []).length; + const meaningfulChars = text.replace(/\s/g, '').length; + const isGarbled = meaningfulChars > 50 && (persianChars / meaningfulChars) < 0.15; + const title = isGarbled ? '' : extractPdfTitle(text); + const excerpt = isGarbled ? '' : text.replace(/\n{3,}/g, '\n\n').trim().slice(0, 800); + const classification = isGarbled ? null : classifyPdfText(text); + res.json({ title, excerpt, text_length: text.length, page_count: pageCount, classification, is_garbled: isGarbled }); + } catch (err) { + console.error('PDF parse error:', err); + res.status(500).json({ error: 'خطا در تجزیه PDF: ' + err.message }); + } +}); + +app.post('/api/pdf-generate', authRequired, async (req, res) => { + const apiKey = process.env.AI_API_KEY || process.env.ANTHROPIC_API_KEY; + if (!apiKey) return res.status(503).json({ error: 'AI_API_KEY در فایل .env تنظیم نشده' }); + + const { text, category } = req.body || {}; + if (!text || text.trim().length < 50) return res.status(400).json({ error: 'متن کافی ارسال نشده' }); + + const prompt = `متن زیر از یک گزارش تخصصی در حوزه صنعت فولاد است. +بر اساس این متن، یک مقاله جامع و تحلیلی به زبان فارسی بنویس که شامل این بخش‌ها باشد: + +۱. **عنوان** (یک عنوان جذاب و حرفه‌ای) +۲. **خلاصه اجرایی** (۲ تا ۳ پاراگراف — مهم‌ترین یافته‌ها) +۳. **نکات کلیدی** (۴ تا ۶ گلوله — bullet points) +۴. **تحلیل تفصیلی** (۳ تا ۵ پاراگراف — بررسی عمیق) +۵. **نتیجه‌گیری** (۱ تا ۲ پاراگراف) + +خروجی باید کاملاً فارسی، حرفه‌ای، و مناسب برای یک اندیشکده تخصصی باشد. +${category ? `حوزه: ${category}` : ''} + +متن منبع: +--- +${text.slice(0, 6000)} +--- + +فقط محتوای مقاله را بنویس، بدون توضیح اضافه.`; + + try { + const { default: OpenAI } = await import('openai'); + const clientOpts = { apiKey }; + if (process.env.AI_BASE_URL) clientOpts.baseURL = process.env.AI_BASE_URL; + const client = new OpenAI(clientOpts); + const model = process.env.AI_MODEL || 'gpt-4o-mini'; + + const completion = await client.chat.completions.create({ + model, + max_tokens: 2048, + messages: [{ role: 'user', content: prompt }], + }); + + const generated = completion.choices[0]?.message?.content || ''; + res.json({ generated }); + } catch (err) { + console.error('AI generate error:', err); + res.status(500).json({ error: 'خطا در تولید محتوا: ' + err.message }); + } +}); + +// ────────────────────────────────────────── +// Member auth & profile (public users, not admins) +// ────────────────────────────────────────── + +app.post('/api/members/register', loginLimiter, (req, res) => { + const { name, email, password } = req.body || {}; + if (!name || !email || !password) 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 exists = db.prepare('SELECT 1 FROM members WHERE email = ?').get(email); + if (exists) return res.status(409).json({ error: 'این ایمیل قبلاً ثبت شده است' }); + const hash = bcrypt.hashSync(password, 10); + const info = db.prepare('INSERT INTO members (name, email, password_hash) VALUES (?, ?, ?)').run(name.trim(), email.toLowerCase().trim(), hash); + const token = jwt.sign({ sub: info.lastInsertRowid, email, name: name.trim(), role: 'member' }, JWT_SECRET, { expiresIn: '30d' }); + res.status(201).json({ token, user: { id: info.lastInsertRowid, name: name.trim(), email } }); +}); + +app.post('/api/members/login', loginLimiter, (req, res) => { + const { email, password } = req.body || {}; + if (!email || !password) return res.status(400).json({ error: 'ایمیل و رمز عبور الزامی است' }); + const row = db.prepare('SELECT id, name, email, password_hash, is_active FROM members WHERE email = ?').get(email.toLowerCase().trim()); + if (!row || !bcrypt.compareSync(password, row.password_hash)) return res.status(401).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' }); + res.json({ token, user: { id: row.id, name: row.name, email: row.email } }); +}); + +app.get('/api/members/me', memberRequired, (req, res) => { + const row = 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' }); + res.json({ id: row.id, name: row.name, email: row.email, phone: row.phone, avatar: row.avatar, createdAt: row.created_at }); +}); + +app.put('/api/members/me', memberRequired, (req, res) => { + const { name, phone } = req.body || {}; + const row = db.prepare('SELECT id FROM members WHERE id = ?').get(req.member.sub); + if (!row) return res.status(404).json({ error: 'not_found' }); + const newName = name?.trim() || req.member.name; + db.prepare("UPDATE members SET name = ?, phone = ?, updated_at = datetime('now') WHERE id = ?").run(newName, phone || null, req.member.sub); + res.json({ ok: true }); +}); + +app.put('/api/members/me/password', memberRequired, (req, res) => { + const { currentPassword, newPassword } = req.body || {}; + if (!currentPassword || !newPassword) return res.status(400).json({ error: 'فیلدها الزامی است' }); + if (newPassword.length < 6) return res.status(400).json({ error: 'رمز جدید باید حداقل ۶ کاراکتر باشد' }); + const row = db.prepare('SELECT password_hash FROM members WHERE id = ?').get(req.member.sub); + if (!row || !bcrypt.compareSync(currentPassword, row.password_hash)) return res.status(401).json({ error: 'رمز فعلی اشتباه است' }); + const hash = bcrypt.hashSync(newPassword, 10); + db.prepare("UPDATE members SET password_hash = ?, updated_at = datetime('now') WHERE id = ?").run(hash, req.member.sub); + res.json({ ok: true }); +}); + +app.get('/api/members/me/purchases', memberRequired, (req, res) => { + const rows = db.prepare(` + SELECT p.id, p.amount, p.purchased_at, + a.id AS article_id, a.title, a.category, a.type, a.cover_image, a.pages, a.summary, a.author, a.publish_date + FROM purchases p JOIN articles a ON p.article_id = a.id + WHERE p.member_id = ? + ORDER BY p.purchased_at DESC + `).all(req.member.sub); + res.json(rows.map(r => ({ + id: r.id, amount: r.amount, purchasedAt: r.purchased_at, + article: { id: r.article_id, title: r.title, category: r.category, type: r.type, coverImage: r.cover_image, pages: r.pages, summary: r.summary, author: r.author, publishDate: r.publish_date }, + }))); +}); + +// admin: grant purchase to a member +app.post('/api/members/:memberId/purchases', authRequired, (req, res) => { + const { articleId, amount = 0 } = req.body || {}; + if (!articleId) return res.status(400).json({ error: 'articleId الزامی است' }); + const member = db.prepare('SELECT id FROM members WHERE id = ?').get(req.params.memberId); + if (!member) return res.status(404).json({ error: 'عضو پیدا نشد' }); + const article = db.prepare('SELECT id, cover_image FROM articles WHERE id = ?').get(articleId); + if (!article) return res.status(404).json({ error: 'مقاله پیدا نشد' }); + try { + db.prepare('INSERT OR IGNORE INTO purchases (member_id, article_id, amount) VALUES (?, ?, ?)').run(req.params.memberId, articleId, amount); + res.status(201).json({ ok: true }); + } catch (e) { + res.status(409).json({ error: 'قبلاً خریداری شده' }); + } +}); + +// download purchased article file (cover image for now; swap for PDF path later) +app.get('/api/members/me/purchases/:purchaseId/download', memberRequired, (req, res) => { + const row = db.prepare(` + SELECT a.cover_image, a.title FROM purchases p JOIN articles a ON p.article_id = a.id + WHERE p.id = ? AND p.member_id = ? + `).get(req.params.purchaseId, req.member.sub); + if (!row) return res.status(404).json({ error: 'خرید پیدا نشد' }); + const filePath = row.cover_image ? path.join(__dirname, 'uploads', path.basename(row.cover_image)) : null; + if (!filePath || !fs.existsSync(filePath)) return res.status(404).json({ error: 'فایل موجود نیست' }); + res.download(filePath, row.title + path.extname(filePath)); +}); + app.listen(PORT, () => { console.log(`Panel running at ${PUBLIC_ORIGIN}`); const intervalMs = Number(process.env.SCRAPER_INTERVAL_MS) || 2 * 60 * 1000; diff --git a/panel/update-radar-images.mjs b/panel/update-radar-images.mjs new file mode 100644 index 0000000..b0a9049 --- /dev/null +++ b/panel/update-radar-images.mjs @@ -0,0 +1,43 @@ +import Database from 'better-sqlite3'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const db = new Database(path.join(__dirname, 'data.db')); + +// id → image (served from the frontend /public/news folder at /news/...) +const radarImages = { + 'green-demand-growth': '/news/photo-1551288049-bebda4e38f71.jpg', + 'stricter-green-definitions': '/news/photo-1581092160607-ee22731c9b4e.jpg', + 'premium-willingness': '/news/photo-1466611653911-95081537e5b7.jpg', + 'trading-value-growth': '/news/photo-1611974789855-9c2a0a7236a3.jpg', + 'power-demand-surge': '/news/photo-1518770660439-4636190af475.jpg', + 'digital-trading-edge': '/news/photo-1504328345606-18bbc8c9d7d1.jpg', + 'electricity-cost-driver': '/news/photo-1494412519320-aa613dfb7738.jpg', + 'hbi-decoupling': '/news/photo-1578662996442-48f60103fc96.jpg', + 'massive-asset-reconfiguration': '/news/photo-1504307651254-35680f356dfd.jpg', + 'india-demand-doubling': '/news/photo-1567789884554-0b844b597180.jpg', + 'scrap-shortage-crisis': '/news/photo-1566873535350-6586b0b7a1f2.jpg', + 'china-demand-decline': '/news/photo-1570168007204-dfb528c6958f.jpg', +}; + +const stmt = db.prepare('UPDATE radar_items SET img = ? WHERE id = ?'); +let updated = 0; +for (const [id, img] of Object.entries(radarImages)) { + updated += stmt.run(img, id).changes; +} +console.log(`✓ radar images updated: ${updated}/${Object.keys(radarImages).length}`); + +// also set the matching article cover images (for home report cards) +const articleImages = { + 'green-premia-trends': '/news/photo-1473341304170-971dccb5ac1e.jpg', + 'commodity-trading-scurve': '/news/photo-1611974789855-9c2a0a7236a3.jpg', + 'green-steel-hubs-pathway': '/news/photo-1581092160607-ee22731c9b4e.jpg', + 'india-steel-growth-resilience': '/news/photo-1567789884554-0b844b597180.jpg', +}; +const aStmt = db.prepare('UPDATE articles SET cover_image = ? WHERE id = ?'); +let aUpdated = 0; +for (const [id, img] of Object.entries(articleImages)) { + aUpdated += aStmt.run(img, id).changes; +} +console.log(`✓ article covers updated: ${aUpdated}/${Object.keys(articleImages).length}`); diff --git a/rtk.exe b/rtk.exe new file mode 100644 index 0000000..52beb71 Binary files /dev/null and b/rtk.exe differ diff --git a/src/app/layout/Header.tsx b/src/app/layout/Header.tsx index a6bcd81..4854fe0 100644 --- a/src/app/layout/Header.tsx +++ b/src/app/layout/Header.tsx @@ -1,9 +1,10 @@ import React, { useState, useEffect, useRef } from 'react' import type { CSSProperties } from 'react' -import { NavLink } from 'react-router-dom' +import { NavLink, Link } from 'react-router-dom' import { Search, Menu, X, ChevronDown, Radar, Leaf, Globe, Cpu, BarChart3, Video, Mic, CalendarDays, Users, Eye, Phone, type LucideIcon } from 'lucide-react' import { motion, AnimatePresence } from 'framer-motion' import { useLang } from '@/context/LangContext' +import { useAuth } from '@/context/AuthContext' /* ─── tokens ─── */ const T = { @@ -30,9 +31,9 @@ const SECTIONS: Section[] = [ /* ─── dropdown sections under رسانه و رویداد ─── */ const MEDIA: Section[] = [ - { fa: 'ویدیوکست و پادکست', en: 'Videocast & Podcast', to: '#', Icon: Video }, - { fa: 'نشست‌های تخصصی', en: 'Expert Sessions', to: '#', Icon: Mic }, - { fa: 'رویدادها', en: 'Events', to: '#', Icon: CalendarDays }, + { fa: 'ویدیوکست و پادکست', en: 'Videocast & Podcast', to: '/podcast', Icon: Video }, + { fa: 'نشست‌های تخصصی', en: 'Expert Sessions', to: '#', Icon: Mic }, + { fa: 'رویدادها', en: 'Events', to: '/events', Icon: CalendarDays }, ] /* ─── dropdown sections under درباره ما ─── */ @@ -194,7 +195,7 @@ export default function Header() { const searchInputRef = useRef(null) const { lang, toggle: toggleLang } = useLang() - + const { member } = useAuth() useEffect(() => { const mq = window.matchMedia('(max-width: 1023px)') @@ -282,9 +283,28 @@ export default function Header() { {!isMobile && ( <> - - {lang === 'fa' ? 'ورود / ثبت‌نام' : 'Login / Register'} - + {member ? ( + + + + + {member.name.split(' ')[0]} + + ) : ( + + {lang === 'fa' ? 'ورود / ثبت‌نام' : 'Login / Register'} + + )} + +
+ {/* tags */} +
+ {card.tags.map(t => ( + {t} + ))} +
+ +

+ {isFa ? card.titleFa : card.titleEn} +

+ + {/* meta */} +
+ + {card.source} + + + {isFa ? card.dateFa : card.dateEn} + +
+ + {/* body */} +
+

{body[0]}

+ {body.length > 1 && ( +
+
+ {body.slice(1).map((p, i) =>

{p}

)} +
+ {!isLoggedIn && ( +
+

{isFa ? 'برای مطالعه کامل عضو شوید' : 'Join to read the full article'}

+ +
+ )} +
+ )} +
+ + {/* analysis */} + {isLoggedIn && ( +
+
{isFa ? 'تحلیل راهبردی' : 'STRATEGIC ANALYSIS'}
+

{isFa ? card.analysisFa : card.analysisEn}

+
+ )} + + {/* disclaimer */} +
+ + {isFa ? 'این تحلیل توسط هوش مصنوعی تولید شده است.' : 'This analysis was AI-assisted.'} +
+
+ + + ) +} + +/* ─── main page ─── */ export default function AtAGlance() { const { lang } = useLang() const isFa = lang === 'fa' + const [activeTab, setActiveTab] = useState('market') + const [selectedCard, setSelectedCard] = useState(null) + + const filteredCards = CARDS.filter(c => c.tab === activeTab) return (
- {/* page header */} -
-
-
-
-
- AT A GLANCE -
-

- {isFa ? 'در یک نگاه' : 'At a Glance'} -

-

- {isFa - ? 'مهم‌ترین تحولات و رویدادهای راهبردی صنعت فولاد جهان، گزیده و تحلیل‌شده در یک نگاه.' - : 'The most important strategic developments and events in the global steel industry — curated and analyzed at a glance.'} -

+ + {/* ── HERO ── */} +
+ {/* background image */} +
+ +
+
+ + {/* title + tabs — physically right */} +
+
+ {isFa ? 'دیده‌بانِ تحولات' : 'AT A GLANCE'} +
+

+ {isFa ? 'در یک نگاه' : 'At a Glance'} +

+ {/* tab pills */} +
+ {TABS.map(tab => ( + + ))} +
+
+ + {/* cards row — bottom of hero, overflowing down */} +
+
+ + {filteredCards.slice(0, 4).map((card, idx) => ( + setSelectedCard(card)} + style={{ + width: 220, flexShrink: 0, borderRadius: 14, overflow: 'hidden', + cursor: 'pointer', boxShadow: '0 20px 50px -15px rgba(3,35,64,0.5)', + }} + > +
+ +
+
+
+
+ {isFa ? card.titleFa : card.titleEn} +
+
+ + {card.source} +
+
+ + ))} + +
+
+
+ + {/* ── TICKER ── */} +
+
+ {TICKERS.map((t, i) => ( + + + {isFa ? t.fa : t.en} + + ))} +
+
+ + {/* ── BOTTOM: news list + more cards ── */} +
+
+ + {/* news list */} +
+
+ {isFa ? 'آخرین اخبار' : 'LATEST NEWS'} +
+
+ {NEWS_ITEMS.map((n, i) => ( +
+ + {isFa ? n.titleFa : n.titleEn} + + + {isFa ? n.dateFa : n.dateEn} + +
+ ))} +
+
+ + {/* remaining cards grid */} +
+
+ {isFa ? 'تحلیل‌های بیشتر' : 'MORE ANALYSES'} +
+
+ {filteredCards.slice(0, 4).map(card => ( +
setSelectedCard(card)} + style={{ borderRadius: 12, overflow: 'hidden', cursor: 'pointer', background: '#fff', boxShadow: '0 4px 20px rgba(7,29,73,0.08)', border: '1px solid rgba(7,29,73,0.07)' }} + > +
+ +
+
+
+ {isFa ? card.titleFa : card.titleEn} +
+
+ + {isFa ? card.dateFa : card.dateEn} +
+
+
+ ))}
- {/* the slider (reuses the shared section component) */} - + {/* modal */} + + {selectedCard && ( + setSelectedCard(null)} /> + )} +
) } diff --git a/src/pages/Auth/AuthPage.tsx b/src/pages/Auth/AuthPage.tsx new file mode 100644 index 0000000..cf79ef9 --- /dev/null +++ b/src/pages/Auth/AuthPage.tsx @@ -0,0 +1,427 @@ +import React, { useState } from 'react' +import { useNavigate, Link } from 'react-router-dom' +import { useAuth } from '@/context/AuthContext' +import { useLang } from '@/context/LangContext' + +const GOLD = '#CD9E53' +const NAVY = '#032340' + +type Mode = 'login' | 'register' + +export default function AuthPage({ defaultMode = 'login', onSuccess, onClose }: { defaultMode?: Mode; onSuccess?: () => void; onClose?: () => void }) { + const { lang } = useLang() + const { login, register } = useAuth() + const navigate = useNavigate() + const [mode, setMode] = useState(defaultMode) + const handleSuccess = () => { if (onSuccess) { onSuccess() } else { navigate('/profile') } } + const [isMobile, setIsMobile] = useState(() => window.innerWidth < 1024) + + React.useEffect(() => { + const mq = window.matchMedia('(max-width: 1023px)') + const h = (e: MediaQueryListEvent | MediaQueryList) => setIsMobile(e.matches) + h(mq); mq.addEventListener('change', h) + return () => mq.removeEventListener('change', h) + }, []) + + return ( +
+ {onClose && ( + + )} + {/* ── Brand Panel — desktop only ── */} + {!isMobile && } + + {/* ── Form Panel ── */} +
+ + {/* Mobile top bar */} + {isMobile && ( +
+ + + اندیشکده فولاد آینده + + + + + + {lang === 'fa' ? 'بازگشت' : 'Back'} + +
+ )} + +
+ + {/* Tab switcher */} +
+ {(['login', 'register'] as Mode[]).map(m => ( + + ))} +
+ + {mode === 'login' + ? + : + } + + {!isMobile && ( +
+ + + + + {lang === 'fa' ? 'بازگشت به سایت' : 'Back to site'} + +
+ )} +
+
+
+ ) +} + +/* ─── Login Form ─── */ +function LoginForm({ lang, login, navigate }: { + lang: 'fa' | 'en' + login: (e: string, p: string) => Promise + navigate: (to?: string) => void +}) { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [showPass, setShowPass] = useState(false) + const [error, setError] = useState('') + const [loading, setLoading] = useState(false) + + async function submit(e: React.FormEvent) { + e.preventDefault(); setError(''); setLoading(true) + try { await login(email, password); navigate('/profile') } + catch (err: unknown) { setError(err instanceof Error ? err.message : 'خطا در ورود') } + finally { setLoading(false) } + } + + return ( +
+
+

+ {lang === 'fa' ? 'خوش آمدید' : 'Welcome back'} +

+

+ {lang === 'fa' ? 'برای ادامه وارد حساب خود شوید' : 'Sign in to continue'} +

+
+ + + setEmail(e.target.value)} + placeholder="example@email.com" dir="ltr" + icon={} /> + + + + setPassword(e.target.value)} dir="ltr" + icon={} + suffix={ setShowPass(s => !s)} />} /> + + + {error && {error}} + + + {lang === 'fa' ? 'ورود به حساب' : 'Sign In'} + +
+ ) +} + +/* ─── Register Form ─── */ +function RegisterForm({ lang, register, navigate }: { + lang: 'fa' | 'en' + register: (n: string, e: string, p: string, phone?: string, org?: string) => Promise + navigate: (to?: string) => void +}) { + const [name, setName] = useState('') + const [email, setEmail] = useState('') + const [phone, setPhone] = useState('') + const [org, setOrg] = useState('') + const [password, setPassword] = useState('') + const [showPass, setShowPass] = useState(false) + const [error, setError] = useState('') + const [loading, setLoading] = useState(false) + + async function submit(e: React.FormEvent) { + e.preventDefault(); setError(''); setLoading(true) + try { await register(name, email, password, phone, org); navigate('/profile') } + catch (err: unknown) { setError(err instanceof Error ? err.message : 'خطا در ثبت‌نام') } + finally { setLoading(false) } + } + + return ( +
+
+

+ {lang === 'fa' ? 'ثبت‌نام' : 'Create account'} +

+

+ {lang === 'fa' ? 'یک حساب کاربری جدید بسازید' : 'Join the Steel Futures Institute'} +

+
+ + + setName(e.target.value)} + placeholder={lang === 'fa' ? 'علی محمدی' : 'John Doe'} + icon={} /> + + + + setEmail(e.target.value)} + placeholder="example@email.com" dir="ltr" + icon={} /> + + + + setPhone(e.target.value)} + placeholder={lang === 'fa' ? '09123456789' : '+1 555 000 0000'} dir="ltr" + icon={} /> + + + + setOrg(e.target.value)} + placeholder={lang === 'fa' ? 'شرکت فولاد...' : 'Your company...'} + icon={} /> + + + + setPassword(e.target.value)} dir="ltr" + icon={} + suffix={ setShowPass(s => !s)} />} /> +

+ {lang === 'fa' ? 'حداقل ۶ کاراکتر' : 'Min 6 characters'} +

+
+ + {error && {error}} + + + {lang === 'fa' ? 'ساخت حساب کاربری' : 'Create Account'} + +
+ ) +} + +/* ─── Brand Panel ─── */ +function BrandPanel({ mode, lang }: { mode: Mode; lang: 'fa' | 'en' }) { + return ( +
+ {/* grid */} + + + + + + + {/* hex rings */} + + {[0,1,2,3,4].map(i => ( + + ))} + + + {/* Logo */} +
+ + +
+
اندیشکده فولاد آینده
+
The Steel Futures Institute
+
+ +
+ + {/* Center copy */} +
+
+ {mode === 'login' + ? (lang === 'fa' ? 'پلتفرم تخصصی فولاد' : 'Steel Industry Platform') + : (lang === 'fa' ? 'عضویت رایگان' : 'Free Membership')} +
+

+ {mode === 'login' + ? <>{lang === 'fa' ? 'دسترسی به تحلیل‌های تخصصی' : 'Access Expert Analysis'}
{lang === 'fa' ? 'صنعت فولاد ایران' : 'Steel Industry'} + : <>{lang === 'fa' ? 'عضو اندیشکده شوید' : 'Join the Institute'}
{lang === 'fa' ? 'و به تحلیل‌ها دسترسی داشته باشید' : 'Get Exclusive Access'} + } +

+

+ {lang === 'fa' + ? 'گزارش‌های بازار، رصد فناوری، و تحلیل ژئوپلیتیک — همه در یک پلتفرم' + : 'Market reports, technology intelligence, and geopolitical analysis — all in one platform'} +

+
+ + {/* Bottom stats / checklist */} +
+ {mode === 'login' + ? ( +
+ {[['+۲۰۰', lang === 'fa' ? 'گزارش' : 'Reports'], ['۵۰+', lang === 'fa' ? 'کارشناس' : 'Experts'], ['۳ سال', lang === 'fa' ? 'سابقه' : 'History']].map(([n, l]) => ( +
+
{n}
+
{l}
+
+ ))} +
+ ) : ( +
+ {[ + lang === 'fa' ? 'دسترسی به گزارش‌های تخصصی' : 'Access to expert reports', + lang === 'fa' ? 'رصد بازار و قیمت‌ها' : 'Market and price monitoring', + lang === 'fa' ? 'شبکه خبرگان صنعت' : 'Industry expert network', + ].map(item => ( +
+
+ +
+ {item} +
+ ))} +
+ ) + } +
+
+ ) +} + +/* ─── Shared primitives ─── */ +function FField({ label, children, required }: { label: string; children: React.ReactNode; required?: boolean }) { + return ( +
+ + {children} +
+ ) +} + +function FInput({ icon, suffix, ...props }: React.InputHTMLAttributes & { icon?: React.ReactNode; suffix?: React.ReactNode }) { + const [focused, setFocused] = useState(false) + return ( +
+ { setFocused(true); props.onFocus?.(e) }} + onBlur={e => { setFocused(false); props.onBlur?.(e) }} + style={{ + width: '100%', + padding: `0.82rem ${suffix ? '2.75rem' : '1rem'} 0.82rem ${icon ? '2.75rem' : '1rem'}`, + borderRadius: 10, border: `1.5px solid ${focused ? GOLD : '#e5e5e5'}`, + background: props.disabled ? '#f7f7f7' : '#fff', + color: '#1a1a1a', fontSize: '0.9rem', outline: 'none', + boxSizing: 'border-box', transition: 'border-color 0.18s', + fontFamily: 'inherit', ...props.style, + }} + /> + {icon && {icon}} + {suffix && {suffix}} +
+ ) +} + +function EyeToggle({ show, onToggle }: { show: boolean; onToggle: () => void }) { + return ( + + ) +} + +function ErrBox({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ) +} + +function SubmitBtn({ loading, children }: { loading: boolean; children: React.ReactNode }) { + return ( + + ) +} + +/* ─── SVG Icons ─── */ +const MailIcon = () => +const LockIcon = () => +const UserIcon = () => +const PhoneIcon = () => +const BldgIcon = () => diff --git a/src/pages/Auth/LoginPage.tsx b/src/pages/Auth/LoginPage.tsx new file mode 100644 index 0000000..dedc63b --- /dev/null +++ b/src/pages/Auth/LoginPage.tsx @@ -0,0 +1,112 @@ +import React, { useState } from 'react' +import { useNavigate, Link } from 'react-router-dom' +import { useAuth } from '@/context/AuthContext' +import { useLang } from '@/context/LangContext' + +const GOLD = '#CD9E53' +const NAVY = '#032340' + +export default function LoginPage() { + const { lang } = useLang() + const { login } = useAuth() + const navigate = useNavigate() + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState('') + const [loading, setLoading] = useState(false) + + const T = { + title: lang === 'fa' ? 'ورود به حساب' : 'Sign In', + email: lang === 'fa' ? 'ایمیل' : 'Email', + pass: lang === 'fa' ? 'رمز عبور' : 'Password', + submit: lang === 'fa' ? 'ورود' : 'Login', + noAcc: lang === 'fa' ? 'حساب ندارید؟' : "Don't have an account?", + register: lang === 'fa' ? 'ثبت‌نام کنید' : 'Register', + } + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + setError('') + setLoading(true) + try { + await login(email, password) + navigate('/profile') + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'خطا در ورود') + } finally { + setLoading(false) + } + } + + return ( +
+
+
+
+ + + +
+

{T.title}

+
+ +
+
+ + setEmail(e.target.value)} + style={inputStyle} + placeholder="example@email.com" + dir="ltr" + /> +
+
+ + setPassword(e.target.value)} + style={inputStyle} + dir="ltr" + /> +
+ + {error && ( +

{error}

+ )} + + +
+ +

+ {T.noAcc}{' '} + {T.register} +

+
+
+ ) +} + +const inputStyle: React.CSSProperties = { + width: '100%', padding: '0.75rem 1rem', borderRadius: 8, + border: '1px solid var(--rule-thin)', background: 'var(--paper)', + color: 'var(--ink)', fontSize: '0.95rem', outline: 'none', boxSizing: 'border-box', +} + +const btnStyle = (loading: boolean): React.CSSProperties => ({ + width: '100%', padding: '0.85rem', borderRadius: 8, + background: loading ? 'var(--ink-5)' : GOLD, + color: NAVY, fontWeight: 700, fontSize: '1rem', + border: 'none', cursor: loading ? 'not-allowed' : 'pointer', + transition: 'opacity 0.2s', +}) diff --git a/src/pages/Auth/RegisterPage.tsx b/src/pages/Auth/RegisterPage.tsx new file mode 100644 index 0000000..65b4d3b --- /dev/null +++ b/src/pages/Auth/RegisterPage.tsx @@ -0,0 +1,239 @@ +import React, { useState } from 'react' +import { useNavigate, Link } from 'react-router-dom' +import { useAuth } from '@/context/AuthContext' +import { useLang } from '@/context/LangContext' + +const GOLD = '#CD9E53' +const NAVY = '#032340' + +export default function RegisterPage() { + const { lang } = useLang() + const { register } = useAuth() + const navigate = useNavigate() + const [name, setName] = useState('') + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [showPass, setShowPass] = useState(false) + const [error, setError] = useState('') + const [loading, setLoading] = useState(false) + + const isRtl = lang === 'fa' + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + setError('') + setLoading(true) + try { + await register(name, email, password) + navigate('/profile') + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'خطا در ثبت‌نام') + } finally { + setLoading(false) + } + } + + return ( +
+ {/* ── Brand Panel ── */} +
+ + + + + + + + + + + {[0,1,2,3,4].map(i => ( + + ))} + + +
+ + +
+
اندیشکده فولاد آینده
+
The Steel Futures Institute
+
+ +
+ +
+
+ عضویت رایگان +
+

+ عضو اندیشکده شوید
+ و به تحلیل‌ها دسترسی داشته باشید +

+

+ با ثبت‌نام رایگان به آرشیو گزارش‌های تخصصی صنعت فولاد دسترسی پیدا کنید +

+
+ +
+ {['دسترسی به گزارش‌های تخصصی', 'رصد بازار و قیمت‌ها', 'شبکه خبرگان صنعت'].map((item, i) => ( +
+
+ +
+ {item} +
+ ))} +
+
+ + {/* ── Form Panel ── */} +
+
+
+

ثبت‌نام

+

یک حساب کاربری جدید بسازید

+
+ +
+
+ + setName(e.target.value)} + placeholder="علی محمدی" + icon={ + + + + } + /> +
+ +
+ + setEmail(e.target.value)} + placeholder="example@email.com" + icon={ + + + + } + /> +
+ +
+ +
+ setPassword(e.target.value)} + dir="ltr" + style={inputStyle} + onFocus={e => { e.target.style.borderColor = GOLD }} + onBlur={e => { e.target.style.borderColor = '#e0e0e0' }} + /> + + + + +
+

حداقل ۶ کاراکتر

+
+ + {error && ( +
+ {error} +
+ )} + + +
+ +
+ حساب دارید؟ + وارد شوید +
+ +
+ + + + + بازگشت به سایت + +
+
+
+
+ ) +} + +/* helpers */ +const labelStyle: React.CSSProperties = { + display: 'block', color: '#444', fontSize: '0.82rem', fontWeight: 600, marginBottom: 6, +} + +const inputStyle: React.CSSProperties = { + width: '100%', padding: '0.85rem 1rem 0.85rem 2.75rem', borderRadius: 10, + border: '1.5px solid #e0e0e0', background: '#fff', + color: '#1a1a1a', fontSize: '0.95rem', outline: 'none', + boxSizing: 'border-box', transition: 'border-color 0.2s', fontFamily: 'inherit', +} + +function InputWithIcon({ icon, ...props }: React.InputHTMLAttributes & { icon: React.ReactNode }) { + return ( +
+ { e.target.style.borderColor = GOLD }} + onBlur={e => { e.target.style.borderColor = '#e0e0e0' }} + /> + + {icon} + +
+ ) +} diff --git a/src/pages/Home/sections/AtAGlanceSection.tsx b/src/pages/Home/sections/AtAGlanceSection.tsx index 8028e32..62d46ed 100644 --- a/src/pages/Home/sections/AtAGlanceSection.tsx +++ b/src/pages/Home/sections/AtAGlanceSection.tsx @@ -1,7 +1,5 @@ import { useState } from 'react' import { AnimatePresence, motion } from 'framer-motion' -import { ChevronRight } from 'lucide-react' -import { Link } from 'react-router-dom' import { useLang } from '@/context/LangContext' const NAVY = '#032340' @@ -48,117 +46,101 @@ const ITEMS: Glance[] = [ export default function AtAGlanceSection({ embedded = false }: { embedded?: boolean }) { const { lang } = useLang() const isFa = lang === 'fa' - const [idx, setIdx] = useState(0) + const [i, setI] = useState(0) const [dir, setDir] = useState<1 | -1>(1) - const it = ITEMS[idx] + const it = ITEMS[i] const go = (d: 1 | -1) => { setDir(d) - setIdx((prev) => (prev + d + ITEMS.length) % ITEMS.length) + setI((prev) => (prev + d + ITEMS.length) % ITEMS.length) + } + + const T = { + heading: isFa ? 'در یک نگاه' : 'At a Glance', } return (
- {/* ── heading row ── */} {!embedded && ( -
- {/* title — bilingual stacked */} -
-
-
-
- At a Glance -
-

- {isFa ? 'در یک نگاه' : 'At a Glance'} -

-
-
- - {/* مشاهده همه */} - -
- -
- {isFa ? 'مشاهده همه' : 'View All'} - +
+
+

+ {T.heading} +

)} - {/* ── card row: image left, text right ── */} - {/* image is taller; text card is centered vertically and overlaps image by ~48px */} -
- - {/* IMAGE — taller than the text card */} -
- {/* back shadow card */} +
+ {/* ── IMAGE side ── */} +
- {/* image fills full height of its column */} -
+
- {/* TEXT — shorter, overlaps image by 48px on the left */} -
+ {/* ── TEXT card side ── */} +
+ }} className="max-lg:!ms-0 max-lg:!me-0 max-md:!p-6"> -

+

{isFa ? it.title : it.titleEn}

- {it.source} - + {isFa ? it.date : it.dateEn} + {it.source}
-

+

{isFa ? it.desc : it.descEn}

- {/* nav buttons — half inside / half outside bottom of text card */} -
+
go(-1)} aria="prev">‹ go(1)} aria="next">›
@@ -177,15 +159,13 @@ function NavBtn({ children, onClick, aria }: { children: React.ReactNode; onClic aria-label={aria} style={{ width: 38, height: 38, borderRadius: '50%', - background: GOLD, color: '#fff', - border: 'none', cursor: 'pointer', + background: '#fff', color: GOLD, border: `1.5px solid ${GOLD}`, cursor: 'pointer', fontSize: 20, fontWeight: 700, lineHeight: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', - fontFamily: 'inherit', transition: 'background 180ms, transform 180ms', - boxShadow: '0 4px 14px rgba(205,158,83,0.45)', + fontFamily: 'inherit', transition: 'background 180ms, color 180ms', }} - onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.background = '#b88d46'; (e.currentTarget as HTMLElement).style.transform = 'translateY(-2px)' }} - onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.background = GOLD; (e.currentTarget as HTMLElement).style.transform = 'translateY(0)' }} + onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.background = GOLD; (e.currentTarget as HTMLElement).style.color = '#fff' }} + onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.background = '#fff'; (e.currentTarget as HTMLElement).style.color = GOLD }} > {children} @@ -194,7 +174,7 @@ function NavBtn({ children, onClick, aria }: { children: React.ReactNode; onClic function CalendarIcon() { return ( - + diff --git a/src/pages/Home/sections/AtAGlanceSection.tsx.old b/src/pages/Home/sections/AtAGlanceSection.tsx.old new file mode 100644 index 0000000..24641c5 --- /dev/null +++ b/src/pages/Home/sections/AtAGlanceSection.tsx.old @@ -0,0 +1,191 @@ +import { useState } from 'react' +import { AnimatePresence, motion } from 'framer-motion' +import { useLang } from '@/context/LangContext' + +const NAVY = '#032340' +const GOLD = '#CD9E53' + +type Glance = { + source: string + date: string; dateEn: string + title: string; titleEn: string + desc: string; descEn: string + img: string +} + +const ITEMS: Glance[] = [ + { + source: 'Financial Times', + date: 'اردیبهشت ۱۴۰۵', dateEn: 'May 2026', + title: 'شوک هیدروژنی به بازار جهانی؛ آغاز رسمی عصر فولادسازی بدون کربن', + titleEn: 'Hydrogen Shock to Global Markets; the Carbon-Free Steel Era Begins', + desc: 'پروژه عظیم «فولاد سبز H2» در سوئد با جذب بی‌سابقه ۶.۵ میلیارد یورو سرمایه، رسماً وارد فاز اجرایی شد تا نخستین کارخانه فولادسازی در مقیاس تجاری بر پایه هیدروژن سبز (بدون نیاز به زغال‌سنگ) در جهان متولد شود. این تأسیسات با هدف کاهش ۹۵ درصدی انتشار CO₂، قواعد رقابت در زنجیره ارزش اروپا را برای همیشه بازتعریف می‌کند.', + descEn: 'Sweden\'s massive "Green Steel H2" project, having raised an unprecedented €6.5bn, has officially entered its execution phase to birth the world\'s first commercial-scale steel plant powered by green hydrogen — redefining Europe\'s value-chain competition for good.', + img: '/news/photo-1504307651254-35680f356dfd.jpg', + }, + { + source: 'Reuters', + date: 'اردیبهشت ۱۴۰۵', dateEn: 'May 2026', + title: 'مکانیسم تنظیم مرزهای کربنی اروپا وارد فاز اجرای کامل شد', + titleEn: 'EU Carbon Border Mechanism Enters Full Enforcement', + desc: 'با اجرای کامل CBAM، صادرکنندگان فولاد به اتحادیه اروپا اکنون موظف‌اند هزینه کامل کربن نهفته در محصولات خود را بپردازند. این تحول، فشار بی‌سابقه‌ای بر تولیدکنندگان مبتنی بر کوره بلند وارد کرده و مزیت رقابتی را به سمت فولاد کم‌کربن سوق می‌دهد.', + descEn: 'With CBAM now fully enforced, steel exporters to the EU must pay the full cost of embedded carbon — placing unprecedented pressure on blast-furnace producers and shifting the competitive edge toward low-carbon steel.', + img: '/news/photo-1581092160607-ee22731c9b4e.jpg', + }, + { + source: 'Bloomberg', + date: 'فروردین ۱۴۰۵', dateEn: 'Apr 2026', + title: 'هوش مصنوعی، تقاضای جهانی فولاد مراکز داده را بازتعریف می‌کند', + titleEn: 'AI Is Rewriting Global Steel Demand for Data Centers', + desc: 'موج ساخت مراکز داده هوش مصنوعی، تقاضای جدیدی برای مقاطع ساختمانی و فولاد ویژه ایجاد کرده است. تحلیلگران پیش‌بینی می‌کنند این روند تا پایان دهه، یکی از پیشران‌های اصلی رشد مصرف فولاد در اقتصادهای پیشرفته باشد.', + descEn: 'The AI data-center construction wave is creating fresh demand for structural sections and specialty steel — projected to be a leading driver of steel consumption growth in advanced economies through the decade.', + img: '/news/photo-1518770660439-4636190af475.jpg', + }, +] + +export default function AtAGlanceSection({ embedded = false }: { embedded?: boolean }) { + const { lang } = useLang() + const isFa = lang === 'fa' + const [i, setI] = useState(0) + const [dir, setDir] = useState<1 | -1>(1) + const it = ITEMS[i] + + const go = (d: 1 | -1) => { + setDir(d) + setI((prev) => (prev + d + ITEMS.length) % ITEMS.length) + } + + const T = { + heading: isFa ? 'در یک نگاه' : 'At a Glance', + } + + return ( +
+
+ + {!embedded && ( +
+
+

+ {T.heading} +

+
+ )} + + {/* image on the LEFT, text on the RIGHT (matches the reference). + Container is LTR; image is the first DOM child, so plain `row` lands it on the left. */} +
+ {/* ── IMAGE side (with stacked-card shadow) ── */} +
+ {/* back card (offset, behind) — offset away from the text card */} +
+ {/* front image — portrait on desktop, shorter landscape on mobile */} +
+ + + +
+
+ + {/* ── TEXT card side ── */} +
+
+ + + +

+ {isFa ? it.title : it.titleEn} +

+ + {/* date (leading side) + source (trailing side) */} +
+ + + {isFa ? it.date : it.dateEn} + + {it.source} +
+ +

+ {isFa ? it.desc : it.descEn} +

+
+
+ + {/* prev / next controls */} +
+ go(-1)} aria="prev">‹ + go(1)} aria="next">› +
+
+
+
+
+
+ ) +} + +function NavBtn({ children, onClick, aria }: { children: React.ReactNode; onClick: () => void; aria: string }) { + return ( + + ) +} + +function CalendarIcon() { + return ( + + + + + + + ) +} diff --git a/src/pages/Home/sections/FactoryReportsScroll.tsx b/src/pages/Home/sections/FactoryReportsScroll.tsx index 443edaa..c4bbfc8 100644 --- a/src/pages/Home/sections/FactoryReportsScroll.tsx +++ b/src/pages/Home/sections/FactoryReportsScroll.tsx @@ -21,7 +21,7 @@ function ScrollAnimation({ children, direction = 'up' }: { children: React.React whileInView={{ opacity: 1, x: 0, y: 0 }} viewport={{ once: true, amount: 0.35 }} transition={{ duration: 0.7, ease: EASE }} - style={{ breakInside: 'avoid', marginBottom: 18 }} + style={{ breakInside: 'avoid', marginBottom: 18, height: '100%' }} > {children} @@ -36,12 +36,13 @@ function ReportCard({ r, lang }: { r: FactoryReport; lang: 'fa' | 'en' }) { background: '#fff', borderRadius: 14, overflow: 'hidden', cursor: 'pointer', boxShadow: '0 10px 34px -20px rgba(7,29,73,0.30)', transition: 'transform 200ms, box-shadow 200ms', + display: 'flex', flexDirection: 'column', height: '100%', }} onMouseEnter={(e) => { const d = e.currentTarget; d.style.transform = 'translateY(-4px)'; d.style.boxShadow = '0 22px 50px -22px rgba(7,29,73,0.38)' }} onMouseLeave={(e) => { const d = e.currentTarget; d.style.transform = 'translateY(0)'; d.style.boxShadow = '0 10px 34px -20px rgba(7,29,73,0.30)' }} > -
- +
+
{t.tag}
-
+
{t.date}

{t.title}

-

{t.excerpt}

-
+

{t.excerpt}

+ - {/* compact 4-up row */} -
+ {/* compact 3-up row, centered */} +
{reports.map((r) => ( diff --git a/src/pages/Home/sections/LatestNewsBentoSection.tsx b/src/pages/Home/sections/LatestNewsBentoSection.tsx index 8501dd1..3059b56 100644 --- a/src/pages/Home/sections/LatestNewsBentoSection.tsx +++ b/src/pages/Home/sections/LatestNewsBentoSection.tsx @@ -7,7 +7,7 @@ const flashReport = reports.find(r => r.type === 'flash')! const PANEL_API = (import.meta as ImportMeta & { env?: Record }).env - ?.VITE_PANEL_API || 'http://localhost:3001' + ?.VITE_PANEL_API || '' type PriceRow = { label: string; value: string; delta: string; up: boolean } diff --git a/src/pages/Home/sections/LatestReportsSection.tsx b/src/pages/Home/sections/LatestReportsSection.tsx index 0de714f..81e3ec0 100644 --- a/src/pages/Home/sections/LatestReportsSection.tsx +++ b/src/pages/Home/sections/LatestReportsSection.tsx @@ -218,7 +218,7 @@ export default function LatestReportsSection() {
diff --git a/src/pages/Home/sections/MapEventsSection.tsx b/src/pages/Home/sections/MapEventsSection.tsx index 33502a4..d451af1 100644 --- a/src/pages/Home/sections/MapEventsSection.tsx +++ b/src/pages/Home/sections/MapEventsSection.tsx @@ -1,9 +1,21 @@ +import { useState, useEffect } from 'react' import Globe from '@/components/ui/globe' import StrategicEvents from '@/components/ui/StrategicEvents' import { useLang } from '@/context/LangContext' export default function MapEventsSection() { const { lang } = useLang() + const [globeSize, setGlobeSize] = useState(560) + + useEffect(() => { + const update = () => { + const w = window.innerWidth + setGlobeSize(w < 768 ? Math.min(w - 32, 340) : 560) + } + update() + window.addEventListener('resize', update) + return () => window.removeEventListener('resize', update) + }, []) return (
@@ -16,8 +28,8 @@ export default function MapEventsSection() {
{/* ── Globe — LEFT, no text ── */} -
- +
+
diff --git a/src/pages/Home/sections/RadarTechSection.tsx b/src/pages/Home/sections/RadarTechSection.tsx index ab54a68..d20a57b 100644 --- a/src/pages/Home/sections/RadarTechSection.tsx +++ b/src/pages/Home/sections/RadarTechSection.tsx @@ -46,7 +46,6 @@ export default function RadarTechSection({ only }: { only?: 'radar' | 'approach' const t = T[lang] const showRadar = only !== 'approach' const showApproach = only !== 'radar' - return (
diff --git a/src/pages/Home/sections/ReportsCarouselSection.tsx b/src/pages/Home/sections/ReportsCarouselSection.tsx index eb25a7b..0199f45 100644 --- a/src/pages/Home/sections/ReportsCarouselSection.tsx +++ b/src/pages/Home/sections/ReportsCarouselSection.tsx @@ -295,7 +295,7 @@ export default function ReportsCarouselSection() {
}).env - ?.VITE_PANEL_API || 'http://localhost:3001' + ?.VITE_PANEL_API || '' /* ─── Risk data (fallback when panel is offline) ─────────── */ const fallbackRiskItems: RiskItem[] = [ diff --git a/src/pages/Home/sections/SpreadFeatures.tsx b/src/pages/Home/sections/SpreadFeatures.tsx index 23423b2..4a1f12e 100644 --- a/src/pages/Home/sections/SpreadFeatures.tsx +++ b/src/pages/Home/sections/SpreadFeatures.tsx @@ -36,7 +36,7 @@ const FEATURES: Feat[] = [ }, }, { - img: '/hero%202.webp', imgSide: 'left', imgPos: 'center center', + img: '/hero2.webp', imgSide: 'left', imgPos: '100% center', fa: { kicker: 'فناوری صنعتی', title: 'داده، تحلیل، آینده‌نگاری', @@ -53,7 +53,7 @@ const FEATURES: Feat[] = [ }, }, { - img: '/hero%203.webp', imgSide: 'right', imgPos: 'center center', + img: '/hero3.webp', imgSide: 'right', imgPos: '100% center', fa: { kicker: 'ژئواکونومی', title: 'آزمون استراتژی در آینده‌ها', diff --git a/src/pages/Home/sections/VideocastPodcastSection.tsx b/src/pages/Home/sections/VideocastPodcastSection.tsx index 12b0029..b0a1013 100644 --- a/src/pages/Home/sections/VideocastPodcastSection.tsx +++ b/src/pages/Home/sections/VideocastPodcastSection.tsx @@ -272,7 +272,7 @@ export default function VideocastPodcastSection({ only }: { only?: 'video' | 'po alignItems: 'center', gap: 16, flexDirection: lang === 'fa' ? 'row-reverse' : 'row', - width: '100%' + minWidth: 0, }}> {/* Play button */} + ) +} + +/* ─── white book cover ─── */ +function BookCard({ pod, lang }: { pod: typeof PODCASTS[0]; lang: 'fa' | 'en' }) { + const epNum = pod.epNum.replace(/[^0-9۰-۹]/g, '') + return ( +
+
{epNum}
+
+ +
+
+
{pod[lang].title}
+
{pod[lang].guest}
+
+
+ +
+
+ ) +} + +/* ─── video list row ─── */ +function VidRow({ vid, lang }: { vid: typeof VIDEOS[0]; lang: 'fa' | 'en' }) { + const isFa = lang === 'fa' + return ( +
+
+ +
+
+
+
+
+
{vid[lang].title}
+
+ {vid.date} + {vid.duration} +
+
+
+ ) +} + +/* ══ MAIN ══ */ +export default function Podcast() { + const { lang } = useLang() + const t = T[lang] + const isFa = lang === 'fa' + const [active, setActive] = useState(0) + const pod = PODCASTS[active] + const epNum = pod.epNum.replace(/[^0-9۰-۹]/g, '') + + return ( +
+
+ + {/* ══ PODCAST HERO CARD (dark) ══ */} +
+ + {/* dot pattern */} +
+ + {/* COL 1 — episode list */} +
+
+ {t.otherEps} + +
+
+ {PODCASTS.map((p, i) => ( + setActive(i)} lang={lang} /> + ))} +
+
+ + {/* COL 2 — center */} +
+ {/* meta */} +
+ + {pod.date} + + + #{epNum} +
+ + {/* title */} +

{pod[lang].title}

+ + {/* guest */} +
{pod[lang].guest}
+ + {/* body excerpt */} +

{pod[lang].body}

+ + {/* listen + duration */} +
+ + + {pod.duration} + +
+
+ + {/* COL 3 — book cover */} +
+ +
+
+ + {/* ══ VIDEOCAST CARD (light) ══ */} +
+ {/* header */} +
+
+
+ +
+ {t.videocast} +
+ +
+ + {/* grid: featured (left) + list (right) */} +
+ + {/* featured video */} +
+
+ +
+ +
+ {VIDEOS[0].ep} +
+
+
+
{VIDEOS[0][lang].title}
+
{VIDEOS[0][lang].guest}
+
+ + + {VIDEOS[0].duration} + + + {VIDEOS[0].date} + +
+
+
+ + {/* video list */} +
+
+ {t.otherVids} +
+ {VIDEOS.slice(1).map(v => )} +
+
+
+ +
+
+ ) +} diff --git a/src/pages/Profile/ProfilePage.tsx b/src/pages/Profile/ProfilePage.tsx new file mode 100644 index 0000000..11dd9df --- /dev/null +++ b/src/pages/Profile/ProfilePage.tsx @@ -0,0 +1,525 @@ +import React, { useState, useEffect } from 'react' +import { useNavigate } from 'react-router-dom' +import { useAuth, authFetch } from '@/context/AuthContext' +import { useLang } from '@/context/LangContext' + +const GOLD = '#CD9E53' +const NAVY = '#032340' + +type Tab = 'info' | 'password' | 'content' + +interface Purchase { + id: number + article_id: string + title: string + category: string + purchased_at: string + file_url?: string +} + +export default function ProfilePage() { + const { lang } = useLang() + const { member, token, logout } = useAuth() + const navigate = useNavigate() + const [tab, setTab] = useState('info') + + useEffect(() => { + if (!token) navigate('/login') + }, [token, navigate]) + + if (!member || !token) return null + + const initials = member.name.split(' ').map(w => w[0]).join('').slice(0, 2) + + const tabs: { key: Tab; label: string }[] = [ + { key: 'info', label: lang === 'fa' ? 'اطلاعات من' : 'My Info' }, + { key: 'password', label: lang === 'fa' ? 'تغییر رمز' : 'Password' }, + { key: 'content', label: lang === 'fa' ? 'مطالب من' : 'My Content' }, + ] + + return ( +
+ + {/* ── Hero Banner ── */} +
+ {/* grid texture */} + + + + + + + + {/* decorative ring */} + + {[1,2,3].map(i => )} + + +
+ {/* profile card - overlaps the banner */} +
+ {/* avatar + info row */} +
+ {/* avatar */} +
+
+ {initials} +
+
+
+ + {/* name / email */} +
+

{member.name}

+

{member.email}

+
+ + {/* logout */} + +
+ + {/* tabs */} +
+ {tabs.map(t => ( + + ))} +
+
+
+
+ + {/* ── Tab Content ── */} +
+ {tab === 'info' && } + {tab === 'password' && } + {tab === 'content' && } +
+
+ ) +} + +/* ═══════════════════════════════════════ + Info Tab +═══════════════════════════════════════ */ +function InfoTab({ member, token, lang, }: { + member: { name: string; phone?: string; email: string } + token: string + lang: 'fa' | 'en' +}) { + const { updateMember } = useAuth() + const [name, setName] = useState(member.name) + const [phone, setPhone] = useState(member.phone ?? '') + const [status, setStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle') + + async function save(e: React.FormEvent) { + e.preventDefault() + setStatus('saving') + try { + const res = await authFetch(token, '/api/members/me', { method: 'PUT', body: JSON.stringify({ name, phone }) }) + if (!res.ok) throw new Error() + updateMember({ name, phone }) + setStatus('saved') + setTimeout(() => setStatus('idle'), 2500) + } catch { setStatus('error') } + } + + return ( +
+ + {/* form card */} +
+ + + + } title={lang === 'fa' ? 'اطلاعات شخصی' : 'Personal Info'} /> + +
+ + setName(e.target.value)} required /> + + + setPhone(e.target.value)} dir="ltr" placeholder="+98..." /> + + + + + + {status === 'error' && {lang === 'fa' ? 'خطا در ذخیره' : 'Save failed'}} + + +
+
+ + {/* account info card */} +
+ + + + } title={lang === 'fa' ? 'وضعیت حساب' : 'Account Status'} /> + +
+ {[ + { label: lang === 'fa' ? 'نوع حساب' : 'Account Type', value: lang === 'fa' ? 'عضو عادی' : 'Member' }, + { label: lang === 'fa' ? 'وضعیت' : 'Status', + value: + + {lang === 'fa' ? 'فعال' : 'Active'} + + }, + ].map(({ label, value }) => ( +
+ {label} + {value} +
+ ))} +
+
+
+ ) +} + +/* ═══════════════════════════════════════ + Password Tab +═══════════════════════════════════════ */ +function PasswordTab({ token, lang }: { token: string; lang: 'fa' | 'en' }) { + const [current, setCurrent] = useState('') + const [next, setNext] = useState('') + const [confirm, setConfirm] = useState('') + const [status, setStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle') + const [errMsg, setErrMsg] = useState('') + + async function save(e: React.FormEvent) { + e.preventDefault() + if (next !== confirm) { setErrMsg(lang === 'fa' ? 'رمزها یکسان نیستند' : 'Passwords do not match'); return } + setStatus('saving'); setErrMsg('') + try { + const res = await authFetch(token, '/api/members/me/password', { method: 'PUT', body: JSON.stringify({ currentPassword: current, newPassword: next }) }) + const data = await res.json() + if (!res.ok) throw new Error(data.error) + setStatus('saved'); setCurrent(''); setNext(''); setConfirm('') + setTimeout(() => setStatus('idle'), 2500) + } catch (err: unknown) { + setErrMsg(err instanceof Error ? err.message : lang === 'fa' ? 'خطا' : 'Error') + setStatus('error') + } + } + + return ( +
+
+ + + + } title={lang === 'fa' ? 'تغییر رمز عبور' : 'Change Password'} /> + +
+ + setCurrent(e.target.value)} required dir="ltr" /> + + + setNext(e.target.value)} required minLength={6} dir="ltr" /> + + + setConfirm(e.target.value)} required dir="ltr" /> + + + {errMsg && {errMsg}} + + +
+
+ + {/* tip */} +
+ + + +

+ {lang === 'fa' ? 'رمز قوی شامل حروف بزرگ، اعداد و نمادها باشد. حداقل ۶ کاراکتر.' : 'Use uppercase letters, numbers and symbols for a strong password.'} +

+
+
+ ) +} + +/* ═══════════════════════════════════════ + Content Tab +═══════════════════════════════════════ */ +function ContentTab({ token, lang }: { token: string; lang: 'fa' | 'en' }) { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(true) + const [err, setErr] = useState('') + + useEffect(() => { + authFetch(token, '/api/members/me/purchases') + .then(r => r.json()) + .then(data => { setItems(Array.isArray(data) ? data : []); setLoading(false) }) + .catch(() => { setErr(lang === 'fa' ? 'خطا در بارگذاری' : 'Load error'); setLoading(false) }) + }, [token, lang]) + + const BASE = import.meta.env.VITE_PANEL_API ?? '' + + const categoryColors: Record = { + 'گزارش': '#3b82f6', 'تحلیل': '#8b5cf6', 'رادار': '#10b981', + 'بازار': '#f59e0b', 'ویژه': '#ef4444', + } + + return ( +
+ {/* stats bar */} +
+ {[ + { label: lang === 'fa' ? 'مطالب خریداری‌شده' : 'Purchased', value: loading ? '—' : items.length.toString(), icon: '📄' }, + { label: lang === 'fa' ? 'قابل دانلود' : 'Downloads', value: loading ? '—' : items.filter(i => i.file_url).length.toString(), icon: '⬇️' }, + { label: lang === 'fa' ? 'دسته‌بندی' : 'Categories', value: loading ? '—' : new Set(items.map(i => i.category).filter(Boolean)).size.toString(), icon: '🗂️' }, + ].map(stat => ( +
+
{stat.icon}
+
{stat.value}
+
{stat.label}
+
+ ))} +
+ +
+ + + + } title={lang === 'fa' ? 'مطالب من' : 'My Content'} /> + +
+ {loading && ( +
+ {[1, 2, 3].map(i => ( +
+ ))} +
+ )} + + {!loading && err && {err}} + + {!loading && !err && items.length === 0 && ( +
+ + + + +

+ {lang === 'fa' ? 'هنوز مطلبی خریداری نکرده‌اید' : 'No content purchased yet'} +

+

+ {lang === 'fa' ? 'گزارش‌ها و تحلیل‌های اختصاصی را از بخش گزارش‌ها تهیه کنید' : 'Purchase reports and analyses from the reports section'} +

+
+ )} + + {!loading && items.length > 0 && ( +
+ {items.map(item => { + const cat = item.category + const catColor = categoryColors[cat] ?? '#6b7280' + const dateStr = new Date(item.purchased_at).toLocaleDateString(lang === 'fa' ? 'fa-IR' : 'en-US', { year: 'numeric', month: 'long', day: 'numeric' }) + + return ( +
{ e.currentTarget.style.background = '#fff'; e.currentTarget.style.borderColor = `${GOLD}55`; e.currentTarget.style.boxShadow = `0 2px 12px ${GOLD}15` }} + onMouseLeave={e => { e.currentTarget.style.background = '#fafafa'; e.currentTarget.style.borderColor = '#f0f0f0'; e.currentTarget.style.boxShadow = 'none' }} + > + {/* icon */} +
+ + + +
+ + {/* text */} +
+

+ {item.title || item.article_id} +

+
+ {cat && ( + + {cat} + + )} + {dateStr} +
+
+ + {/* download */} + {item.file_url && ( + { e.currentTarget.style.background = GOLD; e.currentTarget.style.color = NAVY }} + onMouseLeave={e => { e.currentTarget.style.background = NAVY; e.currentTarget.style.color = '#fff' }} + > + + + + {lang === 'fa' ? 'دانلود' : 'Download'} + + )} +
+ ) + })} +
+ )} +
+
+ + +
+ ) +} + +/* ═══════════════════════════════════════ + Shared primitives +═══════════════════════════════════════ */ +const cardStyle: React.CSSProperties = { + background: '#fff', borderRadius: 16, + padding: '1.5rem', border: '1px solid #ebebeb', + boxShadow: '0 1px 6px rgba(0,0,0,0.04)', +} + +function SectionTitle({ icon, title }: { icon: React.ReactNode; title: string }) { + return ( +
+
+ {icon} +
+

{title}

+
+ ) +} + +function FormField({ label, children }: { label: string; children: React.ReactNode }) { + return ( +
+ + {children} +
+ ) +} + +function FancyInput(props: React.InputHTMLAttributes) { + const [focused, setFocused] = useState(false) + return ( + { setFocused(true); props.onFocus?.(e) }} + onBlur={e => { setFocused(false); props.onBlur?.(e) }} + style={{ + width: '100%', padding: '0.78rem 1rem', borderRadius: 10, + border: `1.5px solid ${focused ? GOLD : '#e5e5e5'}`, + background: props.disabled ? '#f7f7f7' : '#fff', + color: '#1a1a1a', fontSize: '0.9rem', outline: 'none', + boxSizing: 'border-box', transition: 'border-color 0.18s', + fontFamily: 'inherit', + ...props.style, + }} + /> + ) +} + +function ErrorMsg({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ) +} + +function submitBtn(disabled: boolean): React.CSSProperties { + return { + padding: '0.8rem 2rem', borderRadius: 10, border: 'none', + background: disabled ? '#e5e5e5' : GOLD, + color: disabled ? '#aaa' : NAVY, + fontWeight: 700, fontSize: '0.9rem', + cursor: disabled ? 'not-allowed' : 'pointer', + alignSelf: 'flex-start', transition: 'all 0.18s', + boxShadow: disabled ? 'none' : `0 4px 16px ${GOLD}44`, + fontFamily: 'inherit', + } +} diff --git a/src/pages/Pulse/Pulse.tsx b/src/pages/Pulse/Pulse.tsx index 26141d4..a417515 100644 --- a/src/pages/Pulse/Pulse.tsx +++ b/src/pages/Pulse/Pulse.tsx @@ -275,7 +275,7 @@ function MembershipForm({ lang }: { lang: 'fa' | 'en' }) { async function submit() { if (!name || !phone) return setStatus('sending') - const PANEL_API = import.meta.env.VITE_PANEL_API || 'http://localhost:3001' + const PANEL_API = import.meta.env.VITE_PANEL_API || '' try { const res = await fetch(`${PANEL_API}/api/contact`, { method: 'POST', diff --git a/src/pages/Radar/Radar.tsx b/src/pages/Radar/Radar.tsx index 379658c..dc68aeb 100644 --- a/src/pages/Radar/Radar.tsx +++ b/src/pages/Radar/Radar.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' +import { useNavigate } from 'react-router-dom' import { motion, AnimatePresence } from 'framer-motion' -import { BarChart3, Cpu, Leaf, Globe, Zap, ChevronLeft, ChevronRight } from 'lucide-react' +import { BarChart3, Cpu, Leaf, Globe, Zap, ChevronLeft, ChevronRight, X, LogIn, UserPlus, AlertTriangle, Calendar, User } from 'lucide-react' import { useLang } from '@/context/LangContext' import { RADAR_ITEMS, type RadarItem, type RadarCategory } from '@/content/radar' @@ -16,14 +17,260 @@ const CATEGORY_ICON: Record = { energy: Zap, } +const CATEGORY_LABEL: Record = { + market: { fa: 'بازار', en: 'Market' }, + tech: { fa: 'فناوری', en: 'Technology' }, + commodity: { fa: 'کامودیتی', en: 'Commodity' }, + geo: { fa: 'ژئوپلیتیک', en: 'Geopolitics' }, + energy: { fa: 'انرژی', en: 'Energy' }, +} + const T = { - fa: { overline: 'FORESIGHT & INTELLIGENCE', heading: 'رادار آینده', sub: 'رصد سیگنال‌های راهبردی و روندهای نوظهور صنعت فولاد جهان', readMore: 'ادامه مطلب' }, - en: { overline: 'FORESIGHT & INTELLIGENCE', heading: 'Future Radar', sub: 'Monitoring strategic signals and emerging trends in the global steel industry', readMore: 'Read more' }, + fa: { + overline: 'FORESIGHT & INTELLIGENCE', heading: 'رادار آینده', + sub: 'رصد سیگنال‌های راهبردی و روندهای نوظهور صنعت فولاد جهان', + readMore: 'ادامه مطلب', + analysis: 'تحلیل راهبردی', + disclaimer: 'این تحلیل توسط هوش مصنوعی تولید شده و جنبه راهنمایی دارد.', + blurTitle: 'برای مطالعه کامل این تحلیل، عضو شوید', + blurSub: 'دسترسی کامل به تمام گزارش‌ها و تحلیل‌های رادار آینده', + login: 'ورود', signup: 'عضویت', + author: 'نویسنده', + }, + en: { + overline: 'FORESIGHT & INTELLIGENCE', heading: 'Future Radar', + sub: 'Monitoring strategic signals and emerging trends in the global steel industry', + readMore: 'Read more', + analysis: 'Strategic Analysis', + disclaimer: 'This analysis was generated with AI assistance and is for guidance purposes.', + blurTitle: 'Become a member to read the full analysis', + blurSub: 'Full access to all Future Radar reports and analyses', + login: 'Login', signup: 'Sign Up', + author: 'Author', + }, +} + +/* ─── membership gate (blur overlay) ─── */ +function MembershipGate({ lang }: { lang: 'fa' | 'en' }) { + const t = T[lang] + return ( +
+ ) +} + +/* ─── radar item modal ─── */ +export function RadarItemModal({ item, lang, onClose }: { item: RadarItem; lang: 'fa' | 'en'; onClose: () => void }) { + const t = T[lang] + const content = item[lang] + const isLoggedIn = (() => { try { return localStorage.getItem('sf_logged_in') === '1' } catch { return false } })() + const Icon = CATEGORY_ICON[item.category] + const catLabel = CATEGORY_LABEL[item.category][lang] + const dir = lang === 'fa' ? 'rtl' : 'ltr' + + const bodyParagraphs = content.body ?? [] + + return ( + + + e.stopPropagation()} + dir={dir} + style={{ + background: '#faf7f4', borderRadius: 20, width: '100%', maxWidth: 720, + maxHeight: '90vh', overflowY: 'auto', position: 'relative', + boxShadow: '0 40px 100px -20px rgba(3,35,64,0.5)', + }} + > + {/* cover image */} +
+ {content.title} +
+ {/* category tag on the image */} +
+ + {catLabel} +
+
+ + {/* close button */} + + + {/* content area */} +
+ {/* title */} +

+ {content.title} +

+ + {/* meta row: author + date — direction:ltr so icon is physically before text */} +
+
+ + {item.author.name} +
+
+ + {content.date} +
+
+ + {/* body paragraphs */} +
+ {bodyParagraphs.length > 0 ? ( + <> + {/* first paragraph always visible */} +

+ {bodyParagraphs[0]} +

+ + {/* rest: visible if logged in, blurred if not */} + {bodyParagraphs.length > 1 && ( +
+
+ {bodyParagraphs.slice(1).map((p, i) => ( +

{p}

+ ))} +
+ {!isLoggedIn && } +
+ )} + + ) : ( +

+ {content.excerpt} +

+ )} +
+ + {/* strategic analysis box (only if logged in) */} + {isLoggedIn && content.analysis && ( +
+
{t.analysis}
+

{content.analysis}

+
+ )} + + {/* AI disclaimer */} + {isLoggedIn && ( +
+ + {t.disclaimer} +
+ )} + + {/* close button at bottom */} + {isLoggedIn && ( +
+ +
+ )} +
+ + + + ) } /* ─── one card: gold bar (default) → navy author panel (hover) ─── */ -export function RadarCard({ item, lang }: { item: RadarItem; lang: 'fa' | 'en' }) { +export function RadarCard({ item, lang, onClick }: { item: RadarItem; lang: 'fa' | 'en'; onClick?: (item: RadarItem) => void }) { const [hover, setHover] = useState(false) + const navigate = useNavigate() const t = item[lang] const Icon = CATEGORY_ICON[item.category] @@ -31,9 +278,10 @@ export function RadarCard({ item, lang }: { item: RadarItem; lang: 'fa' | 'en' } setHover(true)} onMouseLeave={() => setHover(false)} + onClick={() => onClick ? onClick(item) : navigate(`/radar/post/${item.id}`)} style={{ position: 'relative', borderRadius: 16, overflow: 'hidden', cursor: 'pointer', - aspectRatio: '3 / 3.5', background: NAVY, + aspectRatio: '3 / 2.4', background: NAVY, boxShadow: hover ? '0 30px 70px -30px rgba(7,29,73,0.45)' : '0 16px 40px -24px rgba(7,29,73,0.30)', }} animate={{ y: hover ? -4 : 0 }} @@ -67,7 +315,6 @@ export function RadarCard({ item, lang }: { item: RadarItem; lang: 'fa' | 'en' } display: 'flex', alignItems: 'center', gap: 12, direction: lang === 'fa' ? 'rtl' : 'ltr', }} > - {/* icon on the leading (right in RTL) side of the bar */}
@@ -113,7 +360,6 @@ export function RadarCard({ item, lang }: { item: RadarItem; lang: 'fa' | 'en' } /* ─── pagination ─── */ function Pagination({ page, pages, onGo }: { page: number; pages: number; onGo: (p: number) => void }) { if (pages <= 1) return null - // show: first, current-ish, last, with ellipsis const nums: (number | '…')[] = [] for (let i = 1; i <= pages; i++) { if (i === 1 || i === pages || Math.abs(i - page) <= 1) nums.push(i) @@ -153,6 +399,7 @@ export default function Radar() { const { lang } = useLang() const t = T[lang] const [page, setPage] = useState(1) + const [selectedItem, setSelectedItem] = useState(null) const pages = Math.ceil(RADAR_ITEMS.length / PER_PAGE) const start = (page - 1) * PER_PAGE @@ -185,12 +432,17 @@ export default function Radar() { {/* card grid */}
{visible.map((item) => ( - + ))}
+ + {/* modal */} + {selectedItem && ( + setSelectedItem(null)} /> + )}
) } diff --git a/src/pages/RadarCategory/RadarCategory.tsx b/src/pages/RadarCategory/RadarCategory.tsx index ad9fb42..25f4d71 100644 --- a/src/pages/RadarCategory/RadarCategory.tsx +++ b/src/pages/RadarCategory/RadarCategory.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { Link, useParams, Navigate } from 'react-router-dom' +import { Link, useParams, Navigate, useNavigate } from 'react-router-dom' import { motion } from 'framer-motion' import { ArrowLeft, Download, Calendar, ChevronLeft, ChevronRight } from 'lucide-react' import { useLang } from '@/context/LangContext' @@ -50,7 +50,7 @@ function Breadcrumb({ label, lang }: { label: string; lang: 'fa' | 'en' }) { } /* ─── a "latest" mini-card (image on the trailing side, text + CTA) ─── */ -function MiniCard({ img, title, date, lang }: { img: string; title: string; date: string; lang: 'fa' | 'en' }) { +function MiniCard({ img, title, date, lang, onClick }: { img: string; title: string; date: string; lang: 'fa' | 'en'; onClick?: () => void }) { const cta = lang === 'fa' ? 'مشاهده تحلیل' : 'View analysis' return (
@@ -71,7 +71,7 @@ function MiniCard({ img, title, date, lang }: { img: string; title: string; date : 'Beyond a strategic mineral, lithium is now the main lever of power in reshaping the economic order ...'}

- + + + {isFa ? 'ثبت نام' : 'Sign up'} + +
+
+
+ ) +} + +/* ─── related sidebar ─── */ +function RelatedSidebar({ items, currentId, lang }: { items: RadarItem[]; currentId: string; lang: 'fa' | 'en' }) { + const related = items.filter(r => r.id !== currentId).slice(0, 3) + return ( + + ) +} + +/* ─── main ─── */ +export default function RadarPost() { + const { lang } = useLang() + const { id } = useParams<{ id: string }>() + const isFa = lang === 'fa' + + const item = RADAR_ITEMS.find(r => r.id === id) + if (!item) return + + const content = item[lang] + const catLabel = CATEGORY_LABEL[item.category][lang] + + const [loggedIn, setLoggedIn] = useState(() => { + try { return localStorage.getItem('sf_logged_in') === '1' } catch { return false } + }) + const [showAuth, setShowAuth] = useState(false) + const handleAuthSuccess = () => { + try { localStorage.setItem('sf_logged_in', '1') } catch { /* ignore */ } + setLoggedIn(true) + setShowAuth(false) + } + + const body = content.body ?? [] + const firstPara = body[0] + const restParas = body.slice(1) + + return ( +
+ + {/* ── COVER ── */} +
+ {content.title} +
+
+ + {showAuth && ( + setShowAuth(false)} /> + )} + +
+ + {/* ── BODY GRID ── */} +
+ + {/* MAIN ARTICLE */} +
+ + {/* date */} +
+ {content.date} +
+ + {/* title */} +

+ {content.title} +

+ + {/* category + author */} +
+
+ {catLabel} +
+
+ {item.author.name} + {item.author.name} +
+
+ + {/* body */} + {loggedIn ? ( + + {body.map((p, i) => ( +

{p}

+ ))} + + {/* strategic analysis box */} + {content.analysis && ( +
+
+ {isFa ? 'تحلیل راهبردی' : 'STRATEGIC ANALYSIS'} +
+

{content.analysis}

+
+ )} + + {/* AI disclaimer */} +

+ {isFa + ? '⚠ این تحلیل با کمک هوش مصنوعی تهیه شده و جنبه راهنمایی دارد.' + : '⚠ This analysis was prepared with AI assistance and is for guidance purposes.'} +

+
+ ) : ( + <> + {firstPara && ( +

{firstPara}

+ )} +
+
+ {restParas.map((p, i) => ( +

{p}

+ ))} +
+
+ setShowAuth(true)} /> +
+
+ + )} +
+ + {/* RELATED SIDEBAR */} + +
+
+
+ ) +} diff --git a/src/pages/ReportDetail/ReportDetail.tsx b/src/pages/ReportDetail/ReportDetail.tsx index 638152f..feda1a5 100644 --- a/src/pages/ReportDetail/ReportDetail.tsx +++ b/src/pages/ReportDetail/ReportDetail.tsx @@ -103,7 +103,7 @@ export default function ReportDetail() { const report = reports.find((r) => r.id === id) - if (!report) return + if (!report) return return (
گزارش‌ها {report.category} diff --git a/src/pages/Reports/Reports.tsx b/src/pages/Reports/Reports.tsx deleted file mode 100644 index 66631cd..0000000 --- a/src/pages/Reports/Reports.tsx +++ /dev/null @@ -1,581 +0,0 @@ -import { useState, useMemo } from 'react' -import { Link } from 'react-router-dom' -import { Search, ChevronRight, ChevronLeft } from 'lucide-react' -import { reports } from '@/data/reports' -import type { ReportCategory, ReportType } from '@/data/reports' - -/* ─── constants ──────────────────────────────────────────── */ -const CATEGORIES: { label: string; value: ReportCategory | 'همه' }[] = [ - { label: 'همه', value: 'همه' }, - { label: 'بازار جهانی', value: 'بازار جهانی' }, - { label: 'سیاست‌گذاری', value: 'سیاست‌گذاری' }, - { label: 'انرژی و ESG', value: 'انرژی و ESG' }, - { label: 'صادرات', value: 'صادرات' }, - { label: 'تولید داخلی', value: 'تولید داخلی' }, - { label: 'ریسک و بحران', value: 'ریسک و بحران' }, -] - -const TYPES: { label: string; value: ReportType | 'همه' | 'رایگان' }[] = [ - { label: 'همه', value: 'همه' }, - { label: 'رایگان', value: 'رایگان' }, - { label: 'Flash Report', value: 'flash' }, - { label: 'فصلی', value: 'quarterly' }, - { label: 'گزارش ویژه', value: 'special' }, - { label: 'ریسک', value: 'risk' }, -] - -const SORT_OPTIONS = [ - { label: 'جدیدترین', value: 'newest' }, - { label: 'قدیمی‌ترین', value: 'oldest' }, - { label: 'گران‌ترین', value: 'price-desc' }, - { label: 'ارزان‌ترین', value: 'price-asc' }, -] - -const TYPE_COLORS: Record = { - quarterly: '#166534', - flash: '#9b1c1c', - risk: '#92400e', - free: '#1d4ed8', - special: '#4a1d96', -} - -const TYPE_LABELS: Record = { - quarterly: 'فصلی', - flash: 'فلش', - risk: 'ریسک', - free: 'رایگان', - special: 'ویژه', -} - -const PAGE_SIZE = 9 - -/* ─── price formatter ────────────────────────────────────── */ -function formatPrice(price: number): string { - if (price === 0) return 'رایگان' - return price.toLocaleString('fa-IR') + ' تومان' -} - -/* ─── report card ────────────────────────────────────────── */ -function ReportCard({ report }: { report: (typeof reports)[0] }) { - const [hovered, setHovered] = useState(false) - - return ( -
- {/* top meta */} -
- - - {report.category} - - {report.isFree && ( - - رایگان - - )} - - {TYPE_LABELS[report.type]} - -
- - {/* title */} - setHovered(true)} - onMouseLeave={() => setHovered(false)} - > - {report.title} - - - {/* meta row */} -
- {report.author} - · - {report.publishDate} - · - {report.pages} صفحه -
- - {/* divider */} -
- - {/* price row */} -
- - {formatPrice(report.price)} - - - {report.isFree ? 'دانلود' : 'خرید'} - -
-
- ) -} - -/* ─── main component ─────────────────────────────────────── */ -export default function Reports() { - const [activeCategory, setActiveCategory] = useState('همه') - const [activeType, setActiveType] = useState('همه') - const [sortBy, setSortBy] = useState('newest') - const [searchQuery, setSearchQuery] = useState('') - const [page, setPage] = useState(1) - - const filtered = useMemo(() => { - let result = [...reports] - - if (activeCategory !== 'همه') { - result = result.filter((r) => r.category === activeCategory) - } - - if (activeType !== 'همه') { - if (activeType === 'رایگان') { - result = result.filter((r) => r.isFree) - } else { - result = result.filter((r) => r.type === activeType) - } - } - - if (searchQuery.trim()) { - const q = searchQuery.trim().toLowerCase() - result = result.filter( - (r) => - r.title.toLowerCase().includes(q) || - r.author.toLowerCase().includes(q) || - r.category.toLowerCase().includes(q) || - r.tags.some((t) => t.toLowerCase().includes(q)) - ) - } - - switch (sortBy) { - case 'oldest': - result = result.reverse() - break - case 'price-desc': - result = result.sort((a, b) => b.price - a.price) - break - case 'price-asc': - result = result.sort((a, b) => a.price - b.price) - break - } - - return result - }, [activeCategory, activeType, sortBy, searchQuery]) - - const totalPages = Math.ceil(filtered.length / PAGE_SIZE) - const paged = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE) - - // Reset to page 1 on filter change - const handleCategoryChange = (cat: ReportCategory | 'همه') => { - setActiveCategory(cat) - setPage(1) - } - const handleTypeChange = (type: ReportType | 'همه' | 'رایگان') => { - setActiveType(type) - setPage(1) - } - - return ( -
- {/* Page header */} -
-

- گزارش‌ها -

-

- تحلیل‌های تخصصی بازار فولاد ایران و جهان — گزارش‌های فصلی، ریسک، و فلش -

-
- - {/* ─── Filter bar ──────────────────────────────────── */} -
- {/* Category pills */} -
- {CATEGORIES.map((cat) => ( - - ))} -
- - {/* Second row: type + sort + search */} -
- {/* Type filter */} -
- {TYPES.map((t) => ( - - ))} -
- -
- - {/* Sort */} -
- - مرتب‌سازی - - -
- - {/* Search */} -
- { - setSearchQuery(e.target.value) - setPage(1) - }} - style={{ - border: '1px solid var(--ink)', - padding: '8px 36px 8px 12px', - fontSize: 12, - fontFamily: 'inherit', - background: 'transparent', - color: 'var(--ink)', - borderRadius: 0, - outline: 'none', - width: 200, - direction: 'rtl', - }} - /> - -
-
-
- - {/* ─── Report grid ─────────────────────────────────── */} - {paged.length > 0 ? ( -
- {paged.map((report) => ( - - ))} - {/* Fill empty cells in last row to keep grid consistent */} - {paged.length % 3 !== 0 && - Array.from({ length: 3 - (paged.length % 3) }).map((_, i) => ( -
- ))} -
- ) : ( -
-

نتیجه‌ای یافت نشد

-

- جستجو یا فیلترهای خود را تغییر دهید -

-
- )} - - {/* ─── Pagination ──────────────────────────────────── */} - {totalPages > 1 && ( -
- - - - صفحه {page.toLocaleString('fa-IR')} از {totalPages.toLocaleString('fa-IR')} - - - -
- )} - - {/* ─── Mobile responsive styles ────────────────────── */} - -
- ) -} diff --git a/vite.config.ts b/vite.config.ts index 585c8d8..5cd4727 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -15,6 +15,16 @@ export default defineConfig({ }, server: { allowedHosts: ALLOWED_TUNNEL_HOSTS, + proxy: { + '/api': { + target: 'http://localhost:3001', + changeOrigin: true, + }, + '/uploads': { + target: 'http://localhost:3001', + changeOrigin: true, + }, + }, }, preview: { allowedHosts: ALLOWED_TUNNEL_HOSTS,