diff options
| author | Yuqian Yang <crupest@crupest.life> | 2025-03-03 23:29:38 +0800 | 
|---|---|---|
| committer | Yuqian Yang <crupest@crupest.life> | 2025-03-03 23:29:38 +0800 | 
| commit | 4bf9b57cca977760a89bda7b419a85ca471ba1ce (patch) | |
| tree | 1a50b7716ec7369d2dfeaa885d8c055226bcf734 /www/assets/res/js | |
| parent | cfa5ea2f4a8fd79136cfe95ad1fdfe88d52eece8 (diff) | |
| download | crupest-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.ts | 110 | 
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 +    } +  }) +})  | 
