v3: overhauled journal view
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 42s
This commit is contained in:
61
src/utils/journalLayout.ts
Normal file
61
src/utils/journalLayout.ts
Normal 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) }));
|
||||
}
|
||||
Reference in New Issue
Block a user