aboutsummaryrefslogtreecommitdiff
path: root/www/assets/res/js
diff options
context:
space:
mode:
authorYuqian Yang <crupest@crupest.life>2025-03-03 23:29:38 +0800
committerYuqian Yang <crupest@crupest.life>2025-03-03 23:29:38 +0800
commit4bf9b57cca977760a89bda7b419a85ca471ba1ce (patch)
tree1a50b7716ec7369d2dfeaa885d8c055226bcf734 /www/assets/res/js
parentcfa5ea2f4a8fd79136cfe95ad1fdfe88d52eece8 (diff)
downloadcrupest-4bf9b57cca977760a89bda7b419a85ca471ba1ce.tar.gz
crupest-4bf9b57cca977760a89bda7b419a85ca471ba1ce.tar.bz2
crupest-4bf9b57cca977760a89bda7b419a85ca471ba1ce.zip
feat(www): hurd and todos.
Diffstat (limited to 'www/assets/res/js')
-rw-r--r--www/assets/res/js/color-scheme.ts110
1 files changed, 110 insertions, 0 deletions
diff --git a/www/assets/res/js/color-scheme.ts b/www/assets/res/js/color-scheme.ts
new file mode 100644
index 0000000..db6a3aa
--- /dev/null
+++ b/www/assets/res/js/color-scheme.ts
@@ -0,0 +1,110 @@
+function createResetTimer(cleanup: () => void, timeout = 1) {
+ let tag = 0
+ return () => {
+ clearTimeout(tag)
+ tag = setTimeout(() => {
+ cleanup()
+ }, timeout * 1000)
+ }
+}
+
+function createToast(duration: number = 1): (text: string) => void {
+ const toast = document.createElement("div")
+ toast.className = "toast"
+
+ const reset = createResetTimer(() => {
+ toast.remove()
+ }, duration)
+
+ return (text) => {
+ if (!toast.isConnected) {
+ document.body.appendChild(toast)
+ }
+ toast.textContent = text
+ reset()
+ }
+}
+
+const setToast = createToast()
+
+const key = "force-color-scheme"
+const dark = "dark"
+const light = "light"
+type Scheme = typeof dark | typeof light
+
+const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
+
+function fromMediaQuery(value: boolean | null): Scheme {
+ if (value == null) { value = mediaQuery.matches}
+ return value ? dark : light
+}
+
+function opposite(scheme: Scheme): Scheme {
+ return scheme === dark ? light : dark
+}
+
+function updateScheme(theme: Scheme | null): Scheme {
+ if (theme == null) { theme = fromMediaQuery(null) }
+ document.querySelector("html")!.dataset["theme"] = theme
+ return theme
+}
+
+mediaQuery.addEventListener("change", (e) => updateScheme(current || fromMediaQuery(e.matches)))
+
+let current: Scheme | null = null
+
+{
+ const saved = localStorage.getItem(key)
+ if ([null, dark, light].includes(saved)) {
+ current = saved as never
+ } else {
+ console.log(`invalid saved theme: ${saved}`)
+ localStorage.removeItem(key)
+ }
+}
+
+updateScheme(current)
+
+function saveScheme(value: Scheme | null) {
+ current = value
+
+ if (value == null) {
+ localStorage.removeItem(key)
+ } else {
+ localStorage.setItem(key, value)
+ }
+
+ const real = updateScheme(value)
+ setToast(`theme: ${current == null ? "system" : "force"}(${real})`)
+}
+
+function next(): Scheme | null {
+ const sys = fromMediaQuery(null)
+ if (current == null) {
+ return opposite(sys)
+ } else {
+ if (current === sys) {
+ return null;
+ } else {
+ return opposite(current)
+ }
+ }
+}
+
+window.addEventListener("load", () => {
+ const slogon = document.getElementById("slogan")!
+ let clicks: number = 0
+
+ const reset = createResetTimer(() => {
+ clicks = 0
+ })
+
+ slogon.addEventListener("click", () => {
+ reset()
+ clicks += 1
+ if (clicks === 3) {
+ saveScheme(next())
+ clicks = 0
+ }
+ })
+})