| import {
|
| DARK_MODE,
|
| DEFAULT_THEME,
|
| LIGHT_MODE,
|
| SYSTEM_MODE,
|
| WALLPAPER_BANNER,
|
| WALLPAPER_NONE,
|
| WALLPAPER_OVERLAY,
|
| } from "@constants/constants";
|
| import type { LIGHT_DARK_MODE, WALLPAPER_MODE } from "@/types/config";
|
| import {
|
| backgroundWallpaper,
|
| expressiveCodeConfig,
|
| siteConfig,
|
| } from "../config";
|
| import { isHomePage as checkIsHomePage } from "./layout-utils";
|
|
|
|
|
| declare global {
|
| interface Window {
|
| initSemifullScrollDetection?: () => void;
|
| semifullScrollHandler?: () => void;
|
| }
|
| }
|
|
|
| export function getDefaultHue(): number {
|
| const fallback = "250";
|
|
|
| if (typeof document === "undefined") {
|
| return Number.parseInt(fallback, 10);
|
| }
|
| const configCarrier = document.getElementById("config-carrier");
|
| return Number.parseInt(configCarrier?.dataset.hue || fallback, 10);
|
| }
|
|
|
| export function getDefaultTheme(): LIGHT_DARK_MODE {
|
|
|
|
|
| return siteConfig.themeColor.defaultMode ?? DEFAULT_THEME;
|
| }
|
|
|
|
|
| export function getSystemTheme(): LIGHT_DARK_MODE {
|
| if (typeof window === "undefined") {
|
| return LIGHT_MODE;
|
| }
|
| return window.matchMedia("(prefers-color-scheme: dark)").matches
|
| ? DARK_MODE
|
| : LIGHT_MODE;
|
| }
|
|
|
|
|
| export function resolveTheme(theme: LIGHT_DARK_MODE): LIGHT_DARK_MODE {
|
| if (theme === SYSTEM_MODE) {
|
| return getSystemTheme();
|
| }
|
| return theme;
|
| }
|
|
|
| export function getHue(): number {
|
|
|
| if (typeof window === "undefined" || !window.localStorage) {
|
| return getDefaultHue();
|
| }
|
| const stored = localStorage.getItem("hue");
|
| return stored ? Number.parseInt(stored, 10) : getDefaultHue();
|
| }
|
|
|
| export function setHue(hue: number): void {
|
|
|
| if (
|
| typeof window === "undefined" ||
|
| !window.localStorage ||
|
| typeof document === "undefined"
|
| ) {
|
| return;
|
| }
|
| localStorage.setItem("hue", String(hue));
|
| const r = document.querySelector(":root") as HTMLElement;
|
| if (!r) {
|
| return;
|
| }
|
| r.style.setProperty("--hue", String(hue));
|
| }
|
|
|
| export function applyThemeToDocument(theme: LIGHT_DARK_MODE) {
|
|
|
| if (typeof document === "undefined") {
|
| return;
|
| }
|
|
|
|
|
| const resolvedTheme = resolveTheme(theme);
|
|
|
|
|
| const currentIsDark = document.documentElement.classList.contains("dark");
|
| const currentTheme = document.documentElement.getAttribute("data-theme");
|
|
|
|
|
| let targetIsDark = false;
|
| switch (resolvedTheme) {
|
| case LIGHT_MODE:
|
| targetIsDark = false;
|
| break;
|
| case DARK_MODE:
|
| targetIsDark = true;
|
| break;
|
| default:
|
|
|
| targetIsDark = currentIsDark;
|
| break;
|
| }
|
|
|
|
|
|
|
|
|
| const needsThemeChange = currentIsDark !== targetIsDark;
|
| const expectedTheme = targetIsDark
|
| ? expressiveCodeConfig.darkTheme
|
| : expressiveCodeConfig.lightTheme;
|
| const needsCodeThemeUpdate = currentTheme !== expectedTheme;
|
|
|
|
|
| if (!needsThemeChange && !needsCodeThemeUpdate) {
|
| return;
|
| }
|
|
|
|
|
| if (needsThemeChange) {
|
|
|
|
|
|
|
|
|
| if (targetIsDark) {
|
| document.documentElement.classList.add("dark");
|
| } else {
|
| document.documentElement.classList.remove("dark");
|
| }
|
| }
|
|
|
|
|
| if (needsCodeThemeUpdate) {
|
| document.documentElement.setAttribute("data-theme", expectedTheme);
|
| }
|
| }
|
|
|
|
|
| let systemThemeListener:
|
| | ((e: MediaQueryListEvent | MediaQueryList) => void)
|
| | null = null;
|
|
|
| export function setTheme(theme: LIGHT_DARK_MODE): void {
|
|
|
| if (
|
| typeof localStorage === "undefined" ||
|
| typeof localStorage.setItem !== "function"
|
| ) {
|
| return;
|
| }
|
|
|
|
|
| applyThemeToDocument(theme);
|
|
|
|
|
| localStorage.setItem("theme", theme);
|
|
|
|
|
| if (theme === SYSTEM_MODE) {
|
| setupSystemThemeListener();
|
| } else {
|
|
|
| cleanupSystemThemeListener();
|
| }
|
| }
|
|
|
|
|
| export function setupSystemThemeListener() {
|
|
|
| cleanupSystemThemeListener();
|
|
|
| if (typeof window === "undefined") {
|
| return;
|
| }
|
|
|
| const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
|
|
|
| const handleSystemThemeChange = (e: MediaQueryListEvent | MediaQueryList) => {
|
| const isDark = e.matches;
|
| const currentIsDark = document.documentElement.classList.contains("dark");
|
|
|
|
|
| if (currentIsDark === isDark) {
|
| return;
|
| }
|
|
|
|
|
| if (isDark) {
|
| document.documentElement.classList.add("dark");
|
| } else {
|
| document.documentElement.classList.remove("dark");
|
| }
|
|
|
|
|
| const expressiveTheme = isDark
|
| ? expressiveCodeConfig.darkTheme
|
| : expressiveCodeConfig.lightTheme;
|
| document.documentElement.setAttribute("data-theme", expressiveTheme);
|
|
|
|
|
| window.dispatchEvent(new CustomEvent("theme-change"));
|
| };
|
|
|
|
|
| handleSystemThemeChange(mediaQuery);
|
|
|
|
|
| if (mediaQuery.addEventListener) {
|
| mediaQuery.addEventListener("change", handleSystemThemeChange);
|
| } else {
|
|
|
| mediaQuery.addListener(handleSystemThemeChange);
|
| }
|
|
|
| systemThemeListener = handleSystemThemeChange;
|
| }
|
|
|
|
|
| function cleanupSystemThemeListener() {
|
| if (typeof window === "undefined" || !systemThemeListener) {
|
| return;
|
| }
|
|
|
| const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
|
| if (mediaQuery.removeEventListener) {
|
| mediaQuery.removeEventListener("change", systemThemeListener);
|
| } else {
|
|
|
| mediaQuery.removeListener(systemThemeListener);
|
| }
|
|
|
| systemThemeListener = null;
|
| }
|
|
|
| export function getStoredTheme(): LIGHT_DARK_MODE {
|
|
|
| if (
|
| typeof localStorage === "undefined" ||
|
| typeof localStorage.getItem !== "function"
|
| ) {
|
| return getDefaultTheme();
|
| }
|
| return (
|
| (localStorage.getItem("theme") as LIGHT_DARK_MODE) || getDefaultTheme()
|
| );
|
| }
|
|
|
|
|
| export function initThemeListener() {
|
| if (
|
| typeof localStorage === "undefined" ||
|
| typeof localStorage.getItem !== "function"
|
| ) {
|
| return;
|
| }
|
|
|
| const theme = getStoredTheme();
|
|
|
|
|
| if (theme === SYSTEM_MODE) {
|
| setupSystemThemeListener();
|
| }
|
| }
|
|
|
|
|
| export function applyWallpaperModeToDocument(mode: WALLPAPER_MODE) {
|
|
|
| const isSwitchable = backgroundWallpaper.switchable ?? true;
|
| if (!isSwitchable) {
|
|
|
| return;
|
| }
|
|
|
|
|
| const currentMode =
|
| (document.documentElement.getAttribute(
|
| "data-wallpaper-mode",
|
| ) as WALLPAPER_MODE) || backgroundWallpaper.mode;
|
|
|
|
|
| if (currentMode === mode) {
|
|
|
| ensureWallpaperState(mode);
|
| return;
|
| }
|
|
|
|
|
| document.documentElement.classList.add("is-wallpaper-transitioning");
|
|
|
|
|
| document.documentElement.setAttribute("data-wallpaper-mode", mode);
|
|
|
|
|
| requestAnimationFrame(() => {
|
| const body = document.body;
|
|
|
|
|
| body.classList.remove("enable-banner", "wallpaper-transparent");
|
|
|
|
|
| switch (mode) {
|
| case WALLPAPER_BANNER:
|
| body.classList.add("enable-banner");
|
| showBannerMode();
|
| break;
|
| case WALLPAPER_OVERLAY:
|
| body.classList.add("wallpaper-transparent");
|
| showOverlayMode();
|
| break;
|
| case WALLPAPER_NONE:
|
| hideAllWallpapers();
|
| break;
|
| default:
|
| hideAllWallpapers();
|
| break;
|
| }
|
|
|
|
|
| updateNavbarTransparency(mode);
|
|
|
|
|
| requestAnimationFrame(() => {
|
| document.documentElement.classList.remove("is-wallpaper-transitioning");
|
| });
|
| });
|
| }
|
|
|
|
|
| function ensureWallpaperState(mode: WALLPAPER_MODE) {
|
| const body = document.body;
|
|
|
|
|
| body.classList.remove("enable-banner", "wallpaper-transparent");
|
|
|
|
|
| switch (mode) {
|
| case WALLPAPER_BANNER:
|
| body.classList.add("enable-banner");
|
| showBannerMode();
|
| break;
|
| case WALLPAPER_OVERLAY:
|
| body.classList.add("wallpaper-transparent");
|
| showOverlayMode();
|
| break;
|
| case WALLPAPER_NONE:
|
| hideAllWallpapers();
|
| break;
|
| }
|
|
|
|
|
| updateNavbarTransparency(mode);
|
| }
|
|
|
| function showBannerMode() {
|
|
|
| const overlayContainer = document.querySelector(
|
| "[data-overlay-wallpaper]",
|
| ) as HTMLElement;
|
| if (overlayContainer) {
|
| overlayContainer.style.display = "none";
|
| overlayContainer.classList.add("hidden");
|
| overlayContainer.classList.add("opacity-0");
|
| overlayContainer.classList.remove("opacity-100");
|
| }
|
|
|
|
|
| const bannerWrapper = document.getElementById("banner-wrapper");
|
| if (bannerWrapper) {
|
|
|
| const isHomePage = checkIsHomePage(window.location.pathname);
|
| const isMobile = window.innerWidth < 1024;
|
|
|
|
|
| if (isMobile && !isHomePage) {
|
| bannerWrapper.style.display = "none";
|
| bannerWrapper.classList.add("mobile-hide-banner");
|
| } else {
|
|
|
| bannerWrapper.style.display = "block";
|
| bannerWrapper.style.setProperty("display", "block", "important");
|
| requestAnimationFrame(() => {
|
| bannerWrapper.classList.remove("hidden");
|
| bannerWrapper.classList.remove("opacity-0");
|
| bannerWrapper.classList.add("opacity-100");
|
| bannerWrapper.classList.remove("mobile-hide-banner");
|
| });
|
| }
|
| }
|
|
|
|
|
| const creditDesktop = document.getElementById("banner-credit-desktop");
|
| const creditMobile = document.getElementById("banner-credit-mobile");
|
| if (creditDesktop) creditDesktop.style.display = "";
|
| if (creditMobile) creditMobile.style.display = "";
|
|
|
|
|
| const bannerTextOverlay = document.querySelector(".banner-text-overlay");
|
| if (bannerTextOverlay) {
|
|
|
| const homeTextEnabled = backgroundWallpaper.banner?.homeText?.enable;
|
|
|
|
|
| const isHomePage = checkIsHomePage(window.location.pathname);
|
|
|
|
|
| if (homeTextEnabled && isHomePage) {
|
| bannerTextOverlay.classList.remove("hidden");
|
| } else {
|
| bannerTextOverlay.classList.add("hidden");
|
| }
|
| }
|
|
|
|
|
| adjustMainContentPosition("banner");
|
|
|
|
|
| const mainContentWrapper = document.querySelector(".absolute.w-full.z-30");
|
| if (mainContentWrapper) {
|
| const isHomePage = checkIsHomePage(window.location.pathname);
|
| const isMobile = window.innerWidth < 1024;
|
|
|
| if (isMobile && !isHomePage) {
|
| mainContentWrapper.classList.add("mobile-main-no-banner");
|
| } else {
|
| mainContentWrapper.classList.remove("mobile-main-no-banner");
|
| }
|
| }
|
|
|
|
|
| adjustMainContentTransparency(false);
|
|
|
|
|
| const navbar = document.getElementById("navbar");
|
| if (navbar) {
|
|
|
| const transparentMode =
|
| backgroundWallpaper.banner?.navbar?.transparentMode || "semi";
|
| navbar.setAttribute("data-transparent-mode", transparentMode);
|
|
|
|
|
| if (
|
| transparentMode === "semifull" &&
|
| typeof window.initSemifullScrollDetection === "function"
|
| ) {
|
| window.initSemifullScrollDetection();
|
| }
|
| }
|
| }
|
|
|
| function showOverlayMode() {
|
|
|
| const overlayContainer = document.querySelector(
|
| "[data-overlay-wallpaper]",
|
| ) as HTMLElement;
|
| if (overlayContainer) {
|
|
|
| overlayContainer.style.display = "block";
|
| overlayContainer.style.setProperty("display", "block", "important");
|
| requestAnimationFrame(() => {
|
| overlayContainer.classList.remove("hidden");
|
| overlayContainer.classList.remove("opacity-0");
|
| overlayContainer.classList.add("opacity-100");
|
| });
|
| }
|
|
|
|
|
| const bannerWrapper = document.getElementById("banner-wrapper");
|
| if (bannerWrapper) {
|
| bannerWrapper.style.display = "none";
|
| bannerWrapper.classList.add("hidden");
|
| bannerWrapper.classList.add("opacity-0");
|
| bannerWrapper.classList.remove("opacity-100");
|
| }
|
|
|
|
|
| const creditDesktop = document.getElementById("banner-credit-desktop");
|
| const creditMobile = document.getElementById("banner-credit-mobile");
|
| if (creditDesktop) creditDesktop.style.display = "none";
|
| if (creditMobile) creditMobile.style.display = "none";
|
|
|
|
|
| const bannerTextOverlay = document.querySelector(".banner-text-overlay");
|
| if (bannerTextOverlay) {
|
| bannerTextOverlay.classList.add("hidden");
|
| }
|
|
|
|
|
| adjustMainContentTransparency(true);
|
|
|
|
|
| adjustMainContentPosition("overlay");
|
| }
|
|
|
| function hideAllWallpapers() {
|
|
|
| const bannerWrapper = document.getElementById("banner-wrapper");
|
| const overlayContainer = document.querySelector(
|
| "[data-overlay-wallpaper]",
|
| ) as HTMLElement;
|
|
|
| if (bannerWrapper) {
|
| bannerWrapper.style.display = "none";
|
| bannerWrapper.classList.add("hidden");
|
| bannerWrapper.classList.add("opacity-0");
|
| }
|
|
|
| if (overlayContainer) {
|
| overlayContainer.style.display = "none";
|
| overlayContainer.classList.add("hidden");
|
| overlayContainer.classList.add("opacity-0");
|
| overlayContainer.classList.remove("opacity-100");
|
| }
|
|
|
|
|
| const creditDesktop = document.getElementById("banner-credit-desktop");
|
| const creditMobile = document.getElementById("banner-credit-mobile");
|
| if (creditDesktop) creditDesktop.style.display = "none";
|
| if (creditMobile) creditMobile.style.display = "none";
|
|
|
|
|
| const bannerTextOverlay = document.querySelector(".banner-text-overlay");
|
| if (bannerTextOverlay) {
|
| bannerTextOverlay.classList.add("hidden");
|
| }
|
|
|
|
|
| adjustMainContentPosition("none");
|
| adjustMainContentTransparency(false);
|
| }
|
|
|
| function updateNavbarTransparency(mode: WALLPAPER_MODE) {
|
| const navbar = document.getElementById("navbar");
|
| if (!navbar) return;
|
|
|
| let transparentMode: string;
|
| let enableBlur: boolean;
|
|
|
|
|
| if (mode === WALLPAPER_OVERLAY) {
|
|
|
| transparentMode = "none";
|
| enableBlur = false;
|
| } else if (mode === WALLPAPER_NONE) {
|
|
|
| transparentMode = "none";
|
| enableBlur = false;
|
| } else {
|
|
|
| transparentMode =
|
| backgroundWallpaper.banner?.navbar?.transparentMode || "semi";
|
| enableBlur = backgroundWallpaper.banner?.navbar?.enableBlur ?? true;
|
| }
|
|
|
|
|
| navbar.setAttribute("data-transparent-mode", transparentMode);
|
| navbar.setAttribute("data-enable-blur", String(enableBlur));
|
|
|
|
|
| navbar.classList.remove(
|
| "navbar-transparent-semi",
|
| "navbar-transparent-full",
|
| "navbar-transparent-semifull",
|
| );
|
|
|
|
|
| navbar.classList.remove("scrolled");
|
|
|
|
|
| if (
|
| transparentMode === "semifull" &&
|
| mode === WALLPAPER_BANNER &&
|
| typeof window.initSemifullScrollDetection === "function"
|
| ) {
|
|
|
| window.initSemifullScrollDetection();
|
| } else if (window.semifullScrollHandler) {
|
|
|
| window.removeEventListener("scroll", window.semifullScrollHandler);
|
| delete window.semifullScrollHandler;
|
| }
|
| }
|
|
|
| function adjustMainContentPosition(
|
| mode: WALLPAPER_MODE | "banner" | "none" | "overlay",
|
| ) {
|
| const mainContent = document.querySelector(
|
| ".absolute.w-full.z-30",
|
| ) as HTMLElement;
|
| if (!mainContent) return;
|
|
|
|
|
| mainContent.classList.remove("mobile-main-no-banner", "no-banner-layout");
|
|
|
| switch (mode) {
|
| case "banner":
|
|
|
| mainContent.style.top = "calc(var(--banner-height) - 3rem)";
|
| break;
|
| case "overlay":
|
|
|
| mainContent.classList.add("no-banner-layout");
|
| mainContent.style.top = "5.5rem";
|
| break;
|
| case "none":
|
|
|
| mainContent.classList.add("no-banner-layout");
|
| mainContent.style.top = "5.5rem";
|
| break;
|
| default:
|
| mainContent.style.top = "5.5rem";
|
| break;
|
| }
|
| }
|
|
|
| function adjustMainContentTransparency(enable: boolean) {
|
| const mainContent = document.querySelector(".absolute.w-full.z-30");
|
| const body = document.body;
|
|
|
| if (!mainContent || !body) return;
|
|
|
| if (enable) {
|
| mainContent.classList.add("wallpaper-transparent");
|
| body.classList.add("wallpaper-transparent");
|
| } else {
|
| mainContent.classList.remove("wallpaper-transparent");
|
| body.classList.remove("wallpaper-transparent");
|
| }
|
| }
|
|
|
| export function setWallpaperMode(mode: WALLPAPER_MODE): void {
|
|
|
| if (
|
| typeof localStorage === "undefined" ||
|
| typeof localStorage.setItem !== "function"
|
| ) {
|
| return;
|
| }
|
| localStorage.setItem("wallpaperMode", mode);
|
| applyWallpaperModeToDocument(mode);
|
| }
|
|
|
| export function initWallpaperMode(): void {
|
| const storedMode = getStoredWallpaperMode();
|
| applyWallpaperModeToDocument(storedMode);
|
| }
|
|
|
| export function getStoredWallpaperMode(): WALLPAPER_MODE {
|
|
|
| if (
|
| typeof localStorage === "undefined" ||
|
| typeof localStorage.getItem !== "function"
|
| ) {
|
| return backgroundWallpaper.mode;
|
| }
|
| return (
|
| (localStorage.getItem("wallpaperMode") as WALLPAPER_MODE) ||
|
| backgroundWallpaper.mode
|
| );
|
| }
|
|
|