// Mosaic site — FEED page (direction F3: the comparison IS the front page)
// Exports: FeedPage
// Each story links to its permanent static page at /topics/{slug} (crawlable + shareable).
const topicHref = (s) => "/topics/" + (s.slug || s.id);
// Use the real generated hero (assets/heroes/{id}.png) when present; else the procedural SArt collage.
const Art = ({ story, radius = 0, style }) => story.hero
?
: ;
const LensColumns = ({ story, mode, bare, onOpen }) => (
{story.framings.map((f, i) => (
{f.t}
{f.b}
{f.q && (
{f.q}
— {f.a}
)}
{f.takes && f.takes.length > 0 && (
{f.takes.map((t, k) => (
{t.voice}{t.view}
“{mdInline(t.evidence)}”
))}
)}
{f.srcs && f.srcs.length > 0 && (
{f.n} source{f.n > 1 ? "s" : ""}
)}
))}
);
// Accordion queue row — click to expand its framings inline; only one open at a time.
const QueueRow = ({ story, mode, density, expanded, onToggle, onOpenStory }) => {
const total = story.srcCount || story.framings.reduce((s, f) => s + f.n, 0);
const allSrcs = [...new Set(story.framings.flatMap((f) => f.srcs))];
return (