diff --git a/src/render.ts b/src/render.ts index d5109b2..894d771 100644 --- a/src/render.ts +++ b/src/render.ts @@ -18,7 +18,9 @@ const icons = { download: ``, image: ``, share: ``, - chevron: `` + chevron: ``, + sun: ``, + moon: `` }; function css(): string { @@ -57,12 +59,19 @@ html[data-theme="dark"] { --bg: #212121; --panel: #212121; --text: #ececec; --muted: #a8a8a8; --border: #3f3f46; --soft: #2f2f2f; --code: #e5e7eb; --code-bg: #171717; --user-bubble: #303030; } * { box-sizing: border-box; } +body, .topbar, .message.user .content, .content pre, .content :not(pre) > code, .code-block, .code-block .code-header, .content th, .content td, .content blockquote, .downloads, .icon-button, .shot-menu, .shot-menu button, .toast { + transition: background-color .22s ease, border-color .22s ease, color .22s ease, box-shadow .22s ease; +} body { margin: 0; background: var(--bg); color: var(--text); font-size: 16px; line-height: 1.65; } a { color: inherit; } .page { min-height: 100vh; } .topbar { position: sticky; top: 0; z-index: 2; border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--bg) 92%, transparent); backdrop-filter: blur(12px); } .topbar-inner { max-width: 880px; margin: 0 auto; padding: 14px 20px; display: flex; align-items: center; justify-content: space-between; gap: 12px; } +.topbar-actions { display: inline-flex; align-items: center; gap: 8px; min-width: 0; } .snapshot-label { font-size: 13px; color: var(--muted); white-space: nowrap; } +.theme-toggle .moon-icon { display: none; } +html[data-theme="dark"] .theme-toggle .sun-icon { display: none; } +html[data-theme="dark"] .theme-toggle .moon-icon { display: block; } .title { max-width: 880px; margin: 0 auto; padding: 34px 20px 18px; } h1 { margin: 0; font-size: clamp(28px, 5vw, 44px); line-height: 1.12; letter-spacing: 0; } .conversation { max-width: 880px; margin: 0 auto; padding: 4px 20px 36px; } @@ -125,6 +134,7 @@ h1 { margin: 0; font-size: clamp(28px, 5vw, 44px); line-height: 1.12; letter-spa @media (max-width: 640px) { body { font-size: 15px; } .topbar-inner { padding: 12px 16px; } + .topbar-actions { gap: 6px; } .title { padding: 26px 16px 12px; } .conversation { padding: 0 16px 28px; } .message { padding: 16px 0; } @@ -137,17 +147,35 @@ h1 { margin: 0; font-size: clamp(28px, 5vw, 44px); line-height: 1.12; letter-spa }`; } +function initialThemeScript(): string { + return `!function(){var p=new URLSearchParams(location.search),t=p.get("theme");if(t!=="light"&&t!=="dark")t=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";document.documentElement.dataset.theme=t}();`; +} + function pageScript(slug: string): string { return ` (function () { function currentVariant() { var device = matchMedia("(max-width: 640px)").matches ? "mobile" : "desktop"; - var theme = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; + var theme = document.documentElement.dataset.theme || (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"); return device + "-" + theme; } function downloadShot(variant) { location.href = "screenshots/" + variant + ".png"; } + var systemTheme = matchMedia("(prefers-color-scheme: dark)"); + var themeToggle = document.querySelector("[data-theme-toggle]"); + var manualTheme = false; + function preferredTheme() { + return systemTheme.matches ? "dark" : "light"; + } + function applyTheme(theme, manual) { + document.documentElement.dataset.theme = theme; + if (manual) manualTheme = true; + if (themeToggle) { + themeToggle.setAttribute("aria-label", theme === "dark" ? "Switch to light theme" : "Switch to dark theme"); + themeToggle.setAttribute("title", theme === "dark" ? "Switch to light theme" : "Switch to dark theme"); + } + } var toastTimer; function showToast(message) { var region = document.querySelector("[data-toast-region]"); @@ -176,7 +204,14 @@ function pageScript(slug: string): string { } var params = new URLSearchParams(location.search); var theme = params.get("theme"); - if (theme === "light" || theme === "dark") document.documentElement.dataset.theme = theme; + manualTheme = theme === "light" || theme === "dark"; + applyTheme(theme === "light" || theme === "dark" ? theme : preferredTheme(), theme === "light" || theme === "dark"); + systemTheme.addEventListener("change", function () { + if (!manualTheme) applyTheme(preferredTheme(), false); + }); + if (themeToggle) themeToggle.addEventListener("click", function () { + applyTheme(document.documentElement.dataset.theme === "dark" ? "light" : "dark", true); + }); var copy = document.querySelector("[data-copy]"); if (copy) copy.addEventListener("click", async function () { await copyText(location.href.replace(/[?#].*$/, ""), "Link copied"); @@ -229,11 +264,12 @@ export function renderConversationPage(slug: string, conversation: NormalizedCon