import React, { useEffect, useMemo, useRef, useState } from "react"; const START_COLOR = { r: 26, g: 47, b: 107 }; // #1A2F6B const END_COLOR = { r: 128, g: 0, b: 32 }; // #800020 const TOTAL_DAYS = 300; const START_DATE = new Date(2026, 4, 8); const END_DATE = new Date(2027, 4, 28); // TEMPORAL VISIBILITY WINDOW // These dates appear faded/lightened on the timeline const FADED_START = new Date(2026, 4, 28); const FADED_END = new Date(2027, 2, 3); function interpolateColor(t: number) { const r = Math.round(START_COLOR.r + (END_COLOR.r - START_COLOR.r) * t); const g = Math.round(START_COLOR.g + (END_COLOR.g - START_COLOR.g) * t); const b = Math.round(START_COLOR.b + (END_COLOR.b - START_COLOR.b) * t); return `rgb(${r}, ${g}, ${b})`; } function formatDate(date: Date) { const d = date.getDate().toString().padStart(2, "0"); const m = (date.getMonth() + 1).toString().padStart(2, "0"); const y = date.getFullYear(); return `${d}/${m}/${y}`; } export default function App() { const [selectedDay, setSelectedDay] = useState(null); const [hoveredDay, setHoveredDay] = useState(null); const [input, setInput] = useState(""); const [predictions, setPredictions] = useState>({}); // Active day controlled by Surface Dial / wheel navigation const [activeDay, setActiveDay] = useState(150); // Timeline container reference const timelineRef = useRef(null); const days = useMemo(() => { return Array.from({ length: TOTAL_DAYS }, (_, i) => { const dayNumber = i + 1; const t = i / (TOTAL_DAYS - 1); // Generate gradient color for each temporal segment const color = interpolateColor(t); const currentDate = new Date(START_DATE); currentDate.setDate(currentDate.getDate() + i); // Temporal fading effect // The original color still exists underneath const isFaded = currentDate >= FADED_START && currentDate <= FADED_END; const visibleColor = color; return { index: dayNumber, color, visibleColor, isFaded, position: (i / (TOTAL_DAYS - 1)) * 100, }; }); }, []); const currentDate = useMemo(() => { if (selectedDay === null) return null; const d = new Date(START_DATE); d.setDate(d.getDate() + (selectedDay - 1)); return d; }, [selectedDay]); /* SURFACE DIAL / WHEEL NAVIGATION - Rotating right moves forward one day - Rotating left moves backward one day - Movement snaps discretely day-by-day - Clicking / pressing opens the entry form Microsoft Surface Dial behaves similarly to a wheel event, so this interaction remains installation-friendly while also supporting mouse wheels and other rotational controllers. */ useEffect(() => { const timelineElement = timelineRef.current; if (!timelineElement) return; let isThrottled = false; const handleWheel = (e: WheelEvent) => { // Prevent accidental rapid movement if (isThrottled) return; // Only capture horizontal-style wheel intention if (Math.abs(e.deltaY) < 2) return; isThrottled = true; setTimeout(() => { isThrottled = false; }, 45); // Rotate RIGHT → move toward Day 1 if (e.deltaY > 0) { setActiveDay((prev) => Math.max(prev - 1, 1)); } // Rotate LEFT → move toward Day 300 if (e.deltaY < 0) { setActiveDay((prev) => Math.min(prev + 1, TOTAL_DAYS)); } }; // Surface Dial / wheel listener timelineElement.addEventListener("wheel", handleWheel, { passive: true, }); // SURFACE DIAL PRESS SIMULATION // We use keyboard Enter/Space instead of attaching // another click listener to the whole timeline. // The previous version created event conflicts with // the individual day bars and blocked reliable clicking. const handleKeyPress = (e: KeyboardEvent) => { if (e.key === "Enter" || e.key === " ") { setSelectedDay(activeDay); } }; window.addEventListener("keydown", handleKeyPress); return () => { timelineElement.removeEventListener("wheel", handleWheel); window.removeEventListener("keydown", handleKeyPress); }; }, [activeDay]); function addPrediction(dayIndex: number) { if (!input.trim()) return; setPredictions((prev) => { const existing = prev[dayIndex] || []; return { ...prev, [dayIndex]: [...existing, input.trim()], }; }); setInput(""); } return (
{/* HEADER */}

Speculative Timeline Interface

Click a segment to attach an expectation to time

{/* TIMELINE */}
{ if (timelineRef.current) { timelineRef.current.scrollLeft += e.deltaY; } }} >
{days.map((d) => { const hoverDate = new Date(START_DATE); hoverDate.setDate(hoverDate.getDate() + (d.index - 1)); return (
{ // Immediate reliable interaction for tiny timeline segments // onMouseDown works better than onClick for very narrow elements setActiveDay(d.index); setSelectedDay(d.index); }} onMouseEnter={() => setHoveredDay(d.index)} onMouseLeave={() => setHoveredDay(null)} style={{ background: d.visibleColor, width: "8.8cm", minWidth: "8.8cm", height: "100%", cursor: "pointer", position: "relative", transition: "all 0.18s ease", borderRight: "1px solid rgba(255,255,255,0.12)", borderLeft: "1px solid rgba(0,0,0,0.04)", ...(d.index <= 150 ? { borderTopLeftRadius: "10px", borderBottomLeftRadius: "10px", borderTopRightRadius: "0px", borderBottomRightRadius: "0px", } : { borderTopLeftRadius: "0px", borderBottomLeftRadius: "0px", borderTopRightRadius: "10px", borderBottomRightRadius: "10px", }), filter: d.index < 100 ? hoveredDay === d.index || activeDay === d.index ? "brightness(1.12) saturate(1.05) opacity(0.72)" : "brightness(1.08) saturate(1.02) opacity(0.58)" : hoveredDay === d.index || activeDay === d.index ? "brightness(1.05)" : "brightness(1)", }} className="relative overflow-hidden" > {/* ACTIVE DAY INDICATOR */} {activeDay === d.index && (
)} {/* HOVER + SURFACE DIAL TOOLTIP */} {(hoveredDay === d.index || activeDay === d.index) && (
Day {d.index} • {formatDate(hoverDate)}
)}
); })}
{/* MODAL */} {selectedDay !== null && (
setSelectedDay(null)} >
e.stopPropagation()} >

Day {selectedDay}

{currentDate ? formatDate(currentDate) : ""}

{(predictions[selectedDay] || []).length > 0 && ( <>

Public expectations

{(predictions[selectedDay] || []).map((p, i) => (
{p}
))}
)}