// Mosaic Mobile — narrative comparison bottom sheet
// Depends on: framingColor, tickLabel (site-shared), Moodline, MFavRow (mobile-cards)
// Exports: StorySheet
const StorySheet = ({ story, mode, onClose }) => {
const [open, setOpen] = React.useState(false);
React.useEffect(() => {
const r = requestAnimationFrame(() => setOpen(true));
const onKey = (e) => { if (e.key === "Escape") close(); };
window.addEventListener("keydown", onKey);
return () => { cancelAnimationFrame(r); window.removeEventListener("keydown", onKey); };
}, []);
const close = () => { setOpen(false); setTimeout(onClose, 320); };
return (
{story.hero
?

:
}
{story.cat} · updated {story.upd}
{story.title}
{tickLabel(story, mode)}
How it's framed · {story.framings.length} frames
{story.framings.map((f, i) => (
{f.t}
{f.b}
{f.q && (
{f.q}
— {f.a}
)}
{f.n} source{f.n > 1 ? "s" : ""}
))}
{story.body_md ? (
) : (
The fault line
{story.fault}
The facts — what everyone agrees on
{story.facts.map((f, i) => (
✓{f}
))}
The blind spot
{story.blind}
)}
{(() => {
const rec = recordOf(story);
const total = rec.reduce((s, g) => s + g.items.length, 0);
return (
The full record · {total} items, grouped by framing
{rec.map((g, gi) => {
const f = story.framings[g.f];
return (
{f.t}
{g.items.length}
{g.items.map((it, i) => (
{(FAVS[it.s] || {}).l || "?"}
{it.h || (FAVS[it.s] || {}).n}
{it.d}
))}
);
})}
);
})()}
);
};
Object.assign(window, { StorySheet });