686f09ed5ca8

give each palette colour one meaning on every page: amber for links, green for additions and success, red for removals and errors, lavender for versions, rose for people; the timeline returns to amber tones (v3.1.14)

time
2026-10-08 00:39 UTC · 5 days ago
machine
Junes-MacBook-Pro.local
agent
claude-opus-5-5
session
vivid kepr palette
parent
34c906a3bdd2
7 files package.json+1 −1public/css/kepr.css+28 −34public/js/kepr.js+4 −4src/admin/repos.ts+1 −1src/pages/repo.ts+2 −2tests/releases.test.ts+6 −2tests/version.test.ts+4 −2
package.json+1 −1
@@ -1,6 +1,6 @@11 {22 	"name": "kepr",3-	"version": "3.1.13",3+	"version": "3.1.14",44 	"private": true,55 	"type": "module",66 	"scripts": {

view file

public/css/kepr.css+28 −34
@@ -13,7 +13,6 @@1313 	--accent: #ffad33;1414 	--accent-bright: #ffc966;1515 	--accent-dim: #b8741c;16-	--accent-flash: #4da3ff;1716 	--red: #ff5c4d;1817 	--accent-tint-04: rgba(255, 173, 51, 0.04);1918 	--accent-tint-12: rgba(255, 173, 51, 0.12);@@ -295,7 +294,7 @@295294 	animation: fadeUp 280ms 40ms ease both;296295 }297296 .repo-contributor a:hover {298-	color: var(--accent);297+	text-decoration-color: var(--rose);299298 }300299 .repo-desc,301300 .repo-homepage {@@ -326,13 +325,16 @@326325 .lang-badge {327326 	font-family: var(--mono);328327 	font-size: 0.6875rem;329-	color: var(--lavender);328+	color: var(--text-secondary);330329 	background: var(--bg-surface);331330 	border: 1px solid var(--border);332331 	border-radius: 3px;333332 	padding: 1px 7px;334333 	letter-spacing: 0.02em;335334 }335+.repo-version {336+	color: var(--lavender);337+}336338 .repo-meta {337339 	margin: 0;338340 	font-size: 0.875rem;@@ -392,7 +394,7 @@392394 	max-width: 100%;393395 	font-family: var(--mono);394396 	font-size: 0.75rem;395-	color: var(--sage);397+	color: var(--text-muted);396398 	background: var(--bg-surface);397399 	border: 1px solid var(--border);398400 	border-radius: 3px;@@ -406,11 +408,14 @@406408 		color 120ms,407409 		border-color 120ms;408410 }409-.repo-steal-btn:hover,410-.repo-steal-btn.copied {411+.repo-steal-btn:hover {411412 	color: var(--accent);412413 	border-color: var(--accent-dim);413414 }415+.repo-steal-btn.copied {416+	color: var(--sage);417+	border-color: var(--sage);418+}414419 .install-lines {415420 	display: flex;416421 	flex-direction: column;@@ -448,10 +453,12 @@448453 	color: var(--text-faint);449454 	letter-spacing: 0.04em;450455 }451-.copy-btn:hover,452-.copy-btn.copied {456+.copy-btn:hover {453457 	color: var(--accent);454458 }459+.copy-btn.copied {460+	color: var(--sage);461+}455462 456463 /* ----- sections ----------------------------------------------------------- */457464 @@ -494,7 +501,7 @@494501 .section-label {495502 	font-size: 0.6875rem;496503 	letter-spacing: 0.12em;497-	color: var(--lavender);504+	color: var(--text-secondary);498505 	text-transform: uppercase;499506 }500507 .section-count {@@ -1466,7 +1473,7 @@14661473 .release-version {14671474 	font-family: "Martian Mono", monospace;14681475 	font-size: 1rem;1469-	color: var(--accent);1476+	color: var(--lavender);14701477 }14711478 .release-latest .release-version {14721479 	font-size: clamp(2rem, 5vw, 3.5rem);@@ -1593,7 +1600,7 @@15931600 	color: var(--text-muted);15941601 }15951602 .issue-symbol--open {1596-	color: var(--accent);1603+	color: var(--sage);15971604 }15981605 .issue-symbol--closed {15991606 	color: var(--text-faint);@@ -1603,7 +1610,7 @@16031610 }16041611 .issue-reporter {16051612 	font-size: 0.6875rem;1606-	color: var(--text-muted);1613+	color: var(--rose);16071614 	overflow-wrap: anywhere;16081615 }16091616 .issues-filter {@@ -1620,7 +1627,7 @@16201627 	background: var(--bg-surface);16211628 }16221629 .issue-post--operator {1623-	border-color: var(--accent-dim);1630+	border-color: var(--rose);16241631 }16251632 .issue-post-head {16261633 	display: flex;@@ -1629,10 +1636,10 @@16291636 	margin: 0 0 8px;16301637 }16311638 .issue-author {1632-	color: var(--text);1639+	color: var(--rose);16331640 }16341641 .issue-badge {1635-	color: var(--accent);1642+	color: var(--rose);16361643 	text-transform: uppercase;16371644 	letter-spacing: 0.08em;16381645 }@@ -1877,24 +1884,12 @@18771884 	font-family: var(--mono);18781885 	letter-spacing: 0.03em;18791886 	white-space: nowrap;1880-	color: var(--accent);1887+	color: var(--accent-dim);18811888 	transition:18821889 		color 0.2s,18831890 		opacity 0.2s,18841891 		text-shadow 0.2s;18851892 }1886-.tc-tag:nth-child(5n + 2) {1887-	color: var(--red);1888-}1889-.tc-tag:nth-child(5n + 3) {1890-	color: var(--sage);1891-}1892-.tc-tag:nth-child(5n + 4) {1893-	color: var(--rose);1894-}1895-.tc-tag:nth-child(5n) {1896-	color: var(--lavender);1897-}18981893 .tc-tag:hover {18991894 	color: var(--accent);19001895 	opacity: 1;@@ -2088,7 +2083,7 @@20882083 	line-height: 1;20892084 	letter-spacing: -0.02em;20902085 	text-transform: lowercase;2091-	color: var(--text-heading);2086+	color: var(--rose);20922087 	user-select: none;20932088 	animation: fadeUp 500ms ease both;20942089 }@@ -2108,7 +2103,7 @@21082103 	display: block;21092104 	font-size: 0.6875rem;21102105 	letter-spacing: 0.18em;2111-	color: var(--accent);2106+	color: var(--rose);21122107 }21132108 21142109 .profile-portrait-section {@@ -2206,14 +2201,13 @@22062201 	opacity: 0.6;22072202 }22082203 .profile-lang-bar rect:nth-child(2) {2209-	fill: var(--sage);2204+	fill: var(--accent-bright);22102205 }22112206 .profile-lang-bar rect:nth-child(3) {2212-	fill: var(--accent-flash);2213-	opacity: 0.45;2207+	fill: var(--accent-dim);22142208 }22152209 .profile-lang-bar rect:nth-child(4) {2216-	fill: var(--accent-dim);2210+	fill: var(--text-muted);22172211 }22182212 .profile-lang-labels {22192213 	display: flex;

view file

public/js/kepr.js+4 −4
@@ -6,14 +6,14 @@66 // Kepr 2's save kinds (canvas.js), first match wins: each dot's colour and strength.77 const KINDS = [88 	[/\b(release|version|bump|tag)\b/i, "accent", 1],9-	[/\b(fix|bug|patch|issue|crash|error|regress|revert)\b/i, "red", 0.9],10-	[/\b(add|new|implement|feat|introduce|wire|support)\b/i, "sage", 1],9+	[/\b(fix|bug|patch|issue|crash|error|regress|revert)\b/i, "accent-dim", 0.8],10+	[/\b(add|new|implement|feat|introduce|wire|support)\b/i, "accent", 1],1111 	[1212 		/\b(refactor|restructure|reorganize|extract|move|rename|split|cleanup|clean up)\b/i,13-		"lavender",13+		"text",1414 		0.85,1515 	],16-	[/\b(perf|optim|speed|fast|slow|cache|memory|alloc)\b/i, "rose", 0.9],16+	[/\b(perf|optim|speed|fast|slow|cache|memory|alloc)\b/i, "accent", 0.9],1717 	[/\b(test|spec|coverage|assert)\b/i, "text-secondary", 0.74],1818 	[/\b(doc|readme|comment|example|guide)\b/i, "text-secondary", 0.7],1919 	[/\b(polish|style|format|lint|whitespace|typo|wording|copy)\b/i, "text-secondary", 0.66],

view file

src/admin/repos.ts+1 −1
@@ -365,7 +365,7 @@365365 	const id = `release-${repo}-${release.version}`;366366 	return html`<article class="issue-post" id="${id}">367367 <p class="issue-post-head mono">368-${/* this row stays one line: splitting its spans changes the HTML that tests/console.test.ts pins */ ""}${html`<span class="issue-author">${release.version}</span>`}${html`<span>${release.hidden ? "hidden" : "shown"}</span>`}${html`<span>${plural(total, "download")}</span>`}${release.downloads.map(368+${/* this row stays one line: splitting its spans changes the HTML that tests/console.test.ts pins */ ""}${html`<span class="repo-version">${release.version}</span>`}${html`<span>${release.hidden ? "hidden" : "shown"}</span>`}${html`<span>${plural(total, "download")}</span>`}${release.downloads.map(369369 	([platform, n]) => html`<span class="muted">${platform} ${n}</span>`,370370 )}371371 </p>

view file

src/pages/repo.ts+2 −2
@@ -139,7 +139,7 @@139139 		visibleReleases(ctx.kepr, repo.mirror, name)[0]?.version ||140140 		mirrorState(repo.mirror, "declared_version");141141 	const meta = [142-		version && `v${version}`,142+		version && html`<span class="repo-version">v${version}</span>`,143143 		plural(stats.saves, "save"),144144 		stats.lines > 0 && plural(stats.lines, "line"),145145 		stats.updated !== null && `updated ${relativeTime(stats.updated)}`,@@ -158,7 +158,7 @@158158 		(language) => html`<span class="lang-badge">${language}</span>`,159159 	)}</div>`160160 }161-<p class="repo-meta">${meta.join(" · ")}</p>161+<p class="repo-meta">${meta.flatMap((part, i) => (i === 0 ? [part] : [" · ", part]))}</p>162162 <div class="repo-tabbar">163163 <nav class="repo-tabs" aria-label="Repository panels">${PANELS.map(164164 		([label, path]) =>

view file

tests/releases.test.ts+6 −2
@@ -207,12 +207,16 @@207207 	});208208 209209 	test("hidden versions don't show, and the repo header names the newest visible release", async () => {210-		expect(await (await get("/tool")).text()).toContain("v0.11.0 · ");210+		expect(await (await get("/tool")).text()).toContain(211+			`<span class="repo-version">v0.11.0</span> · `,212+		);211213 		const kepr = releaseKepr.get(app);212214 		kepr?.run("INSERT INTO release_settings (repo, version, hidden) VALUES ('tool', '0.11.0', 1)");213215 		const body = await (await get("/tool/releases")).text();214216 		expect(body).not.toContain("0.11.0");215-		expect(await (await get("/tool")).text()).toContain("v0.10.0 · ");217+		expect(await (await get("/tool")).text()).toContain(218+			`<span class="repo-version">v0.10.0</span> · `,219+		);216220 		kepr?.run("DELETE FROM release_settings");217221 	});218222 

view file

tests/version.test.ts+4 −2
@@ -81,7 +81,9 @@8181 	const inst = await instance(repos.bucketDir);8282 	inst.kepr.run("UPDATE repos SET public = 1");8383 	const meta = async () =>84-		/<p class="repo-meta">([^<]*)<\/p>/.exec(await (await inst.app.request("/app")).text())?.[1];84+		/<p class="repo-meta">(.*?)<\/p>/85+			.exec(await (await inst.app.request("/app")).text())?.[1]86+			?.replace(/<[^>]+>/g, "");8587 	expect(await meta()).toStartWith("v1.2.3 · ");8688 	const head = JSON.parse(repos.koh("app", "view", "--json")).head_id as string;8789 	await writeRelease(repos.bucketDir, "app", {@@ -107,7 +109,7 @@107109 	const inst = await instance(repos.bucketDir);108110 	inst.kepr.run("UPDATE repos SET public = 1");109111 	const page = await (await inst.app.request("/app")).text();110-	const meta = /<p class="repo-meta">([^<]*)<\/p>/.exec(page)?.[1];112+	const meta = /<p class="repo-meta">(.*?)<\/p>/.exec(page)?.[1]?.replace(/<[^>]+>/g, "");111113 	expect(meta).toBeDefined();112114 	expect(meta).not.toStartWith("v");113115 });

view file