From 29d869e7563b098458617e56c6ebe8f05240115b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 01:57:09 +0000 Subject: [PATCH 1/3] feat: implement Token Horoscope daily satirical AI horoscope (Phase 3 PRD #1) Agent-Logs-Url: https://github.com/nitrocode/token-deathclock/sessions/f00ab819-68de-4607-b872-c314765f8507 Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- death-clock-core.js | 61 +++++++++++++++++++++++++++++ index.html | 18 +++++++++ scripts/build-js.js | 1 + src/js/00-state.js | 2 + src/js/21-boot.js | 1 + src/js/22-horoscope.js | 63 ++++++++++++++++++++++++++++++ styles/features.css | 54 ++++++++++++++++++++++++++ tests/death-clock.test.js | 82 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 282 insertions(+) create mode 100644 src/js/22-horoscope.js diff --git a/death-clock-core.js b/death-clock-core.js index 04da8e6..d1477dd 100644 --- a/death-clock-core.js +++ b/death-clock-core.js @@ -733,6 +733,65 @@ function getSimulatedViewerCount(dateMs) { return Math.max(12, Math.round(raw / 5) * 5); } +// ============================================================ +// TOKEN HOROSCOPE +// ============================================================ + +/** + * Pool of satirical daily horoscope templates. + * All 30 entries follow the pattern: astrological opener → AI sin → dramatic + * consequence → satirical advice. The pool is deliberately larger than 7 so + * that weekly visitors see fresh content. + */ +const HOROSCOPE_TEMPLATES = [ + 'Mercury is in retrograde and so is your judgment. Today you will ask AI to rewrite a perfectly good email four times. The data centres hum approvingly. Consider a typewriter.', + 'The alignment of Jupiter and your idle fingers portends a reckless afternoon. You will use AI to summarise a Wikipedia article you could have read in 90 seconds. Three cooling towers exhale in unison.', + 'Venus rises in your browser history. You will prompt an image generator for 45 variations of \'a cat wearing sunglasses\' before choosing the first one. The oceans do not forget.', + 'Your lunar node suggests creative avoidance. You will ask AI to write a birthday card for your own parent. The glaciers note this.', + 'Saturn\'s gaze falls upon your clipboard. You will run the same prompt five times with minor wording changes to see if the answer improves. It will not. The servers will not forget.', + 'Mars enters your productivity suite. You will spend forty minutes crafting the perfect system prompt for a task that would have taken six minutes to do yourself. A polar ice sheet sighs.', + 'The moon is waxing and so is your token budget. You will ask four different AI models the same question and then average their answers. The data centres add a shift.', + 'Neptune\'s fog blankets your decision-making. You will use AI to proofread a single-sentence Slack message. Somewhere, a glacier retreats another centimetre.', + 'Pluto looms in your house of procrastination. You will generate seventeen logo variations at midnight, pick none of them, and try again tomorrow. The cooling fans never sleep.', + 'The stars align in your browser tabs. You will ask AI to explain a concept, then ask it to simplify, then ask it to use an analogy involving sandwiches. A power grid somewhere flickers.', + 'Uranus transits your todo list. You will automate a two-minute task using an AI workflow that takes four hours to configure and still breaks on Tuesdays. The irony is 100 % renewable-free.', + 'Your rising sign is \'Perpetually Online\'. You will re-generate the same cover letter template for the eleventh time this month. The carbon credit markets remain unmoved.', + 'Chiron wounds your judgment. You will ask an AI chatbot for medical advice and then spend the next hour asking it to clarify the advice it just gave. Three data centres log the session.', + 'The sun conjuncts your API key. You will iterate on an AI-generated poem for ninety minutes before concluding the original draft was better. The atmosphere notes the irony.', + 'Mercury stations direct into your calendar. You will use AI to schedule a meeting that could have been resolved with one reply-all email. The GPU cluster does not judge you. It simply bills.', + 'The north node crosses your workflow. You will ask AI to translate a two-word phrase you already know. A server farm in the desert does not blink.', + 'Venus sextiles your clipboard. You will generate a recipe for a dish you have made weekly for ten years. The ocean temperature rises an imperceptible fraction of a degree.', + 'Your descendant is in Generative AI. You will use AI to name a project, reject all thirty suggestions, and name it what you were already going to call it. The data centre logs this as a success.', + 'Jupiter retrogrades into your browser history. You will ask an AI to debate both sides of an argument and then agree with whichever side it presented last. A glacier does not take sides.', + 'The full moon illuminates your rubber duck. You will explain a coding problem to an AI chatbot and solve it yourself in the process of typing the question. You used 40,000 tokens to find this out.', + 'Neptune squares your attention span. You will open an AI chat, type \'so\', stare at it for three minutes, and close the tab. The servers charged the session as active compute.', + 'Mars trines your deployment pipeline. You will ask AI to write unit tests for a function, then ask it to fix the tests it wrote, then ask it to explain why the tests are wrong. The cycle is complete.', + 'Your midheaven is in Large Language Model. You will prompt AI to generate a motivational quote about productivity, read it, and feel no more productive. The irony consumes 8,000 tokens.', + 'The waning crescent of common sense grows thin. You will ask AI to write a disclaimer for a disclaimer. Legal fees are offset; carbon costs are not.', + 'Saturn conjuncts your delete key. You will use AI to draft an out-of-office message, iterate on the tone for twenty minutes, and then change it back to your previous one. The servers had opinions.', + 'The cosmic tides favour bold copy-pastes. You will copy an AI response directly into a document, and then ask AI to polish the document it just created. The recursion is not lost on the power grid.', + 'Your sun sign is \'Needs More Context\'. Today you will spend six prompts establishing context that you could have provided in the first message. A server rack quietly judges your token efficiency.', + 'Retrograde season begins in your prompt history. You will ask AI to be more concise, then ask it to elaborate, then ask it to be concise again. A wind turbine that could have powered this sits idle.', + 'The ascendant whispers of lost afternoons. You will use AI to generate a packing list for a trip you have taken a dozen times. The data centre does not know you own seven pairs of socks already.', + 'Cosmic rays align with your refresh button. You will regenerate the same AI image with imperceptibly different seeds, select the third result, and tell no one. The planet absorbs the lesson silently.', +]; + +/** + * Return the horoscope template for a given UTC day. + * + * All visitors on the same UTC day receive the same text, creating a shared + * cultural moment and a reason to compare notes. + * + * @param {number} nowMs - current epoch milliseconds + * @param {string[]} templates - array of horoscope template strings + * @returns {string} today's horoscope text + */ +function getDailyHoroscope(nowMs, templates) { + if (!Array.isArray(templates) || templates.length === 0) return ''; + const day = Math.floor(nowMs / 86400000); // UTC day index + return templates[day % templates.length]; +} + // ============================================================ // EXPORTS — CommonJS for Jest; window global for the browser // ============================================================ @@ -772,6 +831,8 @@ const DeathClockCore = { computePassiveRate, getCompanyStage, getSimulatedViewerCount, + HOROSCOPE_TEMPLATES, + getDailyHoroscope, }; /* istanbul ignore else */ diff --git a/index.html b/index.html index 2847df4..a719fbd 100644 --- a/index.html +++ b/index.html @@ -203,6 +203,24 @@

Global Token Counter

+ +
+
+ +

🔮 Your Daily AI Horoscope

+
+ + Today's reading + + +

+
+ +
+
+
+
+
diff --git a/scripts/build-js.js b/scripts/build-js.js index dd8ae90..eb2059d 100644 --- a/scripts/build-js.js +++ b/scripts/build-js.js @@ -43,6 +43,7 @@ const PARTS = [ '18-scary-features.js', '19-milestone-alert.js', '20-tabs.js', + '22-horoscope.js', '21-boot.js', ]; diff --git a/src/js/00-state.js b/src/js/00-state.js index 1b0e9bf..6b86a4c 100644 --- a/src/js/00-state.js +++ b/src/js/00-state.js @@ -34,6 +34,8 @@ computePassiveRate, getCompanyStage, getSimulatedViewerCount, + HOROSCOPE_TEMPLATES, + getDailyHoroscope, } = window.DeathClockCore; // ---- Unpack changelog data ---------------------------------- diff --git a/src/js/21-boot.js b/src/js/21-boot.js index be5a48c..aad9156 100644 --- a/src/js/21-boot.js +++ b/src/js/21-boot.js @@ -48,6 +48,7 @@ initReceiptModal(); initCalculator(); initAccelerator(); + initHoroscope(); // Engagement features initPresenceStrip(); initEventLog(); diff --git a/src/js/22-horoscope.js b/src/js/22-horoscope.js new file mode 100644 index 0000000..8a50760 --- /dev/null +++ b/src/js/22-horoscope.js @@ -0,0 +1,63 @@ + // ---- Daily AI Horoscope (Phase 3 PRD #1) -------------------- + + const LS_HOROSCOPE_DATE_KEY = 'tokenDeathclockHoroscopeDate'; + + /** Return today's UTC date as a YYYY-MM-DD string. */ + function utcDateString(nowMs) { + const d = new Date(nowMs); + const yyyy = d.getUTCFullYear(); + const mm = String(d.getUTCMonth() + 1).padStart(2, '0'); + const dd = String(d.getUTCDate()).padStart(2, '0'); + return `${yyyy}-${mm}-${dd}`; + } + + function initHoroscope() { + const details = document.getElementById('horoscope-details'); + const dateEl = document.getElementById('horoscope-date'); + const textEl = document.getElementById('horoscope-text'); + const shareBtn = document.getElementById('horoscope-share-btn'); + if (!details || !textEl) return; + + const nowMs = Date.now(); + const today = utcDateString(nowMs); + const text = getDailyHoroscope(nowMs, HOROSCOPE_TEMPLATES); + + // Render the horoscope text safely + textEl.textContent = text; + if (dateEl) dateEl.textContent = today; + + // Start collapsed if already viewed today; otherwise open + persist date + let alreadySeen = false; + try { + const stored = JSON.parse(localStorage.getItem(LS_HOROSCOPE_DATE_KEY) || 'null'); + alreadySeen = stored && stored.date === today; + if (!alreadySeen) { + localStorage.setItem(LS_HOROSCOPE_DATE_KEY, JSON.stringify({ date: today })); + } + } catch (_) { /* ignore quota / access errors */ } + + if (alreadySeen) { + details.removeAttribute('open'); + } else { + details.setAttribute('open', ''); + } + + // Persist collapsed state when user toggles + details.addEventListener('toggle', () => { + if (details.open) { + try { + localStorage.setItem(LS_HOROSCOPE_DATE_KEY, JSON.stringify({ date: today })); + } catch (_) { /* ignore */ } + } + }); + + // Share button + if (shareBtn) { + shareBtn.addEventListener('click', () => { + const shareText = + `\uD83D\uDD2E Today's AI Horoscope: "${text}"\n` + + `Will this be you today? \u2192 ${SITE_URL} #TokenDeathClock #AIHoroscope`; + openSharePopup(shareText); + }); + } + } diff --git a/styles/features.css b/styles/features.css index 2c28233..b6775bf 100644 --- a/styles/features.css +++ b/styles/features.css @@ -2,6 +2,60 @@ NEW FUN FEATURES ============================================================= */ +/* ---- Daily AI Horoscope ---- */ +.horoscope-card { + background: var(--surface-2); + border: 1px solid var(--border); + border-radius: 0.75rem; + padding: 1rem 1.25rem; + margin-top: 1.5rem; +} + +.horoscope-card[open] { + border-color: var(--accent-2); +} + +.horoscope-summary { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.5rem; + cursor: pointer; + list-style: none; + user-select: none; +} + +.horoscope-summary::-webkit-details-marker { display: none; } + +.horoscope-summary-title { + font-size: 0.85rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--accent-2); + font-weight: 700; +} + +.horoscope-date { + font-size: 0.78rem; + color: var(--text-dim); + font-family: 'Share Tech Mono', monospace; +} + +.horoscope-text { + margin: 0.9rem 0 1rem; + font-size: 1rem; + line-height: 1.6; + color: var(--text); + font-style: italic; +} + +.horoscope-actions { + display: flex; + gap: 0.6rem; + flex-wrap: wrap; +} + /* ---- Equivalences Strip ---- */ .equiv-wrap { margin-top: 1.5rem; diff --git a/tests/death-clock.test.js b/tests/death-clock.test.js index 573f609..4f7633f 100644 --- a/tests/death-clock.test.js +++ b/tests/death-clock.test.js @@ -1549,3 +1549,85 @@ describe('getSimulatedViewerCount', () => { expect(count).toBeGreaterThanOrEqual(12); }); }); + +// ============================================================ +// HOROSCOPE_TEMPLATES +// ============================================================ +const { HOROSCOPE_TEMPLATES, getDailyHoroscope } = core; + +describe('HOROSCOPE_TEMPLATES', () => { + test('is a non-empty array', () => { + expect(Array.isArray(HOROSCOPE_TEMPLATES)).toBe(true); + expect(HOROSCOPE_TEMPLATES.length).toBeGreaterThan(0); + }); + + test('has at least 30 entries', () => { + expect(HOROSCOPE_TEMPLATES.length).toBeGreaterThanOrEqual(30); + }); + + test('every entry is a non-empty string', () => { + HOROSCOPE_TEMPLATES.forEach((t) => { + expect(typeof t).toBe('string'); + expect(t.length).toBeGreaterThan(0); + }); + }); + + test('all entries are unique', () => { + const unique = new Set(HOROSCOPE_TEMPLATES); + expect(unique.size).toBe(HOROSCOPE_TEMPLATES.length); + }); +}); + +// ============================================================ +// getDailyHoroscope +// ============================================================ +describe('getDailyHoroscope', () => { + const templates = ['Alpha', 'Beta', 'Gamma']; + + test('returns a string from the templates array', () => { + const result = getDailyHoroscope(0, templates); + expect(templates).toContain(result); + }); + + test('cycles through templates by UTC day index', () => { + // Day 0 → templates[0], day 1 → templates[1], day 3 → templates[0] (wraps) + expect(getDailyHoroscope(0, templates)).toBe('Alpha'); + expect(getDailyHoroscope(86400000, templates)).toBe('Beta'); + expect(getDailyHoroscope(172800000, templates)).toBe('Gamma'); + expect(getDailyHoroscope(259200000, templates)).toBe('Alpha'); + }); + + test('same UTC day always returns the same horoscope', () => { + const dayMs = new Date('2026-04-27T00:00:00Z').getTime(); + const midMs = new Date('2026-04-27T12:00:00Z').getTime(); + expect(getDailyHoroscope(dayMs, templates)).toBe( + getDailyHoroscope(midMs, templates), + ); + }); + + test('different UTC days return different results when pool is large enough', () => { + const day1 = new Date('2026-04-27T00:00:00Z').getTime(); + const day2 = new Date('2026-04-28T00:00:00Z').getTime(); + // With a 3-entry pool consecutive days are guaranteed to differ + expect(getDailyHoroscope(day1, templates)).not.toBe( + getDailyHoroscope(day2, templates), + ); + }); + + test('returns empty string for an empty templates array', () => { + expect(getDailyHoroscope(0, [])).toBe(''); + }); + + test('returns empty string for non-array templates', () => { + expect(getDailyHoroscope(0, null)).toBe(''); + expect(getDailyHoroscope(0, undefined)).toBe(''); + expect(getDailyHoroscope(0, 'oops')).toBe(''); + }); + + test('works correctly with the real HOROSCOPE_TEMPLATES pool', () => { + const result = getDailyHoroscope(Date.now(), HOROSCOPE_TEMPLATES); + expect(typeof result).toBe('string'); + expect(result.length).toBeGreaterThan(0); + expect(HOROSCOPE_TEMPLATES).toContain(result); + }); +}); From 31d2df55dbd460f62f5812ae969ddcf9f06be3ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 01:59:08 +0000 Subject: [PATCH 2/3] docs: update LEARNINGS.md and project-stats for PR #95 Token Horoscope Agent-Logs-Url: https://github.com/nitrocode/token-deathclock/sessions/f00ab819-68de-4607-b872-c314765f8507 Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- docs/LEARNINGS.md | 9 +++++++++ project-stats.yaml | 4 ++-- scripts/build-js.js | 4 ++-- src/js/{22-horoscope.js => 21-horoscope.js} | 0 src/js/{21-boot.js => 22-boot.js} | 0 5 files changed, 13 insertions(+), 4 deletions(-) rename src/js/{22-horoscope.js => 21-horoscope.js} (100%) rename src/js/{21-boot.js => 22-boot.js} (100%) diff --git a/docs/LEARNINGS.md b/docs/LEARNINGS.md index 7f2203f..c6c72bb 100644 --- a/docs/LEARNINGS.md +++ b/docs/LEARNINGS.md @@ -143,6 +143,15 @@ Entries are grouped by release. Add new entries at the top of the appropriate re ### v1.7.x +#### PR #95 — feat: implement Token Horoscope daily satirical AI horoscope (Phase 3 PRD #1) + +- **Problem:** The site had no daily-rotating content to drive return visits; Phase 3 PRD #1 (Token Horoscope) was the highest-impact lowest-effort unimplemented feature. +- **Approach:** Added `HOROSCOPE_TEMPLATES` (30 entries) and `getDailyHoroscope(nowMs, templates)` pure function to `death-clock-core.js`; wired up a new `src/js/21-horoscope.js` DOM module with `
/` collapse, localStorage date tracking, and a share button reusing `openSharePopup()`. +- **Learning:** When inserting a new numbered source file between two existing ones, renumber to maintain strict sequential order in both the filename and the `PARTS` array in `build-js.js` — out-of-order names confuse future agents. (→ A1) +- **Key files:** `death-clock-core.js`, `src/js/21-horoscope.js`, `src/js/22-boot.js`, `scripts/build-js.js`, `index.html`, `styles/features.css`, `tests/death-clock.test.js` + +--- + #### PR #94 — docs: add Phase 3 PRDs — 8 satirical engagement features - **Problem:** No PRDs existed for the planned Phase 3 virality features. diff --git a/project-stats.yaml b/project-stats.yaml index 83173df..2a549f6 100644 --- a/project-stats.yaml +++ b/project-stats.yaml @@ -8,5 +8,5 @@ # Run `npm run build:project-stats` (or let the deploy workflow do it) to # regenerate project-stats-data.js from this file. -pr_count: 48 -total_tokens: 7200000 +pr_count: 49 +total_tokens: 7400000 diff --git a/scripts/build-js.js b/scripts/build-js.js index eb2059d..b6feda7 100644 --- a/scripts/build-js.js +++ b/scripts/build-js.js @@ -43,8 +43,8 @@ const PARTS = [ '18-scary-features.js', '19-milestone-alert.js', '20-tabs.js', - '22-horoscope.js', - '21-boot.js', + '21-horoscope.js', + '22-boot.js', ]; const HEADER = [ diff --git a/src/js/22-horoscope.js b/src/js/21-horoscope.js similarity index 100% rename from src/js/22-horoscope.js rename to src/js/21-horoscope.js diff --git a/src/js/21-boot.js b/src/js/22-boot.js similarity index 100% rename from src/js/21-boot.js rename to src/js/22-boot.js From 855d6a83f48152c94ae8269701d135bb56fcd9d4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 02:32:32 +0000 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20correct=20PR=20number=20in=20LEARNI?= =?UTF-8?q?NGS.md=20horoscope=20entry=20(#95=20=E2=86=92=20#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/nitrocode/token-deathclock/sessions/adcbfbe9-8f33-4cc9-8dfe-077823619d37 Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> --- docs/LEARNINGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/LEARNINGS.md b/docs/LEARNINGS.md index c6c72bb..75b4ea4 100644 --- a/docs/LEARNINGS.md +++ b/docs/LEARNINGS.md @@ -143,7 +143,7 @@ Entries are grouped by release. Add new entries at the top of the appropriate re ### v1.7.x -#### PR #95 — feat: implement Token Horoscope daily satirical AI horoscope (Phase 3 PRD #1) +#### PR #103 — feat: implement Token Horoscope daily satirical AI horoscope (Phase 3 PRD #1) - **Problem:** The site had no daily-rotating content to drive return visits; Phase 3 PRD #1 (Token Horoscope) was the highest-impact lowest-effort unimplemented feature. - **Approach:** Added `HOROSCOPE_TEMPLATES` (30 entries) and `getDailyHoroscope(nowMs, templates)` pure function to `death-clock-core.js`; wired up a new `src/js/21-horoscope.js` DOM module with `
/` collapse, localStorage date tracking, and a share button reusing `openSharePopup()`.