diff --git a/site/css/components.css b/site/css/components.css index 50b788b..39771f7 100644 --- a/site/css/components.css +++ b/site/css/components.css @@ -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; } diff --git a/site/css/sections.css b/site/css/sections.css index 8074a29..4d23a43 100644 --- a/site/css/sections.css +++ b/site/css/sections.css @@ -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 { diff --git a/site/index.html b/site/index.html index 0b55785..478b638 100644 --- a/site/index.html +++ b/site/index.html @@ -576,14 +576,12 @@
  • > there are 16 themes.
  • > there is no theme 17.
  • -
  • - > chill, designer. +
  • + > chill, designer.
  • - tap any key - - to return to the page + returning to the page in a moment

    diff --git a/site/js/main.js b/site/js/main.js index 259d9ea..0e78303 100644 --- a/site/js/main.js +++ b/site/js/main.js @@ -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());