// Mosaic Mobile — app shell: header, feed, bottom tab bar, sources, sheet state // Depends on: SITE_STORIES, SOURCE_DIR, FAVS (site-data), CardReader/CardPulse (mobile-cards), // StorySheet (mobile-sheet) // Exports: MosaicApp const M_TABS = [ { k: "today", label: "Today", icon: "ph-newspaper", cat: null }, { k: "politics", label: "Politics", icon: "ph-bank", cat: "Politics" }, { k: "economy", label: "Economy", icon: "ph-trend-up", cat: "Economy" }, { k: "sources", label: "Sources", icon: "ph-list-bullets", sources: true }, ]; const SourcesScreen = () => { // One flat list — filter chips do the grouping, no section headings (parity with web). const all = [ ...SOURCE_DIR.outlets.map((r) => ({ ...r, g: "outlets" })), ...SOURCE_DIR.independent.map((r) => ({ ...r, g: "independent" })), ...SOURCE_DIR.youtube.map((r) => ({ ...r, g: "youtube" })), ...SOURCE_DIR.international.map((r) => ({ ...r, g: "international" })), ]; const [filter, setFilter] = React.useState("all"); const rows = filter === "all" ? all : all.filter((r) => r.g === filter); const chips = [["all", "All"], ["outlets", "Outlets"], ["independent", "Independent"], ["youtube", "YouTube"], ["international", "International"]]; return (
Source directory · who owns what

Sources

Verifiable ownership facts only. Where a source leans is shown per story, based on what it actually published.

{chips.map(([k, label]) => ( ))}
{rows.map((r, i) => { const f = FAVS[r.f] || { l: "?", bg: "#666", n: r.f }; return (
{f.l} {f.n} {r.k} · {r.t} {r.c}
); })}
); }; function MosaicApp({ variant, theme, mode, onTheme }) { const [tab, setTab] = React.useState("today"); const feedRef = React.useRef(null); const current = M_TABS.find((t) => t.k === tab); const stories = current.sources ? [] : SITE_STORIES.filter((s) => !current.cat || s.cat === current.cat); const Card = variant === "pulse" ? CardPulse : CardReader; const goTab = (k) => { setTab(k); if (feedRef.current) feedRef.current.scrollTop = 0; }; return (
Mosaic
{current.sources ? : (
{current.k === "today" ? "Today · " + new Date().toLocaleDateString("en-GB", { day: "numeric", month: "long" }) : current.label} {stories.length} stories
{stories.map((s) => { window.location.href = "/topics/" + (s.slug || s.id); }} />)}
)}
); } Object.assign(window, { MosaicApp, SourcesScreen });