Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,34 @@
<header>
<div class="container">
<span class="header-skull" aria-hidden="true">💀</span>
<h1 class="site-title glow-text">AI DEATH CLOCK</h1>
<p class="site-subtitle">The True Environmental Cost of Global AI Token Consumption</p>
<h1 class="site-title glow-text">HUMAN EXTINCTION COUNTDOWN</h1>
<div class="extinction-timer" id="extinctionTimer" aria-live="polite" aria-label="Human extinction countdown">
<div class="extinction-unit">
<span class="extinction-value" id="extYears">—</span>
<span class="extinction-unit-label">YRS</span>
</div>
<span class="extinction-sep" aria-hidden="true">:</span>
<div class="extinction-unit">
<span class="extinction-value" id="extDays">—</span>
<span class="extinction-unit-label">DAYS</span>
</div>
<span class="extinction-sep" aria-hidden="true">:</span>
<div class="extinction-unit">
<span class="extinction-value" id="extHours">—</span>
<span class="extinction-unit-label">HRS</span>
</div>
<span class="extinction-sep" aria-hidden="true">:</span>
<div class="extinction-unit">
<span class="extinction-value" id="extMins">—</span>
<span class="extinction-unit-label">MINS</span>
</div>
<span class="extinction-sep" aria-hidden="true">:</span>
<div class="extinction-unit">
<span class="extinction-value" id="extSecs">—</span>
<span class="extinction-unit-label">SECS</span>
</div>
</div>
<p class="extinction-caption">at current global AI token consumption rate &middot; <a class="extinction-link" href="#milestones-section">see milestones ↓</a></p>
</div>
</header>

Expand Down
54 changes: 54 additions & 0 deletions src/js/02-counter.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,57 @@
if (el) el.textContent = value;
}

// ---- Extinction countdown (header) -----------------------
// Module-level constants and helpers so they are created once, not on every
// call to updateExtinctionCountdown() (which runs every second).
const _EXT_SECS_PER_YEAR = 365.25 * 24 * 3600;
const _EXT_SECS_PER_DAY = 24 * 3600;
const _EXT_SECS_PER_HOUR = 3600;
const _EXT_SECS_PER_MIN = 60;
const _EXT_UNIT_IDS = ['extYears', 'extDays', 'extHours', 'extMins', 'extSecs'];
const _extPad2 = (n) => String(Math.floor(n)).padStart(2, '0');
const _extPad3 = (n) => String(Math.floor(n)).padStart(3, '0');

// Finds the milestone flagged `extinctionMarker: true`, computes the
// remaining seconds at the current dynamic rate, and updates the header
// countdown display once per second.
function updateExtinctionCountdown() {
const extinctionMilestone = MILESTONES.find(m => m.extinctionMarker);
if (!extinctionMilestone) return;

const tokens = getCurrentTokens();
const currentRate = getDynamicRate(new Date());
const tokensRemaining = extinctionMilestone.tokens - tokens;

if (tokensRemaining <= 0) {
// Extinction threshold reached — show zeroed-out timer
_EXT_UNIT_IDS.forEach(id => {
const el = document.getElementById(id);
if (el) el.textContent = '00';
});
return;
}

const secsRemaining = tokensRemaining / currentRate;
const years = Math.floor(secsRemaining / _EXT_SECS_PER_YEAR);
const remAfterY = secsRemaining % _EXT_SECS_PER_YEAR;
const days = Math.floor(remAfterY / _EXT_SECS_PER_DAY);
const remAfterD = remAfterY % _EXT_SECS_PER_DAY;
const hours = Math.floor(remAfterD / _EXT_SECS_PER_HOUR);
const remAfterH = remAfterD % _EXT_SECS_PER_HOUR;
const mins = Math.floor(remAfterH / _EXT_SECS_PER_MIN);
const secs = Math.floor(remAfterH % _EXT_SECS_PER_MIN);

const yrsEl = document.getElementById('extYears');
const daysEl = document.getElementById('extDays');
const hrsEl = document.getElementById('extHours');
const minsEl = document.getElementById('extMins');
const secsEl = document.getElementById('extSecs');

if (yrsEl) yrsEl.textContent = String(years);
if (daysEl) daysEl.textContent = _extPad3(days);
if (hrsEl) hrsEl.textContent = _extPad2(hours);
if (minsEl) minsEl.textContent = _extPad2(mins);
if (secsEl) secsEl.textContent = _extPad2(secs);
}

