diff --git a/README.md b/README.md index 30b72a8..a35272e 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,113 @@ -# 数据看板 +# Halo 数据统计插件 -## 简介 +## 📊 简介 -为 Halo2 提供强大的数据可视化统计功能,支持 Umami 流量统计、网站内部数据图表(标签、分类、文章趋势、评论排行、热门文章等),可在编辑器中灵活插入 +为 Halo2 提供强大的数据可视化统计功能,支持: -## 致谢 +- **Umami 流量统计**:实时访客、访问趋势、网站分析 +- **网站内部数据图表**:标签分布、分类统计、文章发布趋势、评论排行、热门文章 +- **服务状态监控**:集成 Uptime Kuma 状态页面监控 +- **GitHub 统计展示**:访客可查看博主的 GitHub 活跃度 +- **灵活插入**:可在编辑器中任意位置插入数据图表 -**文章热力图设计借鉴微浸主题**:[了解主题](https://www.webjing.cn/archives/roLydXtD) +## ✨ 特性 + +- 🚀 **高性能**:内置连接池优化,解决 WebClient 连接复用问题 +- 🛡️ **高可用**:自动重试机制,提升服务稳定性 +- ⚡ **响应式**:支持暗色/亮色主题自动切换 +- 📱 **移动端适配**:完美适配手机和平板设备 +- 🔧 **易配置**:简洁的后台配置界面 +- 🎨 **美观设计**:现代化 UI 设计,视觉效果出色 + +## 🔧 技术栈 + +- **后端**:Spring Boot WebFlux、Reactor Netty、Hutool +- **前端**:Vue 3、TypeScript、Rsbuild +- **UI 组件**:Halo Components +- **数据可视化**:Chart.js +- **构建工具**:Gradle、pnpm + +## 🙏 致谢 + +**文章热力图设计灵感来源于微浸主题**:[了解主题](https://www.webjing.cn/archives/roLydXtD) ## 🌐 演示与交流 - **演示站点1**:[https://www.xhhao.com/](https://www.xhhao.com/chart) - **演示站点2**:[https://blog.timxs.com/](https://blog.timxs.com/) -- **文档**:[https://docs.lik.cc/](https://docs.lik.cc/) +- **文档中心**:[https://docs.lik.cc/](https://docs.lik.cc/) - **QQ 交流群**:[![QQ群](https://www.xhhao.com/upload/iShot_2025-03-03_16.03.00.png)](https://www.xhhao.com/upload/iShot_2025-03-03_16.03.00.png) -- -## 开发环境 + +## 📦 最近更新 + +### v1.0.5 (2026-02-15) + +**🚀 性能优化** +- 优化 WebClient 连接池配置,解决 `PrematureCloseException` 连接复用问题 +- 添加自动重试机制,提升服务调用稳定性 +- 实现数据缓存机制,减少重复计算 + +**🔧 功能完善** +- 统一 API 响应格式,提升接口规范性 +- 抽取通用常量类,提高代码可维护性 +- 优化日志级别,减少生产环境日志噪音 + +**🎨 代码质量** +- 重构 Umami 服务实现,抽取公共方法 +- 完善错误处理机制 +- 添加详细的代码注释 +## ⚙️ 开发环境 - Java 21+ - Node.js 18+ -- pnpm +- pnpm 9+ +- Gradle 8+ + +## 🛠️ 开发指南 -## 开发 +### 环境准备 ```bash -# 构建插件 -./gradlew build +# 克隆项目 +git clone +cd plugin-data-statistics -# 开发前端 +# 安装前端依赖 cd ui pnpm install -pnpm dev +cd .. ``` -## 构建 +### 开发命令 ```bash +# 后端开发(监听模式) +./gradlew build --continuous + +# 前端开发(热重载) +cd ui +pnpm dev + +# 构建插件 ./gradlew build + +# 只压缩 CSS +./gradlew minifyCss + +# 清理构建 +cd .. +./gradlew clean ``` -构建完成后,可以在 `build/libs` 目录找到插件 jar 文件。 +## 📄 许可证 + +[GPL-3.0](./LICENSE) © Handsome + +## 💖 支持作者 -## 许可证 +如果你觉得这个插件对你有帮助,欢迎: -[GPL-3.0](./LICENSE) © Handsome \ No newline at end of file +- Star 本项目 ⭐ +- 分享给更多人 📢 +- 提交 Issue 或 PR 🤝 +- 捐赠支持开发者 ☕ \ No newline at end of file diff --git a/build.gradle b/build.gradle index a7b65ae..ff48118 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,37 @@ test { useJUnitPlatform() } +// CSS 压缩任务 +tasks.register('minifyCss') { + description = '压缩 CSS 文件到 min 目录' + group = 'build' + + def cssDir = file('src/main/resources/static/css') + def minDir = file('src/main/resources/static/min') + + inputs.dir cssDir + outputs.dir minDir + + doLast { + minDir.mkdirs() + cssDir.listFiles({ it.name.endsWith('.css') } as FileFilter)?.each { cssFile -> + def content = cssFile.text + // 简单的 CSS 压缩:移除注释、多余空白、换行 + def minified = content + .replaceAll('/\\*[\\s\\S]*?\\*/', '') // 移除块注释 + .replaceAll('//[^\n]*', '') // 移除行注释 + .replaceAll('\\s+', ' ') // 多个空白合并为一个 + .replaceAll('\\s*([{};:,>~+])\\s*', '$1') // 移除符号周围空白 + .replaceAll(';\\}', '}') // 移除最后一个分号 + .trim() + + def minFile = new File(minDir, cssFile.name.replace('.css', '.min.css')) + minFile.text = minified + println "已压缩: ${cssFile.name} -> ${minFile.name}" + } + } +} + java { toolchain { languageVersion = JavaLanguageVersion.of(21) @@ -45,6 +76,12 @@ tasks.register('processUiResources', Copy) { tasks.named('classes') { dependsOn tasks.named('processUiResources') + dependsOn tasks.named('minifyCss') // 构建时自动压缩 CSS +} + +// 确保 processResources 在 minifyCss 之后执行 +tasks.named('processResources') { + dependsOn tasks.named('minifyCss') } halo { diff --git a/gradle.properties b/gradle.properties index aef125e..5011a7f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=1.0.0 +version=1.0.5 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca025c8..18dfcce 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,11 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +org.gradle.daemon=true +org.gradle.jvmargs=-Xmx5120m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.parallel=true +org.gradle.configureondemand=true \ No newline at end of file diff --git a/src/main/java/com/xhhao/dataStatistics/DataStatisticsPlugin.java b/src/main/java/com/xhhao/dataStatistics/DataStatisticsPlugin.java index 852fd93..40b1ce4 100644 --- a/src/main/java/com/xhhao/dataStatistics/DataStatisticsPlugin.java +++ b/src/main/java/com/xhhao/dataStatistics/DataStatisticsPlugin.java @@ -1,6 +1,7 @@ package com.xhhao.dataStatistics; import org.springframework.stereotype.Component; + import run.halo.app.plugin.BasePlugin; import run.halo.app.plugin.PluginContext; @@ -10,7 +11,7 @@ *

Only one main class extending {@link BasePlugin} is allowed per plugin.

