/* RADAR1 — full-frame tile packages (fill the hero inset) */
const DSt = window.Radar1DesignSystem_1f4661;
/* Shared package chrome: header band + washed backdrop */
function TileShell({ kicker, title, accent = "var(--signal-cyan)", right = null, children }) {
return (
);
}
/* ---------- WEATHER ---------- */
function TileWeather({ h24 }) {
const now = useNow();
const L = window.R1DATA.locales || [];
const featIdx = useCycle(L.length, 7000);
const feat = L[featIdx];
if (!feat) return null; /* no live locales yet */
const grid = Array.from({ length: 6 }, (_, k) => L[(featIdx + 1 + k) % L.length]);
useLucide([featIdx]);
return (
{L.length} CITIES · UPDATED {fmtTime(now, "UTC", true)} UTC}>
{/* featured locale */}
{feat.city.toUpperCase()}
LOCAL {fmtTime(now, feat.tz, h24)}
{feat.temp}°
{feat.cond}
H {feat.hi}°L {feat.lo}°
{feat.alert && ▲ ADVISORY IN EFFECT}
{/* grid of others */}
{grid.map((l, i) => (
{l.city.toUpperCase()}
{l.temp}°
{l.cond}
))}
);
}
/* ---------- SPORTS ---------- */
function TileSports() {
const S = window.R1DATA.sports;
if (!S || !S.board || !(S.board.games || []).length) return null; /* nothing on the board — stand down */
return (
{S.board.league}}>
{S.board.games.map((g, i) => (
{g.a}
g.bs ? "var(--text-strong)" : "var(--text-muted)", fontVariantNumeric: "tabular-nums" }}>{g.as}
–
g.as ? "var(--text-strong)" : "var(--text-muted)", fontVariantNumeric: "tabular-nums" }}>{g.bs}
{g.b}
{g.live && }{g.status}
))}
AROUND THE WORLD
{S.fixtures.map((f, i) => (
))}
);
}
/* ---------- NEWS ---------- */
function TileNews() {
const stories = window.R1DATA.stories || [];
if (!stories.length) return null; /* nothing to report — stand down */
const feat = stories[0], rest = stories.slice(1, 5);
return (
{feat.kicker}
{feat.text}
{rest.map((s, i) => (
{String(i + 2).padStart(2, "0")}
{s.kicker} · {s.time}
{s.text}
))}
);
}
/* ---------- STUDIO — ambient network scene, full frame ---------- */
/* No TileShell chrome and nothing overlaid: the scene carries its own faint
RADAR1 wall brand, and HeroInset already draws the standard hairline. */
function TileStudio() {
return ;
}
/* ---------- ELECTIONS ---------- */
function BoPBar({ label, d, r, und, forControl, total }) {
const pct = (n) => (n / total) * 100;
return (
{label}
{forControl} FOR CONTROL
DEM
{und} UNDECIDED
GOP
);
}
function TileElections() {
const E = window.R1DATA.election;
if (!E || !(E.races || []).length) return null; /* no live election content */
return (
LIVE RESULTS}>
RADAR1 DECISION DESK · PROJECTIONS UPDATE AS VOTES COUNT
SENATE RACEINDEMGOPCALL
{E.races.map((r, i) => {
const dLead = r.d.pct > r.r.pct;
return (
{r.state}
{r.pctIn}%
{r.d.name} {r.d.pct}%
{r.r.name} {r.r.pct}%
{r.call
? {r.call === "D" ? "DEM" : "GOP"}
: —}
);
})}
);
}
/* ---------- SOCIAL / VIEWER DESK ---------- */
function TileSocial() {
const msgs = window.R1DATA.social;
const shown = useCycle(1000, 5000);
if (!msgs || !msgs.length) return null; /* guard AFTER hooks so the hook order stays stable */
const list = Array.from({ length: 4 }, (_, k) => msgs[(shown + k) % msgs.length]);
return (
MODERATED · GLOBAL FEED}>
{list.map((m, i) => (
{m.handle[1].toUpperCase()}
{m.handle}
{m.loc.toUpperCase()} · {m.time}
{m.msg}
))}
);
}
/* ---------- CUT SPOT — produced storyboard, plays window.R1DATA.cutspot ---------- */
const CUTSPOT_FRAME = {
position: "absolute", inset: 0, overflow: "hidden",
background: "radial-gradient(130% 120% at 22% 0%, #10233C 0%, #081120 70%)",
};
/* "41 CHICKS — a record season" -> { stat, unit, body } when the slide carries
only `text`; explicit slide.stat / slide.unit always win */
function cutspotStatParts(slide) {
if (slide.stat) return { stat: slide.stat, unit: slide.unit || "", body: slide.text || "" };
const text = String(slide.text || "");
const sep = text.search(/[—–:·]/);
const head = (sep >= 0 ? text.slice(0, sep) : text).trim();
const body = sep >= 0 ? text.slice(sep + 1).trim() : "";
const m = head.match(/^([~$+\-]?[\d][\d.,]*%?)\s*([\s\S]*)$/);
if (!m) return null;
const unit = m[2].trim();
if (unit.length > 24) return { stat: m[1], unit: "", body: body ? unit + " — " + body : unit };
return { stat: m[1], unit, body };
}
function CutSpotSlide({ slide, accent }) {
const type = slide.type;
if (type === "image" && slide.src) {
return (
{slide.text && (
{slide.text}
)}
{slide.credit && (
{slide.credit}
)}
);
}
if (type === "stat") {
const parts = cutspotStatParts(slide);
if (parts) {
return (
{parts.stat}
{parts.unit && {parts.unit}}
{parts.body &&
{parts.body}
}
);
}
/* un-parseable stat — fall through to the title treatment */
}
if (type === "quote") {
return (
”
“{slide.text}”
{slide.credit && (
{slide.credit}
)}
);
}
if (type === "list") {
const items = (Array.isArray(slide.items) ? slide.items : String(slide.text || "").split(/\n+/))
.map((s) => String(s).trim()).filter(Boolean).slice(0, 5);
return (
{items.map((it, i) => (
◆
{it}
))}
);
}
/* "title" and anything unrecognized: the big display card */
return (
);
}
function TileCutSpot() {
const spot = window.R1DATA.cutspot;
const slides = spot && Array.isArray(spot.slides) && spot.slides.length ? spot.slides : null;
/* content signature, not object identity — SSE re-sends must not restart the deck */
const sig = React.useMemo(() => { try { return spot ? JSON.stringify(spot) : ""; } catch (e) { return ""; } }, [spot]);
const [idx, setIdx] = React.useState(0);
React.useEffect(() => { setIdx(0); }, [sig]);
React.useEffect(() => {
const list = (window.R1DATA.cutspot || {}).slides;
if (!Array.isArray(list) || !list.length) return;
const cur = list[idx % list.length] || {};
const ms = Math.max(2, Number(cur.duration_s) || 8) * 1000;
const t = setTimeout(() => setIdx((i) => i + 1), ms); /* modulo below — the deck loops */
return () => clearTimeout(t);
}, [sig, idx]);
if (!slides) return null;
const shown = idx % slides.length;
const slide = slides[shown];
const accent = spot.accent || "var(--signal-cyan)";
const sources = (spot.sources || []).map((s) => String(s).toUpperCase()).filter(Boolean);
return (
SPOT · {shown + 1} / {slides.length}}>
{sources.length > 0 && (
SOURCES · {sources.join(" · ")}
)}
{slides.map((_, i) => (
))}
);
}
const TILES = {
weather: { label: "WEATHER", comp: TileWeather },
sports: { label: "SPORTS", comp: TileSports },
news: { label: "NEWS", comp: TileNews },
studio: { label: "STUDIO", comp: TileStudio },
elections: { label: "ELECTIONS", comp: TileElections },
social: { label: "SOCIAL", comp: TileSocial },
cutspot: { label: "SPOT", comp: TileCutSpot },
};
Object.assign(window, { TILES, TileWeather, TileSports, TileNews, TileStudio, TileElections, TileSocial, TileCutSpot, BoPBar, TileShell });