4 changes: 4 additions & 0 deletions src/js/21-boot.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,14 @@
// Kick off the live counter RAF loop
requestAnimationFrame(updateCounters);

// Populate extinction countdown immediately, then refresh every second
updateExtinctionCountdown();

// Check time-based badges and milestone alert every second
setInterval(() => {
checkTimeBadges();
checkMilestoneAlert();
updateExtinctionCountdown();
}, 1000);
}

Expand Down
77 changes: 73 additions & 4 deletions styles/hero-tabs.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,80 @@ header::after {
text-shadow: 0 0 20px var(--accent-glow);
}

.site-subtitle {
/* ---- Extinction Countdown Timer ---- */
.extinction-timer {
display: flex;
justify-content: center;
align-items: flex-start;
gap: 0.15rem;
margin-top: 0.75rem;
flex-wrap: wrap;
}

.extinction-unit {
display: flex;
flex-direction: column;
align-items: center;
min-width: 3rem;
}

.extinction-value {
font-family: 'Orbitron', monospace;
font-size: clamp(1.8rem, 5vw, 3rem);
font-weight: 900;
color: var(--accent);
text-shadow: 0 0 18px var(--accent-glow);
line-height: 1;
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
}

.extinction-unit-label {
font-family: 'Orbitron', monospace;
font-size: 0.55rem;
letter-spacing: 0.18em;
color: var(--text-dim);
font-size: 1rem;
margin-top: 0.5rem;
letter-spacing: 0.1em;
text-transform: uppercase;
margin-top: 0.25rem;
}

.extinction-sep {
font-family: 'Orbitron', monospace;
font-size: clamp(1.4rem, 4vw, 2.4rem);
font-weight: 900;
color: var(--accent);
opacity: 0.35;
line-height: 1;
padding-top: 0.05em;
align-self: flex-start;
}

.extinction-caption {
font-size: 0.7rem;
color: var(--text-dim);
letter-spacing: 0.08em;
margin-top: 0.75rem;
text-transform: uppercase;
}

.extinction-link {
color: var(--accent);
text-decoration: none;
opacity: 0.7;
}

.extinction-link:hover {
opacity: 1;
text-decoration: underline;
}

@media (max-width: 480px) {
.extinction-unit {
min-width: 2.25rem;
}
.extinction-unit-label {
font-size: 0.48rem;
}
}

/* ---- Tab Bar ---- */
Expand Down
11 changes: 9 additions & 2 deletions tests/e2e/death-clock.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,16 @@ test.describe('AI Death Clock — end-to-end', () => {
await expect(page).toHaveTitle(/Token Deathclock/i);
});

test('renders main header', async ({ page }) => {
test('renders main header with extinction countdown', async ({ page }) => {
await expect(page.locator('h1.site-title')).toBeVisible();
await expect(page.locator('h1.site-title')).toContainText('AI DEATH CLOCK');
await expect(page.locator('h1.site-title')).toContainText('HUMAN EXTINCTION COUNTDOWN');
// Countdown timer should be present and populated after init
await page.waitForFunction(() => {
const el = document.getElementById('extYears');
return el && el.textContent.trim() !== '—' && el.textContent.trim() !== '';
}, { timeout: 3000 });
const years = await page.locator('#extYears').textContent();
expect(parseInt(years, 10)).toBeGreaterThan(0);
});

// ── Live counters ─────────────────────────────────────────────────────────
Expand Down
Loading