dark theme

This commit is contained in:
Tienson Qin
2025-12-29 10:36:27 +08:00
parent db55b2d5ce
commit 6d551e7b68
3 changed files with 235 additions and 34 deletions

View File

@@ -12,7 +12,47 @@
--border: #cecdc3;
--link: #282726;
--action: #24837B;
--shadow: 0 18px 40px rgba(40, 39, 38, 0.12);
--shadow: 0 18px 40px rgba(40, 39, 38, 0.1);
--bg-gradient-1: rgba(218, 112, 44, 0.12);
--bg-gradient-2: rgba(67, 133, 190, 0.1);
--button-bg: #f2e9d6;
--button-bg-hover: #efe0c2;
--code-bg: #1f2933;
--code-ink: #f8f4ec;
--code-muted: #b59d82;
--math-bg: #f6ede2;
--quote-border: #282726;
--card-bg: #fff7ee;
--input-bg: #fffcf0;
--image-shadow: 0 12px 24px rgba(40, 39, 38, 0.08);
--icon-day: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' %3E%3Cpath fill-rule='evenodd' d='M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z' clip-rule='evenodd' /%3E%3C/svg%3E");
--icon-night: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 24 24' %3E%3Cpath d='M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z' /%3E%3C/svg%3E");
}
[data-theme="dark"] {
color-scheme: dark;
--bg: #100f0f;
--bg-accent: #1c1b1a;
--surface: #1c1b1a;
--surface-strong: #282726;
--ink: #e6e4d9;
--muted: #b7b5ac;
--border: #403e3c;
--link: #e6e4d9;
--action: #3AA99F;
--shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
--bg-gradient-1: rgba(218, 112, 44, 0.18);
--bg-gradient-2: rgba(67, 133, 190, 0.14);
--button-bg: #2f2c2b;
--button-bg-hover: #3a3635;
--code-bg: #1a1f24;
--code-ink: #f8f4ec;
--code-muted: #a3a091;
--math-bg: #242220;
--quote-border: #e6e4d9;
--card-bg: #1f1d1c;
--input-bg: #1c1b1a;
--image-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
}
* {
@@ -23,8 +63,8 @@ body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(1200px 600px at 10% -10%, rgba(218, 112, 44, 0.12), transparent 60%),
radial-gradient(900px 400px at 90% 0%, rgba(67, 133, 190, 0.1), transparent 60%),
radial-gradient(1200px 600px at 10% -10%, var(--bg-gradient-1), transparent 60%),
radial-gradient(900px 400px at 90% 0%, var(--bg-gradient-2), transparent 60%),
linear-gradient(180deg, var(--bg) 0%, var(--bg-accent) 100%);
color: var(--ink);
font-family: "Inter", "Segoe UI", sans-serif;
@@ -63,12 +103,16 @@ a:hover {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
justify-content: flex-end;
flex-wrap: wrap;
padding: 12px 0;
margin: -8px 0 24px;
}
.page-toolbar .toolbar-btn:first-child {
margin-right: auto;
}
.toolbar-btn {
background: transparent;
border: none;
@@ -83,6 +127,76 @@ a:hover {
transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.theme-toggle {
position: relative;
width: 62px;
height: 30px;
padding: 0 8px;
background: var(--button-bg);
border: none;
border-radius: 999px;
color: var(--ink);
display: inline-flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
transition: background 0.2s ease, box-shadow 0.2s ease;
}
.theme-toggle:hover {
background: var(--button-bg-hover);
box-shadow: 0 6px 16px rgba(40, 39, 38, 0.12);
}
.theme-toggle__thumb {
position: absolute;
top: 3px;
left: 3px;
width: 24px;
height: 24px;
border-radius: 999px;
background: var(--surface);
box-shadow: 0 4px 10px rgba(40, 39, 38, 0.12);
transition: transform 0.2s ease;
}
.theme-toggle.is-dark .theme-toggle__thumb {
transform: translateX(32px);
}
.theme-toggle__icon {
width: 14px;
height: 14px;
background-color: currentColor;
opacity: 0.6;
transition: opacity 0.2s ease;
mask
-repeat: no-repeat;
mask-position: center;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
-webkit-mask-size: contain;
}
.theme-toggle__icon--day {
mask-image: var(--icon-day);
-webkit-mask-image: var(--icon-day);
}
.theme-toggle__icon--night {
mask-image: var(--icon-night);
-webkit-mask-image: var(--icon-night);
}
.theme-toggle.is-dark .theme-toggle__icon--night,
.theme-toggle:not(.is-dark) .theme-toggle__icon--day {
opacity: 1;
}
.blocks {
margin: 0;
padding-left: 0;
@@ -113,8 +227,10 @@ a:hover {
.code-block {
flex: 1;
position: relative;
margin: 1.5em 0;
border-radius: 14px;
overflow: hidden;
/* background: var(--code-bg); */
}
.code-block[data-lang]:before {
@@ -125,11 +241,14 @@ a:hover {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.16em;
color: var(--code-muted);
z-index: 2;
}
.code-block .cm-editor {
height: auto;
/* background: var(--code-bg); */
/* color: var(--code-ink); */
font-size: 13px;
line-height: 1.6;
}
@@ -148,17 +267,15 @@ a:hover {
}
.quote-block {
flex: 1;
margin: 0;
padding: 10px 14px;
border-left: 3px solid var(--ink);
border-radius: 12px;
padding-left: 1.5em;
margin: 1.5em 0;
border-left: 2px solid var(--quote-border);
}
.asset-image {
max-width: 100%;
border-radius: 14px;
box-shadow: 0 12px 24px rgba(40, 39, 38, 0.08);
box-shadow: var(--image-shadow);
}
.asset-video,
@@ -227,7 +344,7 @@ a:hover {
}
.block-toggle:focus {
outline: 2px solid rgba(217, 125, 63, 0.5);
outline: 2px solid var(--action);
outline-offset: 2px;
border-radius: 4px;
}
@@ -247,8 +364,8 @@ a:hover {
.linked-refs h2,
.tagged-pages h2 {
font-size: 20px;
margin: 0 0 16px;
font-size: 14px;
margin: 64px 0 16px 0;
color: var(--muted);
}
@@ -318,7 +435,7 @@ a:hover {
max-width: 460px;
padding: 24px;
border-radius: 18px;
background: #fff7ee;
background: var(--card-bg);
box-shadow: 0 12px 24px rgba(40, 39, 38, 0.12);
text-align: center;
}
@@ -341,15 +458,14 @@ a:hover {
padding: 10px 12px;
border-radius: 10px;
border: none;
background: #fffcf0;
background: var(--input-bg);
box-shadow: inset 0 0 0 1px rgba(40, 39, 38, 0.1);
font-size: 15px;
font-family: inherit;
}
.password-input:focus {
outline: 2px solid rgba(217, 125, 63, 0.4);
border-color: rgba(217, 125, 63, 0.6);
outline: 2px solid var(--action);
}
.password-error {
@@ -431,7 +547,7 @@ a:hover {
.page-toolbar {
gap: 10px;
justify-content: flex-start;
justify-content: flex-end;
}
.properties {

View File

@@ -24,6 +24,7 @@ import { css } from "https://esm.sh/@codemirror/lang-css@6";
import { clojure } from "https://esm.sh/@nextjournal/lang-clojure";
const katex = katexPkg.default || katexPkg;
const THEME_KEY = "publish-theme";
document.addEventListener("click", (event) => {
const btn = event.target.closest(".block-toggle");
@@ -34,6 +35,13 @@ document.addEventListener("click", (event) => {
btn.setAttribute("aria-expanded", String(!collapsed));
});
document.addEventListener("click", (event) => {
const toggle = event.target.closest(".theme-toggle");
if (!toggle) return;
event.preventDefault();
window.toggleTheme();
});
window.toggleTopBlocks = (btn) => {
const list = document.querySelector(".blocks");
if (!list) return;
@@ -50,7 +58,32 @@ window.toggleTopBlocks = (btn) => {
}
};
const applyTheme = (theme) => {
document.documentElement.setAttribute("data-theme", theme);
document.querySelectorAll(".theme-toggle").forEach((toggle) => {
toggle.classList.toggle("is-dark", theme === "dark");
toggle.setAttribute("aria-checked", String(theme === "dark"));
});
};
const preferredTheme = () => {
const stored = window.localStorage.getItem(THEME_KEY);
if (stored) return stored;
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
};
window.toggleTheme = () => {
const current = document.documentElement.getAttribute("data-theme") || "light";
const next = current === "dark" ? "light" : "dark";
applyTheme(next);
window.localStorage.setItem(THEME_KEY, next);
};
const initPublish = () => {
applyTheme(preferredTheme());
document.querySelectorAll(".math-block").forEach((el) => {
const tex = el.textContent;
try {

View File

@@ -80,6 +80,29 @@
[]
nodes-list))
(defn- theme-toggle-node
[]
[:button.theme-toggle
{:type "button"
:role "switch"
:aria-checked "false"}
[:span.theme-toggle__icon.theme-toggle__icon--day {:aria-hidden "true"}]
[:span.theme-toggle__thumb {:aria-hidden "true"}]
[:span.theme-toggle__icon.theme-toggle__icon--night {:aria-hidden "true"}]])
(defn- toolbar-node
[& nodes]
(into [:div.page-toolbar] nodes))
(defn- theme-init-script
[]
[:script
"(function(){try{var k='publish-theme';var t=localStorage.getItem(k);if(!t){t=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';}document.documentElement.setAttribute('data-theme',t);}catch(e){}})();"])
(defn- publish-script
[]
[:script {:type "module" :src "/static/publish.js"}])
(defn property-type
[prop-key property-type-by-ident]
(or (get property-type-by-ident prop-key)
@@ -568,15 +591,14 @@
[:meta {:charset "utf-8"}]
[:meta {:name "viewport" :content "width=device-width,initial-scale=1"}]
[:title page-title]
(theme-init-script)
[:link {:rel "stylesheet" :href "/static/publish.css"}]]
[:body
[:main.wrap
[:div.page-toolbar
(toolbar-node
(when graph-uuid
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])
[:a.toolbar-btn
{:onclick "window.toggleTopBlocks(this)"}
"Collapse all"]]
(theme-toggle-node))
[:h1 page-title]
@@ -586,7 +608,7 @@
(when blocks blocks)
(when tagged-section tagged-section)
(when linked-refs linked-refs)]
[:script {:type "module" :src "/static/publish.js"}]]]]
(publish-script)]]]
(str "<!doctype html>" (render-hiccup doc))))
(defn render-graph-html
@@ -611,9 +633,12 @@
[:meta {:charset "utf-8"}]
[:meta {:name "viewport" :content "width=device-width,initial-scale=1"}]
[:title (str "Published pages - " graph-uuid)]
(theme-init-script)
[:link {:rel "stylesheet" :href "/static/publish.css"}]]
[:body
[:main.wrap
(toolbar-node
(theme-toggle-node))
[:h1 "Published pages"]
(if (seq rows)
[:ul.page-list
@@ -625,7 +650,8 @@
[:a.short-link {:href (str "/s/" short-id)}
(str "/s/" short-id)])]
[:span.page-meta (or (format-timestamp updated-at) "—")]])]
[:p "No pages have been published yet."])]]]]
[:p "No pages have been published yet."])
(publish-script)]]]]
(str "<!doctype html>" (render-hiccup doc))))
(defn render-tag-html
@@ -637,9 +663,14 @@
[:meta {:charset "utf-8"}]
[:meta {:name "viewport" :content "width=device-width,initial-scale=1"}]
[:title (str "Tag - " title)]
(theme-init-script)
[:link {:rel "stylesheet" :href "/static/publish.css"}]]
[:body
[:main.wrap
(toolbar-node
(when graph-uuid
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])
(theme-toggle-node))
[:h1 title]
[:p.tag-sub (str "Tag: " tag-uuid)]
[:p.graph-meta (str "Graph: " graph-uuid)]
@@ -647,7 +678,8 @@
[:ul.page-list
(for [item rows]
(render-tagged-item graph-uuid item))]
[:p "No published nodes use this tag yet."])]]]]
[:p "No published nodes use this tag yet."])
(publish-script)]]]]
(str "<!doctype html>" (render-hiccup doc))))
(defn render-tag-name-html
@@ -659,9 +691,12 @@
[:meta {:charset "utf-8"}]
[:meta {:name "viewport" :content "width=device-width,initial-scale=1"}]
[:title (str "Tag - " title)]
(theme-init-script)
[:link {:rel "stylesheet" :href "/static/publish.css"}]]
[:body
[:main.wrap
(toolbar-node
(theme-toggle-node))
[:h1 title]
[:p.tag-sub (str "Tag: " tag-name)]
(if (seq rows)
@@ -682,7 +717,8 @@
[:a.short-link {:href (str "/s/" short-id)}
(str "/s/" short-id)])]
[:span.page-meta (or (format-timestamp (tag-item-val row :updated_at)) "—")]])]
[:p "No published pages use this tag yet."])]]]]
[:p "No published pages use this tag yet."])
(publish-script)]]]]
(str "<!doctype html>" (render-hiccup doc))))
(defn render-ref-html
@@ -694,9 +730,14 @@
[:meta {:charset "utf-8"}]
[:meta {:name "viewport" :content "width=device-width,initial-scale=1"}]
[:title (str "Ref - " title)]
(theme-init-script)
[:link {:rel "stylesheet" :href "/static/publish.css"}]]
[:body
[:main.wrap
(toolbar-node
(when graph-uuid
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])
(theme-toggle-node))
[:h1 title]
[:p.tag-sub (str "Reference: " ref-name)]
(if (seq rows)
@@ -717,7 +758,8 @@
[:a.short-link {:href (str "/s/" short-id)}
(str "/s/" short-id)])]
[:span.page-meta (or (format-timestamp (tag-item-val row :updated_at)) "—")]])]
[:p "No published pages reference this yet."])]]]]
[:p "No published pages reference this yet."])
(publish-script)]]]]
(str "<!doctype html>" (render-hiccup doc))))
(defn render-not-published-html
@@ -728,14 +770,17 @@
[:meta {:charset "utf-8"}]
[:meta {:name "viewport" :content "width=device-width,initial-scale=1"}]
[:title title]
(theme-init-script)
[:link {:rel "stylesheet" :href "/static/publish.css"}]]
[:body
[:main.wrap
[:div.page-toolbar
(toolbar-node
(when graph-uuid
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])]
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])
(theme-toggle-node))
[:h1 title]
[:p.tag-sub "This page hasn't been published yet."]]]]]
[:p.tag-sub "This page hasn't been published yet."]
(publish-script)]]]]
(str "<!doctype html>" (render-hiccup doc))))
(defn render-password-html
@@ -746,12 +791,14 @@
[:meta {:charset "utf-8"}]
[:meta {:name "viewport" :content "width=device-width,initial-scale=1"}]
[:title title]
(theme-init-script)
[:link {:rel "stylesheet" :href "/static/publish.css"}]]
[:body
[:main.wrap
[:div.page-toolbar
(toolbar-node
(when graph-uuid
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])]
[:a.toolbar-btn {:href (str "/graph/" graph-uuid)} "Home"])
(theme-toggle-node))
[:div.password-card
[:h1 title]
[:p.tag-sub "This page is password protected."]
@@ -766,7 +813,8 @@
:type "password"
:placeholder "Password"
:required true}]
[:button.toolbar-btn {:type "submit"} "Unlock"]]]]]]]
[:button.toolbar-btn {:type "submit"} "Unlock"]]]
(publish-script)]]]]
(str "<!doctype html>" (render-hiccup doc))))
(defn render-404-html
@@ -777,11 +825,15 @@
[:meta {:charset "utf-8"}]
[:meta {:name "viewport" :content "width=device-width,initial-scale=1"}]
[:title title]
(theme-init-script)
[:link {:rel "stylesheet" :href "/static/publish.css"}]]
[:body
[:main.wrap
(toolbar-node
(theme-toggle-node))
[:div.not-found
[:p.not-found-eyebrow "404"]
[:h1 title]
[:p.tag-sub "We couldn't find that page. It may have been removed or never published."]]]]]]
[:p.tag-sub "We couldn't find that page. It may have been removed or never published."]]
(publish-script)]]]]
(str "<!doctype html>" (render-hiccup doc))))