diff --git a/death-clock-core.js b/death-clock-core.js index 9ae989d..f27fea0 100644 --- a/death-clock-core.js +++ b/death-clock-core.js @@ -514,6 +514,80 @@ function sessionEquivalences(sessionTokens) { return list; } +// ============================================================ +// ACCELERATOR GAME β€” Pure Helpers +// ============================================================ + +/** + * All possible session challenge definitions. + * Each entry: { id, icon, label, desc, type, target, rewardDp } + * type: 'taps' | 'tokens' | 'combo' | 'speed' | 'upgrade' | 'co2' + */ +const SESSION_CHALLENGE_DEFS = [ + { id: 'rapid_fire', icon: 'πŸ‘†', label: 'Rapid Fire', desc: 'Tap 100 times', type: 'taps', target: 100, rewardDp: 200 }, + { id: 'billionaire', icon: 'πŸ’Ž', label: 'Token Billionaire', desc: 'Contribute 1 billion personal tokens', type: 'tokens', target: 1e9, rewardDp: 100 }, + { id: 'trillion', icon: 'πŸ’°', label: 'Trillion Touched', desc: 'Contribute 1 trillion personal tokens', type: 'tokens', target: 1e12, rewardDp: 1000 }, + { id: 'combo_king', icon: 'πŸ”₯', label: 'Combo King', desc: 'Hit 10Γ— combo 3 times', type: 'combo', target: 3, rewardDp: 500 }, + { id: 'speed_demon', icon: '⚑', label: 'Speed Demon', desc: 'Tap 50 times in under 10 seconds', type: 'speed', target: 50, rewardDp: 500 }, + { id: 'first_upgrade', icon: 'πŸ›’', label: 'Consumer Capitalism',desc: 'Purchase your first upgrade', type: 'upgrade', target: 1, rewardDp: 50 }, + { id: 'carbon_sprint', icon: 'πŸ’¨', label: 'Carbon Sprint', desc: 'Generate 1 tonne COβ‚‚-equivalent in one session', type: 'co2', target: 1000, rewardDp: 750 }, +]; + +/** + * Return the first personal milestone that the player has not yet crossed. + * @param {number} personalTokens + * @param {Array} milestones + * @returns {Object|null} + */ +function getNextMilestoneForPlayer(personalTokens, milestones) { + if (typeof personalTokens !== 'number' || !Array.isArray(milestones)) return null; + return milestones.find((m) => personalTokens < m.tokens) || null; +} + +/** + * Calculate the combo multiplier based on recent tap timestamps. + * Counts taps within the last 1,000 ms of the most recent tap, capped at 10. + * @param {number[]} tapTimestamps - array of epoch-ms tap times (oldest first) + * @returns {number} integer 1–10 + */ +function computeComboMultiplier(tapTimestamps) { + if (!Array.isArray(tapTimestamps) || tapTimestamps.length === 0) return 1; + const latest = tapTimestamps[tapTimestamps.length - 1]; + if (typeof latest !== 'number' || !isFinite(latest)) return 1; + const cutoff = latest - 1000; + const recent = tapTimestamps.filter((t) => typeof t === 'number' && t >= cutoff); + return Math.min(10, Math.max(1, recent.length)); +} + +/** + * Return 3 session challenges selected deterministically from SESSION_CHALLENGE_DEFS + * using a daily seed (changes once per UTC day). + * @param {number} [seedMs] - seed timestamp in ms (defaults to Date.now()) + * @returns {Array} exactly 3 challenge definition objects + */ +function getSessionChallenges(seedMs) { + const ts = typeof seedMs === 'number' && isFinite(seedMs) ? seedMs : Date.now(); + const dayBucket = Math.abs(Math.floor(ts / 86400000)); + const start = dayBucket % SESSION_CHALLENGE_DEFS.length; + const result = []; + for (let i = 0; i < 3; i++) { + result.push(SESSION_CHALLENGE_DEFS[(start + i) % SESSION_CHALLENGE_DEFS.length]); + } + return result; +} + +/** + * Format a Doom Points value into a human-readable string. + * @param {number} dp + * @returns {string} + */ +function formatDoomPoints(dp) { + if (typeof dp !== 'number' || isNaN(dp) || dp < 0) return '0 DP'; + if (dp >= 1e6) return (dp / 1e6).toFixed(1).replace(/\.0$/, '') + 'M DP'; + if (dp >= 1e3) return (dp / 1e3).toFixed(1).replace(/\.0$/, '') + 'K DP'; + return Math.round(dp) + ' DP'; +} + // ============================================================ // EXPORTS β€” CommonJS for Jest; window global for the browser // ============================================================ @@ -524,6 +598,7 @@ const DeathClockCore = { HISTORICAL_DATA, MILESTONES, RATE_SCHEDULE, + SESSION_CHALLENGE_DEFS, TOKEN_TIPS, formatTokenCount, formatTokenCountShort, @@ -540,6 +615,10 @@ const DeathClockCore = { generateEquivalences, calculatePersonalFootprint, sessionEquivalences, + getNextMilestoneForPlayer, + computeComboMultiplier, + getSessionChallenges, + formatDoomPoints, }; if (typeof module !== 'undefined' && module.exports) { diff --git a/docs/prd/prd-accelerate-the-doom.md b/docs/prd/prd-accelerate-the-doom.md new file mode 100644 index 0000000..8f8018c --- /dev/null +++ b/docs/prd/prd-accelerate-the-doom.md @@ -0,0 +1,260 @@ +# PRD: Accelerate the Doom πŸš€πŸ’€ + +## Overview + +Turn passive dread into active participation. Give visitors a one-tap "Feed the Machine" clicker +mechanic where every tap represents them personally contributing tokens to the global AI inference +flood. The darker irony: you *know* it's bad and you keep tapping anyway. Built to be fast, +frictionless, and finger-friendly on mobile. + +--- + +## Problem + +The site currently shows what AI is doing *to* the world, but the visitor is a bystander. Adding +an interactive clicker that lets visitors voluntarily accelerate the countdown makes the +complicity visceral β€” and makes the site dramatically more fun and shareable. Cookie Clicker +proved that people will enthusiastically click a button to destroy things if the numbers go up. + +--- + +## Goals + +- Give visitors a direct, tactile way to interact with the token counter +- Surface the dark irony: the visitor is choosing to accelerate environmental milestones +- Make the experience finger-friendly on mobile (large tap targets, haptic-style feedback) +- Integrate with the existing Doom Achievements badge system +- Keep the feature entirely client-side with no new runtime dependencies + +--- + +## Non-Goals + +- Modifying the real global counter β€” the clicker runs a separate "personal acceleration" track +- Persistent server-side leaderboards (localStorage best-score only, v2 for cloud board) +- Complex idle-game mechanics (cookie clicker auto-generators, prestige resets β€” v2) +- Actual AI inference (the tokens are fictional within the game layer) + +--- + +## Feature Description + +### Core Mechanic β€” "Feed the Machine" πŸ€– + +A new collapsible section **(or modal, TBD in design)** titled **"πŸš€ Accelerate the Doom"** contains +a large circular tap target β€” the **Big Red Button** β€” styled as a glowing reactor core or CPU chip. + +Every tap/click: +1. Adds tokens to a **personal acceleration counter** (`personalTokens`) +2. Applies a **tap multiplier** (starts at 1Γ—, upgrades increase it) +3. Triggers a brief CSS pulse animation on the button (visual feedback) +4. On mobile: fires a `navigator.vibrate(30)` haptic pulse (gracefully ignored if unsupported) +5. Increments a **Tap Combo** counter β€” rapid taps within 1 second build a combo multiplier up + to 10Γ— (combo resets if the user pauses > 1.5 s) + +The button label changes with the combo level: +- 1Γ— β€” "Feed the Machine" +- 2×–4Γ— β€” "Keep Going…" +- 5×–9Γ— β€” "FASTER! πŸ”₯" +- 10Γ— β€” "MAXIMUM DOOM ☒️" + +### Personal Acceleration Counter + +A stats strip below the button shows the player's running personal total: + +``` +πŸ€– You've personally contributed: + 1,240,000 tokens (β‰ˆ 0.0004 kWh Β· 0.17 g COβ‚‚ Β· 0.62 mL water) + Tap rate: 3.2/sec +``` + +These stats reuse `calculateEnvironmentalImpact()` from `death-clock-core.js`. + +### Upgrade Shop πŸ›’ + +Earn **Doom Points** (DP) equal to tokens contributed Γ· 1,000. Spend DP to buy +one-time multiplier upgrades, displayed as a mini "shop" of unlockable cards: + +| Upgrade | Cost (DP) | Effect | Flavour Text | +|---------|-----------|--------|--------------| +| πŸ–₯️ Extra GPU Rack | 10 | 2Γ— tokens per tap | "More cores, more chaos" | +| ⚑ Liquid Cooling Override | 50 | 5Γ— tokens per tap | "Ignore the thermal warnings" | +| 🌍 Global Data Centre | 200 | 10Γ— tokens per tap | "Every continent contributing" | +| πŸ›°οΈ Orbital Inference Array | 1,000 | 25Γ— tokens per tap | "Space itself regrets this" | +| 🧬 AGI Mode | 5,000 | 100Γ— tokens per tap | "It's writing its own prompts now" | + +Upgrades are stored in `localStorage` key `tokenDeathclockUpgrades` (JSON object of upgrade IDs +β†’ boolean). Shop cards use a disabled/greyed state when unaffordable. + +### Milestone Racing 🏁 + +The section header shows a **"Next milestone you could trigger"** target β€” the next upcoming +environmental milestone that the player's personal counter is approaching. A progress bar fills +as `personalTokens` approaches that threshold. + +When the player's personal counter crosses a milestone threshold: +- The milestone card in the Milestones section flashes with a special **"Accelerated by You"** + red pulse border for 5 seconds +- A distinct achievement toast fires: *"πŸš€ You personally triggered [Milestone Name]! Catastrophe achieved."* +- A new milestone-accelerator badge is earned (see Achievements integration below) + +Milestones are ordered ascending by `tokens` from the existing `MILESTONES` array in `death-clock-core.js`. +The player's first reachable milestone is the one with the smallest `tokens` value greater than +`personalTokens`. + +### Challenges ⚑ + +Three rotating daily/session challenges (seeded by `Date.now()` divided into 24-hour buckets) +give the player short-term goals. Examples: + +| Challenge | Goal | Reward | +|-----------|------|--------| +| Speed Demon | Reach 10 tap/sec for 3 consecutive seconds | 2Γ— DP bonus for 60 s | +| Trillion Touched | Contribute 1 trillion personal tokens | Unlock "Trillion Villain" badge | +| Combo King | Hit 10Γ— combo 3 times | +500 DP bonus | +| Carbon Sprint | Generate 1 kg COβ‚‚-equivalent in under 60 seconds | Special toast | +| Nocturnal Accelerator | Complete any challenge between 00:00–04:00 local time | Easter-egg badge | + +Challenges are rendered as a horizontal scrollable row of cards on mobile, visible above the +upgrade shop. A completed challenge card shows a green βœ” checkmark. Challenges reset per session +(no localStorage persistence, keeping the UX frictionless). + +### Best Score / Hall of Shame + +A single row below the button shows: +``` +πŸ† Your best session: 42,000,000,000 tokens (vs. today: 1,240,000) +``` + +Stored in `localStorage` key `tokenDeathclockBestScore`. Resets if the user clears localStorage. +A subtle **"😱 New Record!"** pulse animation triggers when the current session beats the stored best. + +--- + +## Mobile UX Requirements + +| Requirement | Detail | +|-------------|--------| +| **Touch target size** | Big Red Button minimum 96 Γ— 96 px (WCAG 2.5.5 Target Size) | +| **Tap responsiveness** | CSS `:active` state responds within 16 ms (no `touchstart` delay) | +| **Haptic feedback** | `navigator.vibrate(30)` on each tap; 80ms on milestone trigger | +| **No hover-only affordances** | All tooltips/shop descriptions accessible via tap-to-expand | +| **Combo counter** | Positioned above button, large font β€” readable at arm's length | +| **Horizontal challenge row** | `-webkit-overflow-scrolling: touch` + `scroll-snap` for snap-card UX | +| **Upgrade shop** | 2-column grid on mobile (<480 px), 3–4 columns on desktop | +| **No layout shift** | Section collapses/expands with `max-height` transition, not `hidden` toggle | + +--- + +## New Doom Achievements (extend existing badge system) + +The following badges integrate with the existing `tokenDeathclockBadges` localStorage key: + +| Badge | Name | Condition | +|-------|------|-----------| +| πŸš€ | Accelerant | First tap on the Big Red Button | +| πŸ”₯ | Arsonist | Reach 10Γ— combo for the first time | +| ⚑ | Trillion Villain | Personally contribute 1 trillion tokens | +| 🌍 | Continental Threat | Purchase the Global Data Centre upgrade | +| πŸ›°οΈ | Space Criminal | Purchase the Orbital Inference Array upgrade | +| 🧬 | Godlike | Purchase the AGI Mode upgrade | +| 🏁 | First Blood | Personally trigger your first milestone | +| ☠️ | Apex Accelerant | Personally trigger 5 milestones | +| πŸŒ™ | Nocturnal Accelerator | Complete a challenge between 00:00–04:00 | +| πŸ“€ | Bragging Rights | Share your personal acceleration total | + +--- + +## Shareable Outcome + +A **"πŸ“€ Share My Destruction"** button (shown once `personalTokens > 0`) generates: + +> πŸš€ I personally accelerated AI's environmental doom by contributing **1.24 billion tokens** +> in one sitting. That's **0.37 g of COβ‚‚**, **0.62 mL of water**, and one step closer to +> [Next Milestone Name]. Come join me in accelerating the apocalypse. +> β†’ [link] #AccelerateTheDoom #TokenDeathClock + +Share flow reuses the existing `renderSharePanel()` / Twitter-X deep-link pattern already +established in `script.js`. + +--- + +## Implementation Notes + +### Pure Functions (death-clock-core.js) + +- `calculatePersonalImpact(personalTokens, tapMultiplier)` β†’ `{ tokens, kWh, co2Kg, waterL }` β€” thin wrapper around existing `calculateEnvironmentalImpact()` +- `getNextMilestoneForPlayer(personalTokens, milestones)` β†’ milestone object or `null` +- `computeComboMultiplier(tapTimestamps)` β†’ integer 1–10 (input: array of recent tap epoch-ms values) +- `getSessionChallenge(seedMs)` β†’ array of 3 challenge objects `{ id, label, goal, rewardDesc }` +- `formatDoomPoints(dp)` β†’ string (e.g. `"1,240 DP"`) + +### DOM / Script (script.js) + +- `initAcceleratorSection()` β€” sets up button listeners, combo timer, RAF-driven stat strip updates +- `handleTap()` β€” called on `pointerdown` (covers mouse + touch in one event); updates `personalTokens`, combo state, DP balance, checks challenges and milestone crossings +- `renderUpgradeShop()` β€” renders upgrade cards, wires purchase buttons, reads/writes `localStorage` +- `checkAcceleratorAchievements()` β€” called from `handleTap()` and on milestone cross; delegates to existing badge unlock flow +- Combo timer: `setInterval` at 100 ms checking last-tap timestamp to reset combo + +### HTML (index.html) + +New `
` inserted between `#achievements-section` and `
`. +Contains: +- `.accelerator-header` (`

`, description text) +- `#bigRedButton` (`

+ +
+
+ +

πŸš€ Accelerate the Doom

+

You know it's bad. Tap anyway. Every click adds tokens to your personal contribution to the apocalypse. Earn Doom Points, unlock upgrades, and trigger environmental milestones yourself.

+ + +
+
+