mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
feat(landing): add Community link + first-party Ambassadors page (#3066)
* feat(landing): add Community link to top nav
Adds a 'Community' entry to the landing-page nav between Blog and the
Star CTA, linking to /community/ which Cloudflare Pages 302-redirects to
the contributors honor-cards page (currently a Vercel deploy).
Translations added for all 18 locales. The nav slot was previously empty
after Blog because Contact had been intentionally pulled from the bar
(left as a footer + #contact anchor).
* fix(landing): use literal /community/ href so non-default locales don't 404
PerishCode review caught that href('/community/') routes through
localizedHref and produces /zh/community/, /ja/community/, etc. The
_redirects rule only matches the literal /community/ and the
[locale]/[...path].astro catch-all does not generate community pages,
so 17 of the 18 translated locales would have hit a Cloudflare 404.
The destination is a single non-locale-aware external page, so skip
the locale prefix entirely — same shape as the GitHub Star and
Download CTAs.
* feat(landing): host community + ambassadors page first-party
Lands the contributors / ambassadors page as a static asset at
apps/landing-page/public/community/index.html, served at /community/
on open-design.ai. Drops the temporary 302 to the Vercel preview URL
(d5458c46-…vercel.app) — that hostname was a deploy-time UUID Vercel
could recycle, which the reviewer correctly flagged as a follow-up.
The page now opens with an Ambassadors section: vocation, patronage,
covenant — three columns of the program in Renaissance-atelier voice,
with a single Apply on Discord CTA pointing at the ambassador channel
(discord.gg/2p7Ajbxw3h). Maintainers / leaderboards / good-first-issues
sit below as before. Header.tsx comment updated to point at the new
source of truth instead of the deleted redirect rule.
* fix(community): drop time-bound claims, tighten bot heuristic, drop dead CORE_TEAM entry
PerishCode review on ff1cd44b flagged three correctness issues with
the static community page. Addressing each:
* The 'This week's signal' / 'This week's leader' / 'Last 7 days' /
'PRs · 7d' framing made promises a frozen RANKING_SNAPSHOT can't
keep — three weeks from build, the page would be calling the
2026-05-26 leaderboard 'this week's leader.' Renamed to time-neutral
copy ('Recent signal', 'A recent leader', 'Snapshot', 'Recent PRs')
and dropped the snapshot's 'since' field so we don't pin a window
we can't honour. Real refresh pipeline is a follow-up.
* 'Showing first N · resets every 30 minutes' didn't describe the code
(no caching of any kind exists; each page load re-hits /search/issues
and /users/:login). Replaced with a truthful 'Showing first N open
good-first-issues.'
* The bot exclusion heuristic used substring match on bot/cursor/agent,
which would silently drop real logins like 'agentina', 'cursorsmith',
'robothai'. Tightened to a whole-token regex (/(?:^|[-_])(bot|cursor|
agent)(?:$|[-_])/) and dropped 'leon wang' from CORE_TEAM — it had
an embedded space, which GitHub logins never do, so the entry was
unreachable dead code.
---------
Co-authored-by: koki yanlai xu <koki@kokideMacBook-Air.local>
This commit is contained in:
parent
e9944e0783
commit
ee1eab77c6
3 changed files with 915 additions and 1 deletions
|
|
@ -45,7 +45,8 @@ export interface HeaderProps {
|
|||
| 'templates'
|
||||
| 'craft'
|
||||
| 'blog'
|
||||
| 'tutorials';
|
||||
| 'tutorials'
|
||||
| 'community';
|
||||
/**
|
||||
* Live counts from the Markdown catalogs. Required so we can never
|
||||
* silently render stale fallback numbers when a caller forgets to
|
||||
|
|
@ -247,6 +248,23 @@ export function Header({
|
|||
{headerCopy.nav.blog}
|
||||
</a>
|
||||
</li>
|
||||
{/*
|
||||
Community is a static contributors / ambassadors page served
|
||||
from `apps/landing-page/public/community/index.html` — Astro
|
||||
copies `public/` verbatim, so this hits Cloudflare Pages as a
|
||||
first-party route at `/community/`.
|
||||
|
||||
The href is the literal `/community/` rather than
|
||||
`href('/community/')` because the page is a single non-
|
||||
locale-aware destination — locale-prefixed variants like
|
||||
`/zh/community/` would fall through to a 404 since the
|
||||
`[locale]/[...path].astro` catch-all does not generate it.
|
||||
*/}
|
||||
<li>
|
||||
<a href='/community/' className={linkClass('community')}>
|
||||
{headerCopy.nav.community}
|
||||
</a>
|
||||
</li>
|
||||
{/*
|
||||
Contact intentionally NOT exposed in the top nav: it's a
|
||||
page-internal anchor (`#contact` on the homepage CTA section)
|
||||
|
|
|
|||
|
|
@ -173,6 +173,8 @@ export interface HeaderCopy {
|
|||
/** Standalone link to the YouTube tutorials channel. */
|
||||
tutorials: string;
|
||||
blog: string;
|
||||
/** External community / contributors page (currently a Vercel deploy). */
|
||||
community: string;
|
||||
contact: string;
|
||||
};
|
||||
download: string;
|
||||
|
|
@ -965,6 +967,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Craft',
|
||||
tutorials: 'Tutorials',
|
||||
blog: 'Blog',
|
||||
community: 'Community',
|
||||
contact: 'Contact',
|
||||
},
|
||||
download: 'Download',
|
||||
|
|
@ -997,6 +1000,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: '工艺',
|
||||
tutorials: '教程',
|
||||
blog: '博客',
|
||||
community: '社区',
|
||||
contact: '联系',
|
||||
},
|
||||
download: '下载',
|
||||
|
|
@ -1029,6 +1033,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: '工藝',
|
||||
tutorials: '教程',
|
||||
blog: '部落格',
|
||||
community: '社群',
|
||||
contact: '聯絡',
|
||||
},
|
||||
download: '下載',
|
||||
|
|
@ -1061,6 +1066,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'クラフト',
|
||||
tutorials: 'チュートリアル',
|
||||
blog: 'ブログ',
|
||||
community: 'コミュニティ',
|
||||
contact: '連絡',
|
||||
},
|
||||
download: 'ダウンロード',
|
||||
|
|
@ -1093,6 +1099,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: '크래프트',
|
||||
tutorials: '튜토리얼',
|
||||
blog: '블로그',
|
||||
community: '커뮤니티',
|
||||
contact: '문의',
|
||||
},
|
||||
download: '다운로드',
|
||||
|
|
@ -1125,6 +1132,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Gestaltung',
|
||||
tutorials: 'Tutorials',
|
||||
blog: 'Blog',
|
||||
community: 'Community',
|
||||
contact: 'Kontakt',
|
||||
},
|
||||
download: 'Download',
|
||||
|
|
@ -1157,6 +1165,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Conception',
|
||||
tutorials: 'Tutoriels',
|
||||
blog: 'Blog',
|
||||
community: 'Communauté',
|
||||
contact: 'Contact',
|
||||
},
|
||||
download: 'Télécharger',
|
||||
|
|
@ -1189,6 +1198,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Правила',
|
||||
tutorials: 'Уроки',
|
||||
blog: 'Блог',
|
||||
community: 'Сообщество',
|
||||
contact: 'Контакт',
|
||||
},
|
||||
download: 'Скачать',
|
||||
|
|
@ -1221,6 +1231,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Oficio',
|
||||
tutorials: 'Tutoriales',
|
||||
blog: 'Blog',
|
||||
community: 'Comunidad',
|
||||
contact: 'Contacto',
|
||||
},
|
||||
download: 'Descargar',
|
||||
|
|
@ -1253,6 +1264,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Ofício',
|
||||
tutorials: 'Tutoriais',
|
||||
blog: 'Blog',
|
||||
community: 'Comunidade',
|
||||
contact: 'Contato',
|
||||
},
|
||||
download: 'Baixar',
|
||||
|
|
@ -1285,6 +1297,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Regole',
|
||||
tutorials: 'Tutorial',
|
||||
blog: 'Blog',
|
||||
community: 'Comunità',
|
||||
contact: 'Contatto',
|
||||
},
|
||||
download: 'Scarica',
|
||||
|
|
@ -1317,6 +1330,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Quy tắc',
|
||||
tutorials: 'Hướng dẫn',
|
||||
blog: 'Blog',
|
||||
community: 'Cộng đồng',
|
||||
contact: 'Liên hệ',
|
||||
},
|
||||
download: 'Tải xuống',
|
||||
|
|
@ -1349,6 +1363,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Reguły',
|
||||
tutorials: 'Samouczki',
|
||||
blog: 'Blog',
|
||||
community: 'Społeczność',
|
||||
contact: 'Kontakt',
|
||||
},
|
||||
download: 'Pobierz',
|
||||
|
|
@ -1381,6 +1396,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Aturan',
|
||||
tutorials: 'Tutorial',
|
||||
blog: 'Blog',
|
||||
community: 'Komunitas',
|
||||
contact: 'Kontak',
|
||||
},
|
||||
download: 'Unduh',
|
||||
|
|
@ -1413,6 +1429,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Regels',
|
||||
tutorials: 'Tutorials',
|
||||
blog: 'Blog',
|
||||
community: 'Community',
|
||||
contact: 'Contact',
|
||||
},
|
||||
download: 'Download',
|
||||
|
|
@ -1445,6 +1462,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'حرفة',
|
||||
tutorials: 'الدروس',
|
||||
blog: 'المدونة',
|
||||
community: 'المجتمع',
|
||||
contact: 'تواصل',
|
||||
},
|
||||
download: 'تنزيل',
|
||||
|
|
@ -1477,6 +1495,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Kurallar',
|
||||
tutorials: 'Eğitimler',
|
||||
blog: 'Blog',
|
||||
community: 'Topluluk',
|
||||
contact: 'İletişim',
|
||||
},
|
||||
download: 'İndir',
|
||||
|
|
@ -1509,6 +1528,7 @@ const COMMON_COPY: Record<LandingLocaleCode, CommonCopy> = {
|
|||
craft: 'Правила',
|
||||
tutorials: 'Туторіали',
|
||||
blog: 'Блог',
|
||||
community: 'Спільнота',
|
||||
contact: 'Контакт',
|
||||
},
|
||||
download: 'Завантажити',
|
||||
|
|
|
|||
876
apps/landing-page/public/community/index.html
Normal file
876
apps/landing-page/public/community/index.html
Normal file
|
|
@ -0,0 +1,876 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#efe7d2" />
|
||||
<title>Contributors — Open Design</title>
|
||||
<link rel="preconnect" href="https://rsms.me/" />
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||
<style>
|
||||
:root{
|
||||
--paper:#efe7d2;
|
||||
--paper-warm:#ece4cf;
|
||||
--paper-dark:#ddd2b6;
|
||||
--bone:#f7f1de;
|
||||
--ink:#15140f;
|
||||
--ink-soft:#2a2620;
|
||||
--ink-mute:#5a5448;
|
||||
--ink-faint:#8b8676;
|
||||
--coral:#ed6f5c;
|
||||
--coral-soft:#f08e7c;
|
||||
--mustard:#e9b94a;
|
||||
--olive:#6e7448;
|
||||
--line:rgba(21,20,15,.16);
|
||||
--line-soft:rgba(21,20,15,.08);
|
||||
--shadow:0 30px 60px -30px rgba(21,20,15,.18);
|
||||
--shadow-card:0 18px 40px -28px rgba(21,20,15,.32);
|
||||
--sans:"Inter Tight", "Inter", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
||||
--body:"Inter", -apple-system, system-ui, sans-serif;
|
||||
--serif:"Playfair Display", "Iowan Old Style", "Times New Roman", serif;
|
||||
--mono:"JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace;
|
||||
}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
html,body{background:var(--paper);color:var(--ink);font:16px/1.55 var(--body);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-feature-settings:"ss01","cv11"}
|
||||
::selection{background:var(--coral);color:var(--bone)}
|
||||
a{color:inherit;text-decoration:none}
|
||||
img{display:block;max-width:100%}
|
||||
|
||||
/* --------- shared ---------- */
|
||||
.wrap{max-width:1280px;margin:0 auto;padding:0 56px}
|
||||
.kicker{font:600 11.5px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;color:var(--ink-mute)}
|
||||
.kicker .dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--coral);margin-right:10px;vertical-align:1px}
|
||||
.h-display{font-family:var(--serif);font-weight:500;line-height:.98;letter-spacing:-.015em;color:var(--ink)}
|
||||
.h-display em{font-style:italic;font-weight:500;color:var(--coral)}
|
||||
.lead{font:400 19px/1.55 var(--body);color:var(--ink-soft);max-width:54ch}
|
||||
.num{font:500 inherit/0.95 var(--mono);font-variant-numeric:tabular-nums;letter-spacing:-.02em}
|
||||
|
||||
.btn{display:inline-flex;align-items:center;gap:10px;padding:14px 22px;border-radius:999px;font:500 14px/1 var(--sans);letter-spacing:.01em;border:1px solid transparent;transition:transform .15s ease,background .15s ease,color .15s ease}
|
||||
.btn svg{width:14px;height:14px}
|
||||
.btn-primary{background:var(--ink);color:var(--bone)}
|
||||
.btn-primary:hover{background:var(--coral);color:var(--bone);transform:translateY(-1px)}
|
||||
.btn-coral{background:var(--coral);color:var(--bone)}
|
||||
.btn-coral:hover{background:var(--ink);color:var(--bone)}
|
||||
.btn-ghost{background:transparent;color:var(--ink);border-color:var(--line)}
|
||||
.btn-ghost:hover{border-color:var(--ink);background:var(--ink);color:var(--bone)}
|
||||
|
||||
/* --------- nav ---------- */
|
||||
.nav{position:sticky;top:0;z-index:50;background:rgba(239,231,210,.86);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border-bottom:1px solid var(--line-soft)}
|
||||
.nav-inner{display:flex;align-items:center;justify-content:space-between;height:64px}
|
||||
.brand{display:flex;align-items:center;gap:10px;font:600 14px/1 var(--sans);letter-spacing:-.01em}
|
||||
.brand-mark{width:22px;height:22px;border-radius:50%;background:var(--ink);display:grid;place-items:center;color:var(--bone);font:700 11px/1 var(--sans)}
|
||||
.brand .sep{color:var(--ink-faint);margin:0 6px;font-weight:400}
|
||||
.brand .crumb{color:var(--ink-mute);font-weight:500}
|
||||
.nav-links{display:flex;gap:28px;align-items:center;font:500 13.5px/1 var(--sans)}
|
||||
.nav-links a{color:var(--ink-soft);transition:color .15s}
|
||||
.nav-links a:hover{color:var(--coral)}
|
||||
.nav-links .pill{display:inline-flex;align-items:center;gap:8px;padding:8px 14px;border-radius:999px;background:var(--ink);color:var(--bone)}
|
||||
.nav-links .pill:hover{background:var(--coral);color:var(--bone)}
|
||||
|
||||
/* --------- hero ---------- */
|
||||
.hero{position:relative;padding:90px 0 110px;overflow:hidden}
|
||||
.hero-grid{display:grid;grid-template-columns:1.45fr .9fr;gap:80px;align-items:center}
|
||||
.hero-copy .kicker{margin-bottom:28px}
|
||||
.hero h1{font-size:clamp(56px, 7.2vw, 104px);margin:14px 0 32px}
|
||||
.hero .lead{font-size:21px;max-width:46ch;margin-bottom:40px}
|
||||
.hero-cta{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
|
||||
.hero-meta{margin-top:54px;display:flex;gap:48px;border-top:1px solid var(--line-soft);padding-top:28px}
|
||||
.hero-meta .item{display:flex;flex-direction:column;gap:4px}
|
||||
.hero-meta .item .v{font:500 28px/1 var(--mono);font-variant-numeric:tabular-nums;letter-spacing:-.02em;color:var(--ink)}
|
||||
.hero-meta .item .l{font:500 11px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;color:var(--ink-faint)}
|
||||
|
||||
.hero-card{position:relative;justify-self:center}
|
||||
.hero-card .card-frame{width:300px;aspect-ratio:9/16;background:var(--bone);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow);overflow:hidden;transform:rotate(2.5deg);transition:transform .35s ease}
|
||||
.hero-card .card-frame:hover{transform:rotate(0deg) translateY(-6px)}
|
||||
.hero-card .card-frame img{width:100%;height:100%;object-fit:cover}
|
||||
.hero-card .card-tag{position:absolute;top:-14px;left:-30px;background:var(--coral);color:var(--bone);font:600 10.5px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;padding:8px 14px;border-radius:6px;transform:rotate(-4deg);box-shadow:var(--shadow-card)}
|
||||
.hero-card .card-meta{position:absolute;bottom:-22px;right:-20px;background:var(--ink);color:var(--bone);padding:14px 18px;border-radius:10px;display:flex;flex-direction:column;gap:2px;box-shadow:var(--shadow-card);transform:rotate(-2deg)}
|
||||
.hero-card .card-meta .h{font:600 13px/1.1 var(--sans)}
|
||||
.hero-card .card-meta .s{font:500 10px/1 var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--ink-faint)}
|
||||
|
||||
.hero-decor{position:absolute;right:-160px;top:60px;width:540px;height:540px;border-radius:50%;background:radial-gradient(circle at 30% 30%, rgba(237,111,92,.18), transparent 65%);pointer-events:none;z-index:0}
|
||||
.hero-grid > *{position:relative;z-index:1}
|
||||
|
||||
/* --------- section header pattern ---------- */
|
||||
.section{padding:96px 0}
|
||||
.section-head{display:grid;grid-template-columns:1fr auto;align-items:end;gap:40px;margin-bottom:56px;padding-bottom:24px;border-bottom:1px solid var(--line)}
|
||||
.section-head h2{font-size:clamp(40px,5vw,68px);max-width:18ch}
|
||||
.section-head .right{font:400 15px/1.55 var(--body);color:var(--ink-mute);max-width:38ch;text-align:right;padding-bottom:12px}
|
||||
.section-head .kicker{margin-bottom:20px;display:block}
|
||||
|
||||
/* --------- recent signal ---------- */
|
||||
.signal{background:linear-gradient(180deg, var(--paper) 0%, var(--paper-warm) 100%)}
|
||||
.signal-grid{display:grid;grid-template-columns:1.1fr 1fr;gap:48px}
|
||||
.signal-feature{background:var(--ink);color:var(--bone);border-radius:18px;padding:42px;position:relative;overflow:hidden;display:flex;flex-direction:column;justify-content:space-between;min-height:520px}
|
||||
.signal-feature::before{content:"";position:absolute;right:-120px;bottom:-160px;width:380px;height:380px;border-radius:50%;background:radial-gradient(circle, var(--coral) 0%, transparent 70%);opacity:.45}
|
||||
.signal-feature .top{display:flex;justify-content:space-between;align-items:flex-start;position:relative;z-index:2}
|
||||
.signal-feature .rank{font:500 13px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;color:var(--coral);display:flex;align-items:center;gap:10px}
|
||||
.signal-feature .rank .badge{background:var(--coral);color:var(--ink);padding:5px 9px;border-radius:5px;font-weight:700;letter-spacing:.1em}
|
||||
.signal-feature .week{font:500 12px/1 var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--ink-faint)}
|
||||
.signal-feature .body{position:relative;z-index:2;margin-top:48px}
|
||||
.signal-feature .avatar{width:84px;height:84px;border-radius:50%;border:2px solid var(--coral);overflow:hidden;margin-bottom:24px}
|
||||
.signal-feature .avatar img{width:100%;height:100%;object-fit:cover}
|
||||
.signal-feature .name{font:500 38px/1.05 var(--serif);letter-spacing:-.01em;margin-bottom:6px}
|
||||
.signal-feature .handle{font:500 13px/1 var(--mono);color:var(--ink-faint);margin-bottom:30px}
|
||||
.signal-feature .quote{font-family:var(--serif);font-style:italic;font-size:22px;line-height:1.4;color:var(--paper);max-width:24ch;margin-bottom:34px;border-left:2px solid var(--coral);padding-left:18px}
|
||||
.signal-feature .stats{padding:0;margin-top:auto}
|
||||
.signal-feature .feature-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;border-top:1px solid rgba(247,241,222,.12);padding-top:26px;position:relative;z-index:2}
|
||||
.signal-feature .feature-stats .item .v{font:500 36px/1 var(--mono);font-variant-numeric:tabular-nums;letter-spacing:-.03em;color:var(--bone)}
|
||||
.signal-feature .feature-stats .item .l{font:500 10.5px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;color:var(--ink-faint);margin-top:8px}
|
||||
.signal-feature .feature-stats .item .v.coral{color:var(--coral)}
|
||||
.signal.alltime{background:var(--paper)}
|
||||
.signal-feature.alltime{background:linear-gradient(135deg, var(--ink) 0%, #2b2924 100%)}
|
||||
.signal-feature.alltime::before{background:radial-gradient(circle, var(--coral) 0%, transparent 70%);opacity:.32}
|
||||
.signal-feature.alltime .feature-stats{grid-template-columns:repeat(2,1fr)}
|
||||
|
||||
.leaderboard{display:flex;flex-direction:column}
|
||||
.leaderboard-head{display:grid;grid-template-columns:40px 1fr 80px 80px 60px;gap:16px;padding:12px 0;border-bottom:1px solid var(--line);font:500 10.5px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;color:var(--ink-faint)}
|
||||
.leaderboard-head span:nth-child(3),.leaderboard-head span:nth-child(4){text-align:right}
|
||||
.row{display:grid;grid-template-columns:40px 1fr 80px 80px 60px;gap:16px;align-items:center;padding:14px 0;border-bottom:1px solid var(--line-soft);transition:background .15s}
|
||||
.row:hover{background:var(--bone)}
|
||||
.row .rk{font:500 13px/1 var(--mono);color:var(--ink-faint);font-variant-numeric:tabular-nums}
|
||||
.row .who{display:flex;align-items:center;gap:14px}
|
||||
.row .who img{width:36px;height:36px;border-radius:50%;background:var(--paper-dark)}
|
||||
.row .who .n{font:500 15px/1.2 var(--sans);color:var(--ink)}
|
||||
.row .who .h{font:400 12px/1 var(--mono);color:var(--ink-faint);margin-top:3px}
|
||||
.row .v{text-align:right;font:500 16px/1 var(--mono);font-variant-numeric:tabular-nums;color:var(--ink)}
|
||||
.row .v.coral{color:var(--coral)}
|
||||
.row .arr{text-align:right;color:var(--ink-faint);transition:color .15s,transform .15s}
|
||||
.row:hover .arr{color:var(--coral);transform:translateX(2px)}
|
||||
|
||||
/* --------- good first issues ---------- */
|
||||
.issues{}
|
||||
.issue-list{display:flex;flex-direction:column;border-top:1px solid var(--line)}
|
||||
.issue{display:grid;grid-template-columns:64px 1fr auto auto;gap:24px;align-items:center;padding:26px 0;border-bottom:1px solid var(--line-soft);transition:padding .2s ease}
|
||||
.issue:hover{padding-left:8px}
|
||||
.issue:hover .title{color:var(--coral)}
|
||||
.issue .num{font:500 28px/1 var(--mono);color:var(--ink-faint);font-variant-numeric:tabular-nums;letter-spacing:-.02em}
|
||||
.issue .body{display:flex;flex-direction:column;gap:8px}
|
||||
.issue .title{font:500 22px/1.25 var(--serif);color:var(--ink);transition:color .15s;letter-spacing:-.005em}
|
||||
.issue .meta{display:flex;gap:8px;flex-wrap:wrap}
|
||||
.label{display:inline-flex;align-items:center;gap:6px;padding:5px 10px;border-radius:999px;font:500 11.5px/1 var(--mono);letter-spacing:.04em;background:var(--bone);border:1px solid var(--line-soft);color:var(--ink-mute)}
|
||||
.label.good{background:#fff7e2;color:#7a5a00;border-color:rgba(122,90,0,.18)}
|
||||
.label.docs{background:#e6f1e7;color:var(--olive);border-color:rgba(110,116,72,.25)}
|
||||
.label.bug{background:#fde2dd;color:#a23a28;border-color:rgba(162,58,40,.2)}
|
||||
.label.design{background:#f1e8fa;color:#5b3a8a;border-color:rgba(91,58,138,.2)}
|
||||
.label.lang{background:transparent;border-color:var(--line);color:var(--ink-mute)}
|
||||
.issue .lang{font:500 12px/1 var(--mono);color:var(--ink-faint);text-align:right;min-width:80px}
|
||||
.issue .arr{color:var(--ink-faint);transition:color .15s,transform .15s}
|
||||
.issue:hover .arr{color:var(--coral);transform:translateX(3px)}
|
||||
.issues-foot{margin-top:36px;display:flex;justify-content:space-between;align-items:center;font:500 13px/1 var(--mono);color:var(--ink-mute)}
|
||||
|
||||
/* --------- onboarding steps ---------- */
|
||||
.onboard{background:var(--ink);color:var(--paper);position:relative;overflow:hidden}
|
||||
.onboard::before{content:"";position:absolute;top:-200px;left:-200px;width:500px;height:500px;border-radius:50%;background:radial-gradient(circle, rgba(237,111,92,.22) 0%, transparent 70%);pointer-events:none}
|
||||
.onboard .section-head{border-bottom-color:rgba(247,241,222,.14)}
|
||||
.onboard .section-head h2{color:var(--paper)}
|
||||
.onboard .section-head h2 em{color:var(--coral)}
|
||||
.onboard .section-head .right{color:var(--ink-faint)}
|
||||
.onboard .kicker{color:var(--coral)}
|
||||
.steps{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border-top:1px solid rgba(247,241,222,.1);border-bottom:1px solid rgba(247,241,222,.1)}
|
||||
.step{padding:42px 32px 48px;border-right:1px solid rgba(247,241,222,.1);position:relative}
|
||||
.step:last-child{border-right:0}
|
||||
.step .n{font:500 12px/1 var(--mono);letter-spacing:.22em;color:var(--coral);margin-bottom:18px}
|
||||
.step h3{font:500 30px/1.1 var(--serif);letter-spacing:-.005em;margin-bottom:14px;color:var(--paper)}
|
||||
.step h3 em{color:var(--coral);font-style:italic}
|
||||
.step p{font:400 14.5px/1.55 var(--body);color:var(--ink-faint);margin-bottom:22px}
|
||||
.step .ic{width:40px;height:40px;border-radius:50%;border:1px solid rgba(247,241,222,.2);display:grid;place-items:center;margin-bottom:24px;color:var(--coral)}
|
||||
.step .ic svg{width:18px;height:18px}
|
||||
.onboard-foot{margin-top:48px;display:flex;justify-content:center;gap:14px}
|
||||
|
||||
/* --------- maintainers ---------- */
|
||||
.maintainers-grid{display:grid;grid-template-columns:repeat(2,minmax(320px,1fr));gap:24px;width:100%}
|
||||
.m-card{background:var(--bone);border:1px solid var(--line-soft);border-radius:14px;padding:28px;display:flex;flex-direction:column;gap:18px;transition:transform .2s,box-shadow .2s}
|
||||
.m-card:hover{transform:translateY(-4px);box-shadow:var(--shadow)}
|
||||
.m-card .av{width:64px;height:64px;border-radius:50%;overflow:hidden;background:var(--paper-dark)}
|
||||
.m-card .av img{width:100%;height:100%;object-fit:cover}
|
||||
.m-card .n{font:500 19px/1.2 var(--serif);letter-spacing:-.005em}
|
||||
.m-card .role{font:500 11.5px/1 var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--coral);margin-top:6px}
|
||||
.m-card .bio{font:400 13.5px/1.5 var(--body);color:var(--ink-mute);min-height:46px}
|
||||
.m-card .links{display:flex;gap:14px;font:500 12.5px/1 var(--mono);color:var(--ink-faint);padding-top:18px;border-top:1px solid var(--line-soft)}
|
||||
.m-card .links a:hover{color:var(--coral)}
|
||||
.m-card .links svg{width:13px;height:13px;vertical-align:-2px;margin-right:5px}
|
||||
|
||||
/* --------- ambassadors ---------- */
|
||||
.ambassadors{background:linear-gradient(180deg, var(--paper-warm) 0%, var(--paper) 100%)}
|
||||
.amb-tagline{font-family:var(--serif);font-style:italic;font-weight:500;font-size:clamp(22px,2.4vw,30px);line-height:1.35;color:var(--ink-soft);max-width:34ch;margin:8px 0 0}
|
||||
.amb-tagline em{color:var(--coral);font-style:italic}
|
||||
.amb-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:0;border-top:1px solid var(--line);border-bottom:1px solid var(--line);background:var(--bone)}
|
||||
.amb-col{padding:42px 36px 48px;border-right:1px solid var(--line-soft);position:relative;display:flex;flex-direction:column}
|
||||
.amb-col:last-child{border-right:0}
|
||||
.amb-col .n{font:500 12px/1 var(--mono);letter-spacing:.22em;color:var(--coral);margin-bottom:18px;text-transform:uppercase}
|
||||
.amb-col h3{font:500 30px/1.1 var(--serif);letter-spacing:-.005em;margin-bottom:14px;color:var(--ink)}
|
||||
.amb-col h3 em{color:var(--coral);font-style:italic}
|
||||
.amb-col .lede{font:400 14.5px/1.55 var(--body);color:var(--ink-mute);margin-bottom:22px}
|
||||
.amb-col ul{list-style:none;display:flex;flex-direction:column;gap:14px}
|
||||
.amb-col li{display:grid;grid-template-columns:22px 1fr;gap:12px;align-items:start;font:400 14px/1.5 var(--body);color:var(--ink-soft)}
|
||||
.amb-col li .ic{width:22px;height:22px;border-radius:50%;background:var(--paper-warm);display:grid;place-items:center;color:var(--coral);font:600 11px/1 var(--mono);margin-top:2px}
|
||||
.amb-col li b{font-weight:600;color:var(--ink)}
|
||||
.amb-foot{margin-top:48px;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:20px}
|
||||
.amb-foot .note{font:500 12.5px/1.5 var(--mono);color:var(--ink-faint);max-width:46ch;letter-spacing:.04em}
|
||||
.amb-foot .cta{display:flex;gap:12px;flex-wrap:wrap}
|
||||
.amb-more{margin-top:36px;border-top:1px solid var(--line-soft);padding-top:28px}
|
||||
.amb-more summary{cursor:pointer;font:500 12.5px/1 var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--ink-mute);list-style:none;display:inline-flex;align-items:center;gap:8px;padding:10px 16px;border-radius:999px;border:1px solid var(--line);background:var(--bone);transition:color .15s,border-color .15s}
|
||||
.amb-more summary::-webkit-details-marker{display:none}
|
||||
.amb-more summary::after{content:"+";font-weight:600;color:var(--coral)}
|
||||
.amb-more[open] summary::after{content:"–"}
|
||||
.amb-more summary:hover{color:var(--ink);border-color:var(--ink)}
|
||||
.amb-more-grid{display:grid;grid-template-columns:1fr 1fr;gap:48px;margin-top:28px}
|
||||
.amb-more-grid h4{font:500 11.5px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;color:var(--coral);margin-bottom:16px}
|
||||
.amb-more-grid ul{list-style:disc;padding-left:18px;display:flex;flex-direction:column;gap:8px;font:400 13.5px/1.55 var(--body);color:var(--ink-mute)}
|
||||
.amb-side{display:flex;flex-direction:column;align-items:flex-end;gap:20px;text-align:right}
|
||||
.amb-side .amb-apply{font:600 14px/1 var(--sans);padding:16px 28px;letter-spacing:.01em;box-shadow:var(--shadow-card)}
|
||||
.amb-side .amb-apply:hover{transform:translateY(-2px)}
|
||||
.amb-side p{font:400 15px/1.55 var(--body);color:var(--ink-mute);max-width:38ch;margin:0}
|
||||
|
||||
/* --------- discord cta ---------- */
|
||||
.discord{padding:120px 0}
|
||||
.discord-card{background:var(--coral);color:var(--ink);border-radius:24px;padding:88px 72px;display:grid;grid-template-columns:1.4fr .8fr;gap:64px;align-items:center;position:relative;overflow:hidden;box-shadow:var(--shadow)}
|
||||
.discord-card::after{content:"";position:absolute;top:-80px;right:-100px;width:340px;height:340px;border-radius:50%;background:radial-gradient(circle, rgba(247,241,222,.32) 0%, transparent 70%);pointer-events:none}
|
||||
.discord-card .kicker{color:var(--ink-soft)}
|
||||
.discord-card .kicker .dot{background:var(--ink)}
|
||||
.discord-card h2{font-size:clamp(44px,5vw,68px);font-family:var(--serif);font-weight:500;line-height:1;letter-spacing:-.015em;margin:18px 0 22px}
|
||||
.discord-card h2 em{font-style:italic}
|
||||
.discord-card p{font:400 17px/1.55 var(--body);color:var(--ink-soft);max-width:42ch;margin-bottom:34px}
|
||||
.discord-card .btn-primary{background:var(--ink);color:var(--coral)}
|
||||
.discord-card .btn-primary:hover{background:var(--bone);color:var(--ink)}
|
||||
.discord-card .btn-ghost{border-color:var(--ink);color:var(--ink)}
|
||||
.discord-card .btn-ghost:hover{background:var(--ink);color:var(--coral)}
|
||||
.discord-side{position:relative;z-index:2}
|
||||
.discord-side .pop{font:500 12px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;color:var(--ink-soft);margin-bottom:22px}
|
||||
.discord-side .stack{background:var(--ink);color:var(--bone);border-radius:14px;padding:22px}
|
||||
.discord-side .stack .row-d{display:flex;align-items:center;gap:14px;padding:8px 0;font:500 13.5px/1 var(--sans)}
|
||||
.discord-side .stack .row-d .dot-g{width:8px;height:8px;border-radius:50%;background:var(--coral)}
|
||||
.discord-side .stack .row-d .h{font:400 11px/1 var(--mono);color:var(--ink-faint);margin-left:auto;text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.55}}
|
||||
|
||||
/* --------- footer ---------- */
|
||||
.foot{padding:56px 0 64px;border-top:1px solid var(--line-soft);font:400 13px/1.5 var(--body);color:var(--ink-mute)}
|
||||
.foot-inner{display:flex;justify-content:space-between;flex-wrap:wrap;gap:24px}
|
||||
.foot a:hover{color:var(--coral)}
|
||||
.foot .l{display:flex;gap:24px}
|
||||
|
||||
/* --------- responsive softening (desktop-first per brief) ---------- */
|
||||
@media (max-width:1100px){
|
||||
.wrap{padding:0 32px}
|
||||
.hero-grid,.signal-grid,.discord-card{grid-template-columns:1fr;gap:48px}
|
||||
.steps,.maintainers-grid{grid-template-columns:repeat(2,1fr);row-gap:56px}
|
||||
.step:nth-child(2){border-right:0}
|
||||
.section-head{grid-template-columns:1fr}
|
||||
.section-head .right{text-align:left}
|
||||
.amb-grid{grid-template-columns:1fr}
|
||||
.amb-col{border-right:0;border-bottom:1px solid var(--line-soft)}
|
||||
.amb-col:last-child{border-bottom:0}
|
||||
.amb-more-grid{grid-template-columns:1fr;gap:32px}
|
||||
.amb-side{align-items:flex-start;text-align:left}
|
||||
}
|
||||
@media (max-width:640px){
|
||||
.wrap{padding:0 20px}
|
||||
.nav-links a:not(.pill){display:none}
|
||||
.nav-inner{height:58px}
|
||||
.hero{padding:64px 0 78px}
|
||||
.hero h1{font-size:54px}
|
||||
.hero-card .card-frame{width:min(280px, 82vw)}
|
||||
.section,.discord{padding:72px 0}
|
||||
.section-head{margin-bottom:36px}
|
||||
.steps,.maintainers-grid{grid-template-columns:1fr;row-gap:48px}
|
||||
.step{border-right:0;padding:0 !important}
|
||||
.leaderboard-head,.row{grid-template-columns:32px 1fr auto}
|
||||
.leaderboard-head span:nth-child(3),.leaderboard-head span:nth-child(4),.row .v:not(.coral),.row .arr{display:none}
|
||||
.amb-col{padding:32px 24px 36px}
|
||||
}
|
||||
|
||||
/* loading skeletons */
|
||||
.skel{background:linear-gradient(90deg, var(--paper-dark) 0%, var(--paper-warm) 50%, var(--paper-dark) 100%);background-size:200% 100%;animation:shimmer 1.5s infinite}
|
||||
@keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============ NAV ============ -->
|
||||
<nav class="nav">
|
||||
<div class="wrap nav-inner">
|
||||
<a class="brand" href="https://open-design.ai/">
|
||||
<span class="brand-mark">O</span>
|
||||
Open Design
|
||||
<span class="sep">/</span>
|
||||
<span class="crumb">Contributors</span>
|
||||
</a>
|
||||
<div class="nav-links">
|
||||
<a href="#ambassadors">Ambassadors</a>
|
||||
<a href="https://github.com/nexu-io/open-design">GitHub</a>
|
||||
<a href="https://open-design.ai/skills">Skills</a>
|
||||
<a href="https://open-design.ai/design-systems">Design systems</a>
|
||||
<a class="pill" href="#discord">
|
||||
<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M19.27 5.33A18 18 0 0 0 14.72 4l-.2.4a13.7 13.7 0 0 0-5.04 0L9.27 4a18 18 0 0 0-4.54 1.33C2.4 8.94 1.78 12.45 2.09 15.9a18.4 18.4 0 0 0 5.6 2.83l1.13-1.55a11.6 11.6 0 0 1-1.78-.86l.44-.34a13 13 0 0 0 11.04 0l.44.34c-.55.33-1.16.61-1.78.86l1.13 1.55a18.3 18.3 0 0 0 5.6-2.83c.45-4.05-.5-7.53-2.64-10.57ZM9.5 14.07c-1.07 0-1.95-.99-1.95-2.21 0-1.22.86-2.22 1.95-2.22 1.1 0 1.97 1 1.95 2.22 0 1.22-.86 2.21-1.95 2.21Zm5 0c-1.07 0-1.95-.99-1.95-2.21 0-1.22.87-2.22 1.96-2.22 1.1 0 1.96 1 1.95 2.22 0 1.22-.86 2.21-1.96 2.21Z"/></svg>
|
||||
Join Discord
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- ============ HERO ============ -->
|
||||
<section class="hero">
|
||||
<div class="hero-decor"></div>
|
||||
<div class="wrap hero-grid">
|
||||
<div class="hero-copy">
|
||||
<span class="kicker"><span class="dot"></span>Contributors · <span class="num">2026 cycle</span></span>
|
||||
<h1 class="h-display">Open design <em>takes shape</em><br/>when you ship it.</h1>
|
||||
<p class="lead">Open Design is built by people, in public. Skills, DESIGN.md systems, plugins, docs — every commit is a brushstroke. Pick an issue, send a PR, and earn a one-of-one honor card the moment you're merged.</p>
|
||||
<div class="hero-cta">
|
||||
<a class="btn btn-primary" href="#issues">
|
||||
Pick a first issue
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||||
</a>
|
||||
<a class="btn btn-ghost" href="#how">How contributing works</a>
|
||||
<a class="btn btn-coral" href="#discord">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19.27 5.33A18 18 0 0 0 14.72 4l-.2.4a13.7 13.7 0 0 0-5.04 0L9.27 4a18 18 0 0 0-4.54 1.33C2.4 8.94 1.78 12.45 2.09 15.9a18.4 18.4 0 0 0 5.6 2.83l1.13-1.55a11.6 11.6 0 0 1-1.78-.86l.44-.34a13 13 0 0 0 11.04 0l.44.34c-.55.33-1.16.61-1.78.86l1.13 1.55a18.3 18.3 0 0 0 5.6-2.83c.45-4.05-.5-7.53-2.64-10.57Z"/></svg>
|
||||
Join the Discord
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-card">
|
||||
<span class="card-tag">Honor card · Giotto tier</span>
|
||||
<div class="card-frame">
|
||||
<img src="https://raw.githubusercontent.com/nexu-io/open-design/bot-cards/data/cards/dev-kp-eloper-signal-2026-05-26T03-19-40-361Z.png" alt="Open Design contributor honor card — @dev-kp-eloper, top 99.9%, Giotto tier" loading="eager"/>
|
||||
</div>
|
||||
<div class="card-meta">
|
||||
<span class="h">Auto-minted on first merge</span>
|
||||
<span class="s">PNG · shared on X</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- ============ AMBASSADORS ============ -->
|
||||
<section class="section ambassadors" id="ambassadors">
|
||||
<div class="wrap">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<span class="kicker"><span class="dot"></span>Open Design Ambassadors</span>
|
||||
<h2 class="h-display">Be Open Design's <em>voice</em> in your city.</h2>
|
||||
<p class="amb-tagline">Open a local atelier. Convene the meetups, the demos, the late-night critiques — the studio carries the work with budget, materials, and a line straight to the team.</p>
|
||||
</div>
|
||||
<div class="right amb-side">
|
||||
<a class="btn btn-coral amb-apply" href="https://discord.gg/2p7Ajbxw3h" target="_blank" rel="noopener">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19.27 5.33A18 18 0 0 0 14.72 4l-.2.4a13.7 13.7 0 0 0-5.04 0L9.27 4a18 18 0 0 0-4.54 1.33C2.4 8.94 1.78 12.45 2.09 15.9a18.4 18.4 0 0 0 5.6 2.83l1.13-1.55a11.6 11.6 0 0 1-1.78-.86l.44-.34a13 13 0 0 0 11.04 0l.44.34c-.55.33-1.16.61-1.78.86l1.13 1.55a18.3 18.3 0 0 0 5.6-2.83c.45-4.05-.5-7.53-2.64-10.57Z"/></svg>
|
||||
Apply on Discord
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||||
</a>
|
||||
<p>Ambassadors turn Open Design from a repository into something contributors can meet — in a room, with ink on the table and coffee gone cold.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="amb-grid">
|
||||
<div class="amb-col">
|
||||
<div class="n">I · Vocation</div>
|
||||
<h3>Painters of <em>the local scene</em>.</h3>
|
||||
<p class="lede">Designers, developers, organizers — the kind who already gather others. We give the gathering a flag.</p>
|
||||
<ul>
|
||||
<li><span class="ic">·</span><span><b>Local Atelier Host</b> — you keep a recurring meetup, study group, or late-night hack alive.</span></li>
|
||||
<li><span class="ic">·</span><span><b>Online community lead</b> — Discord, WeChat, Telegram, X spaces.</span></li>
|
||||
<li><span class="ic">·</span><span><b>Practising contributor or evangelist</b> — already shipping work, posting craft, ushering newcomers.</span></li>
|
||||
<li><span class="ic">·</span><span><b>Comfortable carrying the name</b> — bound to the Code of Conduct, mindful of the brand.</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="amb-col">
|
||||
<div class="n">II · Patronage</div>
|
||||
<h3>What the <em>atelier</em> extends.</h3>
|
||||
<p class="lede">Not a volunteer badge. A working bond — with budget, standing, and access.</p>
|
||||
<ul>
|
||||
<li><span class="ic">·</span><span><b>A page on the site</b> — portrait, city, biography, socials, the chronicle of your events.</span></li>
|
||||
<li><span class="ic">·</span><span><b>First sight</b> — beta features, internal roadmap previews, releases ahead of the queue.</span></li>
|
||||
<li><span class="ic">·</span><span><b>The atelier kit</b> — posters, slide decks, demo pieces, swag; a purse for venue, drinks, and photography.</span></li>
|
||||
<li><span class="ic">·</span><span><b>A line to the studio</b> — private channel, monthly sync, a dedicated path for your feedback.</span></li>
|
||||
<li><span class="ic">·</span><span><b>A way forward</b> — honor cards and tiers, with a path into regional lead, speaker, or paid community roles.</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="amb-col">
|
||||
<div class="n">III · Covenant</div>
|
||||
<h3>The <em>discipline</em> of the studio.</h3>
|
||||
<p class="lede">A modest commitment, but binding. Extended absence folds into alumni status — the circle stays small and serious.</p>
|
||||
<ul>
|
||||
<li><span class="ic">·</span><span><b>Convene</b> at least one event per month or quarter — local or online.</span></li>
|
||||
<li><span class="ic">·</span><span><b>Welcome the new hand</b> — usher newcomers through their first contribution.</span></li>
|
||||
<li><span class="ic">·</span><span><b>Listen close</b> — gather honest feedback from users, designers, developers, teams.</span></li>
|
||||
<li><span class="ic">·</span><span><b>Leave a record</b> — publish a recap after every gathering: attendance, photographs, links, leads.</span></li>
|
||||
<li><span class="ic">·</span><span><b>Carry the name well</b> — hold to the Code of Conduct; no misuse of the mark, no deals signed on the studio's behalf.</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ MAINTAINERS ============ -->
|
||||
<section class="section">
|
||||
<div class="wrap">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<span class="kicker"><span class="dot"></span>Steering the ship</span>
|
||||
<h2 class="h-display">The <em>maintainers</em>.</h2>
|
||||
</div>
|
||||
<p class="right">Maintainers protect the direction and quality of Open Design: they review contributions, keep the standard coherent, and make room for more contributors to earn their place in the project.</p>
|
||||
</div>
|
||||
|
||||
<div class="maintainers-grid" id="maintainers-grid">
|
||||
<article class="m-card">
|
||||
<div class="av"><img src="https://github.com/Nagendhra-web.png" onerror="this.style.background='var(--ink)';this.removeAttribute('src')" alt=""/></div>
|
||||
<div>
|
||||
<div class="n" id="m-1-name">Nagendhra-web</div>
|
||||
<div class="role" id="m-1-role">Maintainer</div>
|
||||
</div>
|
||||
<p class="bio" id="m-1-bio">Nagendhra brings a data engineer's instinct for production truth: find the failure, measure the edge case, and fix it properly. In Open Design, that shows up in deploy preflight work, asset-bundling hardening, and Windows fixes that make the project feel trustworthy when contributors ship.</p>
|
||||
<div class="links">
|
||||
<a href="https://github.com/Nagendhra-web" target="_blank"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .5C5.7.5.5 5.7.5 12c0 5.1 3.3 9.4 7.8 10.9.6.1.8-.2.8-.6v-2c-3.2.7-3.9-1.5-3.9-1.5-.5-1.3-1.3-1.7-1.3-1.7-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.7 1.3 3.4 1 .1-.8.4-1.3.8-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.3 1.2.9-.3 2-.4 3-.4s2.1.1 3 .4c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 2.9.1 3.2.7.8 1.2 1.8 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6 4.5-1.5 7.8-5.8 7.8-10.9C23.5 5.7 18.3.5 12 .5z"/></svg>github</a>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="m-card">
|
||||
<div class="av"><img src="https://github.com/Sid-Qin.png" onerror="this.style.background='var(--coral)';this.removeAttribute('src')" alt=""/></div>
|
||||
<div>
|
||||
<div class="n" id="m-2-name">Sid-Qin</div>
|
||||
<div class="role" id="m-2-role">Maintainer</div>
|
||||
</div>
|
||||
<p class="bio" id="m-2-bio">Sid is the generalist engineer with a designer's eye for detail: the kind of maintainer who notices both the broken CLI path and the crooked interaction affordance. In Open Design, Sid keeps export flows, plugin actions, Windows shims, MIME handling, and agent plumbing sharp enough for a community to build on.</p>
|
||||
<div class="links">
|
||||
<a href="https://github.com/Sid-Qin" target="_blank"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .5C5.7.5.5 5.7.5 12c0 5.1 3.3 9.4 7.8 10.9.6.1.8-.2.8-.6v-2c-3.2.7-3.9-1.5-3.9-1.5-.5-1.3-1.3-1.7-1.3-1.7-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.7 1.3 3.4 1 .1-.8.4-1.3.8-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.3 1.2.9-.3 2-.4 3-.4s2.1.1 3 .4c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 2.9.1 3.2.7.8 1.2 1.8 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6 4.5-1.5 7.8-5.8 7.8-10.9C23.5 5.7 18.3.5 12 .5z"/></svg>github</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- ============ ALL-TIME SIGNAL ============ -->
|
||||
<section class="section signal alltime" id="alltime">
|
||||
<div class="wrap">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<span class="kicker"><span class="dot"></span>All-time signal</span>
|
||||
<h2 class="h-display">The contributors with <em>deep roots</em>.</h2>
|
||||
</div>
|
||||
<p class="right">A long-running record of talented contributors who keep turning ideas, fixes, and craft into the shared Open Design standard.</p>
|
||||
</div>
|
||||
|
||||
<div class="signal-grid">
|
||||
<article class="signal-feature alltime">
|
||||
<div class="top">
|
||||
<div class="rank"><span class="badge">01</span> All-time contributor</div>
|
||||
<div class="week">Repository history</div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="avatar"><img id="feat-avatar-at" src="" alt="" /></div>
|
||||
<div class="name" id="feat-name-at">—</div>
|
||||
<div class="handle" id="feat-handle-at">—</div>
|
||||
<p class="quote">The long tail matters: design systems, docs fixes, examples, and small repairs are how an open design language becomes dependable.</p>
|
||||
</div>
|
||||
<div class="feature-stats">
|
||||
<div class="item"><div class="v coral" id="feat-commits-at">—</div><div class="l">Commits</div></div>
|
||||
<div class="item"><div class="v">#01</div><div class="l">External rank</div></div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div class="leaderboard">
|
||||
<div class="leaderboard-head">
|
||||
<span>#</span>
|
||||
<span>Contributor</span>
|
||||
<span>Commits</span>
|
||||
<span>Rank</span>
|
||||
<span></span>
|
||||
</div>
|
||||
<div id="leaderboard-rows-at">
|
||||
<div class="row"><span class="rk">02</span><span class="who"><div class="skel" style="width:36px;height:36px;border-radius:50%"></div><div><div class="n skel" style="width:120px;height:14px"></div><div class="h skel" style="width:80px;height:10px;margin-top:6px"></div></div></span><span class="v">—</span><span class="v coral">—</span><span class="arr">→</span></div>
|
||||
<div class="row"><span class="rk">03</span><span class="who"><div class="skel" style="width:36px;height:36px;border-radius:50%"></div><div><div class="n skel" style="width:140px;height:14px"></div><div class="h skel" style="width:80px;height:10px;margin-top:6px"></div></div></span><span class="v">—</span><span class="v coral">—</span><span class="arr">→</span></div>
|
||||
<div class="row"><span class="rk">04</span><span class="who"><div class="skel" style="width:36px;height:36px;border-radius:50%"></div><div><div class="n skel" style="width:120px;height:14px"></div><div class="h skel" style="width:80px;height:10px;margin-top:6px"></div></div></span><span class="v">—</span><span class="v coral">—</span><span class="arr">→</span></div>
|
||||
<div class="row"><span class="rk">05</span><span class="who"><div class="skel" style="width:36px;height:36px;border-radius:50%"></div><div><div class="n skel" style="width:120px;height:14px"></div><div class="h skel" style="width:80px;height:10px;margin-top:6px"></div></div></span><span class="v">—</span><span class="v coral">—</span><span class="arr">→</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- ============ RECENT SIGNAL ============ -->
|
||||
<section class="section signal weekly" id="signal">
|
||||
<div class="wrap">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<span class="kicker"><span class="dot"></span>Recent signal</span>
|
||||
<h2 class="h-display">Ten contributors with <em>recent momentum</em>.</h2>
|
||||
</div>
|
||||
<p class="right">A snapshot of sharp contributors landing PRs, improving the product, and making Open Design feel alive.</p>
|
||||
</div>
|
||||
|
||||
<div class="signal-grid">
|
||||
|
||||
<article class="signal-feature" id="feature-card">
|
||||
<div class="top">
|
||||
<div class="rank"><span class="badge">01</span> A recent leader</div>
|
||||
<div class="week">Snapshot</div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="avatar"><img id="feat-avatar" src="" alt="" /></div>
|
||||
<div class="name" id="feat-name">—</div>
|
||||
<div class="handle" id="feat-handle">—</div>
|
||||
<p class="quote" id="feat-blurb">A contributor taking the lead with focused, generous work.</p>
|
||||
<p class="quote-prs" id="feat-prs-list" style="margin-top:14px;font-family:var(--body);font-style:normal;font-size:14px;line-height:1.55;color:rgba(255,255,255,.62);font-weight:400;letter-spacing:0"></p>
|
||||
</div>
|
||||
<div class="feature-stats">
|
||||
<div class="item"><div class="v coral" id="feat-rank">#01</div><div class="l">Rank</div></div>
|
||||
<div class="item"><div class="v" id="feat-prs">—</div><div class="l">Recent PRs</div></div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div class="leaderboard">
|
||||
<div class="leaderboard-head">
|
||||
<span>#</span>
|
||||
<span>Contributor</span>
|
||||
<span>PRs</span>
|
||||
<span>Rank</span>
|
||||
<span></span>
|
||||
</div>
|
||||
<div id="leaderboard-rows">
|
||||
<div class="row"><span class="rk">02</span><span class="who"><div class="skel" style="width:36px;height:36px;border-radius:50%"></div><div><div class="n skel" style="width:120px;height:14px"></div><div class="h skel" style="width:80px;height:10px;margin-top:6px"></div></div></span><span class="v">—</span><span class="v coral">—</span><span class="arr">→</span></div>
|
||||
<div class="row"><span class="rk">03</span><span class="who"><div class="skel" style="width:36px;height:36px;border-radius:50%"></div><div><div class="n skel" style="width:140px;height:14px"></div><div class="h skel" style="width:80px;height:10px;margin-top:6px"></div></div></span><span class="v">—</span><span class="v coral">—</span><span class="arr">→</span></div>
|
||||
<div class="row"><span class="rk">04</span><span class="who"><div class="skel" style="width:36px;height:36px;border-radius:50%"></div><div><div class="n skel" style="width:120px;height:14px"></div><div class="h skel" style="width:80px;height:10px;margin-top:6px"></div></div></span><span class="v">—</span><span class="v coral">—</span><span class="arr">→</span></div>
|
||||
<div class="row"><span class="rk">05</span><span class="who"><div class="skel" style="width:36px;height:36px;border-radius:50%"></div><div><div class="n skel" style="width:120px;height:14px"></div><div class="h skel" style="width:80px;height:10px;margin-top:6px"></div></div></span><span class="v">—</span><span class="v coral">—</span><span class="arr">→</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ GOOD FIRST ISSUES ============ -->
|
||||
<section class="section issues" id="issues">
|
||||
<div class="wrap">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<span class="kicker"><span class="dot"></span>Pick your first contribution</span>
|
||||
<h2 class="h-display">Open issues, <em>tagged for you</em>.</h2>
|
||||
</div>
|
||||
<p class="right">Live from <span class="num">label:“good first issue”</span> on the Open Design repo. Comment on an issue to claim it — a maintainer will assign it within a day.</p>
|
||||
</div>
|
||||
|
||||
<div class="issue-list" id="issue-list">
|
||||
<div class="issue">
|
||||
<span class="num">—</span>
|
||||
<div class="body"><div class="title skel" style="height:24px;width:60%"></div><div class="meta"><span class="label good">good first issue</span></div></div>
|
||||
<span class="lang">—</span>
|
||||
<span class="arr">→</span>
|
||||
</div>
|
||||
<div class="issue">
|
||||
<span class="num">—</span>
|
||||
<div class="body"><div class="title skel" style="height:24px;width:50%"></div><div class="meta"><span class="label good">good first issue</span></div></div>
|
||||
<span class="lang">—</span>
|
||||
<span class="arr">→</span>
|
||||
</div>
|
||||
<div class="issue">
|
||||
<span class="num">—</span>
|
||||
<div class="body"><div class="title skel" style="height:24px;width:65%"></div><div class="meta"><span class="label good">good first issue</span></div></div>
|
||||
<span class="lang">—</span>
|
||||
<span class="arr">→</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="issues-foot">
|
||||
<span>Showing first <span class="num" id="issue-count">—</span> open good-first-issues</span>
|
||||
<a href="https://github.com/nexu-io/open-design/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22" target="_blank" rel="noopener" style="color:var(--coral)">See all on GitHub →</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ ONBOARDING — HOW TO BECOME A CONTRIBUTOR ============ -->
|
||||
<section class="section onboard" id="how">
|
||||
<div class="wrap">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<span class="kicker"><span class="dot"></span>Four steps · any skill level</span>
|
||||
<h2 class="h-display">From zero to <em>merged</em>, in an afternoon.</h2>
|
||||
</div>
|
||||
<p class="right">Whether you're a designer, a writer, an engineer, or someone who just spotted a typo — there's a contribution shape for you. Here's the path.</p>
|
||||
</div>
|
||||
|
||||
<div class="steps">
|
||||
<div class="step">
|
||||
<div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.35-4.35"/></svg></div>
|
||||
<div class="n">Step 01</div>
|
||||
<h3>Find a <em>spark</em>.</h3>
|
||||
<p>Browse the good-first-issues list above, or open a new issue describing something you'd improve. Designers — DESIGN.md systems are the easiest entry.</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 4H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9zM14 4v5h5"/></svg></div>
|
||||
<div class="n">Step 02</div>
|
||||
<h3>Open a <em>draft</em> PR.</h3>
|
||||
<p>Fork, branch, push. Mark it draft — it signals you want feedback early. Mention which issue it closes. The CI is fast; bot-cards stays on its own branch.</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div>
|
||||
<div class="n">Step 03</div>
|
||||
<h3>Review with <em>a human</em>.</h3>
|
||||
<p>A maintainer reviews within 24h. We're kind, specific, and never gatekeep. If you're stuck, drop the PR link in Discord #help.</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 12l2 2 4-4M3 6l2 2 4-4M3 18l2 2 4-4M13 6h8M13 12h8M13 18h8"/></svg></div>
|
||||
<div class="n">Step 04</div>
|
||||
<h3>Merge → <em>card</em>.</h3>
|
||||
<p>The bot mints your honor card the moment you're merged and pushes it to the bot-cards branch. Share it on X with #openDesign — we repost the best ones.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="onboard-foot">
|
||||
<a class="btn btn-coral" href="https://github.com/nexu-io/open-design/blob/main/CONTRIBUTING.md">
|
||||
Read the contributing guide
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||||
</a>
|
||||
<a class="btn btn-ghost" href="https://github.com/nexu-io/open-design/blob/main/CODE_OF_CONDUCT.md" style="color:var(--paper);border-color:rgba(247,241,222,.25)">Code of Conduct</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ DISCORD CTA ============ -->
|
||||
<section class="discord" id="discord">
|
||||
<div class="wrap">
|
||||
<div class="discord-card">
|
||||
<div>
|
||||
<span class="kicker"><span class="dot"></span>Where contributors hang out</span>
|
||||
<h2>Talk to the people who'll <em>review your PR</em>.</h2>
|
||||
<p>Our Discord is where contributors show shipped work, discuss plugins, join beta tests, and get help when a PR gets stuck. No fake activity counters — just the channels people can actually use.</p>
|
||||
<div style="display:flex;gap:14px;flex-wrap:wrap">
|
||||
<a class="btn btn-primary" href="https://discord.gg/3C6EWXbdQQ">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19.27 5.33A18 18 0 0 0 14.72 4l-.2.4a13.7 13.7 0 0 0-5.04 0L9.27 4a18 18 0 0 0-4.54 1.33C2.4 8.94 1.78 12.45 2.09 15.9a18.4 18.4 0 0 0 5.6 2.83l1.13-1.55a11.6 11.6 0 0 1-1.78-.86l.44-.34a13 13 0 0 0 11.04 0l.44.34c-.55.33-1.16.61-1.78.86l1.13 1.55a18.3 18.3 0 0 0 5.6-2.83c.45-4.05-.5-7.53-2.64-10.57Z"/></svg>
|
||||
Join the Discord
|
||||
</a>
|
||||
<a class="btn btn-ghost" href="https://github.com/nexu-io/open-design/discussions">GitHub Discussions</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="discord-side">
|
||||
<div class="pop">Community Discord</div>
|
||||
<div class="stack">
|
||||
<div class="row-d"><span class="dot-g"></span>#showcase<span class="h">work shipped</span></div>
|
||||
<div class="row-d"><span class="dot-g"></span>#plugin<span class="h">builders</span></div>
|
||||
<div class="row-d"><span class="dot-g"></span>#beta-test<span class="h">early feedback</span></div>
|
||||
<div class="row-d"><span class="dot-g"></span>#help<span class="h">unstuck</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ FOOTER ============ -->
|
||||
<footer class="foot">
|
||||
<div class="wrap foot-inner">
|
||||
<span>© 2026 Open Design · MIT-licensed · Built by contributors, in public.</span>
|
||||
<div class="l">
|
||||
<a href="https://github.com/nexu-io/open-design">GitHub</a>
|
||||
<a href="https://discord.gg/3C6EWXbdQQ">Discord</a>
|
||||
<a href="https://x.com/nexu_io">X / Twitter</a>
|
||||
<a href="https://open-design.ai/">open-design.ai</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- =================== Page data + GitHub helpers =================== -->
|
||||
<script>
|
||||
/**
|
||||
* Open Design — Contributors page
|
||||
* Open Design — Contributors page
|
||||
* Leaderboards render from curated project records. GitHub calls hydrate
|
||||
* good-first issues and maintainer profile details only.
|
||||
*/
|
||||
|
||||
const REPO = 'nexu-io/open-design';
|
||||
const API = (window.OPEN_DESIGN_API || 'https://api.github.com');
|
||||
const HEADERS = (() => {
|
||||
const h = { 'Accept': 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28' };
|
||||
if (window.GITHUB_TOKEN) h['Authorization'] = 'Bearer ' + window.GITHUB_TOKEN;
|
||||
return h;
|
||||
})();
|
||||
|
||||
/* Open Design core team and internal accounts. Lowercased GitHub logins. */
|
||||
const CORE_TEAM = new Set([
|
||||
'pftom','mrcfps','sophia','ashleyashli','qiongyu1999','zoeforfun',
|
||||
'perishcode','nettee','anthhub','siri-ray','lefarcen',
|
||||
'alchemistklk','shangxinyu1','joeylee12629-star','tuola-waj',
|
||||
'leilei926524-tech','nagendhra-web','sid-qin','chaoxiaoche'
|
||||
].map(s => s.toLowerCase()));
|
||||
function isCore(login){ return login && CORE_TEAM.has(String(login).toLowerCase()); }
|
||||
const BOT_LOGINS = new Set(['open-design-bot','opendesign-bot','nexu-bot','open-design-bot[bot]']);
|
||||
/* Whole-token match on bot/cursor/agent so real logins like
|
||||
'agentina', 'cursorsmith', 'robothai' are not silently dropped.
|
||||
Token boundaries are start, end, '-', or '_'. */
|
||||
const BOT_TOKEN_RE = /(?:^|[-_])(?:bot|cursor|agent)(?:$|[-_])/;
|
||||
function isBot(c){
|
||||
if (!c) return false;
|
||||
if (c.type === 'Bot') return true;
|
||||
const login = String(c.login || '').toLowerCase();
|
||||
if (!login) return false;
|
||||
if (login.endsWith('[bot]')) return true;
|
||||
if (BOT_TOKEN_RE.test(login)) return true;
|
||||
return BOT_LOGINS.has(login);
|
||||
}
|
||||
function isExcluded(c){
|
||||
const login = c && c.login;
|
||||
return !login || isCore(login) || isBot(c);
|
||||
}
|
||||
|
||||
const RANKING_SNAPSHOT = {
|
||||
generatedAt: '2026-05-26T12:25:59Z',
|
||||
source: 'Curated contributor records for the Open Design community page.',
|
||||
filters: ['core maintainers', 'known internal staff', 'type:Bot', '[bot] suffix', 'whole-token match: bot / cursor / agent'],
|
||||
weekly: [
|
||||
{ login:'bulai0408', avatar:'https://avatars.githubusercontent.com/u/31983330?v=4', prs:19, lastMerged:'2026-05-26', examples:[{number:2006,title:'fix(daemon): fail disallowed connector tool selections'},{number:2331,title:'fix(web): align HomeHero prompt overlay metrics'}] },
|
||||
{ login:'522700967-wq', avatar:'https://avatars.githubusercontent.com/u/270050048?v=4', prs:14, lastMerged:'2026-05-26', examples:[{number:2958,title:'feat(landing-page): plugin detail page interactive preview + share dialog'},{number:2880,title:'fix(landing-page): copy example.html sibling assets in post-build'}] },
|
||||
{ login:'YUHAO-corn', avatar:'https://avatars.githubusercontent.com/u/201702441?v=4', prs:10, lastMerged:'2026-05-26', examples:[{number:2971,title:'fix(web): align draw note enter action'},{number:2036,title:'fix(plugins): reject symlinked plugin assets'}] },
|
||||
{ login:'xxiaoxiong', avatar:'https://avatars.githubusercontent.com/u/27723864?v=4', prs:10, lastMerged:'2026-05-26', examples:[{number:2932,title:'fix: synchronously update URL when creating new conversation to prevent route-sync conflict'},{number:2931,title:'fix: re-activate srcDoc transport when exiting Edit mode to prevent blank preview'}] },
|
||||
{ login:'YOMXXX', avatar:'https://avatars.githubusercontent.com/u/18409951?v=4', prs:10, lastMerged:'2026-05-26', examples:[{number:2419,title:'feat(daemon): structured diagnostics for agent connection test results'},{number:2576,title:'fix(web): route chat file links to workspace preview instead of new window'}] },
|
||||
{ login:'portseif', avatar:'https://avatars.githubusercontent.com/u/13489304?v=4', prs:9, lastMerged:'2026-05-25', examples:[{number:2847,title:'Capture native Swift source in the GitHub design import'},{number:2848,title:'Polish the design system review panel'}] },
|
||||
{ login:'leessju', avatar:'https://avatars.githubusercontent.com/u/40141791?v=4', prs:7, lastMerged:'2026-05-26', examples:[{number:2844,title:'fix(web): live-update preview during Comment mode'},{number:2839,title:'fix(web): preserve chat composer drafts across refreshes'}] },
|
||||
{ login:'GHX5T-SOL', avatar:'https://avatars.githubusercontent.com/u/200635707?v=4', prs:7, lastMerged:'2026-05-23', examples:[{number:2483,title:'Create design-system conversations from New action'},{number:2491,title:'fix(web): retry failed chat runs without duplicating user message'}] },
|
||||
{ login:'neogenix', avatar:'https://avatars.githubusercontent.com/u/141967?v=4', prs:6, lastMerged:'2026-05-26', examples:[{number:2311,title:'chore(deps): upgrade express 4 -> 5 in daemon'},{number:2305,title:'chore(e2e): improve test framework quality'}] },
|
||||
{ login:'prantikmedhi', avatar:'https://avatars.githubusercontent.com/u/140103052?v=4', prs:5, lastMerged:'2026-05-26', examples:[{number:2940,title:'fix: keep raw HTML source out of artifact chat prose'},{number:1556,title:'fix: hide preview chrome in source view'}] }
|
||||
],
|
||||
allTime: [
|
||||
{ login:'bulai0408', avatar:'https://avatars.githubusercontent.com/u/31983330?v=4', commits:37, lastMerged:'long-running contributor' },
|
||||
{ login:'Nicholas-Xiong', avatar:'https://github.com/Nicholas-Xiong.png', commits:35, lastMerged:'long-running contributor' },
|
||||
{ login:'YUHAO-corn', avatar:'https://avatars.githubusercontent.com/u/201702441?v=4', commits:34, lastMerged:'long-running contributor' },
|
||||
{ login:'leessju', avatar:'https://avatars.githubusercontent.com/u/40141791?v=4', commits:16, lastMerged:'long-running contributor' },
|
||||
{ login:'prantikmedhi', avatar:'https://avatars.githubusercontent.com/u/140103052?v=4', commits:15, lastMerged:'long-running contributor' },
|
||||
{ login:'522700967-wq', avatar:'https://avatars.githubusercontent.com/u/270050048?v=4', commits:14, lastMerged:'long-running contributor' },
|
||||
{ login:'mturac', avatar:'https://avatars.githubusercontent.com/u/345446?v=4', commits:13, lastMerged:'long-running contributor' },
|
||||
{ login:'Mason', avatar:'https://github.com/Mason.png', commits:12, lastMerged:'long-running contributor' },
|
||||
{ login:'portseif', avatar:'https://avatars.githubusercontent.com/u/13489304?v=4', commits:10, lastMerged:'long-running contributor' },
|
||||
{ login:'GHX5T-SOL', avatar:'https://avatars.githubusercontent.com/u/200635707?v=4', commits:8, lastMerged:'long-running contributor' }
|
||||
]
|
||||
};
|
||||
|
||||
async function gh(path) {
|
||||
const res = await fetch(`${API}${path}`, { headers: HEADERS });
|
||||
if (!res.ok) throw new Error(`GitHub ${path} → ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
function fmt(n){ return n == null ? '—' : (n >= 10000 ? (n/1000).toFixed(1)+'k' : n.toLocaleString()) }
|
||||
function pad2(n){ return (n<10?'0':'')+n }
|
||||
|
||||
function leaderboardRows(list, metricKey, metricLabel){
|
||||
return list.slice(1).map((c,i) => `
|
||||
<a class="row" href="https://github.com/${c.login}" target="_blank" rel="noopener" title="${escapeHtml(metricLabel)} · rank ${pad2(i+2)}">
|
||||
<span class="rk">${pad2(i+2)}</span>
|
||||
<span class="who">
|
||||
<img src="${c.avatar}" alt="${c.login}" loading="lazy" onerror="this.style.visibility='hidden'"/>
|
||||
<span><span class="n">${c.login}</span><span class="h">@${c.login}</span></span>
|
||||
</span>
|
||||
<span class="v">${fmt(c[metricKey])}</span>
|
||||
<span class="v coral">#${pad2(i+2)}</span>
|
||||
<span class="arr">→</span>
|
||||
</a>`).join('');
|
||||
}
|
||||
|
||||
function exampleCopy(c){
|
||||
return (c.examples || []).slice(0,2).map(pr => `#${pr.number} ${pr.title}`).join(' · ');
|
||||
}
|
||||
|
||||
/* --------- weekly top 10 --------- */
|
||||
async function loadWeeklyTop(){
|
||||
const ranked = RANKING_SNAPSHOT.weekly.filter(c => !isExcluded(c));
|
||||
const f = ranked[0];
|
||||
if (!f) return;
|
||||
document.getElementById('feat-avatar').src = f.avatar;
|
||||
document.getElementById('feat-avatar').alt = f.login;
|
||||
setText('feat-name', f.login);
|
||||
setText('feat-handle', '@' + f.login + ' · recent contribution');
|
||||
setText('feat-blurb', `${f.login} has set the pace with ${f.prs} merged PR${f.prs === 1 ? '' : 's'} and the kind of steady craft that keeps Open Design moving.`);
|
||||
setText('feat-prs-list', exampleCopy(f));
|
||||
setText('feat-rank', '#01');
|
||||
setText('feat-prs', f.prs);
|
||||
document.getElementById('leaderboard-rows').innerHTML = leaderboardRows(ranked, 'prs', 'Merged PRs');
|
||||
}
|
||||
|
||||
/* --------- all-time top 10 --------- */
|
||||
async function loadAllTimeTop(){
|
||||
const top = RANKING_SNAPSHOT.allTime.filter(c => !isExcluded(c));
|
||||
const f = top[0];
|
||||
if (!f) return;
|
||||
document.getElementById('feat-avatar-at').src = f.avatar;
|
||||
document.getElementById('feat-avatar-at').alt = f.login;
|
||||
setText('feat-name-at', f.login);
|
||||
setText('feat-handle-at', '@' + f.login + ' · deep contributor signal');
|
||||
setText('feat-commits-at', fmt(f.commits));
|
||||
document.getElementById('leaderboard-rows-at').innerHTML = leaderboardRows(top, 'commits', 'Commits');
|
||||
}
|
||||
|
||||
/* --------- good first issues --------- */
|
||||
async function loadGoodFirstIssues(){
|
||||
try {
|
||||
const r = await gh(`/search/issues?q=repo:${REPO}+is:issue+is:open+label:%22good+first+issue%22&sort=created&order=desc&per_page=8`);
|
||||
const items = r.items || [];
|
||||
setText('issue-count', items.length);
|
||||
if (!items.length){
|
||||
document.getElementById('issue-list').innerHTML = '<div class="issue" style="color:var(--ink-faint);padding:36px 0">No open good-first-issues right now. Check back tomorrow, or open one yourself ↗</div>';
|
||||
return;
|
||||
}
|
||||
const html = items.map((it,i) => {
|
||||
const lang = inferLang(it.title);
|
||||
return `
|
||||
<a class="issue" href="${it.html_url}" target="_blank" rel="noopener">
|
||||
<span class="num">${pad2(i+1)}</span>
|
||||
<div class="body">
|
||||
<div class="title">${escapeHtml(it.title)}</div>
|
||||
<div class="meta">
|
||||
<span class="label good">good first issue</span>
|
||||
${(it.labels||[]).filter(l=>l.name!=='good first issue').slice(0,3).map(l=>`<span class="label ${inferLabelClass(l.name)}">${escapeHtml(l.name)}</span>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<span class="lang">${lang}</span>
|
||||
<span class="arr">→</span>
|
||||
</a>`;
|
||||
}).join('');
|
||||
document.getElementById('issue-list').innerHTML = html;
|
||||
} catch(e){
|
||||
console.warn('issues failed', e);
|
||||
setText('issue-count', '—');
|
||||
const list = document.getElementById('issue-list');
|
||||
if (list) list.innerHTML = '<a class="issue" href="https://github.com/nexu-io/open-design/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22" target="_blank" rel="noopener"><span class="num">—</span><div class="body"><div class="title">GitHub rate limit reached in preview. Open the live good-first-issue search on GitHub.</div><div class="meta"><span class="label good">good first issue</span><span class="label docs">live GitHub search</span></div></div><span class="lang">WEB</span><span class="arr">→</span></a>';
|
||||
}
|
||||
}
|
||||
|
||||
function inferLabelClass(name){
|
||||
const n = (name||'').toLowerCase();
|
||||
if (n.includes('docs')||n.includes('doc')) return 'docs';
|
||||
if (n.includes('bug')) return 'bug';
|
||||
if (n.includes('design')||n.includes('ui')) return 'design';
|
||||
return 'lang';
|
||||
}
|
||||
function inferLang(title){
|
||||
const t = (title||'').toLowerCase();
|
||||
if (t.includes('typescript')||t.includes('.ts')) return 'TS';
|
||||
if (t.includes('python')) return 'PY';
|
||||
if (t.includes('css')||t.includes('html')||t.includes('design')) return 'CSS';
|
||||
if (t.includes('docs')||t.includes('readme')) return 'MD';
|
||||
return 'JS/TS';
|
||||
}
|
||||
|
||||
/* --------- maintainers (real GitHub profiles) --------- */
|
||||
const MAINTAINERS = ['Nagendhra-web', 'Sid-Qin'];
|
||||
async function loadMaintainers(){
|
||||
await Promise.all(MAINTAINERS.map(async (login, i) => {
|
||||
try {
|
||||
const u = await gh(`/users/${login}`);
|
||||
const idx = i + 1;
|
||||
if (u.name) setText(`m-${idx}-name`, u.name);
|
||||
// Keep the curated maintainer story visible; profile bios are too short for this page.
|
||||
} catch (e) { /* leave the static fallback */ }
|
||||
}));
|
||||
}
|
||||
|
||||
/* --------- helpers --------- */
|
||||
function setText(id, v){ const el = document.getElementById(id); if (el && v != null) el.textContent = v; }
|
||||
function escapeHtml(s){ return String(s||'').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
|
||||
|
||||
/* --------- boot --------- */
|
||||
(async function(){
|
||||
await Promise.all([ loadWeeklyTop(), loadAllTimeTop(), loadGoodFirstIssues(), loadMaintainers() ]);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue