front.ts

4.0 KB · raw

1import type { Hono } from "hono";2import { knownObjects, languageLines } from "../mirror/queries.ts";3import { setting } from "../server/db.ts";4import { type Html, html } from "./html.ts";5import { cacheFor, type PageContext, page, plural } from "./layout.ts";6import { type LibraryRepo, publicLibrary, recentSaves, repoCards } from "./library.ts";78const TICKER_ROWS = 7;9const TICKER_ITEMS = 10;10const LATEST_OFFERED = 4;1112/** §21.1's front page and §21.3's browse. */13export function registerFrontPages(app: Hono, ctx: PageContext): void {14	app.get("/", (c) => {15		const library = publicLibrary(ctx);16		const tagline = setting(ctx.kepr, "tagline") ?? "";17		// §21.1: distinct objects across every public repo's real saves.18		const known = new Set(library.flatMap((repo) => knownObjects(repo.mirror))).size;19		const latest = library20			.filter((repo) => repo.stats.updated !== null)21			.sort((a, b) => (b.stats.updated ?? 0) - (a.stats.updated ?? 0))22			.slice(0, LATEST_OFFERED);23		cacheFor(c, false);24		return c.html(25			page(ctx, {26				title: "",27				description: tagline,28				path: "/",29				body: html`${ticker(library)}30<section class="headline">31<div class="headline-text">32<h1>The library knows<br>${plural(known, "thing")}.</h1>33${tagline && html`<p class="headline-tagline">${tagline}</p>`}34</div>35${wordCloud(library)}36</section>37${38	latest.length > 0 &&39	html`<section class="featured"><h2 class="featured-label mono">latest offered</h2>${repoCards(latest)}</section>`40}41<p class="landing-self-host mono"><a href="/docs">run your own library →</a></p>`,42			}),43		);44	});45	app.get("/browse", (c) => {46		const library = publicLibrary(ctx);47		cacheFor(c, false);48		return c.html(49			page(ctx, {50				title: "browse",51				description: `Every public repository, ${plural(library.length, "repo")}.`,52				path: "/browse",53				body: html`<h1 class="file-page-title">browse</h1>54<p class="muted mono">${plural(library.length, "repo")}</p>55${56	library.length > 057		? repoCards(library)58		: html`<p class="muted mono save-empty">nothing public yet</p>`59}`,60			}),61		);62	});63}6465// Rows of recent save messages drifting at different depths; the motion is CSS, and stops under66// reduced motion. Each row's messages are written twice so sliding by half its width loops.67function ticker(library: LibraryRepo[]): Html {68	const messages = recentSaves(library, TICKER_ROWS * TICKER_ITEMS).map(69		({ save }) => save.message.split("\n")[0] as string,70	);71	if (messages.length === 0) return html``;72	const rows = Array.from({ length: TICKER_ROWS }, (_, row) => {73		const items = Array.from(74			{ length: TICKER_ITEMS },75			(_, i) => messages[(row * 3 + i) % messages.length],76		);77		const spans = items.map((message) => html`<span>${message}</span>`);78		return html`<div class="ticker-row"><div class="ticker-track">${spans}${spans}</div></div>`;79	});80	return html`<div class="ticker" aria-hidden="true">${rows}</div>`;81}8283// §21.1: file types (the header's languages) mixed with categories, each weighed by how many84// public repos have it, as Kepr 2 weighed its cloud.85function wordCloud(library: LibraryRepo[]): Html {86	const weights = new Map<string, number>();87	for (const repo of library) {88		const labels = languageLines(repo.mirror).map(([language]) => language);89		if (repo.settings.category) labels.push(repo.settings.category);90		for (const label of new Set(labels)) weights.set(label, (weights.get(label) ?? 0) + 1);91	}92	if (weights.size === 0) return html``;93	const max = Math.max(...weights.values());94	// Ordered by a hash of the label, so the big words scatter instead of leading the cloud.95	const tags = [...weights].sort((a, b) => scatter(a[0]) - scatter(b[0]));96	return html`<div class="headline-cloud" aria-label="file types and categories">${tags.map(97		([label, weight]) =>98			html`<span class="tc-tag tc-${Math.ceil((weight / max) * 5)}">${label}</span>`,99	)}</div>`;100}101102function scatter(label: string): number {103	let hash = 0x9e3779b9;104	for (const char of label) hash = (Math.imul(31, hash) + (char.codePointAt(0) ?? 0)) | 0;105	return hash;106}