From 6aa3a7dda8701bce0e447e2a4939cf1158ed3327 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sat, 21 Mar 2026 15:30:23 +0800 Subject: [PATCH] Add landing page and move dashboard to /dashboard --- frontend/app/dashboard/page.tsx | 12 + frontend/app/page.tsx | 8 +- .../components/landing/LandingPage.module.css | 547 ++++++++++++++++++ frontend/components/landing/LandingPage.tsx | 471 +++++++++++++++ 4 files changed, 1034 insertions(+), 4 deletions(-) create mode 100644 frontend/app/dashboard/page.tsx create mode 100644 frontend/components/landing/LandingPage.module.css create mode 100644 frontend/components/landing/LandingPage.tsx diff --git a/frontend/app/dashboard/page.tsx b/frontend/app/dashboard/page.tsx new file mode 100644 index 00000000..982d0910 --- /dev/null +++ b/frontend/app/dashboard/page.tsx @@ -0,0 +1,12 @@ +import type { Metadata } from "next"; +import { DashboardEntry } from "@/components/dashboard/DashboardEntry"; + +export const metadata: Metadata = { + title: "PolyWeather - Global Weather Intelligence Map", + description: + "PolyWeather dashboard with METAR, MGM, DEB fusion forecast, multi-model comparison, and history reconciliation.", +}; + +export default function DashboardPage() { + return ; +} diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index af0bebb0..fcdeb953 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -1,12 +1,12 @@ import type { Metadata } from "next"; -import { DashboardEntry } from "@/components/dashboard/DashboardEntry"; +import { LandingPage } from "@/components/landing/LandingPage"; export const metadata: Metadata = { - title: "PolyWeather - Global Weather Intelligence Map", + title: "PolyWeather | Weather Market Intelligence", description: - "PolyWeather dashboard with METAR, MGM, DEB fusion forecast, multi-model comparison, and history reconciliation.", + "PolyWeather turns real-time weather observations and model spreads into settlement-focused probabilities, market scan signals, and Pro decision support.", }; export default function HomePage() { - return ; + return ; } diff --git a/frontend/components/landing/LandingPage.module.css b/frontend/components/landing/LandingPage.module.css new file mode 100644 index 00000000..066f7af7 --- /dev/null +++ b/frontend/components/landing/LandingPage.module.css @@ -0,0 +1,547 @@ +.page { + position: relative; + overflow: hidden; +} + +.shell { + margin: 0 auto; + width: min(1180px, calc(100% - 32px)); +} + +.heroGrid { + display: grid; + gap: 24px; + grid-template-columns: minmax(0, 1.2fr) minmax(320px, 0.8fr); +} + +.panel { + border: 1px solid rgba(148, 163, 184, 0.16); + background: + linear-gradient(180deg, rgba(15, 23, 42, 0.88), rgba(2, 6, 23, 0.78)); + box-shadow: + 0 24px 80px rgba(2, 6, 23, 0.42), + inset 0 1px 0 rgba(255, 255, 255, 0.06); + backdrop-filter: blur(14px); +} + +.heroCard { + padding: 28px; + border-radius: 28px; +} + +.heroEyebrow { + display: inline-flex; + align-items: center; + gap: 8px; + margin-bottom: 18px; + border-radius: 999px; + border: 1px solid rgba(34, 211, 238, 0.24); + background: rgba(8, 47, 73, 0.34); + padding: 8px 14px; + color: #a5f3fc; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.heroTitle { + max-width: 11ch; + font-size: clamp(46px, 8vw, 84px); + line-height: 0.94; + font-weight: 800; + letter-spacing: -0.05em; +} + +.heroLead { + margin-top: 22px; + max-width: 60ch; + color: rgba(226, 232, 240, 0.84); + font-size: 17px; + line-height: 1.7; +} + +.heroActions { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 28px; +} + +.primaryButton, +.secondaryButton { + display: inline-flex; + min-height: 48px; + align-items: center; + gap: 10px; + border-radius: 14px; + padding: 12px 18px; + font-weight: 700; + transition: + transform 180ms ease, + border-color 180ms ease, + background 180ms ease; +} + +.primaryButton:hover, +.secondaryButton:hover { + transform: translateY(-1px); +} + +.primaryButton { + background: linear-gradient(135deg, #22d3ee, #3b82f6); + color: #020617; +} + +.secondaryButton { + border: 1px solid rgba(148, 163, 184, 0.22); + background: rgba(15, 23, 42, 0.58); + color: #e2e8f0; +} + +.bulletGrid { + display: grid; + gap: 14px; + margin-top: 28px; +} + +.bulletItem { + display: flex; + gap: 12px; + align-items: flex-start; +} + +.bulletItem svg { + margin-top: 3px; + color: #67e8f9; + flex: 0 0 auto; +} + +.bulletItem strong { + display: block; + margin-bottom: 4px; + font-size: 15px; +} + +.bulletItem span { + color: rgba(203, 213, 225, 0.76); + font-size: 14px; + line-height: 1.55; +} + +.heroSide { + display: grid; + gap: 16px; +} + +.signalCard { + padding: 22px; + border-radius: 24px; +} + +.signalHeader { + display: flex; + justify-content: space-between; + gap: 16px; + align-items: flex-start; +} + +.signalHeader h3 { + font-size: 18px; + font-weight: 700; +} + +.signalHeader p { + margin-top: 6px; + color: rgba(191, 219, 254, 0.76); + font-size: 13px; +} + +.statusPill { + border-radius: 999px; + background: rgba(34, 197, 94, 0.14); + color: #86efac; + padding: 7px 12px; + font-size: 12px; + font-weight: 700; +} + +.metricGrid { + display: grid; + gap: 12px; + margin-top: 18px; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.metricCard { + border-radius: 18px; + border: 1px solid rgba(148, 163, 184, 0.12); + background: rgba(15, 23, 42, 0.52); + padding: 16px; +} + +.metricCard label { + display: block; + color: rgba(148, 163, 184, 0.9); + font-size: 12px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.metricCard strong { + display: block; + margin-top: 8px; + font-size: 28px; + font-weight: 800; + letter-spacing: -0.03em; +} + +.metricCard span { + display: block; + margin-top: 6px; + color: rgba(226, 232, 240, 0.74); + font-size: 13px; + line-height: 1.5; +} + +.previewStrip { + margin-top: 18px; + display: grid; + gap: 10px; +} + +.previewRow { + display: flex; + justify-content: space-between; + gap: 12px; + align-items: center; + border-radius: 14px; + background: rgba(2, 6, 23, 0.42); + padding: 12px 14px; + color: #cbd5e1; + font-size: 14px; +} + +.previewRow strong { + color: #f8fafc; +} + +.section { + margin-top: 64px; +} + +.sectionHeader { + display: flex; + justify-content: space-between; + gap: 18px; + align-items: end; + margin-bottom: 24px; +} + +.sectionHeader h2 { + font-size: clamp(28px, 4vw, 42px); + line-height: 1.05; + font-weight: 800; + letter-spacing: -0.04em; +} + +.sectionHeader p { + max-width: 58ch; + color: rgba(203, 213, 225, 0.72); + line-height: 1.7; +} + +.problemGrid, +.featureGrid, +.proofGrid, +.pricingGrid, +.ctaGrid { + display: grid; + gap: 18px; +} + +.problemGrid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.featureGrid { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.proofGrid, +.pricingGrid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.ctaGrid { + grid-template-columns: 1.1fr 0.9fr; +} + +.miniCard, +.pricingCard, +.ctaCard { + border-radius: 24px; + padding: 22px; +} + +.miniCard h3, +.pricingCard h3, +.ctaCard h3 { + font-size: 20px; + font-weight: 700; +} + +.miniCard p, +.pricingCard p, +.ctaCard p { + margin-top: 10px; + color: rgba(203, 213, 225, 0.74); + line-height: 1.65; +} + +.miniCard ul, +.pricingCard ul { + margin-top: 18px; + display: grid; + gap: 10px; + color: rgba(241, 245, 249, 0.92); + font-size: 14px; +} + +.miniCard li, +.pricingCard li { + display: flex; + gap: 10px; + align-items: flex-start; +} + +.miniCard li::before, +.pricingCard li::before { + content: ""; + margin-top: 7px; + width: 6px; + height: 6px; + border-radius: 999px; + background: linear-gradient(135deg, #22d3ee, #60a5fa); + flex: 0 0 auto; +} + +.previewPanel { + overflow: hidden; + border-radius: 28px; + padding: 0; +} + +.previewTop { + padding: 22px; + border-bottom: 1px solid rgba(148, 163, 184, 0.12); +} + +.previewTop h3 { + font-size: 22px; + font-weight: 700; +} + +.previewTop p { + margin-top: 10px; + max-width: 56ch; + color: rgba(203, 213, 225, 0.74); + line-height: 1.65; +} + +.previewBody { + display: grid; + gap: 14px; + padding: 22px; + grid-template-columns: 1.2fr 0.8fr; +} + +.previewMap, +.previewStack { + border-radius: 22px; + background: rgba(15, 23, 42, 0.56); +} + +.previewMap { + min-height: 320px; + position: relative; + overflow: hidden; +} + +.previewMap::before, +.previewMap::after { + content: ""; + position: absolute; + inset: 0; +} + +.previewMap::before { + background: + radial-gradient(circle at 20% 40%, rgba(34, 211, 238, 0.2), transparent 20%), + radial-gradient(circle at 68% 30%, rgba(59, 130, 246, 0.22), transparent 20%), + radial-gradient(circle at 76% 72%, rgba(14, 165, 233, 0.16), transparent 18%), + linear-gradient(180deg, rgba(8, 47, 73, 0.24), rgba(2, 6, 23, 0.16)); +} + +.previewMap::after { + background-image: + linear-gradient(rgba(148, 163, 184, 0.09) 1px, transparent 1px), + linear-gradient(90deg, rgba(148, 163, 184, 0.09) 1px, transparent 1px); + background-size: 48px 48px; + mask-image: linear-gradient(180deg, rgba(255,255,255,1), rgba(255,255,255,0.2)); +} + +.mapChip { + position: absolute; + display: inline-flex; + align-items: center; + gap: 6px; + border-radius: 999px; + border: 1px solid rgba(255,255,255,0.14); + background: rgba(2, 6, 23, 0.78); + padding: 8px 12px; + color: #e2e8f0; + font-size: 12px; + font-weight: 700; + box-shadow: 0 14px 32px rgba(2, 6, 23, 0.3); +} + +.mapChip strong { + color: #f8fafc; +} + +.mapChip span { + color: rgba(34, 211, 238, 0.92); +} + +.previewStack { + padding: 18px; + display: grid; + gap: 12px; +} + +.stackCard { + border-radius: 18px; + border: 1px solid rgba(148, 163, 184, 0.1); + background: rgba(2, 6, 23, 0.5); + padding: 16px; +} + +.stackCard h4 { + font-size: 15px; + font-weight: 700; +} + +.stackCard p { + margin-top: 8px; + color: rgba(191, 219, 254, 0.72); + font-size: 13px; + line-height: 1.55; +} + +.price { + margin-top: 18px; + display: flex; + align-items: baseline; + gap: 8px; +} + +.price strong { + font-size: 42px; + line-height: 1; + font-weight: 800; + letter-spacing: -0.04em; +} + +.price span { + color: rgba(203, 213, 225, 0.78); + font-size: 15px; +} + +.pricingCardAccent { + border-color: rgba(34, 211, 238, 0.2); + box-shadow: 0 18px 44px rgba(8, 47, 73, 0.28); +} + +.ctaCard { + min-height: 100%; +} + +.ctaActions { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 20px; +} + +.footer { + margin-top: 72px; + padding: 28px 0 56px; + color: rgba(148, 163, 184, 0.76); + font-size: 13px; +} + +.footerRow { + display: flex; + justify-content: space-between; + gap: 18px; + align-items: center; +} + +.footerLinks { + display: flex; + gap: 18px; + flex-wrap: wrap; +} + +@media (max-width: 1080px) { + .heroGrid, + .previewBody, + .ctaGrid { + grid-template-columns: 1fr; + } + + .featureGrid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .problemGrid, + .proofGrid, + .pricingGrid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 720px) { + .shell { + width: min(100% - 20px, 1180px); + } + + .heroCard, + .signalCard, + .miniCard, + .pricingCard, + .ctaCard, + .previewTop, + .previewBody { + padding: 18px; + } + + .featureGrid { + grid-template-columns: 1fr; + } + + .metricGrid { + grid-template-columns: 1fr; + } + + .section { + margin-top: 48px; + } + + .footerRow, + .sectionHeader { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/frontend/components/landing/LandingPage.tsx b/frontend/components/landing/LandingPage.tsx new file mode 100644 index 00000000..a8fe7053 --- /dev/null +++ b/frontend/components/landing/LandingPage.tsx @@ -0,0 +1,471 @@ +import Link from "next/link"; +import { + ArrowRight, + Bot, + ChartColumnIncreasing, + ChevronRight, + Compass, + MapPinned, + Radar, + ShieldCheck, + Sparkles, + Target, + Wallet, +} from "lucide-react"; +import styles from "./LandingPage.module.css"; + +const problemCards = [ + { + title: "普通天气预报不等于结算判断", + body: "大多数天气产品只给一个最高温数字。温度市场真正关心的是最终会落进哪个结算桶,以及这个判断有多稳。", + points: ["不是看“几度左右”", "而是看结算桶概率", "以及当前市场是否错价"], + }, + { + title: "单一数据源不够稳", + body: "机场实测、官方结算源、集合预报、多模型 spread 会互相打架。PolyWeather 的价值,在于把这些冲突信息组织成决策视图。", + points: ["METAR / HKO / CWA", "Open-Meteo ensemble", "DEB 融合与回看"], + }, + { + title: "交易需要即时上下文", + body: "当日高温窗口、max so far、模型崩盘、盘口偏移,都比一个静态 forecast 更接近真实交易判断。", + points: ["今日日内分析", "历史对账", "market scan 与 edge"], + }, +]; + +const featureCards = [ + { + icon: Radar, + title: "多源天气", + body: "整合 METAR、官方源、Open-Meteo、ensemble 与 multi-model,不把风险押在单一来源上。", + }, + { + icon: Target, + title: "结算概率", + body: "把 forecast 变成 settlement-focused bucket distribution,而不是只给一个模糊最高温。", + }, + { + icon: ChartColumnIncreasing, + title: "错价扫描", + body: "把模型概率和市场报价放在一张桌子上,直接判断 signal、risk 和 tradable 状态。", + }, + { + icon: Bot, + title: "双端交付", + body: "Web 仪表盘负责看全局,Telegram Bot 负责高频使用与即时查询。", + }, +]; + +const trustCards = [ + { + title: "官方参考出口", + body: "重点城市已经补上官方气象机构、机场页面与 METAR 入口。用户可以自己交叉验证,不必盲信结论。", + points: ["Singapore / Wellington / Hong Kong", "机场实测与国家气象机构", "降低临近结算争议成本"], + }, + { + title: "历史对账与日内分析", + body: "不只给今天的判断,也给你过去表现与 intraday 结构,帮助理解模型稳定性而不是只看单点结果。", + points: ["历史最高温对账", "今日日内曲线", "模型与实测偏差回看"], + }, +]; + +export function LandingPage() { + return ( +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ PolyWeather +
+
+ Weather market intelligence +
+
+
+ +
+ + 打开 Dashboard + + + 登录账户 + + +
+
+ +
+
+
+ + Settlement-Focused Weather Intelligence +
+

+ 面向天气结算市场的实时概率情报平台 +

+

+ PolyWeather 把机场实测、官方结算源、ensemble 区间、多模型 spread + 与当日高温窗口,整理成结算导向概率、错价扫描和可操作的风控视图。 + 不是做“明天大概多少度”的天气站,而是做“今天这个市场值不值得动”的决策终端。 +

+ +
+ + 查看仪表盘 + + + + 开通 Pro + + + + Telegram Bot + + +
+ +
+
+ +
+ 30 个城市,覆盖不同结算口径 + 包括 METAR 城市、香港 HKO、台北 CWA,以及多地区风险分布。 +
+
+
+ +
+ 不是黑箱结论 + 已经补上官方参考链接、历史对账、今日日内分析和市场对照。 +
+
+
+ +
+ Dashboard + Bot 双端交付 + Web 看全局,Bot 处理高频操作,Pro 用于更完整的决策支持。 +
+
+
+
+ +
+
+
+
+

交易前真正需要看什么

+

不是单一 forecast,而是结算、分布、盘口和时段上下文。

+
+ Live System +
+ +
+
+ + 30 + 同时覆盖摄氏和华氏结算市场。 +
+
+ + μ + 桶 + 不只给中心值,还给 bucket distribution。 +
+
+ + 实测 + 预报 + METAR、官方源、ensemble、multi-model 共同进入决策链。 +
+
+ + Web + Bot + 既能看地图,也能在 Telegram 里快速查询。 +
+
+ +
+
+ 今日日内分析 + 高温窗口 + max so far +
+
+ 历史对账 + 按结算口径回看命中 +
+
+ 市场扫描 + 模型概率 vs 市场价格 +
+
+
+ +
+
+
+

当前产品定位

+

从天气数据走到市场判断,而不是停留在天气展示。

+
+
+ +
+
+ Free + 基础看板与账户 +
+
+ Pro + 完整分析、Bot 支持、更多上下文 +
+
+ Ops + 会员、补分、支付异常单、运营视图 +
+
+
+
+
+
+
+ +
+
+
+
+

普通天气预报,不等于结算判断

+
+

+ 温度市场真正值钱的不是“明天最高温大概几度”,而是“今天最终更可能落进哪个桶,这个判断有多稳,市场价格有没有偏离”。 +

+
+ +
+ {problemCards.map((card) => ( +
+

{card.title}

+

{card.body}

+
    + {card.points.map((item) => ( +
  • {item}
  • + ))} +
+
+ ))} +
+
+
+ +
+
+
+
+

把天气数据转成可交易的结算情报

+
+

+ PolyWeather 的核心不是增加信息数量,而是把采集、概率、市场和交付整理成一个可执行工作流。 +

+
+ +
+ {featureCards.map(({ icon: Icon, title, body }) => ( +
+
+ +
+

{title}

+

{body}

+
+ ))} +
+
+
+ +
+
+
+
+

不是黑箱结论,而是可验证的判断

+
+

+ 当市场接近结算,真正有价值的是可验证性。用户可以同时看官方参考、机场实测、历史对账和当日结构,而不是只看一个数字。 +

+
+ +
+ {trustCards.map((card) => ( +
+

{card.title}

+

{card.body}

+
    + {card.points.map((item) => ( +
  • {item}
  • + ))} +
+
+ ))} +
+
+
+ +
+
+
+
+

一个页面看完当日温度交易所需信息

+

+ 地图筛城市,侧边栏看风险,详情面板看今日日内分析、历史对账、官方参考和市场扫描。你不需要在多个天气网站、机场页和市场页之间来回切。 +

+
+
+
+
+ London + 12°C +
+
+ Hong Kong + 17°C +
+
+ New York + 54°F +
+
+ Singapore + 31°C +
+
+ +
+
+

今日日内分析

+

高温窗口、max so far、集合区间和死盘提示,解决“今天还有没有变盘空间”。

+
+
+

历史对账

+

把过去真实结算和模型表现放回同一张表里,避免只看当天情绪。

+
+
+

官方参考

+

重点城市直连国家气象机构、机场页面和 METAR,降低临近结算争议。

+
+
+

支付与会员

+

账户页支持浏览器钱包、订阅、积分与支付异常恢复,不再只是“有个付费按钮”。

+
+
+
+
+
+
+ +
+
+
+
+

从公开看板到 Pro 决策支持

+
+

+ 先让用户看到产品价值,再把更重的使用场景放到账户、Bot 和订阅能力里,不靠含糊营销话术成交。 +

+
+ +
+
+

Free

+

先理解产品逻辑,再决定是否进入更高频的使用方式。

+
+ $0 + 基础入口 +
+
    +
  • 地图与城市风险概览
  • +
  • 基础账户能力
  • +
  • 公开可见的页面与数据口径
  • +
+
+ +
+

Pro

+

面向高频使用者、市场观察者和需要 Telegram 辅助的人群。

+
+ 5 USDC + / 月 +
+
    +
  • 完整分析、更多上下文与账户中心能力
  • +
  • Telegram Bot 交互与更高频使用场景
  • +
  • 支付、积分、订阅与异常恢复链路
  • +
+
+
+
+
+ +
+
+
+
+

开始使用 PolyWeather

+

+ 如果你只是想先看产品,直接打开 Dashboard。需要账户、订阅、支付与会员能力,再进入账户中心。 +

+
+ + 打开 Dashboard + + + + 进入账户中心 + +
+
+ +
+

高频使用更适合 Bot

+

+ 如果你本来就习惯在 Telegram 中工作,用 Bot 会比网页切换更快。Web 负责看全局,Bot 负责高频查询和快速反馈。 +

+ +
+
+ + +
+
+
+ ); +}