fuzzyworld

when-am-i / commit

style: align timeline controls and unify the two sliders

d1eb8ad54080c70bc50d701e0b6345e065cb6e22 · Pavel Safronov · 2026-06-18 18:09:40 -0700 · parents 143b640c4860

style: align timeline controls and unify the two sliders

- Unify the day-of-year and year sliders into one shared .timeline-slider
  style: transparent track with a thin playhead thumb over a track drawn
  behind it, so the thumb lines up with the month ticks and era bands.
- Span the intro paragraph and date picker to the full container width so
  the text and controls share the same left/right edges as the map and
  the answer panels.
- Capture screenshots full-page so the answer panels aren't cut off.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
 screenshots/app/01-landing.png                     | Bin 341291 -> 339274 bytes
 screenshots/app/02-boston-1650.png                 | Bin 1571824 -> 1728487 bytes
 screenshots/app/03-boston-1800.png                 | Bin 1573476 -> 1741247 bytes
 .../boston/01-algonquian-lunar-calendar.png        | Bin 1430463 -> 1585654 bytes
 screenshots/calendars/boston/02-julian.png         | Bin 1430040 -> 1585734 bytes
 screenshots/calendars/boston/03-gregorian.png      | Bin 1429997 -> 1593269 bytes
 scripts/generate-screenshots.mts                   |   2 +-
 scripts/screenshot-calendar.mts                    |   2 +-
 src/App.tsx                                        |   6 +-
 src/components/DatePicker.tsx                      |  63 ++++++++++++---------
 src/index.css                                      |  21 ++++---
 11 files changed, 54 insertions(+), 40 deletions(-)

diff --git a/screenshots/app/01-landing.png b/screenshots/app/01-landing.png
index ac6a6da..7bdc697 100644
Binary files a/screenshots/app/01-landing.png and b/screenshots/app/01-landing.png differ
diff --git a/screenshots/app/02-boston-1650.png b/screenshots/app/02-boston-1650.png
index f3b6617..edb9928 100644
Binary files a/screenshots/app/02-boston-1650.png and b/screenshots/app/02-boston-1650.png differ
diff --git a/screenshots/app/03-boston-1800.png b/screenshots/app/03-boston-1800.png
index 7c9378b..0380c39 100644
Binary files a/screenshots/app/03-boston-1800.png and b/screenshots/app/03-boston-1800.png differ
diff --git a/screenshots/calendars/boston/01-algonquian-lunar-calendar.png b/screenshots/calendars/boston/01-algonquian-lunar-calendar.png
index 2400e16..906abc4 100644
Binary files a/screenshots/calendars/boston/01-algonquian-lunar-calendar.png and b/screenshots/calendars/boston/01-algonquian-lunar-calendar.png differ
diff --git a/screenshots/calendars/boston/02-julian.png b/screenshots/calendars/boston/02-julian.png
index 63c16a1..6790485 100644
Binary files a/screenshots/calendars/boston/02-julian.png and b/screenshots/calendars/boston/02-julian.png differ
diff --git a/screenshots/calendars/boston/03-gregorian.png b/screenshots/calendars/boston/03-gregorian.png
index 7eaeb49..3fb646c 100644
Binary files a/screenshots/calendars/boston/03-gregorian.png and b/screenshots/calendars/boston/03-gregorian.png differ
diff --git a/scripts/generate-screenshots.mts b/scripts/generate-screenshots.mts
index 80710cb..787a27e 100644
--- a/scripts/generate-screenshots.mts
+++ b/scripts/generate-screenshots.mts
@@ -39,7 +39,7 @@ async function run() {
             await page.goto(`${BASE_URL}${state.query}`, { waitUntil: 'networkidle' });
             await page.waitForTimeout(800); // let map tiles settle
             const file = path.join(OUT_DIR, `${state.name}.png`);
-            await page.screenshot({ path: file });
+            await page.screenshot({ path: file, fullPage: true });
             console.log(`  ✅  ${state.name}  —  ${state.desc}`);
         }
 
diff --git a/scripts/screenshot-calendar.mts b/scripts/screenshot-calendar.mts
index 0749105..81dd176 100644
--- a/scripts/screenshot-calendar.mts
+++ b/scripts/screenshot-calendar.mts
@@ -97,7 +97,7 @@ async function run() {
             await page.goto(`${BASE_URL}${query}`, { waitUntil: 'networkidle' });
             await page.waitForTimeout(800);
             const name = `${String(i + 1).padStart(2, '0')}-${slug(era.calendar)}.png`;
-            await page.screenshot({ path: path.join(outDir, name) });
+            await page.screenshot({ path: path.join(outDir, name), fullPage: true });
             console.log(`  ✅  ${name}  —  ${era.name} (${year} ${eraParam})`);
         }
 
