diff --git a/docs/prd-life-blocks-always-on.md b/docs/prd-life-blocks-always-on.md new file mode 100644 index 0000000..828e883 --- /dev/null +++ b/docs/prd-life-blocks-always-on.md @@ -0,0 +1,237 @@ +# PRD: Always-On Life Blocks — Automatic Multi-Scale Countdown + +**Status:** Ready for implementation +**Section affected:** Life Blocks (`#life-blocks-section`, `script.js`, `styles.css`) + +--- + +## 1. Problem + +The current Life Blocks section requires the user to click through four drill-down levels (days → hours → minutes → seconds) to see the real-time granular destruction of time. +Most users never reach the seconds view — the most viscerally compelling level — because the interaction model is hidden behind clicks and a breadcrumb they may not notice. + +--- + +## 2. Goal + +Deliver the full emotional impact of time being consumed **without any user interaction**, while keeping the existing drill-down view intact for users who want to explore manually. + +--- + +## 3. Proposed Solution: Always-On Temporal Stack + +Add a new **"Now" panel** directly below the existing Life Blocks heading. +The panel renders six compact, labelled rows of blocks — one row per time scale — all visible simultaneously, all animating in real time as soon as the section enters the viewport. + +| Row | Unit | Blocks | Block = | +|-----|------|--------|---------| +| Years | 1 row | dynamic — see §3.1 | 1 year each | +| Months | 1 row | 12 blocks | 1 month of the current year | +| Days | 1 row | 31 blocks | 1 day of the current month | +| Hours | 1 row | 24 blocks | 1 hour of today | +| Minutes | 1 row | 60 blocks | 1 minute of the current hour | +| Seconds | 1 row | 60 blocks | 1 second of the current minute | + +### 3.1 Year row — dynamic extinction window (OQ-1 resolved) + +The year row is computed from the extinction date rather than a fixed ±5-year window. +This keeps the row thematically consistent with the rest of the page and makes the shrinking number of future blocks visually meaningful as extinction approaches. + +**Algorithm:** +1. Compute `extinctionYear` from `lbExtinctionMs()`. +2. Show every year from `currentYear − 2` to `extinctionYear` (inclusive), capped at **30 blocks** to avoid an unmanageable row when extinction is still decades away. +3. If `extinctionYear − currentYear + 2 > 30`, append an overflow label (e.g. `+Ny`) exactly as the existing days grid does. +4. The dying block is the current calendar year; all years before it are dead; all years after are future. + +Each row always shows **dead** (elapsed), **dying** (current), and **future** blocks using the existing visual vocabulary (`lb-dead`, `lb-dying`, `lb-future`, `lb-exploding`). +The dying block in every row fills progressively and explodes at its natural boundary (second, minute, hour, day, month, year). + +The existing click-through drill-down panel is **preserved unchanged** below the new panel, with its own heading and breadcrumb. + +--- + +## 4. User Experience + +### 4.1 Scroll-to-activate +- Activate on IntersectionObserver (threshold ≈ 20 % visible) — no click required. +- RAF loop for the new panel starts on first intersection, pauses when the section leaves the viewport (performance). + +### 4.2 Visual hierarchy +- New panel sits inside `#life-blocks-section`, before the existing `#lb-container`. +- A single shared section heading and intro paragraph describe both panels. +- The new panel uses a slightly subdued palette (reduced opacity on future blocks, narrower block size) so it reads as a quick overview rather than the full interactive grid. + +### 4.3 Animations — staggered cascade (OQ-4 resolved) + +- Reuse `lb-exploding` keyframe and `lb-pulse-dying` animation from existing CSS. +- At the seconds boundary: the dying second block explodes, the next becomes dying. +- At the minutes boundary (and every higher boundary), explosions **cascade upward with a 100 ms stagger per level**: + - t = 0 ms → seconds row explodes + - t = 100 ms → minutes row explodes + - t = 200 ms → hours row explodes + - t = 300 ms → days row explodes + - t = 400 ms → months row explodes + - t = 500 ms → years row explodes +- Each level only fires if its own boundary has actually been crossed (e.g. an hour boundary only triggers the hours/days/months/years levels). +- After each staggered explosion the affected row re-renders so the new dying block is correct. + +### 4.4 Row labels +Each row has a short left-aligned label (`YEARS`, `MONTHS`, `DAYS`, `HOURS`, `MINS`, `SECS`) in the existing monospace font at subdued opacity, sized to match the existing `.lb-info` style. + +### 4.5 Click-to-drill-down (OQ-2 resolved) + +Clicking any block in the always-on stack panel navigates the **existing drill-down panel** to the corresponding time scale and smoothly scrolls it into view: + +- Click a block in the **years row** → drill-down panel navigates to `days` level (highest supported level) and scrolls to the panel. +- Click a block in the **months or days row** → drill-down panel navigates to `days` level; the specific day block matching the clicked date is highlighted briefly (pulse animation). +- Click a block in the **hours row** → drill-down panel navigates to `hours` level for today (day offset 0). +- Click a block in the **minutes row** → drill-down panel navigates to `minutes` level for the current hour. +- Click a block in the **seconds row** → drill-down panel navigates to `seconds` level for the current minute. + +The link is one-way (stack panel → drill-down panel only). The drill-down panel's own state (`lb` object) is updated directly; `lbFullRender()` and `lbRenderBreadcrumb()` are called after the state change. + +Dead blocks (elapsed time units) in the always-on stack are **not** clickable (consistent with the existing drill-down panel). + +### 4.6 Accessibility +- Each row is a `
` inside a `
`. +- Dying blocks carry a live `aria-label` updated each second (e.g., `"Second 42 of 60 — active"`). +- The entire always-on panel has `aria-live="polite"` with update throttling (update aria labels at most once per second). +- Future (clickable) blocks in the stack have `tabindex="0" role="button"` and an `aria-label` that describes the navigation action (e.g., `"Jump drill-down to minutes view"`). + +--- + +## 5. Out of Scope + +- No changes to the existing drill-down panel's logic or data model beyond the `lb` state updates needed for click-to-drill-down. +- No new npm dependencies. +- No changes to `death-clock-core.js` (pure functions are already sufficient). + +--- + +## 6. Technical Design + +### 6.1 New DOM structure (in `index.html`) + +```html + +
+
+
+
+
+
+
+
+``` + +Placed in `index.html` immediately before `