v3: overhauled journal view
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s

This commit is contained in:
khannurien
2026-06-27 10:57:12 +00:00
parent 4cb537f4d9
commit 01940e14be
6 changed files with 377 additions and 239 deletions

View File

@@ -0,0 +1,61 @@
import type { Dump } from "../model.ts";
import { hotScore } from "./hotScore.ts";
/**
* Grid footprint of a journal entry in the editorial mosaic.
* feature — 2×2 hero tall — 1×2 portrait
* wide — 2×1 banner square — 1×1 unit
* The footprint only describes the grid span; how a card is *rendered*
* (image / quote / text) is derived separately from its content.
*/
export type JournalShape = "feature" | "wide" | "tall" | "square";
export interface JournalEntry {
dump: Dump;
shape: JournalShape;
}
/** A dump that can carry a real preview image (file image or rich thumbnail). */
export function hasThumbnail(dump: Dump): boolean {
if (dump.kind === "file" && dump.fileMime?.startsWith("image/")) return true;
return !!dump.richContent?.thumbnailUrl;
}
/** A dump whose comment is substantial enough to feature as a pull-quote. */
export function hasQuote(dump: Dump): boolean {
return !!dump.comment && dump.comment.trim().length > 0;
}
/**
* Pick a grid footprint for a single entry.
*
* Variety is composed, not tiered: a repeating 11-step rhythm spreads tall and
* wide accents through the feed instead of bucketing every card by score. The
* rhythm is content-aware — only dumps with a thumbnail are allowed to claim the
* image-forward `tall`/`wide`/`feature` footprints; everything else stays a
* compact `square` so quote and text cards never stretch awkwardly.
*/
function pickShape(dump: Dump, index: number): JournalShape {
const thumb = hasThumbnail(dump);
if (index === 0) return thumb ? "feature" : "wide";
const beat = index % 11;
if (thumb) {
if (beat === 2 || beat === 7) return "tall";
if (beat === 0 || beat === 5) return "wide";
return "square";
}
// No thumbnail: an occasional wide quote keeps the rhythm from flattening.
if (beat === 3) return "wide";
return "square";
}
/**
* Sort dumps by hotness, then lay them out as an editorial mosaic: one hero
* feature followed by an asymmetric mix of banners, portraits, squares and
* pull-quotes. Dense grid auto-flow backfills the gaps these spans create.
*/
export function composeJournal(dumps: Dump[]): JournalEntry[] {
const sorted = [...dumps].sort((a, b) => hotScore(b) - hotScore(a));
return sorted.map((dump, index) => ({ dump, shape: pickShape(dump, index) }));
}