feat: wayfinding system across chapters, HUD and project plates; cut boot log, headlight and ops strip

This commit is contained in:
2026-10-10 17:46:17 +03:00
parent 8a77fc02b2
commit 629535ba04
16 changed files with 345 additions and 401 deletions

View File

@@ -1,31 +1,3 @@
export interface Uptime {
readonly months: number;
readonly days: number;
}
/** Whole calendar months plus remaining days since `start`, clamped to zero. */
export function uptimeSince(start: Date, now: Date): Uptime {
if (now.getTime() <= start.getTime()) return { months: 0, days: 0 };
let months = (now.getUTCFullYear() - start.getUTCFullYear()) * 12 + (now.getUTCMonth() - start.getUTCMonth());
let anchor = addMonths(start, months);
if (anchor.getTime() > now.getTime()) {
months -= 1;
anchor = addMonths(start, months);
}
const days = Math.floor((now.getTime() - anchor.getTime()) / 86_400_000);
return { months, days };
}
function addMonths(date: Date, months: number): Date {
return new Date(Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth() + months,
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
));
}
const clockFormat = new Intl.DateTimeFormat('en-GB', {
hour: '2-digit',
minute: '2-digit',
@@ -33,6 +5,7 @@ const clockFormat = new Intl.DateTimeFormat('en-GB', {
timeZone: 'Europe/Bucharest',
});
/** Local Bucharest time as HH:MM (24h), for the decorative dash clock. */
export function bucharestClock(now: Date): string {
return clockFormat.format(now);
}

15
src/lib/route-plate.ts Normal file
View File

@@ -0,0 +1,15 @@
import type { PublicProject } from '@/content/types';
/**
* Decorative registration plate for a project, e.g. "05.1 / PRIVATE / ACTIVE".
* Built only from real content fields; rendered through CSS generated content.
*/
export function routePlate(
exit: string | undefined,
index: number,
project: Pick<PublicProject, 'visibility' | 'maturity'>,
): string {
const code = exit === undefined ? `${index + 1}` : `${exit}.${index + 1}`;
const maturity = project.maturity.replace(/-/gu, ' ');
return `${code} / ${project.visibility} / ${maturity}`.toUpperCase();
}