diff --git a/src/App.tsx b/src/App.tsx
index a881d9e..92a2a8d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -96,11 +96,11 @@ function App() {
 
     return (
         <div style={{ maxWidth: 880, margin: '0 auto', padding: '24px 20px 48px' }}>
-            <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
-                <img src={calendar} alt="Calendar icon" style={{ width: 48, height: 48 }} />
+            <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
                 <h1 style={{ margin: 0 }}>What Year Is It?</h1>
+                <img src={calendar} alt="" style={{ width: 40, height: 40 }} />
             </div>
-            <p style={{ maxWidth: 640 }}>
+            <p style={{ marginTop: 12 }}>
                 You&rsquo;re a time traveler who lands somewhere in history. You stop a local
                 and ask: <em>&ldquo;What year is it?&rdquo;</em> Their answer depends on where
                 you are &mdash; and who you ask. Pick a date and a spot on the map to begin.
diff --git a/src/components/DatePicker.tsx b/src/components/DatePicker.tsx
index a4acc28..912bff1 100644
--- a/src/components/DatePicker.tsx
+++ b/src/components/DatePicker.tsx
@@ -45,7 +45,13 @@ function pct(year: number): number {
 }
 
 const barLabel: React.CSSProperties = { margin: 0, fontSize: '0.8em', color: '#666' };
-const rangeStyle: React.CSSProperties = { width: '100%', margin: 0 };
+const trackStyle: React.CSSProperties = {
+    position: 'absolute',
+    inset: 0,
+    background: '#e9ecef',
+    borderRadius: 3,
+    overflow: 'hidden',
+};
 
 const DatePicker: React.FC<DatePickerProps> = ({ value, onChange, yearBands }) => {
     const astroYear = toHistoricalDate(value).year;
@@ -70,25 +76,39 @@ const DatePicker: React.FC<DatePickerProps> = ({ value, onChange, yearBands }) =
     const { lanes, count: laneCount } = assignLanes(yearBands, (b) => b.start, (b) => b.end);
 
     return (
-        <div style={{ margin: '12px 0', maxWidth: 560 }}>
+        <div style={{ margin: '12px 0' }}>
             <div style={{ fontSize: '1.3em', fontWeight: 700, marginBottom: 12 }}>
                 {MONTHS[value.month - 1]} {value.day}, {yearLabel(astroYear)}
             </div>
 
-            {/* Day-of-year bar */}
+            {/* Day-of-year bar: grey track with a fill up to the current day */}
             <p style={barLabel}>Day of year</p>
-            <input
-                type="range"
-                style={rangeStyle}
-                min={1}
-                max={yearDays}
-                step={1}
-                value={doy}
-                onChange={(e) => emitDoy(Number(e.target.value))}
-                aria-label="Day of year"
-                aria-valuetext={`${MONTHS[value.month - 1]} ${value.day}`}
-            />
-            <div style={{ position: 'relative', height: 16, marginBottom: 16 }}>
+            <div style={{ position: 'relative', height: 28 }}>
+                <div style={trackStyle}>
+                    <div
+                        style={{
+                            position: 'absolute',
+                            left: 0,
+                            width: `${(doy / yearDays) * 100}%`,
+                            top: 0,
+                            bottom: 0,
+                            background: '#bcd9f0',
+                        }}
+                    />
+                </div>
+                <input
+                    className="timeline-slider"
+                    type="range"
+                    min={1}
+                    max={yearDays}
+                    step={1}
+                    value={doy}
+                    onChange={(e) => emitDoy(Number(e.target.value))}
+                    aria-label="Day of year"
+                    aria-valuetext={`${MONTHS[value.month - 1]} ${value.day}`}
+                />
+            </div>
+            <div style={{ position: 'relative', height: 16, marginTop: 4, marginBottom: 16 }}>
                 {MONTH_INITIALS.map((initial, i) => {
                     const left = ((dayOfYear(astroYear, i + 1, 1) - 1) / yearDays) * 100;
                     return (
@@ -105,15 +125,7 @@ const DatePicker: React.FC<DatePickerProps> = ({ value, onChange, yearBands }) =
             {/* Year bar with era-color bands behind the track */}
             <p style={barLabel}>Year</p>
             <div style={{ position: 'relative', height: 28 }}>
-                <div
-                    style={{
-                        position: 'absolute',
-                        inset: 0,
-                        background: '#e9ecef',
-                        borderRadius: 3,
-                        overflow: 'hidden',
-                    }}
-                >
+                <div style={trackStyle}>
                     {lanes.map(({ item: band, lane }, i) => {
                         const left = pct(band.start);
                         const width = pct(band.end) - left;
@@ -135,8 +147,7 @@ const DatePicker: React.FC<DatePickerProps> = ({ value, onChange, yearBands }) =
                     })}
                 </div>
                 <input
-                    className="year-range"
-                    style={{ position: 'relative', zIndex: 1 }}
+                    className="timeline-slider"
                     type="range"
                     min={MIN_YEAR}
                     max={MAX_YEAR}
diff --git a/src/index.css b/src/index.css
index 5672d0e..fee77ef 100644
--- a/src/index.css
+++ b/src/index.css
@@ -7,37 +7,40 @@ body {
     background: #f8f9fa;
 }
 
-/* Year slider: transparent track so the era-color bands behind it show
-   through, with a tall "playhead" thumb. */
-.year-range {
+/* Timeline sliders (day-of-year and year): a transparent-track range input with
+   a thin "playhead" thumb, laid over a track drawn behind it. Shared so both
+   bars look identical and the thin thumb lines up with the ticks/bands. */
+.timeline-slider {
     -webkit-appearance: none;
     appearance: none;
+    position: relative;
+    z-index: 1;
     width: 100%;
     height: 28px;
     margin: 0;
     background: transparent;
     cursor: pointer;
 }
-.year-range::-webkit-slider-runnable-track {
+.timeline-slider::-webkit-slider-runnable-track {
     height: 28px;
     background: transparent;
 }
-.year-range::-moz-range-track {
+.timeline-slider::-moz-range-track {
     height: 28px;
     background: transparent;
 }
-.year-range::-webkit-slider-thumb {
+.timeline-slider::-webkit-slider-thumb {
     -webkit-appearance: none;
     appearance: none;
-    width: 4px;
+    width: 3px;
     height: 36px;
     margin-top: -4px;
     background: #1a1a1a;
     border: 1px solid #fff;
     border-radius: 2px;
 }
-.year-range::-moz-range-thumb {
-    width: 4px;
+.timeline-slider::-moz-range-thumb {
+    width: 3px;
     height: 36px;
     background: #1a1a1a;
     border: 1px solid #fff;