eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1<!-- 🤖 Public contributors index. Fully self-contained: inline CSS/JS, no
2 external requests except ./contributors.json (relative) — avatars ride
3 inside the JSON as data URIs. Like carbon.json, the JSON is COMMITTED
4 data (web/status/contributors.json, refreshed by tools/contributors.mjs),
5 republished as-is by deploy/status-publish.sh. -->
6<!doctype html>
7<html lang="en">
8<head>
9<meta charset="utf-8" />
10<meta name="viewport" content="width=device-width, initial-scale=1" />
11<meta name="color-scheme" content="light dark" />
12<title>Eleven — contributors</title>
13<link rel="icon" type="image/png" href="./icon-192.png" />
14<link rel="stylesheet" href="./status.css" />
15<style>
16 /* Contributors-only: the identity block (avatar / AI mark, name, kind, bio)
17 and the wider first column. Shared .bars supplies the container, rows, bar
18 mark, and count; everything else shared lives in status.css. */
19 .bars li { grid-template-columns: minmax(180px, 260px) 1fr 5em; padding: 12px 16px; }
20 .who { display: flex; gap: 10px; align-items: flex-start; min-width: 0; }
21 .who img, .who .ai-mark {
22 width: 36px; height: 36px; border-radius: 50%; flex: none;
23 border: 1px solid var(--border);
24 }
25 .who .ai-mark {
26 display: inline-flex; align-items: center; justify-content: center;
27 background: var(--plane); color: var(--ink-2);
28 font-size: 11px; font-weight: 650; letter-spacing: 0.04em;
29 }
30 .who .name { font-size: 14px; font-weight: 600; overflow-wrap: anywhere; }
31 .who .name a { color: inherit; text-decoration: none; }
32 .who .name a:hover { text-decoration: underline; }
33 .who .kind { font-size: 12px; color: var(--ink-2); font-weight: 400; margin-left: 6px; }
34 .who .bio { display: block; font-size: 12px; font-weight: 400; color: var(--ink-2); margin-top: 1px; }
35</style>
36</head>
37<body>
38<div class="wrap">
39 <header data-title="Contributors"></header>
40 <nav class="status-nav" aria-label="Status"></nav>
41 <script src="./status-nav.js"></script>
42 <p class="lede">Who builds Eleven — humans and AI models on the same metric.
43 This app is AI-written, so the models appear in the index exactly as the
44 humans do: <span id="metric">—</span>.</p>
45 <p class="asof">As of <time id="asof" datetime="">—</time></p>
46
47 <p id="error" role="alert">Couldn’t load the contributors index. It may be mid-publish — this page retries every minute.</p>
48
49 <section aria-labelledby="h-list">
50 <h2 id="h-list">By commits</h2>
51 <ul class="bars" id="list"><li><span class="who"><span class="name">Loading…</span></span></li></ul>
52 </section>
53
54 <footer>
55 <p>Refreshed from GitHub by tools/contributors.mjs and committed to the
56 repo. Contributors own their row: edit your bio (or hide yourself) in
57 web/status/contributors.json by PR.</p>
58 </footer>
59</div>
60
61<script>
62// contract: the fields this page reads from ./contributors.json — kept in sync
63// with web/status/contributors.json and checked by test/status-contract.test.mjs.
64const FIELDS = [
65 "updated_at", "metric",
66 "contributors[].id", "contributors[].kind", "contributors[].name",
67 "contributors[].bio", "contributors[].commits", "contributors[].url",
68 "contributors[].avatar",
69];
70
71const $ = (id) => document.getElementById(id);
72
73// All rendering is textContent / createElement — never innerHTML of fetched
74// data, even though the feed is our own.
75function render(d) {
76 $("asof").dateTime = d.updated_at;
77 $("asof").textContent = new Date(d.updated_at).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" });
78 $("metric").textContent = d.metric;
79
80 const list = $("list");
81 list.replaceChildren();
82 const rows = (d.contributors || []).filter((c) => !c.hidden);
83 const max = Math.max(1, ...rows.map((c) => c.commits));
84 for (const c of rows) {
85 const li = document.createElement("li");
86
87 const who = document.createElement("span");
88 who.className = "who";
89 if (c.kind === "human" && c.avatar) {
90 const img = document.createElement("img");
91 img.src = c.avatar;
92 img.alt = "";
93 who.append(img);
94 } else {
95 const mark = document.createElement("span");
96 mark.className = "ai-mark";
97 mark.textContent = "AI";
98 mark.setAttribute("aria-hidden", "true");
99 who.append(mark);
100 }
101 const id = document.createElement("span");
102 const name = document.createElement("span");
103 name.className = "name";
104 if (c.url) {
105 const a = document.createElement("a");
106 a.href = c.url;
107 a.textContent = c.name;
108 name.append(a);
109 } else {
110 name.textContent = c.name;
111 }
112 const kind = document.createElement("span");
113 kind.className = "kind";
114 kind.textContent = c.kind === "ai" ? "AI model" : "@" + c.id;
115 name.append(kind);
116 id.append(name);
117 if (c.bio) {
118 const bio = document.createElement("span");
119 bio.className = "bio";
120 bio.textContent = c.bio;
121 name.append(bio);
122 }
123 who.append(id);
124
125 const track = document.createElement("span");
126 track.className = "track";
127 track.setAttribute("aria-hidden", "true");
128 const bar = document.createElement("span");
129 bar.className = "bar";
130 bar.style.width = (100 * c.commits / max) + "%";
131 track.append(bar);
132
133 const count = document.createElement("span");
134 count.className = "count";
135 count.textContent = c.commits.toLocaleString();
136
137 li.append(who, track, count);
138 list.append(li);
139 }
140}
141
142async function refresh() {
143 try {
144 const res = await fetch("./contributors.json", { cache: "no-cache" });
145 if (!res.ok) throw new Error("HTTP " + res.status);
146 render(await res.json());
147 $("error").classList.remove("show");
148 } catch (e) {
149 // Keep the previous render (no skeleton flash); surface the failure inline.
150 $("error").classList.add("show");
151 }
152}
153refresh();
154setInterval(refresh, 60 * 1000);
155void FIELDS;
156</script>
157</body>
158</html>
159