// 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 });