From a8d5d6bc1f07237b2ad6c93e5ffafa8161368a20 Mon Sep 17 00:00:00 2001 From: faweizhao26 Date: Thu, 19 Mar 2026 11:47:18 +0800 Subject: [PATCH] Add contributors wall page --- docusaurus.config.js | 4 + .../community-page.md | 3 + .../docusaurus-theme-classic/navbar.json | 5 +- src/data/contributors.js | 247 ++++++++++++ src/pages/community-page.md | 3 + src/pages/contributors.js | 247 ++++++++++++ src/pages/contributors.module.css | 371 ++++++++++++++++++ static/img/contributors/fawei-zhao.jpeg | Bin 0 -> 18129 bytes 8 files changed, 879 insertions(+), 1 deletion(-) create mode 100644 src/data/contributors.js create mode 100644 src/pages/contributors.js create mode 100644 src/pages/contributors.module.css create mode 100644 static/img/contributors/fawei-zhao.jpeg diff --git a/docusaurus.config.js b/docusaurus.config.js index 6e6dd741..b115de76 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -198,6 +198,10 @@ const config = { href: 'https://atomgit.com/IvorySQL/IvorySQL', label: 'GitCode', }, + { + label: 'Contributors', + to: '/contributors', + }, ] }, { diff --git a/i18n/zh-CN/docusaurus-plugin-content-pages/community-page.md b/i18n/zh-CN/docusaurus-plugin-content-pages/community-page.md index 5fec0f9e..7165b96a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-pages/community-page.md +++ b/i18n/zh-CN/docusaurus-plugin-content-pages/community-page.md @@ -19,6 +19,9 @@ IvorySQL社区包括来自世界各地的开发和使用开源数据库的人员 - [提交bug](https://github.com/IvorySQL/IvorySQL/issues/new/choose) - [提交拉取请求](https://github.com/IvorySQL/IvorySQL/pulls) +## 贡献者墙 +在 [贡献者墙](/zh-cn/contributors) 认识正在推动 IvorySQL 前进的社区贡献者。 + ## 对话 Hey everybody! 中国IvorySQL开源数据库社区已经成立啦!如果想要加入IvorySQL中国技术交流群,可添加IvorySQL小助理微信:IvorySQL_official diff --git a/i18n/zh-CN/docusaurus-theme-classic/navbar.json b/i18n/zh-CN/docusaurus-theme-classic/navbar.json index c0b28d42..2c91374b 100644 --- a/i18n/zh-CN/docusaurus-theme-classic/navbar.json +++ b/i18n/zh-CN/docusaurus-theme-classic/navbar.json @@ -15,6 +15,10 @@ "message": "开发者", "description": "Navbar item with label Developers" }, + "item.label.Contributors": { + "message": "贡献者", + "description": "Navbar item with label Contributors" + }, "item.label.Download": { "message": "下载", "description": "Navbar item with label Download" @@ -96,4 +100,3 @@ "description": "Navbar item with label Ecological Cooperation" } } - diff --git a/src/data/contributors.js b/src/data/contributors.js new file mode 100644 index 00000000..0a42c0d3 --- /dev/null +++ b/src/data/contributors.js @@ -0,0 +1,247 @@ +const CONTRIBUTORS_BY_YEAR = { + 2026: [ + 'cedric-villemain', + 'gaoxueyu', + 'grant-zhou', + 'jiaoshuntian', + 'leiyanliang', + 'liangxiangyu', + 'liuxiaohui', + 'liyuan', + 'oreo-yang', + 'panzhenhao', + 'pierre-forstmann', + 'rophy', + 'shizhuoyan', + 'steven-niu', + 'suige', + 'taozheng', + 'xiaoyu509', + 'xinjie-lyu', + 'yasir-hussain-shah', + 'zhangzhe', + 'zhaofawei', + ], + 2025: [ + 'Alex-Guo', + 'Amberwww1', + 'Bei-Fu', + 'Carlos-Chong', + 'Cary-Huang', + 'Cedric-Villemain', + 'Dapeng-Wang', + 'Denis-Lussier', + 'Fawei-Zhao', + 'Gavin-LYU', + 'Ge-Sui', + 'Grant-Zhou', + 'Hulin-Ji', + 'Imran-Zaheer', + 'JiaoShuntian', + 'Kang-Wang', + 'Lily-Wang', + 'Martin-Gerhardy', + 'Mingran-Feng', + 'Oreo-Yang', + 'Pedro-Lopez', + 'RRRRhl', + 'Renli-Zou', + 'Rophy-Tsai', + 'Ruike-Sun', + 'Ruohang-Feng', + 'Shaolin-Chu', + 'Shawn-Yan', + 'Shoubo-Wang', + 'Shuisen-Tong', + 'Steven-Niu', + 'Xiangyu-Liang', + 'Xiaohui-Liu', + 'Xueyu-Gao', + 'Yanliang-Lei', + 'Yasir-Hussain-Shah', + 'Yuan-Li', + 'Zhe-Zhang', + 'Zheng-Tao', + 'Zhenhao-Pan', + 'Zhibin-Wang', + 'Zhuoyan-Shi', + 'caffiendo', + 'ccwxl', + 'flyingbeecd', + 'huchangqiqi', + 'jerome-peng', + 'luss', + 'omstack', + 'otegami', + 'shangwei007', + 'shlei6067', + 'sjw1933', + 'tiankongbuqi', + 'xuexiaoganghs', + 'yangchunwanwusheng', + ], +}; + +const CONTRIBUTOR_ALIASES = { + gaoxueyu: 'xueyugao', + leiyanliang: 'yanlianglei', + liangxiangyu: 'xiangyuliang', + liuxiaohui: 'xiaohuiliu', + liyuan: 'yuanli', + panzhenhao: 'zhenhaopan', + rophy: 'rophytsai', + shizhuoyan: 'zhuoyanshi', + suige: 'gesui', + taozheng: 'zhengtao', + zhaofawei: 'faweizhao', + zhangzhe: 'zhezhang', +}; + +const CONTRIBUTOR_OVERRIDES = { + carloschong: { github: 'Carlos-Chong200' }, + caryhuang: { github: 'caryhuang' }, + cedricvillemain: { name: 'Cedric Villemain', github: 'c2main' }, + faweizhao: { + name: 'Fawei Zhao', + github: 'faweizhao26', + avatarSrc: '/img/contributors/fawei-zhao.jpeg', + }, + gavinlyu: { name: 'Gavin LYU' }, + gesui: { name: 'Ge Sui', github: 'suige' }, + grantzhou: { github: 'grantzhou' }, + imranzaheer: { github: 'imranzaheer612' }, + jiaoshuntian: { name: 'Jiao Shuntian', github: 'jiaoshuntian' }, + martingerhardy: { github: 'mgerhardy' }, + oreoyang: { name: 'Oreo Yang', github: 'OreoYang' }, + pierreforstmann: { name: 'Pierre Forstmann', github: 'pierreforstmann' }, + renlizou: { github: 'zourenli' }, + rophytsai: { name: 'Rophy Tsai', github: 'rophy' }, + rrrrhl: { github: 'RRRRhl' }, + ruohangfeng: { github: 'Vonng' }, + shaolinchu: { github: 'shaolinchu' }, + shawnyan: { github: 'shawn0915' }, + stevenniu: { name: 'Steven Niu', github: 'bigplaice' }, + xiaohuiliu: { name: 'Xiaohui Liu', github: 'hs-liuxh' }, + xiangyuliang: { name: 'Xiangyu Liang', github: 'balinorLiang' }, + xinjielyu: { name: 'Xinjie Lyu' }, + xueyugao: { name: 'Xueyu Gao', github: 'gaoxueyu' }, + yanlianglei: { name: 'Yanliang Lei', github: 'msdnchina' }, + yasirhussainshah: { name: 'Yasir Hussain Shah', github: 'yasir-hussain-shah' }, + yuanli: { name: 'Yuan Li', github: 'yuanyl630' }, + zhezhang: { name: 'Zhe Zhang', github: 'zhangzhe' }, + zhengtao: { name: 'Zheng Tao', github: 'NotHimmel' }, + zhenhaopan: { name: 'Zhenhao Pan', github: 'panzhenhao' }, + zhibinwang: { github: 'killerwzb' }, + zhuoyanshi: { name: 'Zhuoyan Shi', github: 'shizhuoyan' }, +}; + +function normalizeContributorId(value) { + return value + .normalize('NFKD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/[^a-zA-Z0-9]/g, '') + .toLowerCase(); +} + +function looksLikeGithubHandle(value) { + return /^[a-z0-9-]+$/.test(value); +} + +function slugBeautyScore(value) { + let score = 0; + if (/[-_]/.test(value)) { + score += 2; + } + if (/[A-Z]/.test(value.slice(1))) { + score += 2; + } + if (!looksLikeGithubHandle(value)) { + score += 1; + } + return score; +} + +function humanizeContributorName(value) { + if (/^[a-z0-9]+$/.test(value)) { + return `@${value}`; + } + + const words = value + .replace(/([a-z])([A-Z])/g, '$1 $2') + .replace(/[-_]+/g, ' ') + .trim() + .split(/\s+/); + + return words + .map((word) => { + if (!word) { + return word; + } + if (word === word.toUpperCase()) { + return word; + } + if (/^[a-z0-9]+$/.test(word)) { + return `${word.charAt(0).toUpperCase()}${word.slice(1)}`; + } + return word; + }) + .join(' '); +} + +const contributorMap = new Map(); + +Object.entries(CONTRIBUTORS_BY_YEAR).forEach(([year, slugs]) => { + slugs.forEach((slug) => { + const normalizedId = normalizeContributorId(slug); + const canonicalId = CONTRIBUTOR_ALIASES[normalizedId] || normalizedId; + const existing = contributorMap.get(canonicalId) || { + id: canonicalId, + displaySlug: slug, + github: null, + years: new Set(), + slugScore: slugBeautyScore(slug), + }; + + const nextScore = slugBeautyScore(slug); + if (nextScore > existing.slugScore) { + existing.displaySlug = slug; + existing.slugScore = nextScore; + } + + if (looksLikeGithubHandle(slug) && !existing.github) { + existing.github = slug; + } + + existing.years.add(Number(year)); + contributorMap.set(canonicalId, existing); + }); +}); + +export const contributors = Array.from(contributorMap.values()) + .map((item) => { + const override = CONTRIBUTOR_OVERRIDES[item.id] || {}; + const years = Array.from(item.years).sort((a, b) => b - a); + + return { + id: item.id, + name: override.name || humanizeContributorName(item.displaySlug), + github: override.github || item.github || null, + avatarSrc: override.avatarSrc || null, + avatarMode: override.avatarMode || null, + years, + latestYear: years[0], + }; + }) + .sort((left, right) => { + if (right.latestYear !== left.latestYear) { + return right.latestYear - left.latestYear; + } + + const leftName = left.name.replace(/^@/, '').toLowerCase(); + const rightName = right.name.replace(/^@/, '').toLowerCase(); + return leftName.localeCompare(rightName); + }); + +export const contributorYears = Object.keys(CONTRIBUTORS_BY_YEAR) + .map(Number) + .sort((a, b) => b - a); diff --git a/src/pages/community-page.md b/src/pages/community-page.md index f1b9a788..50b1374a 100644 --- a/src/pages/community-page.md +++ b/src/pages/community-page.md @@ -19,6 +19,9 @@ Read our [**contribution guidelines**](https://github.com/IvorySQL/IvorySQL/blob - [Report a Bug](https://github.com/IvorySQL/IvorySQL/issues/new/choose) - [Submit a pull request](https://github.com/IvorySQL/IvorySQL/pulls) +## Contributors Wall +Meet the people helping IvorySQL grow on our [Contributors Wall](/contributors). + ## Conversations Participate in IvorySQL community discussions through the following channels: diff --git a/src/pages/contributors.js b/src/pages/contributors.js new file mode 100644 index 00000000..ad170f46 --- /dev/null +++ b/src/pages/contributors.js @@ -0,0 +1,247 @@ +import clsx from 'clsx'; +import Link from '@docusaurus/Link'; +import Layout from '@theme/Layout'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import React, { useDeferredValue, useMemo, useState } from 'react'; +import { contributorYears, contributors } from '../data/contributors'; +import styles from './contributors.module.css'; + +const CERTIFICATE_REPO_URL = 'https://github.com/IvorySQL/community'; + +const COPY = { + en: { + pageTitle: 'Contributors Wall', + pageDescription: + 'A wall highlighting the people who contribute to IvorySQL.', + heroTitle: 'IvorySQL Contributors Wall', + heroDescription: + 'This page highlights contributors to IvorySQL. They help move the project forward by submitting code, opening issues, fixing bugs, improving documentation, participating in testing, and growing the community.', + heroMeta: { + contributors: 'contributors', + years: 'included', + category: 'Category: Contributor', + }, + filters: { + all: 'All Contributors', + searchPlaceholder: 'Search by name or GitHub handle', + }, + sectionTitle: 'Community Contributors', + resultLabel: 'contributors shown', + noGithub: 'Community contributor', + empty: 'No contributors matched the current filter.', + ctaText: + 'Want to appear here? Start by joining the IvorySQL contribution flow.', + certificateText: + 'Contributor certificates are available in the IvorySQL community repository.', + certificateLink: 'View or claim your contributor certificate', + ctaButton: 'View Contribution Guidelines', + }, + zh: { + pageTitle: '贡献者墙', + pageDescription: '展示 IvorySQL 贡献者的页面。', + heroTitle: 'IvorySQL 贡献者墙', + heroDescription: + '本页面展示的是 IvorySQL 的贡献者。他们通过提交代码、提交 Issue、修复 Bug、完善文档、参与测试和推广社区等方式,为 IvorySQL 的发展持续做出贡献。', + heroMeta: { + contributors: '位贡献者', + years: '收录年份', + category: '当前类别:Contributor', + }, + filters: { + all: '全部贡献者', + searchPlaceholder: '按姓名或 GitHub handle 搜索', + }, + sectionTitle: '社区贡献者', + resultLabel: '位贡献者', + noGithub: '社区贡献者', + empty: '当前筛选条件下没有匹配的贡献者。', + ctaText: + '也想出现在这里?从参与 IvorySQL 社区贡献开始。', + certificateText: + '贡献者证书已上传到 IvorySQL community 仓库,可前往查看并领取。', + certificateLink: '查看并领取贡献者证书', + ctaButton: '查看贡献指南', + }, +}; + +function getToneClass(id) { + const seed = id.split('').reduce((sum, char) => sum + char.charCodeAt(0), 0); + return styles[`tone${seed % 6}`]; +} + +function getMonogram(name) { + const cleaned = name.replace(/^@/, ''); + const parts = cleaned.split(/[\s-]+/).filter(Boolean); + if (parts.length >= 2) { + return `${parts[0][0]}${parts[1][0]}`.toUpperCase(); + } + return cleaned.slice(0, 2).toUpperCase(); +} + +function ContributorCard({ contributor, copy }) { + const toneClass = getToneClass(contributor.id); + const githubUrl = contributor.github ? `https://github.com/${contributor.github}` : null; + const localAvatarUrl = contributor.avatarSrc ? useBaseUrl(contributor.avatarSrc) : null; + const showGithubAvatar = !localAvatarUrl && githubUrl && contributor.avatarMode !== 'monogram'; + const avatarSrc = localAvatarUrl || (showGithubAvatar + ? `https://github.com/${contributor.github}.png?size=240` + : null); + const handleText = githubUrl ? `@${contributor.github}` : ''; + const yearsLabel = contributor.years.join(' · '); + + return ( +
+
+
+ {avatarSrc ? ( + {contributor.name} + ) : ( + {getMonogram(contributor.name)} + )} +
+
+ {githubUrl ? ( + +

{contributor.name}

+
+ ) : ( +

{contributor.name}

+ )} +

{handleText}

+
+
+ +

{yearsLabel}

+
+ ); +} + +export default function ContributorsPage() { + const { i18n } = useDocusaurusContext(); + const isZh = i18n.currentLocale.toLowerCase().startsWith('zh'); + const copy = isZh ? COPY.zh : COPY.en; + const [selectedYear, setSelectedYear] = useState('all'); + const [query, setQuery] = useState(''); + const deferredQuery = useDeferredValue(query.trim().toLowerCase()); + const includedYears = `${contributorYears[contributorYears.length - 1]}-${contributorYears[0]}`; + + const filterOptions = useMemo( + () => [ + { key: 'all', label: copy.filters.all }, + ...contributorYears.map((year) => ({ key: String(year), label: String(year) })), + ], + [copy.filters.all], + ); + + const filteredContributors = useMemo(() => { + return contributors.filter((item) => { + const matchesYear = + selectedYear === 'all' || item.years.includes(Number(selectedYear)); + const matchesQuery = + !deferredQuery || + item.name.toLowerCase().includes(deferredQuery) || + (item.github && item.github.toLowerCase().includes(deferredQuery)); + + return matchesYear && matchesQuery; + }); + }, [deferredQuery, selectedYear]); + + return ( + +
+
+
+
+

{copy.heroTitle}

+

{copy.heroDescription}

+
+ {contributors.length} {copy.heroMeta.contributors} + {includedYears} {copy.heroMeta.years} + {copy.heroMeta.category} +
+
+
+ +
+
+ {filterOptions.map((option) => ( + + ))} +
+ +
+ setQuery(event.target.value)} + placeholder={copy.filters.searchPlaceholder} + aria-label={copy.filters.searchPlaceholder} + /> +
+
+ +
+
+

{copy.sectionTitle}

+

+ {filteredContributors.length} {copy.resultLabel} +

+
+ + {filteredContributors.length ? ( +
+ {filteredContributors.map((contributor) => ( + + ))} +
+ ) : ( +
{copy.empty}
+ )} +
+ +
+
+

{copy.ctaText}

+

+ {copy.certificateText}{' '} + + {copy.certificateLink} + +

+
+ + {copy.ctaButton} + +
+
+
+
+ ); +} diff --git a/src/pages/contributors.module.css b/src/pages/contributors.module.css new file mode 100644 index 00000000..be29d07e --- /dev/null +++ b/src/pages/contributors.module.css @@ -0,0 +1,371 @@ +.page { + --contributors-accent: #2f74ff; + --contributors-accent-soft: rgba(47, 116, 255, 0.12); + --contributors-ink: #111827; + --contributors-muted: #64748b; + --contributors-line: #e5e9f2; + --contributors-panel: rgba(255, 255, 255, 0.92); + --contributors-panel-strong: rgba(248, 250, 255, 0.98); + background: + radial-gradient(circle at top right, rgba(47, 116, 255, 0.1), transparent 24%), + radial-gradient(circle at left 18%, rgba(116, 157, 255, 0.08), transparent 20%), + linear-gradient(180deg, #f4f8ff 0%, #ffffff 24%, #f6f9ff 100%); + min-height: 100vh; +} + +.hero { + position: relative; + overflow: hidden; + padding: 40px; + margin: 36px 0 26px; + border-radius: 30px; + border: 1px solid var(--contributors-line); + background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(246, 249, 255, 0.96) 100%); + box-shadow: 0 24px 60px rgba(30, 41, 59, 0.07); +} + +.hero::before { + content: ''; + position: absolute; + inset: auto -96px -132px auto; + width: 260px; + height: 260px; + border-radius: 50%; + background: radial-gradient(circle, rgba(47, 116, 255, 0.12) 0%, rgba(47, 116, 255, 0.03) 46%, transparent 72%); +} + +.hero::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(90deg, rgba(47, 116, 255, 0.03) 0%, transparent 40%); + pointer-events: none; +} + +.heroContent { + position: relative; + z-index: 1; +} + +.heroTitle { + margin: 0 0 16px; + font-size: clamp(2.2rem, 5vw, 3.6rem); + line-height: 1.08; + color: var(--contributors-ink); +} + +.heroDescription { + max-width: 920px; + margin: 0; + font-size: 1.04rem; + line-height: 1.85; + color: var(--contributors-muted); +} + +.heroMeta { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 18px; + color: var(--contributors-muted); + font-size: 0.92rem; +} + +.heroMeta span { + display: inline-flex; + align-items: center; + padding: 8px 12px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(47, 116, 255, 0.1); +} + +.controls { + display: flex; + flex-wrap: wrap; + gap: 14px; + align-items: center; + justify-content: space-between; + margin-bottom: 24px; + padding: 14px 0 10px; +} + +.chipGroup { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.chip { + border: 1px solid rgba(17, 24, 39, 0.08); + background: rgba(255, 255, 255, 0.92); + color: var(--contributors-muted); + border-radius: 999px; + padding: 9px 14px; + font-size: 0.92rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.chip:hover { + border-color: rgba(47, 116, 255, 0.35); + color: var(--contributors-accent); + background: #fff; +} + +.chipActive { + border-color: transparent; + background: var(--contributors-accent); + color: #fff; + box-shadow: 0 12px 28px rgba(47, 116, 255, 0.2); +} + +.searchWrap { + flex: 1; + min-width: min(100%, 280px); +} + +.searchInput { + width: 100%; + padding: 11px 15px; + border-radius: 14px; + border: 1px solid rgba(47, 116, 255, 0.12); + background: rgba(255, 255, 255, 0.96); + font-size: 0.96rem; + color: var(--contributors-ink); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7); +} + +.searchInput:focus { + outline: none; + border-color: rgba(47, 116, 255, 0.45); + box-shadow: 0 0 0 4px rgba(47, 116, 255, 0.1); +} + +.section { + padding: 6px 0 30px; +} + +.sectionHeader { + display: flex; + flex-wrap: wrap; + gap: 14px; + align-items: center; + justify-content: space-between; + margin-bottom: 18px; +} + +.sectionTitle { + margin: 0; + font-size: clamp(1.55rem, 3vw, 2rem); + color: var(--contributors-ink); +} + +.resultLabel { + margin: 0; + color: var(--contributors-muted); + font-size: 0.95rem; +} + +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 14px; +} + +.card { + display: flex; + flex-direction: column; + gap: 12px; + min-height: 0; + padding: 18px; + border-radius: 20px; + border: 1px solid rgba(47, 116, 255, 0.08); + background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 250, 255, 0.96) 100%); + box-shadow: 0 12px 28px rgba(15, 23, 42, 0.05); + transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease; +} + +.card:hover { + transform: translateY(-3px); + border-color: rgba(47, 116, 255, 0.18); + box-shadow: 0 18px 34px rgba(47, 116, 255, 0.08); +} + +.cardHeader { + display: flex; + align-items: center; + gap: 14px; +} + +.avatar { + width: 58px; + height: 58px; + border-radius: 18px; + overflow: hidden; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-weight: 700; + font-size: 1.15rem; + letter-spacing: 0.04em; +} + +.avatar img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.avatarFallback { + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + white-space: nowrap; + line-height: 1; + font-size: 1rem; + letter-spacing: 0.08em; +} + +.tone0 { background: linear-gradient(135deg, #2f66ff 0%, #6d8cff 100%); } +.tone1 { background: linear-gradient(135deg, #ff845c 0%, #ffb36f 100%); } +.tone2 { background: linear-gradient(135deg, #161f4b 0%, #3e56b0 100%); } +.tone3 { background: linear-gradient(135deg, #00a37a 0%, #2bc3a0 100%); } +.tone4 { background: linear-gradient(135deg, #5f3dc4 0%, #8f5cf6 100%); } +.tone5 { background: linear-gradient(135deg, #935116 0%, #d78f39 100%); } + +.cardTitleBlock { + min-width: 0; +} + +.cardTitle { + margin: 0; + font-size: 1.02rem; + color: var(--contributors-ink); + line-height: 1.35; +} + +.cardTitleLink { + color: inherit; + text-decoration: none; +} + +.cardTitleLink:hover .cardTitle { + color: var(--contributors-accent); +} + +.cardHandle { + margin: 4px 0 0; + color: var(--contributors-muted); + font-size: 0.92rem; + line-height: 1.4; + min-height: 1.4em; +} + +.cardYears { + margin: 2px 0 0; + color: var(--contributors-accent); + font-size: 0.86rem; + font-weight: 700; +} + +.empty { + padding: 34px 26px; + border-radius: 24px; + border: 1px dashed rgba(47, 116, 255, 0.2); + background: rgba(255, 255, 255, 0.82); + color: var(--contributors-muted); + text-align: center; + line-height: 1.8; +} + +.cta { + display: flex; + flex-wrap: wrap; + gap: 12px; + align-items: center; + justify-content: space-between; + margin: 22px 0 46px; + padding-top: 18px; + border-top: 1px solid rgba(47, 116, 255, 0.12); +} + +.ctaContent { + display: flex; + flex-direction: column; + gap: 8px; + max-width: 760px; +} + +.ctaText { + margin: 0; + color: var(--contributors-muted); + line-height: 1.8; +} + +.ctaSubtext { + margin: 0; + color: var(--contributors-muted); + line-height: 1.75; + font-size: 0.95rem; +} + +.ctaInlineLink { + color: var(--contributors-accent); + text-decoration: none; + font-weight: 600; +} + +.ctaInlineLink:hover { + color: #225fe6; + text-decoration: underline; +} + +.ctaButton { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 12px 18px; + border-radius: 999px; + background: var(--contributors-accent); + color: #fff; + text-decoration: none; + font-weight: 700; + box-shadow: 0 12px 26px rgba(47, 116, 255, 0.18); +} + +.ctaButton:hover { + color: #fff; + background: #225fe6; +} + +@media (max-width: 996px) { + .hero { + padding: 28px; + border-radius: 28px; + } + + .controls { + align-items: stretch; + } + + .searchWrap { + width: 100%; + } +} + +@media (max-width: 640px) { + .heroTitle { + font-size: 2.35rem; + } + + .cta { + align-items: flex-start; + } +} diff --git a/static/img/contributors/fawei-zhao.jpeg b/static/img/contributors/fawei-zhao.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..7c29bb38f03be637f1e762c4ae8f41a6b6dff751 GIT binary patch literal 18129 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 literal 0 HcmV?d00001