Section 03 + easter egg fixes — slop panel theme-adapts · easter actually fires

- Section 03 "Without Hallmark" panel no longer hardcodes #fff bg; uses var(--color-paper) so Manifesto / Midnight / Terminal / Brutal don't show a jarring white box mid-page (the AI-slop tells are still carried by the gradient title, Inter, centred layout, pill CTA)
- Removed four dead per-theme .vs__col:first-child colour overrides (Manifesto / Sport / Brutal / Terminal) — they were painting the old CSS-mockup column, now clashing with the panel-based demo
- Easter egg fix: previous version showed for one frame and was dismissed by the next autorepeat keydown. Now swallows every key while open, auto-dismisses after 4.2s with a fade-out, threshold lowered to 12 so it triggers reliably
- Easter visual: solid #fff, system mono, #111 text, dark cursor — decoupled from theme so it reads as "the page steps in" not "another theme"
- Random punchline picked from 6 lines ("chill, designer.", "sixteen is plenty.", etc.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Youssef
2026-05-01 17:39:21 +01:00
co-authored by Claude Opus 4.7
parent ab39afe87c
commit 2379a5ca78
4 changed files with 124 additions and 134 deletions
+69 -78
View File
@@ -601,18 +601,22 @@
font-size: 0.875rem;
}
/* — "Without Hallmark" — the slop stack ————————————————— */
/* — "Without Hallmark" — the slop stack —————————————————
The bg uses theme paper so the panel integrates on dark themes
(otherwise a pure-white card looks like a glitch on Manifesto /
Midnight / Terminal). The AI-slop tells are still carried by the
centred layout, Inter font, gradient title, and gradient pill. */
.vs__panel[data-variant="slop"] {
text-align: center;
align-content: center;
justify-items: center;
background: #fff;
border-color: #e6e6ec;
background: var(--color-paper);
border-color: var(--color-rule);
font-family: "Inter", system-ui, sans-serif;
}
.vs__panel[data-variant="slop"] .vs__panel-eyebrow {
color: #6b6b76;
color: var(--color-muted);
font-family: inherit;
font-weight: 500;
letter-spacing: 0;
@@ -623,7 +627,7 @@
font-family: inherit;
font-weight: 800;
letter-spacing: -0.025em;
background: linear-gradient(95deg, #6b4de0 0%, #c14de0 50%, #e04db4 100%);
background: linear-gradient(95deg, #8b5dff 0%, #c14de0 50%, #e04db4 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
@@ -631,7 +635,7 @@
}
.vs__panel[data-variant="slop"] .vs__panel-sub {
color: #6b6b76;
color: var(--color-muted);
font-family: inherit;
max-width: 28ch;
}
@@ -982,8 +986,10 @@
/* — Easter egg overlay ——————————————————————————————————
Hidden by default. Revealed when the user spams T faster than the
site can keep up — see js/main.js. Uses theme tokens so the panel
adapts to whichever theme is active when it fires. */
site can keep up — see js/main.js. Pure white full-screen, system
mono font, dark text — deliberately decoupled from the active
theme so it reads as the page itself stepping in. Auto-dismisses
after ~4.2s; while it's open, all keystrokes are swallowed. */
.easter {
position: fixed;
inset: 0;
@@ -991,40 +997,35 @@
display: grid;
place-items: center;
padding: var(--space-xl);
background: color-mix(in oklch, var(--color-paper) 92%, transparent);
backdrop-filter: blur(10px) saturate(1.1);
-webkit-backdrop-filter: blur(10px) saturate(1.1);
animation: easter-fade-in 280ms var(--ease-out) both;
background: #fff;
color: #111;
font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.95rem;
line-height: 1.65;
animation: easter-fade-in 240ms ease-out both;
}
.easter[hidden] { display: none; }
.easter[data-state="closing"] {
animation: easter-fade-out 360ms ease-in forwards;
}
@keyframes easter-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.easter__panel {
display: grid;
gap: var(--space-xl);
max-width: 52rem;
width: 100%;
padding: var(--space-2xl);
background: var(--color-paper);
border: var(--rule-hair) solid var(--color-rule);
font-family: var(--font-mono);
font-size: var(--text-sm);
line-height: 1.7;
color: var(--color-ink);
@keyframes easter-fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
[data-theme="brutal"] .easter__panel,
[data-theme="manifesto"] .easter__panel { border-width: 2px; }
[data-theme="terminal"] .easter__panel,
[data-theme="midnight"] .easter__panel,
[data-theme="manifesto"] .easter__panel {
background: var(--color-paper-2);
.easter__panel {
display: grid;
gap: 1.4rem;
max-width: 38rem;
width: 100%;
}
.easter__lines {
@@ -1032,75 +1033,73 @@
margin: 0;
padding: 0;
display: grid;
gap: 0.4rem;
gap: 0.45rem;
}
.easter__line {
margin: 0;
opacity: 0;
transform: translateY(6px);
animation: easter-line 360ms var(--ease-out) forwards;
animation: easter-line 360ms ease-out forwards;
}
.easter__line:nth-child(1) { animation-delay: 80ms; }
.easter__line:nth-child(2) { animation-delay: 280ms; }
.easter__line:nth-child(3) { animation-delay: 460ms; }
.easter__line:nth-child(4) { animation-delay: 620ms; }
.easter__line:nth-child(5) { animation-delay: 880ms; }
.easter__line:nth-child(1) { animation-delay: 180ms; }
.easter__line:nth-child(2) { animation-delay: 380ms; }
.easter__line:nth-child(3) { animation-delay: 560ms; }
.easter__line:nth-child(4) { animation-delay: 720ms; }
.easter__line:nth-child(5) { animation-delay: 1000ms; }
@keyframes easter-line {
to { opacity: 1; transform: none; }
}
.easter__line--cmd {
font-size: var(--text-md);
font-size: 1rem;
font-weight: 500;
color: var(--color-ink);
margin-block-end: 0.4rem;
color: #111;
margin-block-end: 0.6rem;
letter-spacing: 0;
}
.easter__prompt,
.easter__arrow {
color: var(--color-accent);
color: #b0b0b0;
user-select: none;
margin-inline-end: 0.4rem;
margin-inline-end: 0.5rem;
}
.easter__num {
color: var(--color-ink);
font-weight: 500;
color: #111;
font-weight: 600;
font-feature-settings: "tnum";
font-variant-numeric: tabular-nums;
}
.easter__line--punchline {
margin-block-start: 0.6rem;
font-family: var(--font-display);
font-style: var(--display-style, italic);
font-weight: var(--display-weight, 400);
font-size: clamp(1.5rem, 4vw, 2.25rem);
line-height: 1.15;
letter-spacing: -0.01em;
color: var(--color-ink);
margin-block-start: 0.8rem;
font-size: clamp(1.75rem, 4.5vw, 2.5rem);
line-height: 1.1;
letter-spacing: -0.02em;
font-weight: 500;
color: #111;
}
.easter__line--punchline .easter__arrow {
font-family: var(--font-mono);
font-style: normal;
font-size: var(--text-sm);
vertical-align: middle;
font-size: 0.9rem;
vertical-align: 0.25em;
font-weight: 400;
}
.easter__cursor {
display: inline-block;
width: 0.5em;
height: 1em;
background: var(--color-ink);
margin-inline-start: 0.2em;
vertical-align: -0.12em;
animation: easter-blink 1.05s steps(2) infinite;
animation-delay: 1.3s;
width: 0.42em;
height: 0.95em;
background: #111;
margin-inline-start: 0.18em;
vertical-align: -0.08em;
opacity: 0;
animation: easter-blink 1.05s steps(2) infinite;
animation-delay: 1.4s;
}
@keyframes easter-blink {
@@ -1110,29 +1109,21 @@
.easter__hint {
margin: 0;
padding-block-start: var(--space-md);
border-block-start: var(--rule-hair) solid var(--color-rule);
font-family: var(--font-label);
font-size: var(--text-xs);
letter-spacing: var(--tracking-label);
padding-block-start: 1.2rem;
border-block-start: 1px solid #e5e5e5;
font-size: 0.7rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--color-muted);
color: #888;
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
align-items: center;
}
.easter__hint-key {
color: var(--color-ink);
border: var(--rule-hair) solid var(--color-rule);
padding: 0.18rem 0.55rem;
}
.easter__hint-sep { color: var(--color-rule-2); }
@media (prefers-reduced-motion: reduce) {
.easter,
.easter[data-state="closing"],
.easter__line { animation: none; }
.easter__line { opacity: 1; transform: none; }
.easter__cursor { animation: none; opacity: 1; }
-26
View File
@@ -569,7 +569,6 @@
animation: blink 1.05s steps(2) infinite;
}
@keyframes blink { 50% { opacity: 0; } }
[data-theme="terminal"] .vs__col:first-child { background: var(--color-paper-3); }
[data-theme="terminal"] .step__num::before { content: "[ "; color: var(--color-accent); }
[data-theme="terminal"] .step__num::after { content: " ]"; color: var(--color-accent); }
@@ -611,15 +610,6 @@
max-width: 18ch;
font-weight: 800;
}
[data-theme="manifesto"] .vs { border: 3px solid var(--color-ink); }
[data-theme="manifesto"] .vs__col:first-child {
background: var(--color-ink);
color: var(--color-paper);
}
[data-theme="manifesto"] .vs__col:first-child .vs__label,
[data-theme="manifesto"] .vs__col:first-child .vs__caption {
color: var(--color-paper);
}
[data-theme="manifesto"] .step { border-top: 3px solid var(--color-ink); }
/* ── SALON — centered narrow, fleuron divider ───────────────── */
@@ -771,14 +761,6 @@ html[data-theme="almanac"] body {
}
@media (max-width: 56rem) { [data-theme="sport"] .foundations { grid-template-columns: 1fr; } }
[data-theme="sport"] .spec { border-top: var(--rule-hair) solid var(--color-rule-2); }
[data-theme="sport"] .vs__col:first-child {
background: var(--color-accent);
color: var(--color-paper);
}
[data-theme="sport"] .vs__col:first-child .vs__label,
[data-theme="sport"] .vs__col:first-child .vs__caption {
color: var(--color-paper);
}
[data-theme="sport"] .step__num {
font-style: italic;
color: var(--color-accent);
@@ -796,14 +778,6 @@ html[data-theme="almanac"] body {
[data-theme="brutal"] .section__head {
border-bottom: 3px solid var(--color-ink);
}
[data-theme="brutal"] .vs__col:first-child {
background: var(--color-accent);
color: var(--color-paper);
}
[data-theme="brutal"] .vs__col:first-child .vs__label,
[data-theme="brutal"] .vs__col:first-child .vs__caption {
color: var(--color-paper);
}
/* ── GARDEN existing — augment ──────────────────────────────── */
[data-theme="garden"] .section__head {
+3 -5
View File
@@ -576,14 +576,12 @@
</li>
<li class="easter__line"><span class="easter__arrow">&gt;</span> there are <span class="easter__num">16</span> themes.</li>
<li class="easter__line"><span class="easter__arrow">&gt;</span> there is no theme <span class="easter__num">17</span>.</li>
<li class="easter__line easter__line--punchline">
<span class="easter__arrow">&gt;</span> chill, designer.<span class="easter__cursor" aria-hidden="true"></span>
<li class="easter__line easter__line--punchline" data-easter-punchline>
<span class="easter__arrow">&gt;</span> <span data-easter-line>chill, designer.</span><span class="easter__cursor" aria-hidden="true"></span>
</li>
</ol>
<p class="easter__hint">
<span class="easter__hint-key">tap any key</span>
<span class="easter__hint-sep" aria-hidden="true">·</span>
<span>to return to the page</span>
<span>returning to the page in a moment</span>
</p>
</div>
</aside>
+52 -25
View File
@@ -568,27 +568,41 @@ if (shuffleBtn) {
}
/* — Easter egg — "chill, designer." ————————————————————————
Spam T fast enough and the page intervenes. We track timestamps in a
rolling 3.2-second window; if the user crosses the threshold (≈ a
full catalog cycle inside the window), we show an overlay and reset
the counter. A 30-second cooldown stops it from firing twice in a row. */
Spam T fast enough and the page intervenes. We track timestamps in
a rolling 3.2s window; if the user crosses the threshold (≈ a full
catalog cycle inside the window), the overlay takes over. While
it's visible, every keystroke is swallowed — no theme cycling, no
shortcuts. After ~4s the overlay fades and the page returns. A 30s
cooldown stops it from firing back-to-back. */
const easterEl = document.querySelector("[data-easter-egg]");
const EASTER_WINDOW_MS = 3200;
const EASTER_THRESHOLD = 14; // ≈ 4.4 presses/sec
const EASTER_THRESHOLD = 12; // ≈ 3.8 presses/sec
const EASTER_VISIBLE_MS = 4200; // total time the overlay stays up
const EASTER_FADE_MS = 360; // matches the fade-out animation
const EASTER_COOLDOWN_MS = 30000;
const EASTER_PUNCHLINES = [
"chill, designer.",
"you've seen them all.",
"sixteen is plenty.",
"pick. build. ship.",
"easy on the keyboard.",
"one theme will do.",
];
const tStamps = [];
let easterLastFired = 0;
let easterOpen = false;
let easterDismissTimer = null;
let easterFadeTimer = null;
function showEasterEgg() {
if (!easterEl) return;
if (!easterEl || easterOpen) return;
const now = performance.now();
const span = (now - tStamps[0]) / 1000;
const cycles = (tStamps.length / Object.keys(THEMES).length).toFixed(1);
// Re-mount the lines container so the staggered fade-in animation
// re-triggers each time the easter egg is shown. Populate the live
// values after replacement so we don't write into a detached node.
// Re-mount the lines so the staggered fade-in animation re-runs each
// time. Populate live values + a random punchline after replacement
// so we don't write into a detached node.
const lines = easterEl.querySelector(".easter__lines");
if (lines) {
const fresh = lines.cloneNode(true);
@@ -596,32 +610,42 @@ function showEasterEgg() {
}
const cyclesEl = easterEl.querySelector("[data-easter-cycles]");
const secondsEl = easterEl.querySelector("[data-easter-seconds]");
const lineEl = easterEl.querySelector("[data-easter-line]");
if (cyclesEl) cyclesEl.textContent = cycles;
if (secondsEl) secondsEl.textContent = span.toFixed(1);
if (lineEl) lineEl.textContent = EASTER_PUNCHLINES[Math.floor(Math.random() * EASTER_PUNCHLINES.length)];
delete easterEl.dataset.state;
easterEl.hidden = false;
easterOpen = true;
document.body.style.overflow = "hidden";
clearTimeout(easterDismissTimer);
clearTimeout(easterFadeTimer);
easterDismissTimer = setTimeout(hideEasterEgg, EASTER_VISIBLE_MS);
}
function hideEasterEgg() {
if (!easterEl || !easterOpen) return;
easterEl.hidden = true;
easterOpen = false;
document.body.style.overflow = "";
}
if (easterEl) {
easterEl.addEventListener("click", hideEasterEgg);
clearTimeout(easterDismissTimer);
clearTimeout(easterFadeTimer);
easterEl.dataset.state = "closing";
easterFadeTimer = setTimeout(() => {
easterEl.hidden = true;
delete easterEl.dataset.state;
easterOpen = false;
document.body.style.overflow = "";
}, EASTER_FADE_MS);
}
/* — Keyboard shortcuts ————————————————————————————————— */
// T cycles forward, Shift+T cycles back, R picks random.
document.addEventListener("keydown", (e) => {
// While the easter egg is open, any key dismisses it (and only that).
// While the easter egg is up, swallow every key — including T —
// so the user can't trigger anything until it auto-dismisses.
if (easterOpen) {
e.preventDefault();
hideEasterEgg();
e.stopPropagation();
return;
}
@@ -631,21 +655,24 @@ document.addEventListener("keydown", (e) => {
if (e.key === "t" || e.key === "T") {
e.preventDefault();
const order = Object.keys(THEMES);
const i = order.indexOf(root.dataset.theme);
const dir = e.shiftKey ? -1 : 1;
const next = order[(i + dir + order.length) % order.length];
applyTheme(next);
// Easter-egg counter — track press cadence in a rolling window.
// Push BEFORE applying the theme so the trigger fires on this same
// keystroke if we've crossed the threshold.
const now = performance.now();
while (tStamps.length && now - tStamps[0] > EASTER_WINDOW_MS) tStamps.shift();
tStamps.push(now);
if (tStamps.length >= EASTER_THRESHOLD && now - easterLastFired > EASTER_COOLDOWN_MS) {
easterLastFired = now;
showEasterEgg();
tStamps.length = 0;
showEasterEgg();
return; // skip the theme swap on the trigger keystroke
}
const order = Object.keys(THEMES);
const i = order.indexOf(root.dataset.theme);
const dir = e.shiftKey ? -1 : 1;
const next = order[(i + dir + order.length) % order.length];
applyTheme(next);
} else if (e.key === "r" || e.key === "R") {
e.preventDefault();
applyTheme(pickRandomTheme());