Add reader theme toggle
This commit is contained in:
+40
-4
@@ -18,7 +18,9 @@ const icons = {
|
|||||||
download: `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v10m0 0 4-4m-4 4-4-4"/><path d="M5 18.5h14"/></svg>`,
|
download: `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v10m0 0 4-4m-4 4-4-4"/><path d="M5 18.5h14"/></svg>`,
|
||||||
image: `<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="5" width="16" height="14" rx="2"/><path d="m7 16 4-4 3 3 2-2 3 3"/><circle cx="8.5" cy="8.5" r="1.2"/></svg>`,
|
image: `<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="5" width="16" height="14" rx="2"/><path d="m7 16 4-4 3 3 2-2 3 3"/><circle cx="8.5" cy="8.5" r="1.2"/></svg>`,
|
||||||
share: `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8.5 12.5 15.5 8.5M8.5 11.5 15.5 15.5"/><circle cx="6.5" cy="12" r="2.5"/><circle cx="17.5" cy="7.5" r="2.5"/><circle cx="17.5" cy="16.5" r="2.5"/></svg>`,
|
share: `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8.5 12.5 15.5 8.5M8.5 11.5 15.5 15.5"/><circle cx="6.5" cy="12" r="2.5"/><circle cx="17.5" cy="7.5" r="2.5"/><circle cx="17.5" cy="16.5" r="2.5"/></svg>`,
|
||||||
chevron: `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m8 10 4 4 4-4"/></svg>`
|
chevron: `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m8 10 4 4 4-4"/></svg>`,
|
||||||
|
sun: `<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 3v2m0 14v2M3 12h2m14 0h2M5.6 5.6 7 7m10 10 1.4 1.4M18.4 5.6 17 7M7 17l-1.4 1.4"/></svg>`,
|
||||||
|
moon: `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 14.6A7.6 7.6 0 0 1 9.4 4 8 8 0 1 0 20 14.6Z"/></svg>`
|
||||||
};
|
};
|
||||||
|
|
||||||
function css(): string {
|
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;
|
--bg: #212121; --panel: #212121; --text: #ececec; --muted: #a8a8a8; --border: #3f3f46; --soft: #2f2f2f; --code: #e5e7eb; --code-bg: #171717; --user-bubble: #303030;
|
||||||
}
|
}
|
||||||
* { box-sizing: border-box; }
|
* { 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; }
|
body { margin: 0; background: var(--bg); color: var(--text); font-size: 16px; line-height: 1.65; }
|
||||||
a { color: inherit; }
|
a { color: inherit; }
|
||||||
.page { min-height: 100vh; }
|
.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 { 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-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; }
|
.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; }
|
.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; }
|
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; }
|
.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) {
|
@media (max-width: 640px) {
|
||||||
body { font-size: 15px; }
|
body { font-size: 15px; }
|
||||||
.topbar-inner { padding: 12px 16px; }
|
.topbar-inner { padding: 12px 16px; }
|
||||||
|
.topbar-actions { gap: 6px; }
|
||||||
.title { padding: 26px 16px 12px; }
|
.title { padding: 26px 16px 12px; }
|
||||||
.conversation { padding: 0 16px 28px; }
|
.conversation { padding: 0 16px 28px; }
|
||||||
.message { padding: 16px 0; }
|
.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 {
|
function pageScript(slug: string): string {
|
||||||
return `
|
return `
|
||||||
(function () {
|
(function () {
|
||||||
function currentVariant() {
|
function currentVariant() {
|
||||||
var device = matchMedia("(max-width: 640px)").matches ? "mobile" : "desktop";
|
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;
|
return device + "-" + theme;
|
||||||
}
|
}
|
||||||
function downloadShot(variant) {
|
function downloadShot(variant) {
|
||||||
location.href = "screenshots/" + variant + ".png";
|
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;
|
var toastTimer;
|
||||||
function showToast(message) {
|
function showToast(message) {
|
||||||
var region = document.querySelector("[data-toast-region]");
|
var region = document.querySelector("[data-toast-region]");
|
||||||
@@ -176,7 +204,14 @@ function pageScript(slug: string): string {
|
|||||||
}
|
}
|
||||||
var params = new URLSearchParams(location.search);
|
var params = new URLSearchParams(location.search);
|
||||||
var theme = params.get("theme");
|
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]");
|
var copy = document.querySelector("[data-copy]");
|
||||||
if (copy) copy.addEventListener("click", async function () {
|
if (copy) copy.addEventListener("click", async function () {
|
||||||
await copyText(location.href.replace(/[?#].*$/, ""), "Link copied");
|
await copyText(location.href.replace(/[?#].*$/, ""), "Link copied");
|
||||||
@@ -229,11 +264,12 @@ export function renderConversationPage(slug: string, conversation: NormalizedCon
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="robots" content="noindex">
|
<meta name="robots" content="noindex">
|
||||||
<title>${title}</title>
|
<title>${title}</title>
|
||||||
|
<script>${initialThemeScript()}</script>
|
||||||
<style>${css()}</style>
|
<style>${css()}</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<header class="topbar"><div class="topbar-inner"><strong>AI Share</strong><span class="snapshot-label">ChatGPT snapshot</span></div></header>
|
<header class="topbar"><div class="topbar-inner"><strong>AI Share</strong><div class="topbar-actions"><span class="snapshot-label">ChatGPT snapshot</span><button class="icon-button theme-toggle" type="button" data-theme-toggle aria-label="Switch theme" title="Switch theme"><span class="sun-icon">${icons.sun}</span><span class="moon-icon">${icons.moon}</span></button></div></div></header>
|
||||||
<main>
|
<main>
|
||||||
<section class="title"><h1>${title}</h1></section>
|
<section class="title"><h1>${title}</h1></section>
|
||||||
<section class="conversation">${messages}</section>
|
<section class="conversation">${messages}</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user