feat: integrate custom branding, time-based themes, and LXGW font directly into frontend

This commit is contained in:
Bot
2026-04-16 16:59:01 +08:00
parent 191d44bfd0
commit cb436904ea
6 changed files with 79 additions and 5 deletions
+1 -2
View File
@@ -44,8 +44,7 @@ const MainApp: React.FC = () => {
// 检测是否强制指定了主题颜色
const forceTheme =
// @ts-expect-error ForceTheme is a global variable
(window.ForceTheme as string) !== "" ? window.ForceTheme : undefined;
window.ForceTheme as string !== "" ? window.ForceTheme : undefined;
useEffect(() => {
if (forceTheme === "dark" || forceTheme === "light") {
-3
View File
@@ -70,10 +70,8 @@ function Header() {
const siteName = settingData?.data?.config?.site_name;
// @ts-expect-error CustomLogo is a global variable
const customLogo = window.CustomLogo || "/apple-touch-icon.png";
// @ts-expect-error CustomDesc is a global variable
const customDesc = window.CustomDesc || t("nezha");
const customMobileBackgroundImage =
@@ -208,7 +206,6 @@ type links = {
};
function Links() {
// @ts-expect-error CustomLinks is a global variable
const customLinks = window.CustomLinks as string;
const links: links[] | null = customLinks ? JSON.parse(customLinks) : null;
+24
View File
@@ -1,4 +1,5 @@
@import "tailwindcss";
@import url("https://cdn.jsdelivr.net/npm/lxgw-wenkai-screen-webfont@1.7.0/style.css");
@plugin "tailwindcss-animate";
@@ -189,6 +190,7 @@
@layer base {
* {
@apply border-border;
font-family: 'LXGW WenKai Screen', sans-serif !important;
}
html {
@apply scroll-smooth;
@@ -199,6 +201,9 @@
font-synthesis-weight: none;
text-rendering: optimizeLegibility;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'LXGW WenKai Screen', sans-serif !important;
}
}
@layer base {
@@ -381,3 +386,22 @@
filter: blur(2px);
}
}
/* Custom background overlays */
.bg-cover::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
transition: backdrop-filter 0.3s ease, background 0.3s ease;
}
.dark .bg-cover::after {
backdrop-filter: blur(8px);
background: rgba(0, 0, 0, .6);
}
.light .bg-cover::after {
backdrop-filter: blur(0);
background: rgba(255, 255, 255, .3);
}
+38
View File
@@ -0,0 +1,38 @@
import { DateTime } from "luxon";
export function initCustomConfig() {
try {
const hour = DateTime.now().hour;
const isNight = hour >= 18 || hour < 6;
// Use default values if window variables are not already set (e.g. by backend custom_code)
// although the goal is to hardcode these for "consistency".
window.CustomBackgroundImage = isNight
? 'https://loohui.com/wp-content/uploads/images/background.jpg'
: 'https://loohui.com/wp-content/uploads/images/background_day.jpg';
window.CustomMobileBackgroundImage = window.CustomBackgroundImage;
window.ForceTheme = isNight ? 'dark' : 'light';
/* LOGO / 副标题 / 链接 */
window.CustomLogo = 'https://loohui.com/wp-content/uploads/images/pet.png';
window.CustomDesc = '树树皆秋色,山山唯落晖';
window.CustomLinks = JSON.stringify([
{ "link": "https://loohui.com/", "name": "返回Blog", "blank": false }
]);
// Handle internal redirects if needed
document.addEventListener('click', (e: MouseEvent) => {
const target = e.target as HTMLElement;
const a = target.closest('a');
if (a && a.href === 'https://loohui.com/') {
e.preventDefault();
window.location.href = a.href;
}
});
} catch (e) {
console.error('[Nezha custom_config] crash:', e);
}
}
+3
View File
@@ -12,9 +12,12 @@ import { SortProvider } from "./context/sort-provider";
import { StatusProvider } from "./context/status-provider";
import { TooltipProvider } from "./context/tooltip-provider";
import { WebSocketProvider } from "./context/websocket-provider";
import { initCustomConfig } from "./lib/custom-config";
import "./i18n";
import "./index.css";
initCustomConfig();
const queryClient = new QueryClient();
const rootElement = document.getElementById("root");
+13
View File
@@ -1 +1,14 @@
/// <reference types="vite/client" />
interface Window {
CustomBackgroundImage: string;
CustomMobileBackgroundImage: string;
ForceTheme: string;
CustomLogo: string;
CustomDesc: string;
CustomLinks: string;
ForceShowServices: boolean;
ForceCardInline: boolean;
ForceShowMap: boolean;
ForcePeakCutEnabled: boolean;
}