import type { LanguageShare } from "./languages";
const CARD_LANGUAGES = 6;
/** The card's language strip: the largest languages as whole percents (at least 1), the rest as "Other". */
export function cardLanguages(languages: readonly LanguageShare[]): Array<{ name: string; percent: number }> {
const total = languages.reduce((sum, language) => sum + language.bytes, 0);
if (total === 0) return [];
const shares = languages.map((language) => ({ name: language.name, bytes: language.bytes }));
const kept = shares.filter((language) => language.name !== "Other").slice(0, CARD_LANGUAGES);
const rest = total - kept.reduce((sum, language) => sum + language.bytes, 0);
if (rest > 0) kept.push({ name: "Other", bytes: rest });
return kept.map((language) => ({
name: language.name,
percent: Math.max(1, Math.round((language.bytes / total) * 100)),
}));
}
/** Social card URLs and their version hashes; shared by page heads (client and server) and the /og handler. */
/** Bump when a card layout changes so every cached card URL changes with it. */
export const OG_RENDERER_VERSION = "2";
export const OG_WIDTH = 1200;
export const OG_HEIGHT = 630;
export const OG_PREFIX = "/og/";
export const SITE_OG_PATH = "/og/site.png";
export const SITE_DESCRIPTION =
"gitfor hosts Git repositories with pull requests, Actions-style CI, issues, security scanning and sites. Open source under 0BSD, and you can run your own instance.";
export const SITE_OG_ALT = "gitfor: open-source Git hosting, code review and CI";
/** FNV-1a 32-bit over the joined fields: cheap, sync and stable on both sides; it only needs to change, not resist collisions. */
export function ogVersion(fields: ReadonlyArray<string | number | null | undefined>): string {
const input = [
OG_RENDERER_VERSION,
...fields.map((field) => (field === null || field === undefined ? "" : String(field))),
].join("\u0000");
let hash = 0x811c9dc5;
for (let index = 0; index < input.length; index++) {
hash ^= input.charCodeAt(index);
hash = Math.imul(hash, 0x01000193);