diff --git a/frontend/app/globals.css b/frontend/app/globals.css index d72e460..162f557 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -605,6 +605,101 @@ h1, h2, h3, h4 { white-space: nowrap; } + /* 站点数据 bento 统计瓦片 */ + .j13-pulse { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + } + .j13-pulse-cell { + min-width: 0; + padding: 11px 12px 10px; + background: var(--panel-2); + border: 1px solid var(--line); + border-radius: var(--r-sm); + } + .j13-pulse-lab { + display: flex; + align-items: center; + gap: 5px; + font-size: 11.5px; + color: var(--ink-3); + white-space: nowrap; + } + .j13-pulse-num { + margin-top: 6px; + font-size: 21px; + font-weight: 800; + line-height: 1.05; + letter-spacing: -0.015em; + color: var(--ink); + font-variant-numeric: tabular-nums; + } + .j13-pulse-foot { + display: flex; + align-items: center; + gap: 3px; + margin-top: 6px; + font-size: 10.5px; + line-height: 1; + color: var(--ink-3); + font-variant-numeric: tabular-nums; + white-space: nowrap; + } + .j13-pulse-foot.is-up { + color: var(--ok); + font-weight: 600; + } + .j13-pulse-dot { + position: relative; + width: 6px; + height: 6px; + border-radius: 9999px; + background: var(--ok); + flex-shrink: 0; + } + .j13-pulse-dot::after { + content: ""; + position: absolute; + inset: -3px; + border-radius: 9999px; + background: var(--ok); + opacity: 0.4; + animation: j13-ping 1.8s cubic-bezier(0, 0, 0.2, 1) infinite; + } + @keyframes j13-ping { + 0% { transform: scale(0.6); opacity: 0.5; } + 70%, 100% { transform: scale(1.9); opacity: 0; } + } + @media (prefers-reduced-motion: reduce) { + .j13-pulse-dot::after { + animation: none; + opacity: 0.25; + } + } + /* 卡内签到条:分隔线 + 紧凑按钮 */ + .j13-pulse-checkin { + margin-top: 10px; + padding-top: 11px; + border-top: 1px solid var(--line); + } + .j13-pulse-checkin-btn { + gap: 4px; + padding: 6px 11px; + font-size: 12px; + white-space: nowrap; + } + .j13-pulse-checkin-done { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 6px 11px; + border-radius: 9999px; + font-size: 12px; + font-weight: 600; + white-space: nowrap; + } + /* 热门榜单 */ .j13-rank { position: relative; diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index b232251..d22ecd7 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -201,6 +201,7 @@ export default async function HomePage({ searchParams }: HomePageProps) { {overview && (
(initial ?? null); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); - // 游客态 - if (initial === undefined) { - return ( -
-
- - - -
-

- 每日签到 -

-

登录后每日签到领积分

-
-
- - 登录后签到 - -
- ); - } - const checked = status?.checked_today ?? false; + const guest = initial === undefined; async function doCheckin() { if (loading || checked) return; @@ -55,59 +31,65 @@ export default function CheckinCard({ initial }: { initial?: CheckinStatus | nul } return ( -
-
+
+
- {checked ? : } + {checked ? : } -
-

+

+

每日签到

-

- {checked - ? `已连续签到 ${status?.streak ?? 0} 天 · 累计 ${status?.total_points ?? 0} 积分` - : `连续 ${status?.streak ?? 0} 天 · 今日可得 ${status?.today_points ?? 5} 积分`} +

+ {guest + ? "登录后每日签到领积分" + : checked + ? `连签 ${status?.streak ?? 0} 天 · 累计 ${status?.total_points ?? 0} 积分` + : `连签 ${status?.streak ?? 0} 天 · 今日 +${status?.today_points ?? 5}`}

- {!checked && ( - - +{status?.today_points ?? 5} + {guest ? ( + + 登录 + + ) : checked ? ( + + 已签 + ) : ( + )}
- - - {error && ( -

+

{error}

)} -
+
); } diff --git a/frontend/components/home/CommunityPanels.tsx b/frontend/components/home/CommunityPanels.tsx index fc87cfe..e77b1ac 100644 --- a/frontend/components/home/CommunityPanels.tsx +++ b/frontend/components/home/CommunityPanels.tsx @@ -9,6 +9,8 @@ import { Megaphone, BarChart3, PenLine, + Activity, + TrendingUp, } from "lucide-react"; import type { PostListItem, @@ -19,52 +21,43 @@ import type { CheckinStatus, } from "@/lib/api"; import Avatar from "@/components/Avatar"; -import CheckinCard from "./CheckinCard"; +import { PulseCheckin } from "./CheckinCard"; import { FRIEND_LINKS } from "@/lib/friendLinks"; import { formatCompact, formatShortDate } from "@/lib/format"; -/* ---------- 站点数据(2×2 极简统计表) ---------- */ +/* ---------- 站点数据(2×2 bento 统计瓦片) ---------- */ -function StatCell({ - index, - label, - value, - foot, -}: { - index: number; - label: string; - value: string; - foot: string; -}) { - const rightCol = index % 2 === 1; - const bottomRow = index >= 2; - return ( -
-

- {label} -

-

- {value} -

-

- {foot} -

-
- ); -} - -function PulseCard({ stats }: { stats: OverviewStats }) { +function PulseCard({ stats, checkin }: { stats: OverviewStats; checkin?: CheckinStatus | null }) { + const cells = [ + { + icon: , + label: "注册用户", + value: formatCompact(stats.users), + foot: `+${formatCompact(stats.today_users)} 今日`, + up: true, + }, + { + icon: , + label: "话题总数", + value: formatCompact(stats.posts), + foot: `+${formatCompact(stats.today_posts)} 今日`, + up: true, + }, + { + icon: , + label: "回复总数", + value: formatCompact(stats.comments), + foot: `+${formatCompact(stats.today_comments)} 今日`, + up: true, + }, + { + icon: , + label: "在线用户", + value: formatCompact(stats.online), + foot: `峰值 ${formatCompact(stats.peak_online)}`, + up: false, + }, + ]; return (
@@ -76,15 +69,29 @@ function PulseCard({ stats }: { stats: OverviewStats }) { style={{ color: "var(--ink-3)" }} title="近 5 分钟在线" > - +
-
- - - - +
+ {cells.map((c) => ( +
+

+ + {c.label} +

+

{c.value}

+

+ {c.up &&

+
+ ))} +
+
+
); @@ -319,9 +326,8 @@ export function CommunityPanels({ return (