* * @author Handsome - * @since 1.0.0 + * @since 1.0.5 */ @Component public class DataStatisticsPlugin extends BasePlugin { diff --git a/src/main/java/com/xhhao/dataStatistics/common/ApiResponse.java b/src/main/java/com/xhhao/dataStatistics/common/ApiResponse.java new file mode 100644 index 0000000..6dca3ef --- /dev/null +++ b/src/main/java/com/xhhao/dataStatistics/common/ApiResponse.java @@ -0,0 +1,53 @@ +package com.xhhao.dataStatistics.common; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import lombok.Data; + +/** + * 统一 API 响应格式 + * + * @author Handsome + * @since 1.0.5 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ApiResponse { + + private boolean success; + private String message; + private T data; + private String error; + + private ApiResponse() {} + + public static ApiResponse success(T data) { + ApiResponse response = new ApiResponse<>(); + response.setSuccess(true); + response.setData(data); + return response; + } + + public static ApiResponse success(String message, T data) { + ApiResponse response = new ApiResponse<>(); + response.setSuccess(true); + response.setMessage(message); + response.setData(data); + return response; + } + + public static ApiResponse error(String error) { + ApiResponse response = new ApiResponse<>(); + response.setSuccess(false); + response.setError(error); + return response; + } + + public static ApiResponse error(String message, String error) { + ApiResponse response = new ApiResponse<>(); + response.setSuccess(false); + response.setMessage(message); + response.setError(error); + return response; + } +} diff --git a/src/main/java/com/xhhao/dataStatistics/common/Constants.java b/src/main/java/com/xhhao/dataStatistics/common/Constants.java new file mode 100644 index 0000000..00a83bc --- /dev/null +++ b/src/main/java/com/xhhao/dataStatistics/common/Constants.java @@ -0,0 +1,45 @@ +package com.xhhao.dataStatistics.common; + +import java.time.ZoneId; + +/** + * 通用常量类 + * + * @author Handsome + * @since 1.0.5 + */ +public final class Constants { + + private Constants() { + // 禁止实例化 + } + + /** + * 默认时区:亚洲/上海 + */ + public static final String DEFAULT_TIMEZONE = "Asia/Shanghai"; + public static final ZoneId DEFAULT_ZONE_ID = ZoneId.of(DEFAULT_TIMEZONE); + + /** + * 缓存相关常量 + */ + public static final class Cache { + private Cache() {} + + /** 图表数据缓存时间(分钟) */ + public static final int CHART_DATA_CACHE_MINUTES = 5; + + /** Umami Token 缓存时间(小时) */ + public static final int UMAMI_TOKEN_CACHE_HOURS = 24; + } + + /** + * 默认 URL 常量 + */ + public static final class DefaultUrls { + private DefaultUrls() {} + + public static final String GITHUB_STATS_URL = "https://github-readme-stats.vercel.app/"; + public static final String GITHUB_GRAPH_URL = "https://github-readme-activity-graph.vercel.app/"; + } +} diff --git a/src/main/java/com/xhhao/dataStatistics/config/WebClientConfig.java b/src/main/java/com/xhhao/dataStatistics/config/WebClientConfig.java index faabfc2..390b26b 100644 --- a/src/main/java/com/xhhao/dataStatistics/config/WebClientConfig.java +++ b/src/main/java/com/xhhao/dataStatistics/config/WebClientConfig.java @@ -1,22 +1,65 @@ package com.xhhao.dataStatistics.config; +import java.time.Duration; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; +import io.netty.channel.ChannelOption; +import reactor.netty.http.client.HttpClient; +import reactor.netty.resources.ConnectionProvider; + /** * WebClient 配置类 - * 提供 WebClient.Builder Bean 供其他组件使用 + * 提供 WebClient.Builder 和 WebClient Bean 供其他组件使用 * * @author Handsome - * @since 1.0.0 + * @since 1.0.5 */ @Configuration public class WebClientConfig { + /** + * 配置连接池,设置空闲超时时间避免复用已关闭的连接 + */ + @Bean + public ConnectionProvider connectionProvider() { + return ConnectionProvider.builder("data-statistics") + .maxConnections(50) + .maxIdleTime(Duration.ofSeconds(20)) // 空闲连接最大存活时间 + .maxLifeTime(Duration.ofSeconds(60)) // 连接最大生命周期 + .pendingAcquireTimeout(Duration.ofSeconds(30)) + .evictInBackground(Duration.ofSeconds(30)) // 后台定期清理过期连接 + .build(); + } + + /** + * 配置 HttpClient + */ + @Bean + public HttpClient httpClient(ConnectionProvider connectionProvider) { + return HttpClient.create(connectionProvider) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) // 连接超时 10 秒 + .responseTimeout(Duration.ofSeconds(30)); // 响应超时 30 秒 + } + + /** + * 提供 WebClient.Builder(用于需要自定义 baseUrl 的场景) + */ + @Bean + public WebClient.Builder webClientBuilder(HttpClient httpClient) { + return WebClient.builder() + .clientConnector(new ReactorClientHttpConnector(httpClient)); + } + + /** + * 提供预配置的 WebClient 实例(用于不需要自定义 baseUrl 的场景) + */ @Bean - public WebClient.Builder webClientBuilder() { - return WebClient.builder(); + public WebClient webClient(WebClient.Builder webClientBuilder) { + return webClientBuilder.build(); } } diff --git a/src/main/java/com/xhhao/dataStatistics/endpoint/DataStatisticsEndpoint.java b/src/main/java/com/xhhao/dataStatistics/endpoint/DataStatisticsEndpoint.java index 6feb1ac..8afbfd3 100644 --- a/src/main/java/com/xhhao/dataStatistics/endpoint/DataStatisticsEndpoint.java +++ b/src/main/java/com/xhhao/dataStatistics/endpoint/DataStatisticsEndpoint.java @@ -4,20 +4,24 @@ import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder; -import cn.hutool.core.util.StrUtil; +import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; + +import com.xhhao.dataStatistics.common.ApiResponse; +import com.xhhao.dataStatistics.common.Constants; import com.xhhao.dataStatistics.service.SettingConfigGetter; import com.xhhao.dataStatistics.service.StatisticalService; import com.xhhao.dataStatistics.service.UmamiService; import com.xhhao.dataStatistics.service.UptimeKumaService; import com.xhhao.dataStatistics.vo.PieChartVO; + +import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; import run.halo.app.core.extension.endpoint.CustomEndpoint; import run.halo.app.extension.GroupVersion; @@ -103,27 +107,27 @@ public RouterFunction endpoint() { .build(); } + /** + * 统一错误响应处理 + */ + private Mono handleError(String operation, Throwable e) { + log.error("{}失败", operation, e); + return ServerResponse.status(500) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(ApiResponse.error(operation + "失败", e.getMessage())); + } + private Mono fetchChartData(ServerRequest request) { return statisticalService.getPieChartVO() .flatMap(dataSource -> ServerResponse.ok().bodyValue(dataSource)) .switchIfEmpty(ServerResponse.ok().bodyValue(new PieChartVO())) - .onErrorResume(e -> { - log.error("Failed to fetch chart data", e); - return ServerResponse.status(500) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue("获取图表数据失败: " + e.getMessage()); - }); + .onErrorResume(e -> handleError("获取图表数据", e)); } private Mono fetchUmamiWebsites(ServerRequest request) { return umamiService.getWebsites() .flatMap(data -> ServerResponse.ok().bodyValue(data)) - .onErrorResume(e -> { - log.error("获取Umami网站列表失败", e); - return ServerResponse.status(500) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue("获取Umami网站列表失败: " + e.getMessage()); - }); + .onErrorResume(e -> handleError("获取 Umami 网站列表", e)); } private Mono fetchVisits(ServerRequest request) { @@ -132,66 +136,47 @@ private Mono fetchVisits(ServerRequest request) { if (!typeParam.matches("daily|weekly|monthly|quarterly|yearly")) { return ServerResponse.badRequest() .contentType(MediaType.APPLICATION_JSON) - .bodyValue("type 参数错误,支持的值: daily, weekly, monthly, quarterly, yearly"); + .bodyValue(ApiResponse.error("参数错误", "type 参数错误,支持的值: daily, weekly, monthly, quarterly, yearly")); } return umamiService.getVisitStatistics(null, typeParam) .flatMap(data -> ServerResponse.ok().bodyValue(data)) - .onErrorResume(e -> { - log.error("获取{}访问统计失败", typeParam, e); - return ServerResponse.status(500) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue("获取" + typeParam + "访问统计失败: " + e.getMessage()); - }); + .onErrorResume(e -> handleError("获取" + typeParam + "访问统计", e)); } + private Mono fetchRealtimeVisits(ServerRequest request) { String websiteIdParam = request.queryParam("websiteId").orElse(""); String finalWebsiteId = StrUtil.isBlank(websiteIdParam) ? null : websiteIdParam; return umamiService.getRealtimeVisitStatistics(finalWebsiteId) .flatMap(data -> ServerResponse.ok().bodyValue(data)) - .onErrorResume(e -> { - log.error("获取实时访问统计失败", e); - return ServerResponse.status(500) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue("获取实时访问统计失败: " + e.getMessage()); - }); + .onErrorResume(e -> handleError("获取实时访问统计", e)); } private Mono fetchUptimeKumaStatus(ServerRequest request) { return uptimeKumaService.getStatusPage() .flatMap(data -> ServerResponse.ok().bodyValue(data)) - .onErrorResume(e -> { - log.error("获取 Uptime Kuma 状态页面失败", e); - return ServerResponse.status(500) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue("获取 Uptime Kuma 状态页面失败: " + e.getMessage()); - }); + .onErrorResume(e -> handleError("获取 Uptime Kuma 状态页面", e)); } private Mono fetchGithubConfig(ServerRequest request) { return settingConfigGetter.getGithubConfig() .map(config -> { String proxyUrl = StrUtil.isNotBlank(config.getProxyUrl()) - ? normalizeUrl(config.getProxyUrl(),"https://github-readme-stats.vercel.app/") - : "https://github-readme-stats.vercel.app/"; + ? normalizeUrl(config.getProxyUrl(), Constants.DefaultUrls.GITHUB_STATS_URL) + : Constants.DefaultUrls.GITHUB_STATS_URL; String graphProxyUrl = StrUtil.isNotBlank(config.getGraphProxyUrl()) - ? normalizeUrl(config.getGraphProxyUrl(),"https://github-readme-activity-graph.vercel.app/") - : "https://github-readme-activity-graph.vercel.app/"; - return new GithubConfigResponse(proxyUrl, config.getUsername(),graphProxyUrl); + ? normalizeUrl(config.getGraphProxyUrl(), Constants.DefaultUrls.GITHUB_GRAPH_URL) + : Constants.DefaultUrls.GITHUB_GRAPH_URL; + return new GithubConfigResponse(proxyUrl, config.getUsername(), graphProxyUrl); }) .flatMap(config -> ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .bodyValue(config)) - .onErrorResume(e -> { - log.error("获取 GitHub 配置失败", e); - return ServerResponse.status(500) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue("获取 GitHub 配置失败: " + e.getMessage()); - }); + .onErrorResume(e -> handleError("获取 GitHub 配置", e)); } - private String normalizeUrl(String url,String defaultUrl) { + private String normalizeUrl(String url, String defaultUrl) { if (StrUtil.isBlank(url)) { return defaultUrl; } diff --git a/src/main/java/com/xhhao/dataStatistics/service/impl/StatisticalServiceImpl.java b/src/main/java/com/xhhao/dataStatistics/service/impl/StatisticalServiceImpl.java index 8bbb27a..79a3909 100644 --- a/src/main/java/com/xhhao/dataStatistics/service/impl/StatisticalServiceImpl.java +++ b/src/main/java/com/xhhao/dataStatistics/service/impl/StatisticalServiceImpl.java @@ -1,11 +1,28 @@ package com.xhhao.dataStatistics.service.impl; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; + import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.xhhao.dataStatistics.common.Constants; import com.xhhao.dataStatistics.service.StatisticalService; import com.xhhao.dataStatistics.vo.PieChartVO; + import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Component; +import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Mono; import run.halo.app.core.extension.content.Category; import run.halo.app.core.extension.content.Comment; @@ -14,28 +31,42 @@ import run.halo.app.extension.ListOptions; import run.halo.app.extension.ReactiveExtensionClient; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneId; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; - +@Slf4j @Component @RequiredArgsConstructor public class StatisticalServiceImpl implements StatisticalService { + private final ReactiveExtensionClient client; private final ObjectMapper objectMapper = new ObjectMapper(); + /** + * 缓存的图表数据 + */ + private volatile Mono cachedChartData; + @Override public Mono getPieChartVO() { + // 使用 Mono.cache() 实现缓存,避免重复计算 + if (cachedChartData == null) { + cachedChartData = buildPieChartVO() + .cache(Duration.ofMinutes(Constants.Cache.CHART_DATA_CACHE_MINUTES)); + } + return cachedChartData + .onErrorResume(e -> { + log.warn("缓存的图表数据失效,重新获取: {}", e.getMessage()); + cachedChartData = null; + return buildPieChartVO(); + }); + } + + /** + * 清除缓存(可用于强制刷新) + */ + public void clearCache() { + cachedChartData = null; + } + + private Mono buildPieChartVO() { PieChartVO pieChartVO = new PieChartVO(); Mono> tagsMono = client.listAll(Tag.class, new ListOptions(), @@ -62,89 +93,21 @@ public Mono getPieChartVO() { Sort.by(Sort.Order.desc("metadata.creationTimestamp"))) .filter(post -> post.getSpec().getPublishTime() != null) .collectList() - .map(posts -> { - Map postsByDate = posts.stream() - .collect(Collectors.groupingBy(post -> { - Instant publishTime = post.getSpec().getPublishTime(); - if (publishTime != null) { - return publishTime.atZone(ZoneId.systemDefault()) - .toLocalDate().toString(); - } - return post.getMetadata().getCreationTimestamp() - .atZone(ZoneId.systemDefault()) - .toLocalDate().toString(); - }, Collectors.collectingAndThen(Collectors.counting(), Long::intValue))); - - LocalDate today = LocalDate.now(); - LocalDate startDate = today.minusYears(1); - - return Stream.iterate(startDate, date -> date.plusDays(1)) - .limit(java.time.temporal.ChronoUnit.DAYS.between(startDate, today.plusDays(1))) - .map(date -> { - String dateStr = date.toString(); - PieChartVO.Article articleVO = new PieChartVO.Article(); - articleVO.setName(dateStr); - articleVO.setDate(date.atStartOfDay(ZoneId.systemDefault()).toLocalDateTime()); - articleVO.setTotal(postsByDate.getOrDefault(dateStr, 0)); - return articleVO; - }) - .sorted(Comparator.comparing(PieChartVO.Article::getDate).reversed()) - .collect(Collectors.toList()); - }); + .map(this::buildArticleList); Mono> commentsMono = client.listAll(Comment.class, new ListOptions(), Sort.by(Sort.Order.desc("metadata.creationTimestamp"))) .collectList() - .map(comments -> { - Map> commentsByUser = comments.stream() - .filter(comment -> comment.getSpec().getOwner() != null) - .collect(Collectors.groupingBy(comment -> { - String name = comment.getSpec().getOwner().getName(); - return name != null ? name : "unknown"; - })); - - return commentsByUser.entrySet().stream() - .map(entry -> { - Comment firstComment = entry.getValue().getFirst(); - PieChartVO.Comment commentVO = new PieChartVO.Comment(); - commentVO.setName(firstComment.getSpec().getOwner().getName()); - commentVO.setEmail(entry.getKey()); - commentVO.setUsername(firstComment.getSpec().getOwner().getDisplayName()); - commentVO.setCount(entry.getValue().size()); - return commentVO; - }) - .sorted(Comparator.comparing(PieChartVO.Comment::getCount).reversed()) - .collect(Collectors.toList()); - }); + .map(this::buildCommentList); + Mono> top10ArticlesMono = client.listAll(Post.class, new ListOptions(), Sort.by(Sort.Order.desc("metadata.creationTimestamp"))) .filter(post -> post.getSpec().getPublishTime() != null) - .handle((post, sink) -> { - PieChartVO.Top10Article top10Article = new PieChartVO.Top10Article(); - top10Article.setName(post.getSpec().getTitle()); - int visits = 0; - Map annotations = post.getMetadata().getAnnotations(); - if (annotations != null) { - String statsJson = annotations.get("content.halo.run/stats"); - if (statsJson != null && !statsJson.isEmpty()) { - JsonNode statsNode; - try { - statsNode = objectMapper.readTree(statsJson); - } catch (JsonProcessingException e) { - sink.error(new RuntimeException(e)); - return; - } - if (statsNode.has("visit")) { - visits = statsNode.get("visit").asInt(); - } - } - } - top10Article.setViews(visits); - sink.next(top10Article); - }) + .map(this::buildTop10Article) .sort(Comparator.comparing(PieChartVO.Top10Article::getViews).reversed()) .take(10) .collectList(); + return Mono.zip(tagsMono, categoriesMono, articlesMono, commentsMono, top10ArticlesMono) .map(tuple -> { pieChartVO.setTags(tuple.getT1()); @@ -155,4 +118,80 @@ public Mono getPieChartVO() { return pieChartVO; }); } + + private List buildArticleList(List posts) { + Map postsByDate = posts.stream() + .collect(Collectors.groupingBy(post -> { + Instant publishTime = post.getSpec().getPublishTime(); + if (publishTime != null) { + return publishTime.atZone(Constants.DEFAULT_ZONE_ID) + .toLocalDate().toString(); + } + return post.getMetadata().getCreationTimestamp() + .atZone(Constants.DEFAULT_ZONE_ID) + .toLocalDate().toString(); + }, Collectors.collectingAndThen(Collectors.counting(), Long::intValue))); + + LocalDate today = LocalDate.now(Constants.DEFAULT_ZONE_ID); + LocalDate startDate = today.minusYears(1); + + return Stream.iterate(startDate, date -> date.plusDays(1)) + .limit(java.time.temporal.ChronoUnit.DAYS.between(startDate, today.plusDays(1))) + .map(date -> { + String dateStr = date.toString(); + PieChartVO.Article articleVO = new PieChartVO.Article(); + articleVO.setName(dateStr); + articleVO.setDate(date.atStartOfDay(Constants.DEFAULT_ZONE_ID).toLocalDateTime()); + articleVO.setTotal(postsByDate.getOrDefault(dateStr, 0)); + return articleVO; + }) + .sorted(Comparator.comparing(PieChartVO.Article::getDate).reversed()) + .collect(Collectors.toList()); + } + + private List buildCommentList(List comments) { + Map> commentsByUser = comments.stream() + .filter(comment -> comment.getSpec().getOwner() != null) + .collect(Collectors.groupingBy(comment -> { + String name = comment.getSpec().getOwner().getName(); + return name != null ? name : "unknown"; + })); + + return commentsByUser.entrySet().stream() + .map(entry -> { + Comment firstComment = entry.getValue().getFirst(); + PieChartVO.Comment commentVO = new PieChartVO.Comment(); + commentVO.setName(firstComment.getSpec().getOwner().getName()); + commentVO.setEmail(entry.getKey()); + commentVO.setUsername(firstComment.getSpec().getOwner().getDisplayName()); + commentVO.setCount(entry.getValue().size()); + return commentVO; + }) + .sorted(Comparator.comparing(PieChartVO.Comment::getCount).reversed()) + .collect(Collectors.toList()); + } + + private PieChartVO.Top10Article buildTop10Article(Post post) { + PieChartVO.Top10Article top10Article = new PieChartVO.Top10Article(); + top10Article.setName(post.getSpec().getTitle()); + + int visits = 0; + Map annotations = post.getMetadata().getAnnotations(); + if (annotations != null) { + String statsJson = annotations.get("content.halo.run/stats"); + if (statsJson != null && !statsJson.isEmpty()) { + try { + JsonNode statsNode = objectMapper.readTree(statsJson); + if (statsNode.has("visit")) { + visits = statsNode.get("visit").asInt(); + } + } catch (JsonProcessingException e) { + log.warn("解析文章统计信息失败: {}", e.getMessage()); + } + } + } + top10Article.setViews(visits); + return top10Article; + } + } diff --git a/src/main/java/com/xhhao/dataStatistics/service/impl/UmamiServiceImpl.java b/src/main/java/com/xhhao/dataStatistics/service/impl/UmamiServiceImpl.java index 29525d4..8c6edc0 100644 --- a/src/main/java/com/xhhao/dataStatistics/service/impl/UmamiServiceImpl.java +++ b/src/main/java/com/xhhao/dataStatistics/service/impl/UmamiServiceImpl.java @@ -1,22 +1,27 @@ package com.xhhao.dataStatistics.service.impl; -import cn.hutool.cache.CacheUtil; -import cn.hutool.cache.impl.TimedCache; -import cn.hutool.core.util.StrUtil; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.function.Function; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientRequestException; + import com.fasterxml.jackson.databind.JsonNode; +import com.xhhao.dataStatistics.common.Constants; import com.xhhao.dataStatistics.service.SettingConfigGetter; import com.xhhao.dataStatistics.service.UmamiService; + +import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.TimedCache; +import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.time.ZoneId; +import reactor.util.retry.Retry; @Component @RequiredArgsConstructor @@ -26,11 +31,43 @@ public class UmamiServiceImpl implements UmamiService { private final SettingConfigGetter settingConfigGetter; private final WebClient.Builder webClientBuilder; - private static final long TOKEN_CACHE_EXPIRE_MS = Duration.ofHours(24).toMillis(); + private static final long TOKEN_CACHE_EXPIRE_MS = Duration.ofHours(Constants.Cache.UMAMI_TOKEN_CACHE_HOURS).toMillis(); private final TimedCache tokenCache = CacheUtil.newTimedCache(TOKEN_CACHE_EXPIRE_MS); private static final String CACHE_KEY_PREFIX = "umami_token_"; + /** + * 通用重试策略 + */ + private Mono withRetry(Mono mono, String operationName) { + return mono.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)) + .maxBackoff(Duration.ofSeconds(5)) + .filter(throwable -> throwable instanceof WebClientRequestException) + .doBeforeRetry(signal -> log.warn("{} 请求失败,正在重试 ({}/3): {}", + operationName, signal.totalRetries() + 1, signal.failure().getMessage()))); + } + + /** + * 通用 API 请求方法 + */ + private Mono executeApiRequest( + Function> requestBuilder, + String operationName) { + return getToken() + .flatMap(token -> getBaseUrl() + .flatMap(baseUrl -> { + if (StrUtil.isBlank(baseUrl)) { + return Mono.error(new IllegalStateException("Umami 站点地址未配置")); + } + WebClient client = webClientBuilder + .baseUrl(baseUrl) + .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token) + .build(); + return withRetry(requestBuilder.apply(client), operationName); + })) + .doOnError(error -> log.debug("{} 失败: {}", operationName, error.getMessage())); + } + @Override public Mono getToken() { return settingConfigGetter.getUmamiConfig() @@ -46,7 +83,7 @@ public Mono getToken() { return Mono.just(cachedToken); } - log.info("缓存未命中,请求新的 Umami token"); + log.debug("缓存未命中,请求新的 Umami token"); return requestToken(config) .map(response -> { tokenCache.put(cacheKey, response.token()); @@ -64,15 +101,16 @@ private Mono requestToken(SettingConfigGetter.UmamiConfig config) WebClient client = webClientBuilder.baseUrl(baseUrl).build(); LoginRequest request = new LoginRequest(config.getUserName(), config.getUserPassWord()); - return client.post() - .uri("/api/auth/login") - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(request) - .retrieve() - .bodyToMono(LoginResponse.class) - .doOnSuccess(response -> log.info("成功获取 Umami token")) - .doOnError(error -> log.error("请求 Umami token 失败: {}", error.getMessage())) - .onErrorResume(ex -> Mono.error(new IllegalStateException("请求 Umami token 失败: " + ex.getMessage(), ex))); + return withRetry( + client.post() + .uri("/api/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .retrieve() + .bodyToMono(LoginResponse.class), + "Umami 登录" + ).doOnError(error -> log.error("请求 Umami token 失败: {}", error.getMessage())) + .onErrorResume(ex -> Mono.error(new IllegalStateException("请求 Umami token 失败: " + ex.getMessage(), ex))); } @@ -87,48 +125,35 @@ private record User(String id, String username, String role, String createdAt, b @Override public Mono getWebsites() { - return getToken() - .flatMap(token -> getBaseUrl() - .flatMap(baseUrl -> { - if (StrUtil.isBlank(baseUrl)) { - return Mono.error(new IllegalStateException("Umami 站点地址未配置")); - } - return webClientBuilder.baseUrl(baseUrl).build() - .get() - .uri("/api/websites") - .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) - .retrieve() - .bodyToMono(JsonNode.class); - })); + return executeApiRequest( + client -> client.get() + .uri("/api/websites") + .retrieve() + .bodyToMono(JsonNode.class), + "获取 Umami 网站列表" + ); } + @Override public Mono getRealtimeData(String websiteId) { return resolveWebsiteId(websiteId) - .flatMap(id -> getToken() - .flatMap(token -> getBaseUrl() - .flatMap(baseUrl -> { - if (StrUtil.isBlank(baseUrl)) { - return Mono.error(new IllegalStateException("Umami 站点地址未配置")); - } - return webClientBuilder.baseUrl(baseUrl) - .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token) - .build() - .get() - .uri(uriBuilder -> uriBuilder - .path("/api/realtime/{websiteId}") - .queryParam("timezone", "Asia/Shanghai") - .build(id)) - .retrieve() - .bodyToMono(JsonNode.class); - }))); + .flatMap(id -> executeApiRequest( + client -> client.get() + .uri(uriBuilder -> uriBuilder + .path("/api/realtime/{websiteId}") + .queryParam("timezone", Constants.DEFAULT_TIMEZONE) + .build(id)) + .retrieve() + .bodyToMono(JsonNode.class), + "获取实时数据" + )); } @Override public Mono getVisitStatistics(String websiteId, String type) { - ZoneId zoneId = ZoneId.of("Asia/Shanghai"); return resolveWebsiteId(websiteId) .flatMap(id -> { - LocalDateTime now = LocalDateTime.now(zoneId); + LocalDateTime now = LocalDateTime.now(Constants.DEFAULT_ZONE_ID); var timeRange = switch (type.toLowerCase()) { case "daily" -> new TimeRange(now.minusDays(1), "day"); case "weekly" -> new TimeRange(now.minusDays(7), "day"); @@ -142,8 +167,8 @@ public Mono getVisitStatistics(String websiteId, String type) { return Mono.error(new IllegalArgumentException("不支持的统计类型: " + type)); } - long startAt = timeRange.start.atZone(zoneId).toInstant().toEpochMilli(); - long endAt = now.atZone(zoneId).toInstant().toEpochMilli(); + long startAt = timeRange.start.atZone(Constants.DEFAULT_ZONE_ID).toInstant().toEpochMilli(); + long endAt = now.atZone(Constants.DEFAULT_ZONE_ID).toInstant().toEpochMilli(); return fetchVisitStatistics(id, startAt, endAt, timeRange.unit); }); @@ -157,30 +182,22 @@ public Mono getRealtimeVisitStatistics(String websiteId) { } private Mono fetchVisitStatistics(String websiteId, long startAt, long endAt, String unit) { - return getToken() - .flatMap(token -> getBaseUrl() - .flatMap(baseUrl -> { - if (StrUtil.isBlank(baseUrl)) { - return Mono.error(new IllegalStateException("Umami 站点地址未配置")); - } - return webClientBuilder.baseUrl(baseUrl) - .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token) - .build() - .get() - .uri(uriBuilder -> uriBuilder - .path("/api/websites/{websiteId}/stats") - .queryParam("startAt", startAt) - .queryParam("endAt", endAt) - .queryParam("unit", unit) - .queryParam("timezone", "Asia/Shanghai") - .build(websiteId)) - .retrieve() - .bodyToMono(JsonNode.class); - })); + return executeApiRequest( + client -> client.get() + .uri(uriBuilder -> uriBuilder + .path("/api/websites/{websiteId}/stats") + .queryParam("startAt", startAt) + .queryParam("endAt", endAt) + .queryParam("unit", unit) + .queryParam("timezone", Constants.DEFAULT_TIMEZONE) + .build(websiteId)) + .retrieve() + .bodyToMono(JsonNode.class), + "获取访问统计" + ); } - private Mono resolveWebsiteId(String websiteId) { if (StrUtil.isNotBlank(websiteId)) { return Mono.just(websiteId); diff --git a/src/main/java/com/xhhao/dataStatistics/service/impl/UptimeKumaServiceImpl.java b/src/main/java/com/xhhao/dataStatistics/service/impl/UptimeKumaServiceImpl.java index 0b52699..e32574f 100644 --- a/src/main/java/com/xhhao/dataStatistics/service/impl/UptimeKumaServiceImpl.java +++ b/src/main/java/com/xhhao/dataStatistics/service/impl/UptimeKumaServiceImpl.java @@ -1,18 +1,23 @@ package com.xhhao.dataStatistics.service.impl; -import cn.hutool.core.text.StrFormatter; -import cn.hutool.core.util.StrUtil; +import java.net.URI; +import java.net.URISyntaxException; +import java.time.Duration; + +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientRequestException; + import com.fasterxml.jackson.databind.JsonNode; import com.xhhao.dataStatistics.service.SettingConfigGetter; import com.xhhao.dataStatistics.service.UptimeKumaService; + +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; - -import java.net.URI; -import java.net.URISyntaxException; +import reactor.util.retry.Retry; @Slf4j @Component @@ -20,7 +25,7 @@ public class UptimeKumaServiceImpl implements UptimeKumaService { private final SettingConfigGetter settingConfigGetter; - private final WebClient.Builder webClientBuilder; + private final WebClient webClient; @Override public Mono getStatusPage() { @@ -28,7 +33,7 @@ public Mono getStatusPage() { .flatMap(config -> { var statusPageUrl = StrUtil.trim(config.getUptimeUrl()); if (StrUtil.isBlank(statusPageUrl)) { - log.warn("Uptime Kuma 状态页 URL 未配置,请在插件设置中配置"); + log.debug("Uptime Kuma 状态页 URL 未配置"); return Mono.error(new IllegalStateException("Uptime Kuma 状态页 URL 未配置,请在插件设置中配置状态页 URL")); } @@ -43,14 +48,19 @@ public Mono getStatusPage() { } private Mono requestStatusData(String apiUrl) { - log.info("请求 Uptime Kuma API: {}", apiUrl); - return webClientBuilder.build() + log.debug("请求 Uptime Kuma API: {}", apiUrl); + return webClient .get() .uri(apiUrl) .retrieve() .bodyToMono(JsonNode.class) .map(this::parseStatusData) - .doOnError(error -> log.error("调用 Uptime Kuma API 失败: {}", error.getMessage())); + .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)) + .maxBackoff(Duration.ofSeconds(5)) + .filter(throwable -> throwable instanceof WebClientRequestException) + .doBeforeRetry(signal -> log.warn("Uptime Kuma API 请求失败,正在重试 ({}/3): {}", + signal.totalRetries() + 1, signal.failure().getMessage()))) + .doOnError(error -> log.debug("调用 Uptime Kuma API 失败: {}", error.getMessage())); } private Integer parseStatusData(JsonNode jsonNode) { diff --git a/src/main/resources/static/css/dataStatistics.css b/src/main/resources/static/css/dataStatistics.css index 7dd83e9..e804371 100644 --- a/src/main/resources/static/css/dataStatistics.css +++ b/src/main/resources/static/css/dataStatistics.css @@ -719,53 +719,6 @@ html[theme='dark']:not([theme='light']), } @media (prefers-color-scheme: dark) { - .xhhaocom-dataStatistics-v2-traffic { - background: linear-gradient(180deg, rgba(15, 23, 42, 0.92) 0%, rgba(15, 23, 42, 0.65) 100%); - border-color: rgba(148, 163, 184, 0.15); - box-shadow: 0 18px 30px -24px rgba(59, 130, 246, 0.35); - } - - .xhhaocom-dataStatistics-v2-traffic-title { - color: #bfdbfe; - } - - .xhhaocom-dataStatistics-v2-traffic-badge { - background: rgba(59, 130, 246, 0.2); - color: #bfdbfe; - } - - .xhhaocom-dataStatistics-v2-traffic-subtitle { - color: #94a3b8; - } - - .xhhaocom-dataStatistics-v2-traffic-card { - background: rgba(17, 24, 39, 0.9); - border-color: rgba(59, 130, 246, 0.25); - box-shadow: inset 0 1px 0 rgba(59, 130, 246, 0.08), 0 14px 24px -26px rgba(59, 130, 246, 0.6); - } - - .xhhaocom-dataStatistics-v2-traffic-icon { - background: linear-gradient(140deg, rgba(37, 99, 235, 0.25), rgba(37, 99, 235, 0.08)); - color: #60a5fa; - } - - .xhhaocom-dataStatistics-v2-traffic-value { - color: #bfdbfe; - } - - .xhhaocom-dataStatistics-v2-traffic-label { - color: #cbd5f5; - } - - .xhhaocom-dataStatistics-v2-traffic-realtime { - background: rgba(37, 99, 235, 0.22); - color: #bfdbfe; - } - - .xhhaocom-dataStatistics-v2-traffic-realtime::before { - background: #60a5fa; - } - .xhhaocom-dataStatistics-v2-traffic-loading { color: #6b7280; } diff --git a/src/main/resources/static/css/siteCharts.css b/src/main/resources/static/css/siteCharts.css index 066d60d..8e49a26 100644 --- a/src/main/resources/static/css/siteCharts.css +++ b/src/main/resources/static/css/siteCharts.css @@ -122,7 +122,7 @@ html[theme='dark']:not([theme='light']), flex-direction: column; gap: 24px; width: 100%; - padding: 12px; + padding: 24px 0; box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; list-style: none !important; diff --git a/src/main/resources/static/min/dataStatistics.min.css b/src/main/resources/static/min/dataStatistics.min.css index a2f3821..270844a 100644 --- a/src/main/resources/static/min/dataStatistics.min.css +++ b/src/main/resources/static/min/dataStatistics.min.css @@ -1 +1 @@ -.xhhaocom-dataStatistics-v2-wrapper{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;margin:0;padding:0;box-sizing:border-box}:root{--halo-data-statistics-section-surface:#ffffff;--halo-data-statistics-section-border:#dde3f5;--halo-data-statistics-section-shadow:0 4px 12px rgba(15, 23, 42, 0.05);--halo-data-statistics-title-color:#1b2559;--halo-data-statistics-subtitle-color:#5b647c;--halo-data-statistics-badge-bg:#e8edff;--halo-data-statistics-badge-text:#2563eb;--halo-data-statistics-card-surface:#f6f8ff;--halo-data-statistics-card-border:#dbe1fb;--halo-data-statistics-card-shadow:none;--halo-data-statistics-card-hover-border:#c2cbff;--halo-data-statistics-card-hover-shadow:0 6px 16px rgba(37, 99, 235, 0.1);--halo-data-statistics-card-icon-bg:#e9efff;--halo-data-statistics-card-icon-color:#2563eb;--halo-data-statistics-number-color:#1d4ed8;--halo-data-statistics-label-color:#516079;--halo-data-statistics-activity-surface:#ffffff;--halo-data-statistics-activity-border:#dde3f5;--halo-data-statistics-activity-shadow:0 4px 12px rgba(15, 23, 42, 0.05);--halo-data-statistics-metric-surface:#f6f8ff;--halo-data-statistics-metric-border:#dbe1fb;--halo-data-statistics-metric-icon-bg:#e9efff;--halo-data-statistics-metric-icon-color:#2563eb;--halo-data-statistics-metric-value-color:#1d4ed8;--halo-data-statistics-metric-label-color:#516079;--halo-data-statistics-list-item-border:#e1e7fb;--halo-data-statistics-text-primary:#1b2559;--halo-data-statistics-text-secondary:#516079;--halo-data-statistics-text-muted:#5b647c;--halo-data-statistics-time-text:#334155;--halo-data-statistics-scrollbar-thumb:rgba(148, 163, 184, 0.4);--halo-data-statistics-scrollbar-thumb-hover:rgba(148, 163, 184, 0.65);--halo-data-statistics-tooltip-bg:rgba(15, 23, 42, 0.85);--halo-data-statistics-tooltip-text:#f8fafc}html.dark:not([data-color-scheme='light']):not(.light):not([class*='light']),html[class~='dark']:not([data-color-scheme='light']):not(.light):not([class*='light']),html[class*='dark']:not([data-color-scheme='light']):not(.light):not([class*='light']),html[data-color-scheme='dark']:not([data-color-scheme='light']),html[data-theme='dark']:not([data-theme='light']),html[theme='dark']:not([theme='light']),[data-color-scheme='dark']:not([data-color-scheme='light']),[data-theme='dark']:not([data-theme='light']),[theme='dark']:not([theme='light']){--halo-data-statistics-section-surface:#111827;--halo-data-statistics-section-border:#1f2937;--halo-data-statistics-section-shadow:0 6px 20px rgba(8, 47, 73, 0.35);--halo-data-statistics-title-color:#e2e8f0;--halo-data-statistics-subtitle-color:#94a3b8;--halo-data-statistics-badge-bg:rgba(37, 99, 235, 0.2);--halo-data-statistics-badge-text:#93c5fd;--halo-data-statistics-card-surface:#18223a;--halo-data-statistics-card-border:#25304a;--halo-data-statistics-card-shadow:none;--halo-data-statistics-card-hover-border:#3b4d75;--halo-data-statistics-card-hover-shadow:0 6px 20px rgba(8, 47, 73, 0.45);--halo-data-statistics-card-icon-bg:#1f2d4a;--halo-data-statistics-card-icon-color:#93c5fd;--halo-data-statistics-number-color:#93c5fd;--halo-data-statistics-label-color:#cbd5f5;--halo-data-statistics-activity-surface:#111827;--halo-data-statistics-activity-border:#1f2937;--halo-data-statistics-activity-shadow:0 6px 20px rgba(8, 47, 73, 0.35);--halo-data-statistics-metric-surface:#18223a;--halo-data-statistics-metric-border:#25304a;--halo-data-statistics-metric-icon-bg:#1f2d4a;--halo-data-statistics-metric-icon-color:#93c5fd;--halo-data-statistics-metric-value-color:#93c5fd;--halo-data-statistics-metric-label-color:#cbd5f5;--halo-data-statistics-list-item-border:#25304a;--halo-data-statistics-text-primary:#e2e8f0;--halo-data-statistics-text-secondary:#cbd5f5;--halo-data-statistics-text-muted:#94a3b8;--halo-data-statistics-time-text:#cbd5f5;--halo-data-statistics-scrollbar-thumb:rgba(148, 163, 184, 0.45);--halo-data-statistics-scrollbar-thumb-hover:rgba(148, 163, 184, 0.65);--halo-data-statistics-tooltip-bg:rgba(241, 245, 249, 0.9);--halo-data-statistics-tooltip-text:#0f172a}@media (prefers-color-scheme:dark){:root:not([data-color-scheme]),:root[data-color-scheme='auto'],:root[data-color-scheme='system'],html:not([data-color-scheme]):not(.light):not([class*='light']),html[data-color-scheme='auto']:not(.light):not([class*='light']),html[data-color-scheme='system']:not(.light):not([class*='light']){--halo-data-statistics-section-surface:#111827;--halo-data-statistics-section-border:#1f2937;--halo-data-statistics-section-shadow:0 6px 20px rgba(8, 47, 73, 0.35);--halo-data-statistics-title-color:#e2e8f0;--halo-data-statistics-subtitle-color:#94a3b8;--halo-data-statistics-badge-bg:rgba(37, 99, 235, 0.2);--halo-data-statistics-badge-text:#93c5fd;--halo-data-statistics-card-surface:#18223a;--halo-data-statistics-card-border:#25304a;--halo-data-statistics-card-shadow:none;--halo-data-statistics-card-hover-border:#3b4d75;--halo-data-statistics-card-hover-shadow:0 6px 20px rgba(8, 47, 73, 0.45);--halo-data-statistics-card-icon-bg:#1f2d4a;--halo-data-statistics-card-icon-color:#93c5fd;--halo-data-statistics-number-color:#93c5fd;--halo-data-statistics-label-color:#cbd5f5;--halo-data-statistics-activity-surface:#111827;--halo-data-statistics-activity-border:#1f2937;--halo-data-statistics-activity-shadow:0 6px 20px rgba(8, 47, 73, 0.35);--halo-data-statistics-metric-surface:#18223a;--halo-data-statistics-metric-border:#25304a;--halo-data-statistics-metric-icon-bg:#1f2d4a;--halo-data-statistics-metric-icon-color:#93c5fd;--halo-data-statistics-metric-value-color:#93c5fd;--halo-data-statistics-metric-label-color:#cbd5f5;--halo-data-statistics-list-item-border:#25304a;--halo-data-statistics-text-primary:#e2e8f0;--halo-data-statistics-text-secondary:#cbd5f5;--halo-data-statistics-text-muted:#94a3b8;--halo-data-statistics-time-text:#cbd5f5;--halo-data-statistics-scrollbar-thumb:rgba(148, 163, 184, 0.45);--halo-data-statistics-scrollbar-thumb-hover:rgba(148, 163, 184, 0.65);--halo-data-statistics-tooltip-bg:rgba(241, 245, 249, 0.9);--halo-data-statistics-tooltip-text:#0f172a}}.xhhaocom-dataStatistics-v2-traffic{display:block;margin:0;padding:20px 24px;width:100%;background:var(--halo-data-statistics-section-surface);border:1px solid var(--halo-data-statistics-section-border);border-radius:16px;box-shadow:var(--halo-data-statistics-section-shadow)}.xhhaocom-dataStatistics-v2-traffic-section{display:flex;flex-direction:column;gap:18px}.xhhaocom-dataStatistics-v2-traffic-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.xhhaocom-dataStatistics-v2-traffic-title-box{display:flex;align-items:center;gap:10px}.xhhaocom-dataStatistics-v2-traffic-title{font-size:18px;font-weight:600;color:var(--halo-data-statistics-title-color);letter-spacing:0}.xhhaocom-dataStatistics-v2-traffic-badge{display:inline-flex;align-items:center;justify-content:center;padding:2px 8px;font-size:12px;font-weight:500;color:var(--halo-data-statistics-badge-text);border-radius:6px;background:var(--halo-data-statistics-badge-bg)}.xhhaocom-dataStatistics-v2-traffic-subtitle{font-size:12px;font-weight:400;color:var(--halo-data-statistics-subtitle-color);letter-spacing:.1px}.xhhaocom-dataStatistics-v2-traffic-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:14px}.xhhaocom-dataStatistics-v2-traffic-card{background:var(--halo-data-statistics-card-surface);border:1px solid var(--halo-data-statistics-card-border);border-radius:12px;padding:18px 16px;text-align:center;transition:all 0.2s ease;position:relative;overflow:visible;box-shadow:var(--halo-data-statistics-card-shadow)}.xhhaocom-dataStatistics-v2-traffic-card:hover,.xhhaocom-dataStatistics-v2-traffic-card:focus-within{border-color:var(--halo-data-statistics-card-hover-border);box-shadow:var(--halo-data-statistics-card-hover-shadow);transform:translateY(-2px)}.xhhaocom-dataStatistics-v2-traffic-icon{margin-bottom:12px;display:flex;align-items:center;justify-content:center;line-height:1;color:var(--halo-data-statistics-card-icon-color);width:44px;height:44px;margin-left:auto;margin-right:auto;background:var(--halo-data-statistics-card-icon-bg);border-radius:12px}.xhhaocom-dataStatistics-v2-traffic-icon svg{width:22px;height:22px;display:block;stroke-width:2.3}.xhhaocom-dataStatistics-v2-traffic-value{font-size:26px;font-weight:700;color:var(--halo-data-statistics-number-color);line-height:1.2;margin-bottom:8px;font-variant-numeric:tabular-nums;letter-spacing:-.5px}.xhhaocom-dataStatistics-v2-traffic-label{font-size:13px;color:var(--halo-data-statistics-label-color);font-weight:500;letter-spacing:.2px;text-transform:none}.xhhaocom-dataStatistics-v2-traffic-realtime{position:absolute;top:10px;right:10px;display:inline-flex;align-items:center;justify-content:center;cursor:default;z-index:10;width:10px;height:10px;background:#10b981;border-radius:50%;box-shadow:0 0 0 4px rgb(16 185 129 / .12)}.xhhaocom-dataStatistics-v2-traffic-realtime::before{content:'';position:absolute;inset:0;border-radius:50%;animation:xhhaocom-pulse 2s ease-in-out infinite}.xhhaocom-dataStatistics-v2-traffic-realtime::after{content:attr(data-tooltip);position:absolute;top:-36px;right:-10px;z-index:1000;padding:4px 8px;border-radius:6px;background:var(--halo-data-statistics-tooltip-bg);color:var(--halo-data-statistics-tooltip-text);font-size:10px;font-weight:500;letter-spacing:.2px;white-space:nowrap;opacity:0;transform:translateY(4px);transition:opacity 0.2s ease,transform 0.2s ease;pointer-events:none;box-shadow:0 6px 16px rgb(15 23 42 / .15)}.xhhaocom-dataStatistics-v2-traffic-realtime:hover::after,.xhhaocom-dataStatistics-v2-traffic-realtime:focus-visible::after{opacity:1;transform:translateY(0)}@keyframes xhhaocom-pulse{0%{opacity:1;box-shadow:0 0 0 0 rgb(16 185 129 / .7)}50%{opacity:.8;box-shadow:0 0 0 4px #fff0}100%{opacity:1;box-shadow:0 0 0 0 #fff0}}.xhhaocom-dataStatistics-v2-traffic-loading{display:flex;align-items:center;justify-content:center;min-height:120px;color:var(--halo-data-statistics-text-muted);font-size:13px;gap:8px}.xhhaocom-dataStatistics-v2-traffic-loading::before{content:'';width:16px;height:16px;border:2px solid var(--halo-data-statistics-section-border);border-top-color:var(--halo-data-statistics-badge-text);border-radius:50%;animation:xhhaocom-v2-spin 0.8s linear infinite}.xhhaocom-dataStatistics-v2-traffic-error{display:flex;align-items:center;justify-content:center;min-height:120px;color:#ef4444;font-size:13px;gap:8px;padding:12px;text-align:center}.xhhaocom-dataStatistics-v2-traffic-error::before{content:'⚠';font-size:16px}.xhhaocom-dataStatistics-v2-activity{width:100%;margin:20px 0 0;padding:0;background:var(--halo-data-statistics-activity-surface);border:1px solid var(--halo-data-statistics-activity-border);border-radius:16px;box-shadow:var(--halo-data-statistics-activity-shadow)}.xhhaocom-dataStatistics-v2-activity-section{display:flex;flex-direction:column;gap:18px;padding:22px 24px 24px}.xhhaocom-dataStatistics-v2-activity-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.xhhaocom-dataStatistics-v2-activity-title-box{display:flex;align-items:center;gap:10px}.xhhaocom-dataStatistics-v2-activity-title{font-size:18px;font-weight:600;color:var(--halo-data-statistics-title-color);letter-spacing:0}.xhhaocom-dataStatistics-v2-activity-badge-wrapper{display:inline-flex;align-items:center;gap:6px}.xhhaocom-dataStatistics-v2-activity-badge{display:inline-block;width:8px;height:8px;border-radius:50%;background:#10b981;box-shadow:0 0 0 2px rgb(16 185 129 / .2);animation:pulse-green 2s ease-in-out infinite;flex-shrink:0}.xhhaocom-dataStatistics-v2-activity-badge-text{font-size:12px;font-weight:500;color:#10b981;letter-spacing:.2px}@keyframes pulse-green{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.7;transform:scale(1.1)}}.xhhaocom-dataStatistics-v2-activity-subtitle{font-size:12px;font-weight:400;color:var(--halo-data-statistics-subtitle-color);letter-spacing:.2px}.xhhaocom-dataStatistics-v2-activity-body{display:flex;flex-direction:column;gap:20px}.xhhaocom-dataStatistics-v2-activity-metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0;border:1px solid var(--halo-data-statistics-metric-border);border-radius:12px;background:var(--halo-data-statistics-metric-surface);overflow:visible}.xhhaocom-dataStatistics-v2-activity-metric{display:flex;align-items:center;gap:12px;padding:12px 14px;border-radius:0;background:#fff0;border:none;box-shadow:none;position:relative}.xhhaocom-dataStatistics-v2-activity-metric+.xhhaocom-dataStatistics-v2-activity-metric{border-left:1px solid var(--halo-data-statistics-metric-border)}.xhhaocom-dataStatistics-v2-activity-metric-icon{width:34px;height:34px;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;background:var(--halo-data-statistics-metric-icon-bg);color:var(--halo-data-statistics-metric-icon-color)}.xhhaocom-dataStatistics-v2-activity-metric-icon svg{width:18px;height:18px}.xhhaocom-dataStatistics-v2-activity-metric-content{display:flex;flex-direction:column;gap:3px}.xhhaocom-dataStatistics-v2-activity-metric-value{font-size:20px;font-weight:700;color:var(--halo-data-statistics-metric-value-color);font-variant-numeric:tabular-nums}.xhhaocom-dataStatistics-v2-activity-metric-label{font-size:12px;font-weight:500;color:var(--halo-data-statistics-metric-label-color);letter-spacing:.3px}.xhhaocom-dataStatistics-v2-activity-list{position:relative;display:flex;flex-direction:column;gap:0;max-height:408px;overflow-y:auto;padding:0;overscroll-behavior:contain}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar{width:4px}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar-track{background:#fff0;border-radius:999px}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar-thumb{background:var(--halo-data-statistics-scrollbar-thumb);border-radius:999px}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar-thumb:hover{background:var(--halo-data-statistics-scrollbar-thumb-hover)}.xhhaocom-dataStatistics-v2-activity-item{position:relative;padding:12px 20px;background:#fff0;border:none;border-bottom:1px dashed var(--halo-data-statistics-list-item-border);transition:background 0.2s ease,transform 0.2s ease}.xhhaocom-dataStatistics-v2-activity-item:last-child{border-bottom:none}.xhhaocom-dataStatistics-v2-activity-item:hover{background:rgb(37 99 235 / .05);transform:translateY(-1px)}.xhhaocom-dataStatistics-v2-activity-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}.xhhaocom-dataStatistics-v2-activity-time-line{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--halo-data-statistics-time-text);margin-bottom:4px}.xhhaocom-dataStatistics-v2-activity-time{font-weight:500;font-variant-numeric:tabular-nums;flex-shrink:0;color:var(--halo-data-statistics-text-primary)}.xhhaocom-dataStatistics-v2-activity-separator{color:var(--halo-data-statistics-text-muted);font-size:12px;display:inline-flex;align-items:center;gap:4px;flex:1;min-width:0;overflow:visible}.xhhaocom-dataStatistics-v2-activity-separator>span{overflow:visible;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.xhhaocom-dataStatistics-v2-activity-separator svg{color:var(--halo-data-statistics-text-muted);display:inline-block;vertical-align:middle}.xhhaocom-dataStatistics-v2-activity-detail{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--halo-data-statistics-text-secondary);line-height:1.6}.xhhaocom-dataStatistics-v2-activity-person{flex-shrink:0;display:inline-flex;align-items:center;color:var(--halo-data-statistics-text-secondary)}.xhhaocom-dataStatistics-v2-activity-person svg{width:14px;height:14px;display:inline-block;vertical-align:middle}.xhhaocom-dataStatistics-v2-activity-text{flex:1;color:var(--halo-data-statistics-text-secondary)}.xhhaocom-dataStatistics-v2-activity-loading,.xhhaocom-dataStatistics-v2-activity-empty,.xhhaocom-dataStatistics-v2-activity-error{display:flex;align-items:center;justify-content:center;min-height:120px;color:var(--halo-data-statistics-text-muted);font-size:13px;gap:8px;padding:12px;text-align:center}.xhhaocom-dataStatistics-v2-activity-loading::before{content:'';width:16px;height:16px;border:2px solid var(--halo-data-statistics-activity-border);border-top-color:var(--halo-data-statistics-badge-text);border-radius:50%;animation:xhhaocom-v2-spin 0.8s linear infinite}.xhhaocom-dataStatistics-v2-activity-error{color:#ef4444}.xhhaocom-dataStatistics-v2-activity-error::before{content:'⚠';font-size:16px}@keyframes xhhaocom-v2-spin{to{transform:rotate(360deg)}}@media (max-width:768px){.xhhaocom-dataStatistics-v2-traffic{padding:16px}.xhhaocom-dataStatistics-v2-traffic-grid{grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:12px}.xhhaocom-dataStatistics-v2-traffic-card{padding:16px 14px}.xhhaocom-dataStatistics-v2-traffic-icon{width:44px;height:44px}.xhhaocom-dataStatistics-v2-traffic-value{font-size:22px}.xhhaocom-dataStatistics-v2-traffic-label{font-size:12px}.xhhaocom-dataStatistics-v2-activity{margin-top:16px}.xhhaocom-dataStatistics-v2-activity-section{padding:18px}.xhhaocom-dataStatistics-v2-activity-metrics{grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}.xhhaocom-dataStatistics-v2-activity-item{padding:14px;gap:10px}.xhhaocom-dataStatistics-v2-activity-time-line,.xhhaocom-dataStatistics-v2-activity-detail{font-size:11px}.xhhaocom-dataStatistics-v2-activity-metric-value{font-size:18px}}@media (prefers-color-scheme:dark){.xhhaocom-dataStatistics-v2-traffic{background:linear-gradient(180deg,rgb(15 23 42 / .92) 0%,rgb(15 23 42 / .65) 100%);border-color:rgb(148 163 184 / .15);box-shadow:0 18px 30px -24px rgb(59 130 246 / .35)}.xhhaocom-dataStatistics-v2-traffic-title{color:#bfdbfe}.xhhaocom-dataStatistics-v2-traffic-badge{background:rgb(59 130 246 / .2);color:#bfdbfe}.xhhaocom-dataStatistics-v2-traffic-subtitle{color:#94a3b8}.xhhaocom-dataStatistics-v2-traffic-card{background:rgb(17 24 39 / .9);border-color:rgb(59 130 246 / .25);box-shadow:inset 0 1px 0 rgb(59 130 246 / .08),0 14px 24px -26px rgb(59 130 246 / .6)}.xhhaocom-dataStatistics-v2-traffic-icon{background:linear-gradient(140deg,rgb(37 99 235 / .25),rgb(37 99 235 / .08));color:#60a5fa}.xhhaocom-dataStatistics-v2-traffic-value{color:#bfdbfe}.xhhaocom-dataStatistics-v2-traffic-label{color:#cbd5f5}.xhhaocom-dataStatistics-v2-traffic-realtime{background:rgb(37 99 235 / .22);color:#bfdbfe}.xhhaocom-dataStatistics-v2-traffic-realtime::before{background:#60a5fa}.xhhaocom-dataStatistics-v2-traffic-loading{color:#6b7280}.xhhaocom-dataStatistics-v2-activity{background:linear-gradient(180deg,rgb(15 23 42 / .9) 0%,rgb(15 23 42 / .6) 100%);border-color:rgb(37 99 235 / .18);box-shadow:0 18px 40px -30px rgb(14 165 233 / .4)}.xhhaocom-dataStatistics-v2-activity-title{color:#e0f2fe}.xhhaocom-dataStatistics-v2-activity-badge{background:#10b981;box-shadow:0 0 0 2px rgb(16 185 129 / .3)}.xhhaocom-dataStatistics-v2-activity-badge-text{color:#34d399}.xhhaocom-dataStatistics-v2-activity-subtitle{color:#9ca3af}.xhhaocom-dataStatistics-v2-activity-metrics{gap:10px}.xhhaocom-dataStatistics-v2-activity-metric{background:rgb(8 47 73 / .65);border-color:rgb(14 165 233 / .35);box-shadow:inset 0 1px 0 rgb(125 211 252 / .18)}.xhhaocom-dataStatistics-v2-activity-metric-icon{background:rgb(14 165 233 / .22);color:#7dd3fc}.xhhaocom-dataStatistics-v2-activity-metric-value{color:#e0f2fe}.xhhaocom-dataStatistics-v2-activity-metric-label{color:#bae6fd}.xhhaocom-dataStatistics-v2-activity-item{background:rgb(15 23 42 / .9);border-color:rgb(14 165 233 / .28);box-shadow:inset 0 1px 0 rgb(125 211 252 / .14)}.xhhaocom-dataStatistics-v2-activity-time-line{color:#e2e8f0}.xhhaocom-dataStatistics-v2-activity-detail{color:#cbd5f5}.xhhaocom-dataStatistics-v2-activity-text{color:#cbd5f5}.xhhaocom-dataStatistics-v2-activity-person svg{color:rgb(125 211 252 / .7)}.xhhaocom-dataStatistics-v2-activity-loading,.xhhaocom-dataStatistics-v2-activity-empty{color:#9ca3af}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar-track{background:#fff0}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar-thumb{background:rgb(148 163 184 / .45)}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar-thumb:hover{background:rgb(148 163 184 / .65)}}.xhhaocom-dataStatistics-v2-uptime-kuma{display:inline-flex;align-items:center;justify-content:center;padding:0;background:#fff0;border:none;color:var(--halo-data-statistics-text-primary)}.xhhaocom-dataStatistics-v2-uptime-kuma__content{display:inline-flex;align-items:center;gap:8px;text-decoration:none;color:inherit;cursor:pointer;position:relative}.xhhaocom-dataStatistics-v2-uptime-kuma__content.is-static{cursor:default}.xhhaocom-dataStatistics-v2-uptime-kuma__content:hover,.xhhaocom-dataStatistics-v2-uptime-kuma__content:focus-visible{text-decoration:none}.xhhaocom-dataStatistics-v2-uptime-kuma__content::after{content:attr(data-tip-title);position:absolute;bottom:100%;left:50%;transform:translateX(-50%) translateY(-8px);padding:6px 12px;background:rgb(15 23 42 / .9);color:#fff;font-size:12px;font-weight:500;white-space:nowrap;border-radius:6px;opacity:0;pointer-events:none;transition:opacity 0.2s ease,transform 0.2s ease;z-index:1000;margin-bottom:4px}.xhhaocom-dataStatistics-v2-uptime-kuma__content::before{content:'';position:absolute;bottom:100%;left:50%;transform:translateX(-50%) translateY(-2px);width:0;height:0;border-left:5px solid #fff0;border-right:5px solid #fff0;border-top:5px solid rgb(15 23 42 / .9);opacity:0;pointer-events:none;transition:opacity 0.2s ease,transform 0.2s ease;z-index:1001}.xhhaocom-dataStatistics-v2-uptime-kuma__content:hover::after,.xhhaocom-dataStatistics-v2-uptime-kuma__content:focus-visible::after{opacity:1;transform:translateX(-50%) translateY(0)}.xhhaocom-dataStatistics-v2-uptime-kuma__content:hover::before,.xhhaocom-dataStatistics-v2-uptime-kuma__content:focus-visible::before{opacity:1;transform:translateX(-50%) translateY(0)}.xhhaocom-dataStatistics-v2-uptime-kuma-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;animation:pulse 2s ease-in-out infinite}.xhhaocom-dataStatistics-v2-uptime-kuma-dot--loading{background:#9ca3af;box-shadow:0 0 0 2px rgb(156 163 175 / .2)}.xhhaocom-dataStatistics-v2-uptime-kuma-dot--success{background:#10b981;box-shadow:0 0 0 2px rgb(16 185 129 / .2)}.xhhaocom-dataStatistics-v2-uptime-kuma-dot--warning{background:#f59e0b;box-shadow:0 0 0 2px rgb(245 158 11 / .2)}.xhhaocom-dataStatistics-v2-uptime-kuma-dot--error{background:#ef4444;box-shadow:0 0 0 2px rgb(239 68 68 / .2)}.xhhaocom-dataStatistics-v2-uptime-kuma-text{font-size:14px;font-weight:500;color:#1b2559;letter-spacing:.2px;transition:color 0.2s ease}.xhhaocom-dataStatistics-v2-uptime-kuma__content--success:hover .xhhaocom-dataStatistics-v2-uptime-kuma-text{color:#10b981}.xhhaocom-dataStatistics-v2-uptime-kuma__content--warning:hover .xhhaocom-dataStatistics-v2-uptime-kuma-text{color:#f59e0b}.xhhaocom-dataStatistics-v2-uptime-kuma__content--error:hover .xhhaocom-dataStatistics-v2-uptime-kuma-text{color:#ef4444}.xhhaocom-dataStatistics-v2-uptime-kuma__content--muted:hover .xhhaocom-dataStatistics-v2-uptime-kuma-text{color:var(--halo-data-statistics-text-muted)}.xhhaocom-dataStatistics-v2-uptime-kuma-loading,.xhhaocom-dataStatistics-v2-uptime-kuma-error{display:inline-flex;align-items:center;gap:8px;color:var(--halo-data-statistics-text-muted);font-size:14px}@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.7;transform:scale(1.1)}}.xhhaocom-dataStatistics-v2-uptime-kuma-loading,.xhhaocom-dataStatistics-v2-uptime-kuma-error{color:var(--halo-data-statistics-text-muted)}html.dark .xhhaocom-dataStatistics-v2-uptime-kuma-text,html[class~='dark'] .xhhaocom-dataStatistics-v2-uptime-kuma-text,html[class*='dark'] .xhhaocom-dataStatistics-v2-uptime-kuma-text,html[data-color-scheme='dark'] .xhhaocom-dataStatistics-v2-uptime-kuma-text,html[data-theme='dark'] .xhhaocom-dataStatistics-v2-uptime-kuma-text,html[theme='dark'] .xhhaocom-dataStatistics-v2-uptime-kuma-text{color:#e2e8f0} +.xhhaocom-dataStatistics-v2-wrapper{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;margin:0;padding:0;box-sizing:border-box}:root{--halo-data-statistics-section-surface:#ffffff;--halo-data-statistics-section-border:#dde3f5;--halo-data-statistics-section-shadow:0 4px 12px rgba(15,23,42,0.05);--halo-data-statistics-title-color:#1b2559;--halo-data-statistics-subtitle-color:#5b647c;--halo-data-statistics-badge-bg:#e8edff;--halo-data-statistics-badge-text:#2563eb;--halo-data-statistics-card-surface:#f6f8ff;--halo-data-statistics-card-border:#dbe1fb;--halo-data-statistics-card-shadow:none;--halo-data-statistics-card-hover-border:#c2cbff;--halo-data-statistics-card-hover-shadow:0 6px 16px rgba(37,99,235,0.1);--halo-data-statistics-card-icon-bg:#e9efff;--halo-data-statistics-card-icon-color:#2563eb;--halo-data-statistics-number-color:#1d4ed8;--halo-data-statistics-label-color:#516079;--halo-data-statistics-activity-surface:#ffffff;--halo-data-statistics-activity-border:#dde3f5;--halo-data-statistics-activity-shadow:0 4px 12px rgba(15,23,42,0.05);--halo-data-statistics-metric-surface:#f6f8ff;--halo-data-statistics-metric-border:#dbe1fb;--halo-data-statistics-metric-icon-bg:#e9efff;--halo-data-statistics-metric-icon-color:#2563eb;--halo-data-statistics-metric-value-color:#1d4ed8;--halo-data-statistics-metric-label-color:#516079;--halo-data-statistics-list-item-border:#e1e7fb;--halo-data-statistics-text-primary:#1b2559;--halo-data-statistics-text-secondary:#516079;--halo-data-statistics-text-muted:#5b647c;--halo-data-statistics-time-text:#334155;--halo-data-statistics-scrollbar-thumb:rgba(148,163,184,0.4);--halo-data-statistics-scrollbar-thumb-hover:rgba(148,163,184,0.65);--halo-data-statistics-tooltip-bg:rgba(15,23,42,0.85);--halo-data-statistics-tooltip-text:#f8fafc}html.dark:not([data-color-scheme='light']):not(.light):not([class*='light']),html[class~='dark']:not([data-color-scheme='light']):not(.light):not([class*='light']),html[class*='dark']:not([data-color-scheme='light']):not(.light):not([class*='light']),html[data-color-scheme='dark']:not([data-color-scheme='light']),html[data-theme='dark']:not([data-theme='light']),html[theme='dark']:not([theme='light']),[data-color-scheme='dark']:not([data-color-scheme='light']),[data-theme='dark']:not([data-theme='light']),[theme='dark']:not([theme='light']){--halo-data-statistics-section-surface:#111827;--halo-data-statistics-section-border:#1f2937;--halo-data-statistics-section-shadow:0 6px 20px rgba(8,47,73,0.35);--halo-data-statistics-title-color:#e2e8f0;--halo-data-statistics-subtitle-color:#94a3b8;--halo-data-statistics-badge-bg:rgba(37,99,235,0.2);--halo-data-statistics-badge-text:#93c5fd;--halo-data-statistics-card-surface:#18223a;--halo-data-statistics-card-border:#25304a;--halo-data-statistics-card-shadow:none;--halo-data-statistics-card-hover-border:#3b4d75;--halo-data-statistics-card-hover-shadow:0 6px 20px rgba(8,47,73,0.45);--halo-data-statistics-card-icon-bg:#1f2d4a;--halo-data-statistics-card-icon-color:#93c5fd;--halo-data-statistics-number-color:#93c5fd;--halo-data-statistics-label-color:#cbd5f5;--halo-data-statistics-activity-surface:#111827;--halo-data-statistics-activity-border:#1f2937;--halo-data-statistics-activity-shadow:0 6px 20px rgba(8,47,73,0.35);--halo-data-statistics-metric-surface:#18223a;--halo-data-statistics-metric-border:#25304a;--halo-data-statistics-metric-icon-bg:#1f2d4a;--halo-data-statistics-metric-icon-color:#93c5fd;--halo-data-statistics-metric-value-color:#93c5fd;--halo-data-statistics-metric-label-color:#cbd5f5;--halo-data-statistics-list-item-border:#25304a;--halo-data-statistics-text-primary:#e2e8f0;--halo-data-statistics-text-secondary:#cbd5f5;--halo-data-statistics-text-muted:#94a3b8;--halo-data-statistics-time-text:#cbd5f5;--halo-data-statistics-scrollbar-thumb:rgba(148,163,184,0.45);--halo-data-statistics-scrollbar-thumb-hover:rgba(148,163,184,0.65);--halo-data-statistics-tooltip-bg:rgba(241,245,249,0.9);--halo-data-statistics-tooltip-text:#0f172a}@media (prefers-color-scheme:dark){:root:not([data-color-scheme]),:root[data-color-scheme='auto'],:root[data-color-scheme='system'],html:not([data-color-scheme]):not(.light):not([class*='light']),html[data-color-scheme='auto']:not(.light):not([class*='light']),html[data-color-scheme='system']:not(.light):not([class*='light']){--halo-data-statistics-section-surface:#111827;--halo-data-statistics-section-border:#1f2937;--halo-data-statistics-section-shadow:0 6px 20px rgba(8,47,73,0.35);--halo-data-statistics-title-color:#e2e8f0;--halo-data-statistics-subtitle-color:#94a3b8;--halo-data-statistics-badge-bg:rgba(37,99,235,0.2);--halo-data-statistics-badge-text:#93c5fd;--halo-data-statistics-card-surface:#18223a;--halo-data-statistics-card-border:#25304a;--halo-data-statistics-card-shadow:none;--halo-data-statistics-card-hover-border:#3b4d75;--halo-data-statistics-card-hover-shadow:0 6px 20px rgba(8,47,73,0.45);--halo-data-statistics-card-icon-bg:#1f2d4a;--halo-data-statistics-card-icon-color:#93c5fd;--halo-data-statistics-number-color:#93c5fd;--halo-data-statistics-label-color:#cbd5f5;--halo-data-statistics-activity-surface:#111827;--halo-data-statistics-activity-border:#1f2937;--halo-data-statistics-activity-shadow:0 6px 20px rgba(8,47,73,0.35);--halo-data-statistics-metric-surface:#18223a;--halo-data-statistics-metric-border:#25304a;--halo-data-statistics-metric-icon-bg:#1f2d4a;--halo-data-statistics-metric-icon-color:#93c5fd;--halo-data-statistics-metric-value-color:#93c5fd;--halo-data-statistics-metric-label-color:#cbd5f5;--halo-data-statistics-list-item-border:#25304a;--halo-data-statistics-text-primary:#e2e8f0;--halo-data-statistics-text-secondary:#cbd5f5;--halo-data-statistics-text-muted:#94a3b8;--halo-data-statistics-time-text:#cbd5f5;--halo-data-statistics-scrollbar-thumb:rgba(148,163,184,0.45);--halo-data-statistics-scrollbar-thumb-hover:rgba(148,163,184,0.65);--halo-data-statistics-tooltip-bg:rgba(241,245,249,0.9);--halo-data-statistics-tooltip-text:#0f172a}}.xhhaocom-dataStatistics-v2-traffic{display:block;margin:0;padding:20px 24px;width:100%;background:var(--halo-data-statistics-section-surface);border:1px solid var(--halo-data-statistics-section-border);border-radius:16px;box-shadow:var(--halo-data-statistics-section-shadow)}.xhhaocom-dataStatistics-v2-traffic-section{display:flex;flex-direction:column;gap:18px}.xhhaocom-dataStatistics-v2-traffic-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.xhhaocom-dataStatistics-v2-traffic-title-box{display:flex;align-items:center;gap:10px}.xhhaocom-dataStatistics-v2-traffic-title{font-size:18px;font-weight:600;color:var(--halo-data-statistics-title-color);letter-spacing:0}.xhhaocom-dataStatistics-v2-traffic-badge{display:inline-flex;align-items:center;justify-content:center;padding:2px 8px;font-size:12px;font-weight:500;color:var(--halo-data-statistics-badge-text);border-radius:6px;background:var(--halo-data-statistics-badge-bg)}.xhhaocom-dataStatistics-v2-traffic-subtitle{font-size:12px;font-weight:400;color:var(--halo-data-statistics-subtitle-color);letter-spacing:0.1px}.xhhaocom-dataStatistics-v2-traffic-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:14px}.xhhaocom-dataStatistics-v2-traffic-card{background:var(--halo-data-statistics-card-surface);border:1px solid var(--halo-data-statistics-card-border);border-radius:12px;padding:18px 16px;text-align:center;transition:all 0.2s ease;position:relative;overflow:visible;box-shadow:var(--halo-data-statistics-card-shadow)}.xhhaocom-dataStatistics-v2-traffic-card:hover,.xhhaocom-dataStatistics-v2-traffic-card:focus-within{border-color:var(--halo-data-statistics-card-hover-border);box-shadow:var(--halo-data-statistics-card-hover-shadow);transform:translateY(-2px)}.xhhaocom-dataStatistics-v2-traffic-icon{margin-bottom:12px;display:flex;align-items:center;justify-content:center;line-height:1;color:var(--halo-data-statistics-card-icon-color);width:44px;height:44px;margin-left:auto;margin-right:auto;background:var(--halo-data-statistics-card-icon-bg);border-radius:12px}.xhhaocom-dataStatistics-v2-traffic-icon svg{width:22px;height:22px;display:block;stroke-width:2.3}.xhhaocom-dataStatistics-v2-traffic-value{font-size:26px;font-weight:700;color:var(--halo-data-statistics-number-color);line-height:1.2;margin-bottom:8px;font-variant-numeric:tabular-nums;letter-spacing:-0.5px}.xhhaocom-dataStatistics-v2-traffic-label{font-size:13px;color:var(--halo-data-statistics-label-color);font-weight:500;letter-spacing:0.2px;text-transform:none}.xhhaocom-dataStatistics-v2-traffic-realtime{position:absolute;top:10px;right:10px;display:inline-flex;align-items:center;justify-content:center;cursor:default;z-index:10;width:10px;height:10px;background:#10b981;border-radius:50%;box-shadow:0 0 0 4px rgba(16,185,129,0.12)}.xhhaocom-dataStatistics-v2-traffic-realtime::before{content:'';position:absolute;inset:0;border-radius:50%;animation:xhhaocom-pulse 2s ease-in-out infinite}.xhhaocom-dataStatistics-v2-traffic-realtime::after{content:attr(data-tooltip);position:absolute;top:-36px;right:-10px;z-index:1000;padding:4px 8px;border-radius:6px;background:var(--halo-data-statistics-tooltip-bg);color:var(--halo-data-statistics-tooltip-text);font-size:10px;font-weight:500;letter-spacing:0.2px;white-space:nowrap;opacity:0;transform:translateY(4px);transition:opacity 0.2s ease,transform 0.2s ease;pointer-events:none;box-shadow:0 6px 16px rgba(15,23,42,0.15)}.xhhaocom-dataStatistics-v2-traffic-realtime:hover::after,.xhhaocom-dataStatistics-v2-traffic-realtime:focus-visible::after{opacity:1;transform:translateY(0)}@keyframes xhhaocom-pulse{0%{opacity:1;box-shadow:0 0 0 0 rgba(16,185,129,0.7)}50%{opacity:0.8;box-shadow:0 0 0 4px rgba(16,185,129,0)}100%{opacity:1;box-shadow:0 0 0 0 rgba(16,185,129,0)}}.xhhaocom-dataStatistics-v2-traffic-loading{display:flex;align-items:center;justify-content:center;min-height:120px;color:var(--halo-data-statistics-text-muted);font-size:13px;gap:8px}.xhhaocom-dataStatistics-v2-traffic-loading::before{content:'';width:16px;height:16px;border:2px solid var(--halo-data-statistics-section-border);border-top-color:var(--halo-data-statistics-badge-text);border-radius:50%;animation:xhhaocom-v2-spin 0.8s linear infinite}.xhhaocom-dataStatistics-v2-traffic-error{display:flex;align-items:center;justify-content:center;min-height:120px;color:#ef4444;font-size:13px;gap:8px;padding:12px;text-align:center}.xhhaocom-dataStatistics-v2-traffic-error::before{content:'⚠';font-size:16px}.xhhaocom-dataStatistics-v2-activity{width:100%;margin:20px 0 0;padding:0;background:var(--halo-data-statistics-activity-surface);border:1px solid var(--halo-data-statistics-activity-border);border-radius:16px;box-shadow:var(--halo-data-statistics-activity-shadow)}.xhhaocom-dataStatistics-v2-activity-section{display:flex;flex-direction:column;gap:18px;padding:22px 24px 24px}.xhhaocom-dataStatistics-v2-activity-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.xhhaocom-dataStatistics-v2-activity-title-box{display:flex;align-items:center;gap:10px}.xhhaocom-dataStatistics-v2-activity-title{font-size:18px;font-weight:600;color:var(--halo-data-statistics-title-color);letter-spacing:0}.xhhaocom-dataStatistics-v2-activity-badge-wrapper{display:inline-flex;align-items:center;gap:6px}.xhhaocom-dataStatistics-v2-activity-badge{display:inline-block;width:8px;height:8px;border-radius:50%;background:#10b981;box-shadow:0 0 0 2px rgba(16,185,129,0.2);animation:pulse-green 2s ease-in-out infinite;flex-shrink:0}.xhhaocom-dataStatistics-v2-activity-badge-text{font-size:12px;font-weight:500;color:#10b981;letter-spacing:0.2px}@keyframes pulse-green{0%,100%{opacity:1;transform:scale(1)}50%{opacity:0.7;transform:scale(1.1)}}.xhhaocom-dataStatistics-v2-activity-subtitle{font-size:12px;font-weight:400;color:var(--halo-data-statistics-subtitle-color);letter-spacing:0.2px}.xhhaocom-dataStatistics-v2-activity-body{display:flex;flex-direction:column;gap:20px}.xhhaocom-dataStatistics-v2-activity-metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0;border:1px solid var(--halo-data-statistics-metric-border);border-radius:12px;background:var(--halo-data-statistics-metric-surface);overflow:visible}.xhhaocom-dataStatistics-v2-activity-metric{display:flex;align-items:center;gap:12px;padding:12px 14px;border-radius:0;background:transparent;border:none;box-shadow:none;position:relative}.xhhaocom-dataStatistics-v2-activity-metric+.xhhaocom-dataStatistics-v2-activity-metric{border-left:1px solid var(--halo-data-statistics-metric-border)}.xhhaocom-dataStatistics-v2-activity-metric-icon{width:34px;height:34px;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;background:var(--halo-data-statistics-metric-icon-bg);color:var(--halo-data-statistics-metric-icon-color)}.xhhaocom-dataStatistics-v2-activity-metric-icon svg{width:18px;height:18px}.xhhaocom-dataStatistics-v2-activity-metric-content{display:flex;flex-direction:column;gap:3px}.xhhaocom-dataStatistics-v2-activity-metric-value{font-size:20px;font-weight:700;color:var(--halo-data-statistics-metric-value-color);font-variant-numeric:tabular-nums}.xhhaocom-dataStatistics-v2-activity-metric-label{font-size:12px;font-weight:500;color:var(--halo-data-statistics-metric-label-color);letter-spacing:0.3px}.xhhaocom-dataStatistics-v2-activity-list{position:relative;display:flex;flex-direction:column;gap:0;max-height:408px;overflow-y:auto;padding:0;overscroll-behavior:contain}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar{width:4px}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar-track{background:transparent;border-radius:999px}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar-thumb{background:var(--halo-data-statistics-scrollbar-thumb);border-radius:999px}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar-thumb:hover{background:var(--halo-data-statistics-scrollbar-thumb-hover)}.xhhaocom-dataStatistics-v2-activity-item{position:relative;padding:12px 20px;background:transparent;border:none;border-bottom:1px dashed var(--halo-data-statistics-list-item-border);transition:background 0.2s ease,transform 0.2s ease}.xhhaocom-dataStatistics-v2-activity-item:last-child{border-bottom:none}.xhhaocom-dataStatistics-v2-activity-item:hover{background:rgba(37,99,235,0.05);transform:translateY(-1px)}.xhhaocom-dataStatistics-v2-activity-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}.xhhaocom-dataStatistics-v2-activity-time-line{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--halo-data-statistics-time-text);margin-bottom:4px}.xhhaocom-dataStatistics-v2-activity-time{font-weight:500;font-variant-numeric:tabular-nums;flex-shrink:0;color:var(--halo-data-statistics-text-primary)}.xhhaocom-dataStatistics-v2-activity-separator{color:var(--halo-data-statistics-text-muted);font-size:12px;display:inline-flex;align-items:center;gap:4px;flex:1;min-width:0;overflow:visible}.xhhaocom-dataStatistics-v2-activity-separator>span{overflow:visible;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.xhhaocom-dataStatistics-v2-activity-separator svg{color:var(--halo-data-statistics-text-muted);display:inline-block;vertical-align:middle}.xhhaocom-dataStatistics-v2-activity-detail{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--halo-data-statistics-text-secondary);line-height:1.6}.xhhaocom-dataStatistics-v2-activity-person{flex-shrink:0;display:inline-flex;align-items:center;color:var(--halo-data-statistics-text-secondary)}.xhhaocom-dataStatistics-v2-activity-person svg{width:14px;height:14px;display:inline-block;vertical-align:middle}.xhhaocom-dataStatistics-v2-activity-text{flex:1;color:var(--halo-data-statistics-text-secondary)}.xhhaocom-dataStatistics-v2-activity-loading,.xhhaocom-dataStatistics-v2-activity-empty,.xhhaocom-dataStatistics-v2-activity-error{display:flex;align-items:center;justify-content:center;min-height:120px;color:var(--halo-data-statistics-text-muted);font-size:13px;gap:8px;padding:12px;text-align:center}.xhhaocom-dataStatistics-v2-activity-loading::before{content:'';width:16px;height:16px;border:2px solid var(--halo-data-statistics-activity-border);border-top-color:var(--halo-data-statistics-badge-text);border-radius:50%;animation:xhhaocom-v2-spin 0.8s linear infinite}.xhhaocom-dataStatistics-v2-activity-error{color:#ef4444}.xhhaocom-dataStatistics-v2-activity-error::before{content:'⚠';font-size:16px}@keyframes xhhaocom-v2-spin{to{transform:rotate(360deg)}}@media (max-width:768px){.xhhaocom-dataStatistics-v2-traffic{padding:16px}.xhhaocom-dataStatistics-v2-traffic-grid{grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:12px}.xhhaocom-dataStatistics-v2-traffic-card{padding:16px 14px}.xhhaocom-dataStatistics-v2-traffic-icon{width:44px;height:44px}.xhhaocom-dataStatistics-v2-traffic-value{font-size:22px}.xhhaocom-dataStatistics-v2-traffic-label{font-size:12px}.xhhaocom-dataStatistics-v2-activity{margin-top:16px}.xhhaocom-dataStatistics-v2-activity-section{padding:18px}.xhhaocom-dataStatistics-v2-activity-metrics{grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}.xhhaocom-dataStatistics-v2-activity-item{padding:14px;gap:10px}.xhhaocom-dataStatistics-v2-activity-time-line,.xhhaocom-dataStatistics-v2-activity-detail{font-size:11px}.xhhaocom-dataStatistics-v2-activity-metric-value{font-size:18px}}@media (prefers-color-scheme:dark){.xhhaocom-dataStatistics-v2-traffic-loading{color:#6b7280}.xhhaocom-dataStatistics-v2-activity{background:linear-gradient(180deg,rgba(15,23,42,0.9) 0%,rgba(15,23,42,0.6) 100%);border-color:rgba(37,99,235,0.18);box-shadow:0 18px 40px -30px rgba(14,165,233,0.4)}.xhhaocom-dataStatistics-v2-activity-title{color:#e0f2fe}.xhhaocom-dataStatistics-v2-activity-badge{background:#10b981;box-shadow:0 0 0 2px rgba(16,185,129,0.3)}.xhhaocom-dataStatistics-v2-activity-badge-text{color:#34d399}.xhhaocom-dataStatistics-v2-activity-subtitle{color:#9ca3af}.xhhaocom-dataStatistics-v2-activity-metrics{gap:10px}.xhhaocom-dataStatistics-v2-activity-metric{background:rgba(8,47,73,0.65);border-color:rgba(14,165,233,0.35);box-shadow:inset 0 1px 0 rgba(125,211,252,0.18)}.xhhaocom-dataStatistics-v2-activity-metric-icon{background:rgba(14,165,233,0.22);color:#7dd3fc}.xhhaocom-dataStatistics-v2-activity-metric-value{color:#e0f2fe}.xhhaocom-dataStatistics-v2-activity-metric-label{color:#bae6fd}.xhhaocom-dataStatistics-v2-activity-item{background:rgba(15,23,42,0.9);border-color:rgba(14,165,233,0.28);box-shadow:inset 0 1px 0 rgba(125,211,252,0.14)}.xhhaocom-dataStatistics-v2-activity-time-line{color:#e2e8f0}.xhhaocom-dataStatistics-v2-activity-detail{color:#cbd5f5}.xhhaocom-dataStatistics-v2-activity-text{color:#cbd5f5}.xhhaocom-dataStatistics-v2-activity-person svg{color:rgba(125,211,252,0.7)}.xhhaocom-dataStatistics-v2-activity-loading,.xhhaocom-dataStatistics-v2-activity-empty{color:#9ca3af}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar-track{background:transparent}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar-thumb{background:rgba(148,163,184,0.45)}.xhhaocom-dataStatistics-v2-activity-list::-webkit-scrollbar-thumb:hover{background:rgba(148,163,184,0.65)}}.xhhaocom-dataStatistics-v2-uptime-kuma{display:inline-flex;align-items:center;justify-content:center;padding:0;background:transparent;border:none;color:var(--halo-data-statistics-text-primary)}.xhhaocom-dataStatistics-v2-uptime-kuma__content{display:inline-flex;align-items:center;gap:8px;text-decoration:none;color:inherit;cursor:pointer;position:relative}.xhhaocom-dataStatistics-v2-uptime-kuma__content.is-static{cursor:default}.xhhaocom-dataStatistics-v2-uptime-kuma__content:hover,.xhhaocom-dataStatistics-v2-uptime-kuma__content:focus-visible{text-decoration:none}.xhhaocom-dataStatistics-v2-uptime-kuma__content::after{content:attr(data-tip-title);position:absolute;bottom:100%;left:50%;transform:translateX(-50%) translateY(-8px);padding:6px 12px;background:rgba(15,23,42,0.9);color:#ffffff;font-size:12px;font-weight:500;white-space:nowrap;border-radius:6px;opacity:0;pointer-events:none;transition:opacity 0.2s ease,transform 0.2s ease;z-index:1000;margin-bottom:4px}.xhhaocom-dataStatistics-v2-uptime-kuma__content::before{content:'';position:absolute;bottom:100%;left:50%;transform:translateX(-50%) translateY(-2px);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid rgba(15,23,42,0.9);opacity:0;pointer-events:none;transition:opacity 0.2s ease,transform 0.2s ease;z-index:1001}.xhhaocom-dataStatistics-v2-uptime-kuma__content:hover::after,.xhhaocom-dataStatistics-v2-uptime-kuma__content:focus-visible::after{opacity:1;transform:translateX(-50%) translateY(0)}.xhhaocom-dataStatistics-v2-uptime-kuma__content:hover::before,.xhhaocom-dataStatistics-v2-uptime-kuma__content:focus-visible::before{opacity:1;transform:translateX(-50%) translateY(0)}.xhhaocom-dataStatistics-v2-uptime-kuma-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;animation:pulse 2s ease-in-out infinite}.xhhaocom-dataStatistics-v2-uptime-kuma-dot--loading{background:#9ca3af;box-shadow:0 0 0 2px rgba(156,163,175,0.2)}.xhhaocom-dataStatistics-v2-uptime-kuma-dot--success{background:#10b981;box-shadow:0 0 0 2px rgba(16,185,129,0.2)}.xhhaocom-dataStatistics-v2-uptime-kuma-dot--warning{background:#f59e0b;box-shadow:0 0 0 2px rgba(245,158,11,0.2)}.xhhaocom-dataStatistics-v2-uptime-kuma-dot--error{background:#ef4444;box-shadow:0 0 0 2px rgba(239,68,68,0.2)}.xhhaocom-dataStatistics-v2-uptime-kuma-text{font-size:14px;font-weight:500;color:#1b2559;letter-spacing:0.2px;transition:color 0.2s ease}.xhhaocom-dataStatistics-v2-uptime-kuma__content--success:hover .xhhaocom-dataStatistics-v2-uptime-kuma-text{color:#10b981}.xhhaocom-dataStatistics-v2-uptime-kuma__content--warning:hover .xhhaocom-dataStatistics-v2-uptime-kuma-text{color:#f59e0b}.xhhaocom-dataStatistics-v2-uptime-kuma__content--error:hover .xhhaocom-dataStatistics-v2-uptime-kuma-text{color:#ef4444}.xhhaocom-dataStatistics-v2-uptime-kuma__content--muted:hover .xhhaocom-dataStatistics-v2-uptime-kuma-text{color:var(--halo-data-statistics-text-muted)}.xhhaocom-dataStatistics-v2-uptime-kuma-loading,.xhhaocom-dataStatistics-v2-uptime-kuma-error{display:inline-flex;align-items:center;gap:8px;color:var(--halo-data-statistics-text-muted);font-size:14px}@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:0.7;transform:scale(1.1)}}.xhhaocom-dataStatistics-v2-uptime-kuma-loading,.xhhaocom-dataStatistics-v2-uptime-kuma-error{color:var(--halo-data-statistics-text-muted)}html.dark .xhhaocom-dataStatistics-v2-uptime-kuma-text,html[class~='dark'] .xhhaocom-dataStatistics-v2-uptime-kuma-text,html[class*='dark'] .xhhaocom-dataStatistics-v2-uptime-kuma-text,html[data-color-scheme='dark'] .xhhaocom-dataStatistics-v2-uptime-kuma-text,html[data-theme='dark'] .xhhaocom-dataStatistics-v2-uptime-kuma-text,html[theme='dark'] .xhhaocom-dataStatistics-v2-uptime-kuma-text{color:#e2e8f0} \ No newline at end of file diff --git a/src/main/resources/static/min/siteCharts.min.css b/src/main/resources/static/min/siteCharts.min.css index 74b6aca..4450181 100644 --- a/src/main/resources/static/min/siteCharts.min.css +++ b/src/main/resources/static/min/siteCharts.min.css @@ -1 +1 @@ -:root{--chartboard-surface:#ffffff;--chartboard-border:rgba(226, 232, 240, 0.8);--chartboard-shadow:0 10px 30px -20px rgba(15, 23, 42, 0.15);--chartboard-title-color:#1f2937;--chartboard-subtitle-color:#6b7280;--chartboard-card-bg:linear-gradient(180deg, rgba(248, 250, 252, 0.8) 0%, rgba(255, 255, 255, 0.95) 100%);--chartboard-card-border:rgba(226, 232, 240, 0.8);--chartboard-card-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.6);--chartboard-card-hover-shadow:0 12px 24px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.6);--chartboard-text-primary:#1f2937;--chartboard-text-secondary:#4b5563;--chartboard-text-muted:#6b7280;--chartboard-loading-bg:rgba(241, 245, 249, 0.6);--chartboard-loading-border:rgba(148, 163, 184, 0.25);--chartboard-error-bg:rgba(254, 226, 226, 0.6);--chartboard-error-border:rgba(248, 113, 113, 0.45);--chartboard-error-color:#dc2626;--chartboard-heatmap-cell-bg:#e5e7eb;--chartboard-heatmap-level-1:#bfdbfe;--chartboard-heatmap-level-2:#93c5fd;--chartboard-heatmap-level-3:#60a5fa;--chartboard-heatmap-level-4:#2563eb;--chartboard-heatmap-hover-shadow:rgba(37, 99, 235, 0.2);--chartboard-heatmap-tooltip-bg:#ffffff;--chartboard-heatmap-tooltip-border:rgba(148, 163, 184, 0.25);--chartboard-heatmap-tooltip-shadow:0 12px 24px rgba(15, 23, 42, 0.15);--chartboard-heatmap-tooltip-text:#1f2937;--chartboard-heatmap-tooltip-strong:#111827;--chartboard-heatmap-tooltip-span:#4b5563;--chartboard-legend-color:#4b5563;--chartboard-legend-value-color:#6b7280;--chartboard-heatmap-cell-width:14px;--chartboard-heatmap-cell:12px}html.dark:not([data-color-scheme='light']):not(.light):not([class*='light']),html[class~='dark']:not([data-color-scheme='light']):not(.light):not([class*='light']),html[class*='dark']:not([data-color-scheme='light']):not(.light):not([class*='light']),html[data-color-scheme='dark']:not([data-color-scheme='light']),html[data-theme='dark']:not([data-theme='light']),html[theme='dark']:not([theme='light']),[data-color-scheme='dark']:not([data-color-scheme='light']),[data-theme='dark']:not([data-theme='light']),[theme='dark']:not([theme='light']){--chartboard-surface:rgba(17, 24, 39, 0.92);--chartboard-border:rgba(59, 130, 246, 0.18);--chartboard-shadow:0 20px 40px -28px rgba(30, 64, 175, 0.35);--chartboard-title-color:#e2e8f0;--chartboard-subtitle-color:#94a3b8;--chartboard-card-bg:linear-gradient(180deg, rgba(30, 41, 59, 0.9) 0%, rgba(17, 24, 39, 0.9) 100%);--chartboard-card-border:rgba(59, 130, 246, 0.25);--chartboard-card-shadow:inset 0 1px 0 rgba(59, 130, 246, 0.12);--chartboard-card-hover-shadow:0 12px 24px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(59, 130, 246, 0.12);--chartboard-text-primary:#e2e8f0;--chartboard-text-secondary:#cbd5f5;--chartboard-text-muted:#9ca3af;--chartboard-loading-bg:rgba(30, 41, 59, 0.9);--chartboard-loading-border:rgba(59, 130, 246, 0.25);--chartboard-error-bg:rgba(248, 113, 113, 0.15);--chartboard-error-border:rgba(248, 113, 113, 0.35);--chartboard-error-color:#fecaca;--chartboard-heatmap-cell-bg:rgba(148, 163, 184, 0.25);--chartboard-heatmap-level-1:rgba(191, 219, 254, 0.35);--chartboard-heatmap-level-2:rgba(147, 197, 253, 0.55);--chartboard-heatmap-level-3:rgba(96, 165, 250, 0.75);--chartboard-heatmap-level-4:rgba(37, 99, 235, 0.85);--chartboard-heatmap-hover-shadow:rgba(37, 99, 235, 0.3);--chartboard-heatmap-tooltip-bg:rgba(15, 23, 42, 0.92);--chartboard-heatmap-tooltip-border:rgba(148, 163, 184, 0.25);--chartboard-heatmap-tooltip-shadow:0 16px 28px rgba(15, 23, 42, 0.45);--chartboard-heatmap-tooltip-text:#e2e8f0;--chartboard-heatmap-tooltip-strong:#f8fafc;--chartboard-heatmap-tooltip-span:#cbd5f5;--chartboard-legend-color:#cbd5f5;--chartboard-legend-value-color:#94a3b8}@media (prefers-color-scheme:dark){:root:not([data-color-scheme]),:root[data-color-scheme='auto'],:root[data-color-scheme='system'],html:not([data-color-scheme]):not(.light):not([class*='light']),html[data-color-scheme='auto']:not(.light):not([class*='light']),html[data-color-scheme='system']:not(.light):not([class*='light']){--chartboard-surface:rgba(17, 24, 39, 0.92);--chartboard-border:rgba(59, 130, 246, 0.18);--chartboard-shadow:0 20px 40px -28px rgba(30, 64, 175, 0.35);--chartboard-title-color:#e2e8f0;--chartboard-subtitle-color:#94a3b8;--chartboard-card-bg:linear-gradient(180deg, rgba(30, 41, 59, 0.9) 0%, rgba(17, 24, 39, 0.9) 100%);--chartboard-card-border:rgba(59, 130, 246, 0.25);--chartboard-card-shadow:inset 0 1px 0 rgba(59, 130, 246, 0.12);--chartboard-card-hover-shadow:0 12px 24px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(59, 130, 246, 0.12);--chartboard-text-primary:#e2e8f0;--chartboard-text-secondary:#cbd5f5;--chartboard-text-muted:#9ca3af;--chartboard-loading-bg:rgba(30, 41, 59, 0.9);--chartboard-loading-border:rgba(59, 130, 246, 0.25);--chartboard-error-bg:rgba(248, 113, 113, 0.15);--chartboard-error-border:rgba(248, 113, 113, 0.35);--chartboard-error-color:#fecaca;--chartboard-heatmap-cell-bg:rgba(148, 163, 184, 0.25);--chartboard-heatmap-level-1:rgba(191, 219, 254, 0.35);--chartboard-heatmap-level-2:rgba(147, 197, 253, 0.55);--chartboard-heatmap-level-3:rgba(96, 165, 250, 0.75);--chartboard-heatmap-level-4:rgba(37, 99, 235, 0.85);--chartboard-heatmap-hover-shadow:rgba(37, 99, 235, 0.3);--chartboard-heatmap-tooltip-bg:rgba(15, 23, 42, 0.92);--chartboard-heatmap-tooltip-border:rgba(148, 163, 184, 0.25);--chartboard-heatmap-tooltip-shadow:0 16px 28px rgba(15, 23, 42, 0.45);--chartboard-heatmap-tooltip-text:#e2e8f0;--chartboard-heatmap-tooltip-strong:#f8fafc;--chartboard-heatmap-tooltip-span:#cbd5f5;--chartboard-legend-color:#cbd5f5;--chartboard-legend-value-color:#94a3b8}}.xhhaocom-chartboard{display:flex;flex-direction:column;gap:24px;width:100%;padding:12px;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;list-style:none!important;margin:0;margin-left:0!important;padding-left:0!important}.xhhaocom-chartboard *{list-style-type:none!important}.xhhaocom-chartboard-loading,.xhhaocom-chartboard-error,.xhhaocom-chartboard-empty{display:flex;align-items:center;justify-content:center;padding:32px;border-radius:16px;border:1px solid var(--chartboard-loading-border);background:var(--chartboard-loading-bg);font-size:14px;color:var(--chartboard-text-secondary);text-align:center}.xhhaocom-chartboard-error{color:var(--chartboard-error-color);background:var(--chartboard-error-bg);border-color:var(--chartboard-error-border)}.xhhaocom-chartboard-section{border-radius:18px;border:1px solid var(--chartboard-border);background:var(--chartboard-surface);box-shadow:var(--chartboard-shadow);overflow:hidden}.xhhaocom-chartboard-section:has(.xhhaocom-chartboard-card--heatmap){overflow:hidden}.xhhaocom-chartboard-section:has(.xhhaocom-chartboard-card--heatmap) .xhhaocom-chartboard-section__body{overflow-x:auto;overflow-y:visible;-webkit-overflow-scrolling:touch;scrollbar-width:thin;scrollbar-color:rgb(0 0 0 / .2) rgb(0 0 0 / .05)}@media (max-width:768px){.xhhaocom-chartboard-section:has(.xhhaocom-chartboard-card--heatmap){overflow:visible}.xhhaocom-chartboard-section:has(.xhhaocom-chartboard-card--heatmap) .xhhaocom-chartboard-section__body{overflow-x:auto;overflow-y:visible;-webkit-overflow-scrolling:touch;scrollbar-width:thin;padding-bottom:8px}.xhhaocom-chartboard-card--heatmap{min-width:600px}}.xhhaocom-chartboard-section__header{padding:18px 22px 14px;border-bottom:1px solid var(--chartboard-border);display:flex;flex-direction:column;gap:8px}.xhhaocom-chartboard-section__title{font-size:16px;font-weight:600;color:var(--chartboard-title-color)}.xhhaocom-chartboard-section__subtitle{font-size:13px;color:var(--chartboard-subtitle-color)}.xhhaocom-chartboard-section__body{padding:20px 22px 24px;display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:20px}.xhhaocom-chartboard-card{display:flex;flex-direction:column;gap:12px;border-radius:16px;border:1px solid var(--chartboard-card-border);background:var(--chartboard-card-bg);padding:16px 18px;min-height:260px;box-shadow:var(--chartboard-card-shadow);transition:transform 0.3s ease,box-shadow 0.3s ease}.xhhaocom-chartboard-card--animated{transition:transform 0.3s cubic-bezier(.4,0,.2,1),box-shadow 0.3s cubic-bezier(.4,0,.2,1)}.xhhaocom-chartboard-card--animated:hover{transform:translateY(-4px);box-shadow:var(--chartboard-card-hover-shadow)}.xhhaocom-chartboard-card__canvas{flex:1 1 auto;position:relative;min-height:220px}.xhhaocom-chartboard-card__canvas canvas{width:100%!important;height:auto!important;max-height:220px;display:block}.xhhaocom-chartboard-card__footer{font-size:12px;color:var(--chartboard-text-muted)}.xhhaocom-chartboard-card--heatmap{display:flex;flex-direction:column;gap:16px;padding:18px 20px 16px;min-height:auto;position:relative;overflow:visible;width:100%;min-width:max-content}@media (max-width:768px){.xhhaocom-chartboard-card--heatmap{overflow:visible;min-width:max-content;width:max-content}}.xhhaocom-chartboard-heatmap{display:grid;grid-template-columns:auto 1fr;grid-template-rows:auto 1fr auto;gap:8px 10px}.xhhaocom-chartboard-heatmap__weekdays{grid-column:1;grid-row:2;display:grid;grid-template-rows:repeat(7,var(--chartboard-heatmap-cell));row-gap:4px;padding-right:8px;font-size:11px;color:var(--chartboard-text-muted);align-items:center;justify-items:flex-end}.xhhaocom-chartboard-heatmap__weekday{height:var(--chartboard-heatmap-cell);line-height:var(--chartboard-heatmap-cell);padding:0 4px}.xhhaocom-chartboard-heatmap__weekday.is-placeholder{visibility:hidden}.xhhaocom-chartboard-heatmap__months{grid-column:2;grid-row:1;display:grid;column-gap:4px;font-size:12px;color:var(--chartboard-text-muted)}.xhhaocom-chartboard-heatmap__month{text-align:left}.xhhaocom-chartboard-heatmap__month.is-placeholder{visibility:hidden}.xhhaocom-chartboard-heatmap__grid{grid-column:2;grid-row:2;display:grid;column-gap:4px}.xhhaocom-chartboard-heatmap__column{display:grid;grid-template-rows:repeat(7,var(--chartboard-heatmap-cell));row-gap:4px}.xhhaocom-chartboard-heatmap__day{width:var(--chartboard-heatmap-cell);height:var(--chartboard-heatmap-cell);border-radius:3px;background:var(--chartboard-heatmap-cell-bg);transition:transform 0.15s ease,box-shadow 0.15s ease;cursor:pointer}.xhhaocom-chartboard-heatmap__day[data-level="0"]{background:var(--chartboard-heatmap-cell-bg)}.xhhaocom-chartboard-heatmap__day[data-level="1"]{background:var(--chartboard-heatmap-level-1)}.xhhaocom-chartboard-heatmap__day[data-level="2"]{background:var(--chartboard-heatmap-level-2)}.xhhaocom-chartboard-heatmap__day[data-level="3"]{background:var(--chartboard-heatmap-level-3)}.xhhaocom-chartboard-heatmap__day[data-level="4"]{background:var(--chartboard-heatmap-level-4)}.xhhaocom-chartboard-heatmap__day:not(.is-outside):hover{transform:scale(1.2);box-shadow:0 4px 10px var(--chartboard-heatmap-hover-shadow)}.xhhaocom-chartboard-heatmap__day.is-outside{background:#fff0;opacity:.2;cursor:default}.xhhaocom-chartboard-heatmap__footer{grid-column:2;grid-row:3;display:flex;justify-content:space-between;align-items:center;margin-top:4px}.xhhaocom-chartboard-heatmap__date-range{font-size:12px;color:var(--chartboard-text-muted)}.xhhaocom-chartboard-heatmap__legend{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--chartboard-text-muted);margin-left:auto}.xhhaocom-chartboard-heatmap__legend-dot{width:var(--chartboard-heatmap-cell);height:var(--chartboard-heatmap-cell);border-radius:3px;background:var(--chartboard-heatmap-cell-bg)}.xhhaocom-chartboard-heatmap__legend-dot[data-level="0"]{background:var(--chartboard-heatmap-cell-bg);border:1px solid rgb(148 163 184 / .35)}.xhhaocom-chartboard-heatmap__legend-dot[data-level="1"]{background:var(--chartboard-heatmap-level-1)}.xhhaocom-chartboard-heatmap__legend-dot[data-level="2"]{background:var(--chartboard-heatmap-level-2)}.xhhaocom-chartboard-heatmap__legend-dot[data-level="3"]{background:var(--chartboard-heatmap-level-3)}.xhhaocom-chartboard-heatmap__legend-dot[data-level="4"]{background:var(--chartboard-heatmap-level-4)}.xhhaocom-chartboard-heatmap__tooltip{position:absolute;top:0;left:0;transform:translate(-9999px,-9999px);display:flex;flex-direction:column;gap:2px;background:var(--chartboard-heatmap-tooltip-bg);border-radius:10px;border:1px solid var(--chartboard-heatmap-tooltip-border);box-shadow:var(--chartboard-heatmap-tooltip-shadow);padding:8px 12px;font-size:12px;color:var(--chartboard-heatmap-tooltip-text);pointer-events:none;z-index:5}.xhhaocom-chartboard-heatmap__tooltip strong{font-weight:600;color:var(--chartboard-heatmap-tooltip-strong)}.xhhaocom-chartboard-heatmap__tooltip span{color:var(--chartboard-heatmap-tooltip-span)}@media (max-width:768px){.xhhaocom-chartboard{padding:8px;gap:16px}.xhhaocom-chartboard-section__body{grid-template-columns:1fr}.xhhaocom-chartboard-card{min-height:240px}.xhhaocom-chartboard-card__canvas{min-height:200px}.xhhaocom-chartboard-heatmap__weekdays{font-size:10px;padding-right:4px}.xhhaocom-chartboard-heatmap__months{font-size:11px}.xhhaocom-chartboard-section__body::-webkit-scrollbar{height:8px}.xhhaocom-chartboard-section__body::-webkit-scrollbar-track{background:rgb(0 0 0 / .05);border-radius:4px;margin:0 16px}.xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb{background:rgb(0 0 0 / .25);border-radius:4px}.xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover{background:rgb(0 0 0 / .35)}html.dark:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-track,html[class~='dark']:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-track,html[class*='dark']:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-track,html[data-color-scheme='dark']:not([data-color-scheme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-track,html[data-theme='dark']:not([data-theme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-track,html[theme='dark']:not([theme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-track{background:rgb(255 255 255 / .05)}html.dark:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb,html[class~='dark']:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb,html[class*='dark']:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb,html[data-color-scheme='dark']:not([data-color-scheme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb,html[data-theme='dark']:not([data-theme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb,html[theme='dark']:not([theme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb{background:rgb(255 255 255 / .2)}html.dark:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover,html[class~='dark']:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover,html[class*='dark']:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover,html[data-color-scheme='dark']:not([data-color-scheme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover,html[data-theme='dark']:not([data-theme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover,html[theme='dark']:not([theme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover{background:rgb(255 255 255 / .3)}}.xhhaocom-chartboard-legend{list-style:none;margin:0;padding:10px 0 0;display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:6px 16px;font-size:12px;color:var(--chartboard-legend-color)}.xhhaocom-chartboard-legend__dot{width:10px;height:10px;border-radius:50%;display:inline-block;margin-right:6px;vertical-align:middle}.xhhaocom-chartboard-legend__label{font-weight:500;margin-right:4px}.xhhaocom-chartboard-legend__value{color:var(--chartboard-legend-value-color)} +:root{--chartboard-surface:#ffffff;--chartboard-border:rgba(226,232,240,0.8);--chartboard-shadow:0 10px 30px -20px rgba(15,23,42,0.15);--chartboard-title-color:#1f2937;--chartboard-subtitle-color:#6b7280;--chartboard-card-bg:linear-gradient(180deg,rgba(248,250,252,0.8) 0%,rgba(255,255,255,0.95) 100%);--chartboard-card-border:rgba(226,232,240,0.8);--chartboard-card-shadow:inset 0 1px 0 rgba(255,255,255,0.6);--chartboard-card-hover-shadow:0 12px 24px rgba(0,0,0,0.1),inset 0 1px 0 rgba(255,255,255,0.6);--chartboard-text-primary:#1f2937;--chartboard-text-secondary:#4b5563;--chartboard-text-muted:#6b7280;--chartboard-loading-bg:rgba(241,245,249,0.6);--chartboard-loading-border:rgba(148,163,184,0.25);--chartboard-error-bg:rgba(254,226,226,0.6);--chartboard-error-border:rgba(248,113,113,0.45);--chartboard-error-color:#dc2626;--chartboard-heatmap-cell-bg:#e5e7eb;--chartboard-heatmap-level-1:#bfdbfe;--chartboard-heatmap-level-2:#93c5fd;--chartboard-heatmap-level-3:#60a5fa;--chartboard-heatmap-level-4:#2563eb;--chartboard-heatmap-hover-shadow:rgba(37,99,235,0.2);--chartboard-heatmap-tooltip-bg:#ffffff;--chartboard-heatmap-tooltip-border:rgba(148,163,184,0.25);--chartboard-heatmap-tooltip-shadow:0 12px 24px rgba(15,23,42,0.15);--chartboard-heatmap-tooltip-text:#1f2937;--chartboard-heatmap-tooltip-strong:#111827;--chartboard-heatmap-tooltip-span:#4b5563;--chartboard-legend-color:#4b5563;--chartboard-legend-value-color:#6b7280;--chartboard-heatmap-cell-width:14px;--chartboard-heatmap-cell:12px}html.dark:not([data-color-scheme='light']):not(.light):not([class*='light']),html[class~='dark']:not([data-color-scheme='light']):not(.light):not([class*='light']),html[class*='dark']:not([data-color-scheme='light']):not(.light):not([class*='light']),html[data-color-scheme='dark']:not([data-color-scheme='light']),html[data-theme='dark']:not([data-theme='light']),html[theme='dark']:not([theme='light']),[data-color-scheme='dark']:not([data-color-scheme='light']),[data-theme='dark']:not([data-theme='light']),[theme='dark']:not([theme='light']){--chartboard-surface:rgba(17,24,39,0.92);--chartboard-border:rgba(59,130,246,0.18);--chartboard-shadow:0 20px 40px -28px rgba(30,64,175,0.35);--chartboard-title-color:#e2e8f0;--chartboard-subtitle-color:#94a3b8;--chartboard-card-bg:linear-gradient(180deg,rgba(30,41,59,0.9) 0%,rgba(17,24,39,0.9) 100%);--chartboard-card-border:rgba(59,130,246,0.25);--chartboard-card-shadow:inset 0 1px 0 rgba(59,130,246,0.12);--chartboard-card-hover-shadow:0 12px 24px rgba(0,0,0,0.3),inset 0 1px 0 rgba(59,130,246,0.12);--chartboard-text-primary:#e2e8f0;--chartboard-text-secondary:#cbd5f5;--chartboard-text-muted:#9ca3af;--chartboard-loading-bg:rgba(30,41,59,0.9);--chartboard-loading-border:rgba(59,130,246,0.25);--chartboard-error-bg:rgba(248,113,113,0.15);--chartboard-error-border:rgba(248,113,113,0.35);--chartboard-error-color:#fecaca;--chartboard-heatmap-cell-bg:rgba(148,163,184,0.25);--chartboard-heatmap-level-1:rgba(191,219,254,0.35);--chartboard-heatmap-level-2:rgba(147,197,253,0.55);--chartboard-heatmap-level-3:rgba(96,165,250,0.75);--chartboard-heatmap-level-4:rgba(37,99,235,0.85);--chartboard-heatmap-hover-shadow:rgba(37,99,235,0.3);--chartboard-heatmap-tooltip-bg:rgba(15,23,42,0.92);--chartboard-heatmap-tooltip-border:rgba(148,163,184,0.25);--chartboard-heatmap-tooltip-shadow:0 16px 28px rgba(15,23,42,0.45);--chartboard-heatmap-tooltip-text:#e2e8f0;--chartboard-heatmap-tooltip-strong:#f8fafc;--chartboard-heatmap-tooltip-span:#cbd5f5;--chartboard-legend-color:#cbd5f5;--chartboard-legend-value-color:#94a3b8}@media (prefers-color-scheme:dark){:root:not([data-color-scheme]),:root[data-color-scheme='auto'],:root[data-color-scheme='system'],html:not([data-color-scheme]):not(.light):not([class*='light']),html[data-color-scheme='auto']:not(.light):not([class*='light']),html[data-color-scheme='system']:not(.light):not([class*='light']){--chartboard-surface:rgba(17,24,39,0.92);--chartboard-border:rgba(59,130,246,0.18);--chartboard-shadow:0 20px 40px -28px rgba(30,64,175,0.35);--chartboard-title-color:#e2e8f0;--chartboard-subtitle-color:#94a3b8;--chartboard-card-bg:linear-gradient(180deg,rgba(30,41,59,0.9) 0%,rgba(17,24,39,0.9) 100%);--chartboard-card-border:rgba(59,130,246,0.25);--chartboard-card-shadow:inset 0 1px 0 rgba(59,130,246,0.12);--chartboard-card-hover-shadow:0 12px 24px rgba(0,0,0,0.3),inset 0 1px 0 rgba(59,130,246,0.12);--chartboard-text-primary:#e2e8f0;--chartboard-text-secondary:#cbd5f5;--chartboard-text-muted:#9ca3af;--chartboard-loading-bg:rgba(30,41,59,0.9);--chartboard-loading-border:rgba(59,130,246,0.25);--chartboard-error-bg:rgba(248,113,113,0.15);--chartboard-error-border:rgba(248,113,113,0.35);--chartboard-error-color:#fecaca;--chartboard-heatmap-cell-bg:rgba(148,163,184,0.25);--chartboard-heatmap-level-1:rgba(191,219,254,0.35);--chartboard-heatmap-level-2:rgba(147,197,253,0.55);--chartboard-heatmap-level-3:rgba(96,165,250,0.75);--chartboard-heatmap-level-4:rgba(37,99,235,0.85);--chartboard-heatmap-hover-shadow:rgba(37,99,235,0.3);--chartboard-heatmap-tooltip-bg:rgba(15,23,42,0.92);--chartboard-heatmap-tooltip-border:rgba(148,163,184,0.25);--chartboard-heatmap-tooltip-shadow:0 16px 28px rgba(15,23,42,0.45);--chartboard-heatmap-tooltip-text:#e2e8f0;--chartboard-heatmap-tooltip-strong:#f8fafc;--chartboard-heatmap-tooltip-span:#cbd5f5;--chartboard-legend-color:#cbd5f5;--chartboard-legend-value-color:#94a3b8}}.xhhaocom-chartboard{display:flex;flex-direction:column;gap:24px;width:100%;padding:24px 0;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;list-style:none !important;margin:0;margin-left:0 !important;padding-left:0 !important}.xhhaocom-chartboard *{list-style-type:none !important}.xhhaocom-chartboard-loading,.xhhaocom-chartboard-error,.xhhaocom-chartboard-empty{display:flex;align-items:center;justify-content:center;padding:32px;border-radius:16px;border:1px solid var(--chartboard-loading-border);background:var(--chartboard-loading-bg);font-size:14px;color:var(--chartboard-text-secondary);text-align:center}.xhhaocom-chartboard-error{color:var(--chartboard-error-color);background:var(--chartboard-error-bg);border-color:var(--chartboard-error-border)}.xhhaocom-chartboard-section{border-radius:18px;border:1px solid var(--chartboard-border);background:var(--chartboard-surface);box-shadow:var(--chartboard-shadow);overflow:hidden}.xhhaocom-chartboard-section:has(.xhhaocom-chartboard-card--heatmap){overflow:hidden}.xhhaocom-chartboard-section:has(.xhhaocom-chartboard-card--heatmap) .xhhaocom-chartboard-section__body{overflow-x:auto;overflow-y:visible;-webkit-overflow-scrolling:touch;scrollbar-width:thin;scrollbar-color:rgba(0,0,0,0.2) rgba(0,0,0,0.05)}@media (max-width:768px){.xhhaocom-chartboard-section:has(.xhhaocom-chartboard-card--heatmap){overflow:visible}.xhhaocom-chartboard-section:has(.xhhaocom-chartboard-card--heatmap) .xhhaocom-chartboard-section__body{overflow-x:auto;overflow-y:visible;-webkit-overflow-scrolling:touch;scrollbar-width:thin;padding-bottom:8px}.xhhaocom-chartboard-card--heatmap{min-width:600px}}.xhhaocom-chartboard-section__header{padding:18px 22px 14px;border-bottom:1px solid var(--chartboard-border);display:flex;flex-direction:column;gap:8px}.xhhaocom-chartboard-section__title{font-size:16px;font-weight:600;color:var(--chartboard-title-color)}.xhhaocom-chartboard-section__subtitle{font-size:13px;color:var(--chartboard-subtitle-color)}.xhhaocom-chartboard-section__body{padding:20px 22px 24px;display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:20px}.xhhaocom-chartboard-card{display:flex;flex-direction:column;gap:12px;border-radius:16px;border:1px solid var(--chartboard-card-border);background:var(--chartboard-card-bg);padding:16px 18px;min-height:260px;box-shadow:var(--chartboard-card-shadow);transition:transform 0.3s ease,box-shadow 0.3s ease}.xhhaocom-chartboard-card--animated{transition:transform 0.3s cubic-bezier(0.4,0,0.2,1),box-shadow 0.3s cubic-bezier(0.4,0,0.2,1)}.xhhaocom-chartboard-card--animated:hover{transform:translateY(-4px);box-shadow:var(--chartboard-card-hover-shadow)}.xhhaocom-chartboard-card__canvas{flex:1 1 auto;position:relative;min-height:220px}.xhhaocom-chartboard-card__canvas canvas{width:100% !important;height:auto !important;max-height:220px;display:block}.xhhaocom-chartboard-card__footer{font-size:12px;color:var(--chartboard-text-muted)}.xhhaocom-chartboard-card--heatmap{display:flex;flex-direction:column;gap:16px;padding:18px 20px 16px;min-height:auto;position:relative;overflow:visible;width:100%;min-width:max-content}@media (max-width:768px){.xhhaocom-chartboard-card--heatmap{overflow:visible;min-width:max-content;width:max-content}}.xhhaocom-chartboard-heatmap{display:grid;grid-template-columns:auto 1fr;grid-template-rows:auto 1fr auto;gap:8px 10px}.xhhaocom-chartboard-heatmap__weekdays{grid-column:1;grid-row:2;display:grid;grid-template-rows:repeat(7,var(--chartboard-heatmap-cell));row-gap:4px;padding-right:8px;font-size:11px;color:var(--chartboard-text-muted);align-items:center;justify-items:flex-end}.xhhaocom-chartboard-heatmap__weekday{height:var(--chartboard-heatmap-cell);line-height:var(--chartboard-heatmap-cell);padding:0 4px}.xhhaocom-chartboard-heatmap__weekday.is-placeholder{visibility:hidden}.xhhaocom-chartboard-heatmap__months{grid-column:2;grid-row:1;display:grid;column-gap:4px;font-size:12px;color:var(--chartboard-text-muted)}.xhhaocom-chartboard-heatmap__month{text-align:left}.xhhaocom-chartboard-heatmap__month.is-placeholder{visibility:hidden}.xhhaocom-chartboard-heatmap__grid{grid-column:2;grid-row:2;display:grid;column-gap:4px}.xhhaocom-chartboard-heatmap__column{display:grid;grid-template-rows:repeat(7,var(--chartboard-heatmap-cell));row-gap:4px}.xhhaocom-chartboard-heatmap__day{width:var(--chartboard-heatmap-cell);height:var(--chartboard-heatmap-cell);border-radius:3px;background:var(--chartboard-heatmap-cell-bg);transition:transform 0.15s ease,box-shadow 0.15s ease;cursor:pointer}.xhhaocom-chartboard-heatmap__day[data-level="0"]{background:var(--chartboard-heatmap-cell-bg)}.xhhaocom-chartboard-heatmap__day[data-level="1"]{background:var(--chartboard-heatmap-level-1)}.xhhaocom-chartboard-heatmap__day[data-level="2"]{background:var(--chartboard-heatmap-level-2)}.xhhaocom-chartboard-heatmap__day[data-level="3"]{background:var(--chartboard-heatmap-level-3)}.xhhaocom-chartboard-heatmap__day[data-level="4"]{background:var(--chartboard-heatmap-level-4)}.xhhaocom-chartboard-heatmap__day:not(.is-outside):hover{transform:scale(1.2);box-shadow:0 4px 10px var(--chartboard-heatmap-hover-shadow)}.xhhaocom-chartboard-heatmap__day.is-outside{background:transparent;opacity:0.2;cursor:default}.xhhaocom-chartboard-heatmap__footer{grid-column:2;grid-row:3;display:flex;justify-content:space-between;align-items:center;margin-top:4px}.xhhaocom-chartboard-heatmap__date-range{font-size:12px;color:var(--chartboard-text-muted)}.xhhaocom-chartboard-heatmap__legend{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--chartboard-text-muted);margin-left:auto}.xhhaocom-chartboard-heatmap__legend-dot{width:var(--chartboard-heatmap-cell);height:var(--chartboard-heatmap-cell);border-radius:3px;background:var(--chartboard-heatmap-cell-bg)}.xhhaocom-chartboard-heatmap__legend-dot[data-level="0"]{background:var(--chartboard-heatmap-cell-bg);border:1px solid rgba(148,163,184,0.35)}.xhhaocom-chartboard-heatmap__legend-dot[data-level="1"]{background:var(--chartboard-heatmap-level-1)}.xhhaocom-chartboard-heatmap__legend-dot[data-level="2"]{background:var(--chartboard-heatmap-level-2)}.xhhaocom-chartboard-heatmap__legend-dot[data-level="3"]{background:var(--chartboard-heatmap-level-3)}.xhhaocom-chartboard-heatmap__legend-dot[data-level="4"]{background:var(--chartboard-heatmap-level-4)}.xhhaocom-chartboard-heatmap__tooltip{position:absolute;top:0;left:0;transform:translate(-9999px,-9999px);display:flex;flex-direction:column;gap:2px;background:var(--chartboard-heatmap-tooltip-bg);border-radius:10px;border:1px solid var(--chartboard-heatmap-tooltip-border);box-shadow:var(--chartboard-heatmap-tooltip-shadow);padding:8px 12px;font-size:12px;color:var(--chartboard-heatmap-tooltip-text);pointer-events:none;z-index:5}.xhhaocom-chartboard-heatmap__tooltip strong{font-weight:600;color:var(--chartboard-heatmap-tooltip-strong)}.xhhaocom-chartboard-heatmap__tooltip span{color:var(--chartboard-heatmap-tooltip-span)}@media (max-width:768px){.xhhaocom-chartboard{padding:8px;gap:16px}.xhhaocom-chartboard-section__body{grid-template-columns:1fr}.xhhaocom-chartboard-card{min-height:240px}.xhhaocom-chartboard-card__canvas{min-height:200px}.xhhaocom-chartboard-heatmap__weekdays{font-size:10px;padding-right:4px}.xhhaocom-chartboard-heatmap__months{font-size:11px}.xhhaocom-chartboard-section__body::-webkit-scrollbar{height:8px}.xhhaocom-chartboard-section__body::-webkit-scrollbar-track{background:rgba(0,0,0,0.05);border-radius:4px;margin:0 16px}.xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb{background:rgba(0,0,0,0.25);border-radius:4px}.xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,0.35)}html.dark:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-track,html[class~='dark']:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-track,html[class*='dark']:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-track,html[data-color-scheme='dark']:not([data-color-scheme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-track,html[data-theme='dark']:not([data-theme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-track,html[theme='dark']:not([theme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-track{background:rgba(255,255,255,0.05)}html.dark:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb,html[class~='dark']:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb,html[class*='dark']:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb,html[data-color-scheme='dark']:not([data-color-scheme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb,html[data-theme='dark']:not([data-theme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb,html[theme='dark']:not([theme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.2)}html.dark:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover,html[class~='dark']:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover,html[class*='dark']:not([data-color-scheme='light']):not(.light) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover,html[data-color-scheme='dark']:not([data-color-scheme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover,html[data-theme='dark']:not([data-theme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover,html[theme='dark']:not([theme='light']) .xhhaocom-chartboard-section__body::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,0.3)}}.xhhaocom-chartboard-legend{list-style:none;margin:0;padding:10px 0 0;display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:6px 16px;font-size:12px;color:var(--chartboard-legend-color)}.xhhaocom-chartboard-legend__dot{width:10px;height:10px;border-radius:50%;display:inline-block;margin-right:6px;vertical-align:middle}.xhhaocom-chartboard-legend__label{font-weight:500;margin-right:4px}.xhhaocom-chartboard-legend__value{color:var(--chartboard-legend-value-color)} \ No newline at end of file