Skip to content

Commit d0a420d

Browse files
authored
feat: i18n 双语系统 + 150 篇文档翻译 + Hero UX 迭代 (#281)
* fix(api/docs/history): 兼容 fumadocs 的相对路径参数 fumadocs 的 page.file.path 返回相对 app/docs/ 的路径(如 ai/xxx/index.mdx) 而不是仓库根路径,导致前端调 /api/docs/history 时报 400。 在 normalizeDocsPath 里补上 app/docs/ 前缀兼容。 * style(ui): 对齐 editorial 设计系统,修复 6 处视觉一致性问题 - DocShareButton: 去掉 rounded-md / h-11,改为 font-mono uppercase + border + 翻转色 hover - EditOnGithub: 同步改为相同风格,图标 h-8→h-4 - DocHistoryPanel: 骨架屏和头像去掉 rounded-full,保持方角报纸风 - ContributorRow: Dialog 硬编码 #111111/#F9F9F7 换成 CSS 变量;POWER LEVEL 标签移动端隐藏 - HotDocsTab: 加 sessionStorage 5 分钟缓存,减少重复打后端 - SettingsForm: AI 提供商从原生 select 改为 segment button group;中文 label 去掉 uppercase tracking-widest * fix(settings): 修复主题切换双 Provider 冲突 + 双向同步 问题: - fumadocs 的 RootProvider 内置 next-themes,与自己的 ThemeProvider 同时往 <html class> 写 light/dark,导致进入 /settings 时从黑闪白 - Header 右上角 ThemeToggle 切换主题后,/settings 表单选中态不更新 - Settings 保存时的语言设置写不到文档页能读到的位置 修复: - RootProvider 加 theme={{ enabled: false }},禁用 fumadocs 内置主题 - SettingsForm 用 useTheme() 读当前主题作为初始值 - 监听 currentTheme 变化,同步到表单选中态 - 切主题按钮立即 setTheme(所见即所得,不用等保存) - handleSave 保存成功后把 language 写 cookie,供 /docs Server Component 读 * fix(nav): 非首页点击 Header 也能回主页 - BrandMark 外层包 <Link href="/">,点 logo 回首页 - Header 三个锚点从 #features 改为 /#features 等绝对路径, 从 /rank 或任意子页点导航能跳回主页对应区块 方案 F(最小修复),比引入 Header 全站 layout 或新组件成本更低。 * feat(i18n): 文档站双语切换 locale (cookie + middleware IP 判断) - 新建 middleware.ts:首次访问 /docs 时根据 IP geo / Accept-Language 判断默认 locale,写入 cookie;默认 zh(社区主体语言) - [...slug]/page.tsx: 从 cookie 读 locale,尝试加载 slug.en.mdx / slug.zh.mdx 翻译版,不存在时静默 fallback 到原文(不展示碍眼横幅) - docs-i18n-design.md: 完整双语化设计方案(frontmatter 约定、 contributors 脚本改造、翻译流程、术语词表方向) * feat(home): 首页 Leaderboard 右侧新增'本周最热'面板 (HotDocsPreview) - 新建 HotDocsPreview.tsx(async Server Component + ISR 300s) fetch 自家 /api/analytics/top-docs?window=7d&limit=5, 后端或数据挂掉时静默降级,不影响首页其他模块 - 新建 app/api/analytics/top-docs/route.ts:从 analyticsEvent 表 按 eventData.path 聚合 7d 阅读量,供前端 SSR 使用 - Hero.tsx Leaderboard 区域改 12 列 grid: 贡献者 Top3 (lg:col-span-8) / 热门文档 Top5 (lg:col-span-4) SEO 价值:首页 HTML 中包含 5 条热门文章标题+链接,Google 爬虫 可直接建立首页 → 文章的链接关系,帮助长尾关键词索引。 * feat(scripts): contributors 脚本跳过翻译版文档 翻译版文件 frontmatter 有 translatedFrom 字段,这类文件由 AI 产出、不应污染 contributor 统计。 在 parseDocFrontmatter 新增 isTranslation 字段,主循环里 检测到翻译版则打印跳过日志并 continue,不拉 commit 历史、 不写入 doc_contributors 表。 影响:generate-leaderboard.mjs 无需改动(它从 DB 聚合, 源头已过滤),DB schema 不变。 * feat(docs): i18n MVP — 翻译 5 篇代表性文档 覆盖不同场景的 MVP 样本,用于验证翻译 agent 的能力边界: - ai/reinforcement-learning-overview (zh→en) — AI 术语密集 - ai/compute-platforms-handbook (zh→en) — 代码块保留 - computer-science/01-singly-linked-list (en→zh) — 长文 + ASCII 图 - jobs/bq (zh→en) — 文化语境 / STAR / BQ 行话 - CommunityShare/Geek/cloudflare-r2-sharex (zh→en) — 表格 + 中文 URL 编码 每份翻译版: - 继承原文 docId(共享同一文档的版本) - frontmatter 有 translatedFrom/translatedAt 标记,contributors 脚本会跳过 - 代码块 / math / URL / MDX 属性名原样保留 - 仅翻译 title、description、正文文本与 MDX 文本内容 MVP 5 篇总 token ~29.4k,平均 5.9k/篇。 * feat(docs): i18n CommunityShare 全部翻译完成 (13 篇) translator-community 产出。双向翻译: - Geek: git101 / picturecdn / swanlab / raspberry-guide / CommonUsedMarkdown / Katex×2 (全部 zh→en),leworldmodel (en→zh) - RAG: context_engineering_intro / embedding / rag (全部 zh→en) - Amazing-AI-Tools: perplexity-comet (zh→en), prompt-repetition-improves-non-reasoning-llms (en→zh) 跳过:3 个 <Cards> 索引页、1 个 MVP 已翻译文件。 所有翻译版 frontmatter 继承原文 docId,带 translatedFrom 标记 供 contributors 脚本跳过统计。 * feat(docs): i18n ai/ 前半部分全部翻译完成 (24 篇) translator-ai-1 产出。全部 zh→en。覆盖: - Introduction-of-Multi-agents-system (1) - MoE (2) - Multi-agents-system-on-Code-Translation (1) - agents-todo (2) - ai-math-basics (9) — 含 calculus / linear-algebra / probability 等子目录 - compute-platforms (1,handbook 已在 MVP 提交) - foundation-models (7) — lifecycle / datasets / training / finetune 等 - generative-todo (1) 所有翻译版 frontmatter 继承原文 docId,带 translatedFrom: zh 标记供 contributors 脚本跳过统计。 * feat(docs): i18n computer-science + jobs + all-projects 翻译完成 (23 篇) translator-cs-jobs 产出: - computer-science: 双向翻译 - data-structures (en→zh, 5 篇): index / array / linked-list - frontend (zh→en, 2 篇) - cpp_backend (zh→en, 8 篇): mempool / threadpool / 编译系列 - index.mdx (zh→en) - jobs (zh→en, 5 篇): event-keynote 2 / interview-prep 3 - all-projects (zh→en, 2 篇): ai-town / multimodal-rl 代码块内变量名和 API 原样保留,仅翻译注释。 frontmatter 继承原文 docId,带 translatedFrom 标记。 * feat(docs): i18n Leetcode 目录翻译完成 (42 篇) translator-leetcode 产出。Leetcode 题解目录特殊处理: - 原文件名含方括号/中文/空格/_translated 后缀,命名风格混乱 - 翻译版统一改为 kebab-case 英文 slug: [146]LRU 缓存_translated.md → 146-lru-cache.en.md [121]买卖股票的最佳时期_translated.md → 121-best-time-to-buy-and-sell-stock.en.md 双向翻译: - zh→en: 35 篇(绝大多数原文是中文题解) - en→zh: 7 篇(2241 ATM / 2270 Split Array / 3138 Anagram / 46 全排列 / 9021 TUT / 93 IP / Counting Stars / 等英文原文) 代码块(Python/C++/Java)原样保留,仅译注释。LaTeX 公式保留。 frontmatter 继承原文 docId,带 translatedFrom 标记。 跳过:1 个 <Cards> 索引页。 * feat(docs): i18n ai/ 后半部分翻译完成 + MDX 语法修复 (32 篇) translator-ai-2 产出 + MDX 语法修复。全部 zh→en。 覆盖: - llm-basics (12 篇): courses / cuda / deep-learning / embeddings / pytorch / transformer - methodology (1) - misc-tools (1) - model-datasets-platforms (1) - multimodal (10): courses / llava / mllm / qwenvl / ViT / VAE / VQVAE / RQVAE 等 - recommender-systems (7): 王树森推荐系统笔记全集(粗排/精排/重排/冷启动等) MDX 语法修复: - 2 个 recommender-systems 文件的裸 <1000 / <30 / <24 / <8 用反引号包裹 (MDX 把 <digit 当成 JSX tag 开头导致 build 失败) - app/api/analytics/top-docs/route.ts: Prisma JSON filter startsWith 写法错误,改为内存筛选 术语决策(新发现): - 笔记 → post (Xiaohongshu 场景) - 粗排/精排/重排 → pre-ranking / full ranking / re-ranking - 保量 → exposure guarantee - 融合分数 → fused score - 老汤模型 → aged model (inline explanation) * fix(docs): 翻译格式微调 — 补 H1 标题、修标题层级 - wangshusen_recommend_note_improvement.en.mdx: 补 H1 - wangshusen_recommend_note_retrieval.en.mdx: H5 → H4 - leworldmodel.md / 1545-...en.md: 小修 - generated/site-leaderboard.json: 自动更新 * fix(lint): Header.tsx 的 <a href="/..."> 改 next/link <Link> 修 CI lint error: @next/next/no-html-link-for-pages * feat(home): 用 DispatchNetwork 横条替代 Features + Community 两段 useless 区块 - 新增 app/components/DispatchNetwork.tsx:48px 高报纸末版"发行网络"风格横条 - 内容:GitHub / Discord / Zotero 三个入口 + Join CTA - 移除 page.tsx 对 Features 和 Community 的引用 - 组件文件保留未删,后续可能复用;主页不再渲染 用户原因:Top Rank 之后的 Mission Statement 四格口号和 Community 三卡链接 完全 useless,和 Footer 重复劳动。方案三(极简 bar)被选中。 * fix(pr281): 修复 Copilot Review 提出的 6 处问题 1. top-docs/route.ts: limit 参数加 Number.isFinite 校验防 NaN 2. top-docs/route.ts: 返回结构统一为 ApiResponse{success,data},补齐 title 字段 (通过 fumadocs source.getPage 回填,同时保留埋点带来的 title) 3. HotDocsPreview.tsx: 改走同源 /api/analytics/top-docs 走 ISR, 不再直连 BACKEND_URL,消除 '白做缓存' 问题 4. docs/[...slug]/page.tsx: generateMetadata 也按 locale 取页面, 避免英文页显示中文 title/description 5. middleware.ts: Accept-Language 解析改为按 q 值排序取首选语言, 正确处理 'fr-CA,fr;q=0.9,en;q=0.8' 这类多语言 header 6. computer-science/index.en.mdx: 删除正文末尾混入的整段中文 * fix(search): 按语言分片 search.json,规避 Vercel 19MB ISR 上限 之前 150 篇中文 + 150 篇英文翻译全塞进同一个 /search.json, 构建产物 23MB 触发 FALLBACK_BODY_TOO_LARGE 导致 Vercel 部署失败。 方案: - 新增 lib/search-index.ts 抽出 pageToIndex 和 isEnglishPage 工具 - /search.zh.json — 中文 / 无语言标的原文 + lang="zh" 翻译版(约 13MB) - /search.en.json — 仅 lang="en" 翻译版,用 Orama 英文分词(约 11MB) - layout.tsx 读 locale cookie 动态选 API(默认 zh) - createSearchAPI 的 indexes 必须是 Dynamic<T>=()=>T[]|Promise<T[]>,不是裸 Promise 下一步扩容:文档数量继续增长可按目录再切片(ai/cs/leetcode 各自独立 json)。 * feat(analytics): 埋点迁到 Java 后端 /analytics/events Vercel Fluid CPU 用到 85%,原因是前端 /api/analytics 每次埋点都跑 Next function 做 resolveUserId + Prisma 直写。现改为走 next.config 已有的 /analytics/:path* rewrite 直转 Java,Vercel 只做 edge 代理不跑 function。 前端改动: - lib/analytics.ts: URL /api/analytics → /analytics/events,header x-satoken → satoken - 删除 app/api/analytics/route.ts(Prisma 直写逻辑搬到 Java) 配套后端改动(另开 PR): - AnalyticsController 加 POST /events - 新增 AnalyticsEventIngestService 用 JdbcTemplate INSERT - SaTokenConfigure 白名单放行 /analytics/events(匿名也收) AnalyticsEvent 表保留:GA4 走 top-docs,这张表暂无读取方但 未来自建 dashboard / 登录用户精细追踪可直接复用。
1 parent 69cf324 commit d0a420d

165 files changed

Lines changed: 19301 additions & 204 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/api/analytics/route.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

app/api/docs/history/route.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ function normalizeDocsPath(raw: string): string | null {
4747
if (normalized.startsWith("docs/")) {
4848
normalized = `app/${normalized}`;
4949
}
50+
// fumadocs 的 page.file.path 返回"相对 app/docs/"路径(如 ai/xxx/index.mdx)
51+
// 而不是仓库根。这里补上前缀,和 page.tsx 传参保持兼容。
52+
if (!normalized.startsWith("app/")) {
53+
normalized = `app/docs/${normalized}`;
54+
}
5055
// 必须落在 app/docs/ 下才放行
5156
if (!normalized.startsWith("app/docs/")) {
5257
return null;

app/components/BrandMark.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* @param {boolean} priority - 是否优先加载
1313
*/
1414
import Image from "next/image";
15+
import Link from "next/link";
1516
import { cn } from "@/lib/utils";
1617

1718
export const BRAND_NAME = "Involution Hell";
@@ -38,7 +39,7 @@ export function BrandMark({
3839
const width = Math.round(imageSize * BRAND_LOGO_ASPECT_RATIO);
3940

4041
return (
41-
<div className={cn("flex items-center gap-2", className)}>
42+
<Link href="/" className={cn("flex items-center gap-2", className)}>
4243
<div className="relative">
4344
<Image
4445
src={BRAND_LOGO_LIGHT_SRC}
@@ -65,6 +66,6 @@ export function BrandMark({
6566
>
6667
{BRAND_NAME}
6768
</span>
68-
</div>
69+
</Link>
6970
);
7071
}

app/components/DispatchNetwork.tsx

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import Link from "next/link";
2+
import { Github as GithubIcon } from "./icons/Github";
3+
import { MessageCircle, BookMarked, ArrowRight } from "lucide-react";
4+
5+
/**
6+
* DispatchNetwork — 主页 Top Rank 之后的极简网络入口横条
7+
* 替代原先 Features(口号四格)+ Community(链接三卡)两个 section
8+
* 设计意图:报纸末版的"发行网络"小栏,48px 高横条,不重复 Footer
9+
*/
10+
export function DispatchNetwork() {
11+
return (
12+
<section
13+
id="community"
14+
className="border-t-4 border-[var(--foreground)] bg-[var(--background)]"
15+
>
16+
<div className="container mx-auto px-6">
17+
<div className="flex items-center justify-between gap-4 py-3 font-mono text-[11px] uppercase tracking-[0.25em] text-[var(--foreground)] md:text-xs">
18+
{/* 左:栏目标签 */}
19+
<span className="font-bold whitespace-nowrap">
20+
Dispatch Network
21+
<span className="mx-2 hidden text-neutral-400 md:inline">·</span>
22+
<span className="hidden font-normal text-neutral-500 md:inline">
23+
Sec. Net-01
24+
</span>
25+
</span>
26+
27+
{/* 中:三个外链 */}
28+
<nav className="flex items-center gap-3 md:gap-6">
29+
<Link
30+
href="https://github.com/involutionhell"
31+
target="_blank"
32+
rel="noopener noreferrer"
33+
className="inline-flex items-center gap-1.5 hover:text-[#CC0000] transition-colors"
34+
data-umami-event="social_click"
35+
data-umami-event-platform="github"
36+
data-umami-event-location="dispatch_network"
37+
>
38+
<GithubIcon className="h-3.5 w-3.5" />
39+
<span>GitHub</span>
40+
</Link>
41+
<span className="text-neutral-400">·</span>
42+
<Link
43+
href="https://discord.com/invite/6CGP73ZWbD"
44+
target="_blank"
45+
rel="noopener noreferrer"
46+
className="inline-flex items-center gap-1.5 hover:text-[#CC0000] transition-colors"
47+
data-umami-event="social_click"
48+
data-umami-event-platform="discord"
49+
data-umami-event-location="dispatch_network"
50+
>
51+
<MessageCircle className="h-3.5 w-3.5" />
52+
<span>Discord</span>
53+
</Link>
54+
<span className="text-neutral-400">·</span>
55+
<Link
56+
href="https://www.zotero.org/groups/6053219/involution_hell"
57+
target="_blank"
58+
rel="noopener noreferrer"
59+
className="inline-flex items-center gap-1.5 hover:text-[#CC0000] transition-colors"
60+
data-umami-event="social_click"
61+
data-umami-event-platform="zotero"
62+
data-umami-event-location="dispatch_network"
63+
>
64+
<BookMarked className="h-3.5 w-3.5" />
65+
<span>Zotero</span>
66+
</Link>
67+
</nav>
68+
69+
{/* 右:加入 CTA */}
70+
<Link
71+
href="https://discord.com/invite/6CGP73ZWbD"
72+
target="_blank"
73+
rel="noopener noreferrer"
74+
className="inline-flex items-center gap-1 font-bold whitespace-nowrap hover:text-[#CC0000] transition-colors"
75+
data-umami-event="cta_click"
76+
data-umami-event-label="join_dispatch"
77+
>
78+
<span className="hidden sm:inline">Join</span>
79+
<ArrowRight className="h-3.5 w-3.5" />
80+
</Link>
81+
</div>
82+
</div>
83+
</section>
84+
);
85+
}

app/components/DocHistoryPanel.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ function relativeTime(dateStr: string): string {
5050
function SkeletonRow() {
5151
return (
5252
<div className="flex items-center gap-3 py-2.5 animate-pulse">
53-
<div className="w-6 h-6 rounded-full bg-neutral-200 dark:bg-neutral-700 shrink-0" />
53+
<div className="w-6 h-6 bg-neutral-200 dark:bg-neutral-700 shrink-0" />
5454
<div className="flex-1 flex flex-col gap-1">
55-
<div className="h-3 w-2/3 rounded bg-neutral-200 dark:bg-neutral-700" />
56-
<div className="h-2.5 w-1/3 rounded bg-neutral-100 dark:bg-neutral-800" />
55+
<div className="h-3 w-2/3 bg-neutral-200 dark:bg-neutral-700" />
56+
<div className="h-2.5 w-1/3 bg-neutral-100 dark:bg-neutral-800" />
5757
</div>
5858
</div>
5959
);
@@ -129,15 +129,15 @@ export function DocHistoryPanel({ path }: DocHistoryPanelProps) {
129129
href={item.htmlUrl}
130130
target="_blank"
131131
rel="noopener noreferrer"
132-
className="flex items-start gap-3 py-2.5 group hover:bg-neutral-50 dark:hover:bg-neutral-900 rounded transition-colors px-1 -mx-1"
132+
className="flex items-start gap-3 py-2.5 group hover:bg-neutral-50 dark:hover:bg-neutral-900 transition-colors px-1 -mx-1"
133133
>
134134
{/* 头像 */}
135135
<Image
136136
src={item.avatarUrl || FALLBACK_AVATAR}
137137
alt={item.authorLogin}
138138
width={24}
139139
height={24}
140-
className="rounded-full mt-0.5 shrink-0"
140+
className="mt-0.5 shrink-0"
141141
unoptimized
142142
/>
143143

app/components/DocShareButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function DocShareButton() {
3838
<button
3939
type="button"
4040
onClick={handleCopy}
41-
className="inline-flex items-center gap-2 rounded-md px-4 h-11 text-base font-medium hover:bg-muted/80 hover:text-foreground"
41+
className="inline-flex items-center gap-2 px-3 py-1.5 font-mono text-xs uppercase tracking-widest border border-[var(--foreground)] hover:bg-[var(--foreground)] hover:text-[var(--background)] transition-colors"
4242
aria-label="复制页面链接"
4343
>
4444
<svg

app/components/EditOnGithub.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ export function EditOnGithub({ href }: { href: string }) {
44
return (
55
<Link
66
href={href}
7-
className="inline-flex items-center gap-2 rounded-md px-4 h-11 text-base font-medium hover:bg-muted/80 hover:text-foreground no-underline"
7+
className="inline-flex items-center gap-2 px-3 py-1.5 font-mono text-xs uppercase tracking-widest border border-[var(--foreground)] hover:bg-[var(--foreground)] hover:text-[var(--background)] transition-colors no-underline"
88
data-umami-event="docs_edit_click"
99
data-umami-event-page={href}
1010
>
1111
<svg
1212
aria-hidden="true"
13-
className="h-8 w-8"
13+
className="h-4 w-4"
1414
viewBox="0 0 24 24"
1515
fill="none"
1616
stroke="currentColor"

app/components/Header.tsx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Link from "next/link";
12
import { ThemeToggle } from "./ThemeToggle";
23
import { Button } from "@/components/ui/button";
34
import { MessageCircle } from "lucide-react";
@@ -30,33 +31,42 @@ export function Header() {
3031

3132
<div className="flex items-center justify-between h-10">
3233
<nav className="hidden md:flex items-center gap-8 font-sans text-xs font-bold uppercase tracking-widest text-[var(--foreground)]">
33-
<a
34-
href="#features"
34+
<Link
35+
href="/"
36+
className="hover:text-[#CC0000] transition-colors"
37+
data-umami-event="navigation_click"
38+
data-umami-event-region="header"
39+
data-umami-event-label="home"
40+
>
41+
首页
42+
</Link>
43+
<Link
44+
href="/#features"
3545
className="hover:text-[#CC0000] transition-colors"
3646
data-umami-event="navigation_click"
3747
data-umami-event-region="header"
3848
data-umami-event-label="features"
3949
>
4050
特点
41-
</a>
42-
<a
43-
href="#community"
51+
</Link>
52+
<Link
53+
href="/#community"
4454
className="hover:text-[#CC0000] transition-colors"
4555
data-umami-event="navigation_click"
4656
data-umami-event-region="header"
4757
data-umami-event-label="community"
4858
>
4959
社区
50-
</a>
51-
<a
52-
href="#contact"
60+
</Link>
61+
<Link
62+
href="/#contact"
5363
className="hover:text-[#CC0000] transition-colors"
5464
data-umami-event="navigation_click"
5565
data-umami-event-region="header"
5666
data-umami-event-label="contact"
5767
>
5868
联系我们
59-
</a>
69+
</Link>
6070
</nav>
6171

6272
<div className="flex items-center gap-2">

app/components/Hero.tsx

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Image from "next/image";
55
import { ActivityTicker } from "@/app/components/ActivityTicker";
66
import { cn } from "@/lib/utils";
77
import { AnimatedBar } from "@/app/components/rank/AnimatedBar";
8+
import { HotDocsPreview } from "@/app/components/HotDocsPreview";
89
import leaderboardData from "@/generated/site-leaderboard.json";
910
import { MAINTAINERS } from "@/lib/admins";
1011

@@ -169,49 +170,52 @@ export function Hero() {
169170
</Link>
170171
</div>
171172

172-
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
173-
{(() => {
174-
const rawData = leaderboardData as {
175-
id: string;
176-
name: string;
177-
points: number;
178-
avatarUrl: string;
179-
}[];
180-
const filteredData = rawData.filter(
181-
(user) => !MAINTAINERS.includes(user.name),
182-
);
183-
const top3 = filteredData.slice(0, 3);
184-
const maxPoints = top3.length > 0 ? top3[0].points : 100;
173+
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
174+
<div className="lg:col-span-8 grid grid-cols-1 md:grid-cols-3 gap-6">
175+
{(() => {
176+
const rawData = leaderboardData as {
177+
id: string;
178+
name: string;
179+
points: number;
180+
avatarUrl: string;
181+
}[];
182+
const filteredData = rawData.filter(
183+
(user) => !MAINTAINERS.includes(user.name),
184+
);
185+
const top3 = filteredData.slice(0, 3);
186+
const maxPoints = top3.length > 0 ? top3[0].points : 100;
185187

186-
return top3.map((user, idx) => (
187-
<div
188-
key={user.id}
189-
className="border border-[var(--foreground)] p-6 bg-[var(--background)] relative hard-shadow-hover transition-all group"
190-
>
191-
<div className="absolute top-0 right-0 w-12 h-12 bg-[var(--foreground)] text-[var(--background)] flex items-center justify-center font-mono font-bold text-xl border-b border-l border-[var(--foreground)] z-10">
192-
#{idx + 1}
193-
</div>
194-
<div className="w-16 h-16 bg-neutral-100 dark:bg-neutral-800 border border-[var(--foreground)] mb-4 transition-transform group-hover:scale-110 overflow-hidden">
195-
<Image
196-
src={user.avatarUrl}
197-
alt={user.name}
198-
width={64}
199-
height={64}
200-
className="w-full h-full object-cover transition-all duration-300"
201-
/>
202-
</div>
203-
<div className="font-serif text-2xl font-bold uppercase text-[var(--foreground)] mb-1 truncate">
204-
{user.name}
205-
</div>
206-
<div className="font-mono text-xs text-neutral-500 uppercase tracking-widest mb-4">
207-
{user.points.toLocaleString()} PTS
188+
return top3.map((user, idx) => (
189+
<div
190+
key={user.id}
191+
className="border border-[var(--foreground)] p-6 bg-[var(--background)] relative hard-shadow-hover transition-all group"
192+
>
193+
<div className="absolute top-0 right-0 w-12 h-12 bg-[var(--foreground)] text-[var(--background)] flex items-center justify-center font-mono font-bold text-xl border-b border-l border-[var(--foreground)] z-10">
194+
#{idx + 1}
195+
</div>
196+
<div className="w-16 h-16 bg-neutral-100 dark:bg-neutral-800 border border-[var(--foreground)] mb-4 transition-transform group-hover:scale-110 overflow-hidden">
197+
<Image
198+
src={user.avatarUrl}
199+
alt={user.name}
200+
width={64}
201+
height={64}
202+
className="w-full h-full object-cover transition-all duration-300"
203+
/>
204+
</div>
205+
<div className="font-serif text-2xl font-bold uppercase text-[var(--foreground)] mb-1 truncate">
206+
{user.name}
207+
</div>
208+
<div className="font-mono text-xs text-neutral-500 uppercase tracking-widest mb-4">
209+
{user.points.toLocaleString()} PTS
210+
</div>
211+
<AnimatedBar value={user.points} max={maxPoints} />
208212
</div>
209-
210-
{/* Visual bar chart representing points using motion */}
211-
<AnimatedBar value={user.points} max={maxPoints} />
212-
</div>
213-
));
214-
})()}
213+
));
214+
})()}
215+
</div>
216+
<div className="lg:col-span-4">
217+
<HotDocsPreview />
218+
</div>
215219
</div>
216220
</div>
217221
</div>

0 commit comments

Comments
 (0)