服务
@@ -107,7 +107,7 @@ export default function Error({
如果问题持续存在,请{' '}
-
+
联系我们的技术团队
diff --git a/src/app/favicon.ico b/src/app/favicon.ico
deleted file mode 100644
index 718d6fe..0000000
Binary files a/src/app/favicon.ico and /dev/null differ
diff --git a/src/app/globals.css b/src/app/globals.css
index 268522b..152106f 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -2,132 +2,169 @@
@tailwind components;
@tailwind utilities;
+html {
+ background-color: #FFFFFF !important;
+}
+
+body {
+ background-color: #FFFFFF !important;
+ color: #0A0E14 !important;
+}
+
:root {
- /* === 水墨雅致 · 色彩体系 === */
- --color-primary: #1A1A1A;
- --color-primary-hover: #0D0D0D;
- --color-primary-light: #3D3D3D;
- --color-primary-lighter: #F5F4F0;
- --color-primary-rgb: 26, 26, 26;
+ /* === 咨询专业 · 色彩体系(浅色模式) === */
+ /* 墨色核心 */
+ --color-ink: #0A0E14;
+ --color-ink-light: #0F1419;
+ --color-ink-lighter: #151B23;
+ --color-ink-rgb: 10, 14, 20;
- /* 朱砂点睛:品牌红仅作点缀 */
- --color-brand-primary: #C41E3A;
- --color-brand-primary-hover: #A01830;
- --color-brand-primary-light: #E04A68;
- --color-brand-primary-bg: #FEF2F4;
+ /* 品牌信号色:朱砂红 */
+ --color-brand: #C41E3A;
+ --color-brand-hover: #A01830;
+ --color-brand-light: #E04A68;
+ --color-brand-soft: rgba(196, 30, 58, 0.12);
+ --color-brand-bg: #FEF2F4;
+ --color-brand-rgb: 196, 30, 58;
- /* 宣纸暖白 + 水墨灰梯度 */
- --color-bg-primary: #FAFAF7;
- --color-bg-secondary: #F5F4F0;
- --color-bg-tertiary: #E8E6E0;
- --color-bg-section: #F2F1EC;
- --color-bg-hover: #EDECE7;
+ /* 辅助色:服务/行业编码 */
+ --color-accent-blue: #3B82F6;
+ --color-accent-blue-soft: rgba(59, 130, 246, 0.12);
+ --color-accent-blue-rgb: 59, 130, 246;
- /* 墨色文字梯度 */
- --color-text-primary: #1A1A1A;
- --color-text-secondary: #3A3835;
- --color-text-tertiary: #4A4742;
- --color-text-muted: #6B6560;
- --color-text-subtle: #9C978F;
- --color-text-placeholder: #7A756D;
- --color-text-hint: #A09A92;
+ --color-accent-teal: #14B8A6;
+ --color-accent-teal-soft: rgba(20, 184, 166, 0.12);
+ --color-accent-teal-rgb: 20, 184, 166;
- /* 水墨边框 */
- --color-border-primary: #E0DDD6;
- --color-border-secondary: #D4D0C8;
- --color-border-accent: #1A1A1A;
- --color-border-light: #EDEBE5;
- --color-border-dark: #3A3835;
+ --color-accent-amber: #F59E0B;
+ --color-accent-amber-soft: rgba(245, 158, 11, 0.12);
+ --color-accent-amber-rgb: 245, 158, 11;
- --color-link: #1A1A1A;
+ --color-accent-purple: #8B5CF6;
+ --color-accent-purple-soft: rgba(139, 92, 246, 0.12);
+ --color-accent-purple-rgb: 139, 92, 246;
+
+ /* 背景色层级 */
+ --color-bg-primary: #FFFFFF;
+ --color-bg-secondary: #F8FAFC;
+ --color-bg-tertiary: #F1F5F9;
+ --color-bg-section: #F8FAFC;
+ --color-bg-hover: #F1F5F9;
+ --color-bg-elevated: #FFFFFF;
+
+ /* 文字色层级 */
+ --color-text-primary: #0A0E14;
+ --color-text-secondary: #334155;
+ --color-text-tertiary: #475569;
+ --color-text-muted: #64748B;
+ --color-text-subtle: #94A3B8;
+ --color-text-placeholder: #94A3B8;
+ --color-text-hint: #CBD5E1;
+ --color-text-inverse: #FFFFFF;
+
+ /* 边框色层级 */
+ --color-border-primary: #E2E8F0;
+ --color-border-secondary: #CBD5E1;
+ --color-border-accent: #0A0E14;
+ --color-border-light: #F1F5F9;
+ --color-border-dark: #334155;
+ --color-border-brand: #C41E3A;
+
+ /* 链接色 */
+ --color-link: #0A0E14;
--color-link-hover: #C41E3A;
+ /* 功能色 */
--color-success: #16A34A;
--color-success-hover: #15803D;
--color-success-bg: #F0FDF4;
+ --color-success-rgb: 22, 163, 74;
+
--color-warning: #D97706;
--color-warning-hover: #B45309;
--color-warning-bg: #FFFBEB;
- --color-info: #5C5C5C;
- --color-info-bg: #F5F5F5;
- --color-error: #C41E3A;
- --color-error-bg: #FEF2F4;
-
- --color-accent-blue: #3B6B9E;
- --color-accent-purple: #6B4C8A;
- --color-accent-cyan: #2D7A8A;
-
- --color-brand-primary-rgb: 196, 30, 58;
--color-warning-rgb: 217, 119, 6;
- --color-success-rgb: 22, 163, 74;
- --color-accent-blue-rgb: 59, 107, 158;
- --color-accent-purple-rgb: 107, 76, 138;
- --color-accent-cyan-rgb: 45, 122, 138;
- /* 深墨色底部区域 */
- --color-footer-bg: #1A1816;
- --color-cta-bg: #1A1816;
- --color-hero-dark-end: #1A1816;
- --color-footer-text: #B5AFA5;
- --color-footer-text-muted: #8A8478;
- --color-footer-text-dim: #9E988C;
- --color-footer-text-link: #D4CFC5;
- --color-footer-border: #3A3630;
+ --color-error: #C41E3A;
+ --color-error-hover: #A01830;
+ --color-error-bg: #FEF2F4;
+ --color-error-rgb: 196, 30, 58;
- --color-challenge-isolation: #FEF2F4;
- --color-challenge-isolation-hover: #FDE8EC;
- --color-challenge-growth: #FFFBEB;
- --color-challenge-growth-hover: #FEF3C7;
- --color-challenge-compliance: #F0FDF4;
- --color-challenge-compliance-hover: #DCFCE7;
+ --color-info: #3B82F6;
+ --color-info-bg: #EFF6FF;
- --color-flip-card-bg: #F5F4F0;
- --color-flip-card-border: #E0DDD6;
- --color-flip-card-divider: #A09A92;
- --color-flip-card-divider-subtle: #D4D0C8;
+ /* 深色调性区域 */
+ --color-dark-bg: #0A0E14;
+ --color-dark-bg-secondary: #0F1419;
+ --color-dark-bg-tertiary: #151B23;
+ --color-dark-text-primary: #F8FAFC;
+ --color-dark-text-secondary: #CBD5E1;
+ --color-dark-text-muted: #94A3B8;
+ --color-dark-border: #1E293B;
- /* 宣纸纹理色彩 */
- --color-ink-wash-light: rgba(26, 26, 26, 0.04);
- --color-ink-wash-medium: rgba(26, 26, 26, 0.08);
- --color-ink-wash-dark: rgba(26, 26, 26, 0.15);
+ /* 阴影系统(按层级 + 用途分类) */
+ /* 基础层级:几乎看不见,用于微妙的边界定义 */
+ --shadow-xs: 0 1px 1px rgba(10, 14, 20, 0.03);
+ /* 轻量:卡片默认状态、输入框 */
+ --shadow-sm: 0 1px 3px rgba(10, 14, 20, 0.04), 0 1px 2px rgba(10, 14, 20, 0.02);
+ /* 中等:悬浮状态、下拉菜单 */
+ --shadow-md: 0 4px 12px rgba(10, 14, 20, 0.06), 0 2px 4px rgba(10, 14, 20, 0.03);
+ /* 大型:模态框、悬浮卡片 */
+ --shadow-lg: 0 12px 28px rgba(10, 14, 20, 0.08), 0 4px 8px rgba(10, 14, 20, 0.04);
+ /* 超大:全屏弹窗、重要浮层 */
+ --shadow-xl: 0 24px 56px rgba(10, 14, 20, 0.1), 0 8px 16px rgba(10, 14, 20, 0.06);
+ /* 品牌阴影:CTA 按钮、品牌强调元素 */
+ --shadow-brand: 0 6px 24px rgba(196, 30, 58, 0.22);
+ --shadow-brand-hover: 0 8px 32px rgba(196, 30, 58, 0.32);
+ /* 内阴影:凹陷效果、输入框聚焦 */
+ --shadow-inset: inset 0 2px 4px rgba(10, 14, 20, 0.04);
+ /* 光晕阴影:发光效果(慎用) */
+ --shadow-glow: 0 0 40px rgba(196, 30, 58, 0.15);
- --color-toast-success-bg: #F0FDF4;
- --color-toast-success-border: #BBF7D0;
- --color-toast-error-bg: #FEF2F4;
- --color-toast-error-border: #FECACA;
- --color-toast-info-bg: #EFF6FF;
- --color-toast-info-border: #BFDBFE;
- --color-toast-close: #A3A3A3;
- --color-toast-close-hover: #595959;
-
- --color-skeleton-bg: #E5E5E5;
-
- /* 水墨雅致阴影 - 更柔和 */
- --shadow-sm: 0 1px 3px rgba(26, 24, 22, 0.04);
- --shadow-md: 0 4px 8px rgba(26, 24, 22, 0.05);
- --shadow-lg: 0 10px 20px rgba(26, 24, 22, 0.06);
- --shadow-xl: 0 20px 40px rgba(26, 24, 22, 0.08);
+ /* 字体系统 */
+ --font-sans: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ --font-display: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
+ --font-brand: "STKaiti", "KaiTi", "楷体", "SimKai", serif;
+ --font-calligraphy-system: "STKaiti", "KaiTi", "楷体", "SimKai", serif;
+ --font-serif: "Songti SC", "STSongti-SC", "Noto Serif CJK SC", "SimSun", "STSong", serif;
+ /* 字号系统(12级排版尺度)—— Mobile First,默认移动端 */
+ --font-size-caption: 0.6875rem;
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
- --font-size-lg: 1.125rem;
- --font-size-xl: 1.25rem;
- --font-size-2xl: 1.5rem;
- --font-size-3xl: 1.875rem;
- --font-size-4xl: 2.25rem;
- --font-size-5xl: 3rem;
- --font-size-6xl: 3.75rem;
+ --font-size-lg: 1.0625rem;
+ --font-size-xl: 1.1875rem;
+ --font-size-2xl: 1.25rem;
+ --font-size-3xl: 1.5rem;
+ --font-size-4xl: 1.875rem;
+ --font-size-5xl: 2.25rem;
+ --font-size-6xl: 3rem;
+ --font-size-7xl: 3.5rem;
+ --font-size-display: 4.5rem;
+ /* 行高系统(与字号对应) */
+ --line-height-caption: 1.4;
--line-height-tight: 1.15;
--line-height-snug: 1.35;
- --line-height-normal: 1.6;
- --line-height-relaxed: 1.8;
+ --line-height-normal: 1.65;
+ --line-height-relaxed: 1.85;
- --letter-spacing-tight: -0.025em;
+ /* 字间距系统(按使用场景分层) */
+ --letter-spacing-tighter: -0.04em;
+ --letter-spacing-tight: -0.02em;
--letter-spacing-normal: 0;
- --letter-spacing-wide: 0.025em;
+ --letter-spacing-wide: 0.03em;
+ --letter-spacing-wider: 0.06em;
+ --letter-spacing-widest: 0.12em;
+ --letter-spacing-eyebrow: 0.18em;
+ /* 段落间距 */
+ --paragraph-spacing: 1.25em;
+ --paragraph-spacing-loose: 1.75em;
+
+ /* 间距系统 */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
@@ -137,112 +174,77 @@
--spacing-3xl: 4rem;
--spacing-4xl: 6rem;
--spacing-5xl: 8rem;
+ --spacing-6xl: 12rem;
- --border-width-thin: 0.5px;
- --border-width-normal: 1px;
+ /* 圆角系统(按用途分类) */
+ --radius-xs: 2px; /* 极小:标签、徽章 */
+ --radius-sm: 4px; /* 小:按钮、输入框 */
+ --radius-md: 6px; /* 中:卡片、弹窗 */
+ --radius-lg: 10px; /* 大:大型卡片、容器 */
+ --radius-xl: 14px; /* 特大:英雄卡片、特色模块 */
+ --radius-2xl: 20px; /* 超大:全屏弹窗、大型容器 */
+ --radius-3xl: 28px; /* 巨大:展示卡片 */
+ --radius-full: 9999px; /* 圆形:头像、圆形按钮 */
- --transition-fast: 200ms;
- --transition-normal: 300ms;
- --transition-slow: 500ms;
+ /* 过渡系统(按时长分级) */
+ --transition-instant: 100ms;
+ --transition-fast: 180ms;
+ --transition-normal: 280ms;
+ --transition-slow: 450ms;
+ --transition-slower: 700ms;
+ --transition-gentle: 1000ms;
- --ease-out: cubic-bezier(0.25, 1, 0.5, 1);
- --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
-}
+ /* 缓动曲线(按性格分类) */
+ /* 标准:通用入场、状态变化(最常用) */
+ --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
+ /* 减速:元素入场(快速开始,轻柔结束) */
+ --ease-decelerate: cubic-bezier(0, 0, 0.2, 1);
+ /* 加速:元素退场(轻柔开始,快速结束) */
+ --ease-accelerate: cubic-bezier(0.4, 0, 1, 1);
+ /* 弹性:有生命感的交互(按钮按压、卡片回弹) */
+ --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
+ --ease-spring-soft: cubic-bezier(0.34, 1.2, 0.64, 1);
+ /* 锐利:精确、专业的感觉(数据变化、指标跳动) */
+ --ease-sharp: cubic-bezier(0.65, 0, 0.35, 1);
+ /* 水墨:柔和、有呼吸感(背景过渡、光晕变化) */
+ --ease-ink: cubic-bezier(0.22, 1, 0.36, 1);
+ /* ease-out 别名:默认使用水墨曲线(Porsche 风格:快速开始,缓慢结束) */
+ --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
-[data-theme="dark"] {
- /* === 水墨雅致 · 暗色模式 === */
- --color-primary: #E0DCD4;
- --color-primary-hover: #EDEAE2;
- --color-primary-light: #A09A92;
- --color-primary-lighter: #2A2722;
- --color-primary-rgb: 224, 220, 212;
+ /* 动效延迟序列(错落动效用) */
+ --stagger-xs: 30ms;
+ --stagger-sm: 60ms;
+ --stagger-md: 100ms;
+ --stagger-lg: 150ms;
+ --stagger-xl: 200ms;
- --color-brand-primary: #D43650;
- --color-brand-primary-hover: #E04A68;
- --color-brand-primary-light: #C41E3A;
- --color-brand-primary-bg: rgba(196, 30, 58, 0.15);
+ /* 容器宽度 */
+ --container-max: 1360px;
+ --container-padding: 1rem;
- --color-bg-primary: #0E0D0B;
- --color-bg-secondary: #151412;
- --color-bg-tertiary: #1E1C18;
- --color-bg-section: #181614;
- --color-bg-hover: #2A2722;
+ /* === 组件令牌层(Component Tokens)=== */
+ /* 按钮 */
+ --btn-height-sm: 2.25rem;
+ --btn-height-md: 2.75rem;
+ --btn-height-lg: 3.25rem;
+ --btn-padding-x: 1.5rem;
+ --btn-font-size: 0.875rem;
- --color-text-primary: #E0DCD4;
- --color-text-secondary: #B5AFA5;
- --color-text-tertiary: #9E988C;
- --color-text-muted: #8A8478;
- --color-text-subtle: #6B6560;
- --color-text-placeholder: #6B6560;
- --color-text-hint: #5A554D;
+ /* 卡片 */
+ --card-padding: 1.5rem;
+ --card-padding-lg: 2.5rem;
+ --card-gap: 1.5rem;
+ --card-border-radius: var(--radius-lg);
- --color-border-primary: #2A2722;
- --color-border-secondary: #3A3630;
- --color-border-accent: #E0DCD4;
- --color-border-light: #1E1C18;
- --color-border-dark: #B5AFA5;
+ /* 输入框 */
+ --input-height: 2.75rem;
+ --input-padding-x: 1rem;
+ --input-border-radius: var(--radius-sm);
- --color-link: #E0DCD4;
- --color-link-hover: #E04A68;
-
- --color-success: #22C55E;
- --color-success-hover: #16A34A;
- --color-success-bg: rgba(22, 163, 74, 0.15);
- --color-warning: #F59E0B;
- --color-warning-hover: #D97706;
- --color-warning-bg: rgba(217, 119, 6, 0.15);
- --color-info: #8C8C8C;
- --color-info-bg: #1A1A1A;
- --color-error: #D43650;
- --color-error-bg: rgba(196, 30, 58, 0.15);
-
- --color-accent-blue: #5A8BB8;
- --color-accent-purple: #8B6BAE;
- --color-accent-cyan: #4A9AAA;
-
- --color-brand-primary-rgb: 212, 54, 80;
- --color-warning-rgb: 245, 158, 11;
- --color-success-rgb: 34, 197, 94;
- --color-accent-blue-rgb: 90, 139, 184;
- --color-accent-purple-rgb: 139, 107, 174;
- --color-accent-cyan-rgb: 74, 154, 170;
-
- --color-footer-bg: #0A0908;
- --color-cta-bg: #141210;
- --color-hero-dark-end: #141210;
- --color-footer-text: #9E988C;
- --color-footer-text-muted: #6B6560;
- --color-footer-text-dim: #8A8478;
- --color-footer-text-link: #B5AFA5;
- --color-footer-border: #2A2722;
-
- --color-challenge-isolation: rgba(196, 30, 58, 0.12);
- --color-challenge-isolation-hover: rgba(196, 30, 58, 0.2);
- --color-challenge-growth: rgba(217, 119, 6, 0.12);
- --color-challenge-growth-hover: rgba(217, 119, 6, 0.2);
- --color-challenge-compliance: rgba(22, 163, 74, 0.12);
- --color-challenge-compliance-hover: rgba(22, 163, 74, 0.2);
-
- --color-flip-card-bg: #1E1C18;
- --color-flip-card-border: #2A2722;
- --color-flip-card-divider: #5A554D;
- --color-flip-card-divider-subtle: #3A3630;
-
- --color-toast-success-bg: rgba(22, 163, 74, 0.15);
- --color-toast-success-border: rgba(34, 197, 94, 0.3);
- --color-toast-error-bg: rgba(196, 30, 58, 0.15);
- --color-toast-error-border: rgba(224, 74, 104, 0.3);
- --color-toast-info-bg: rgba(59, 130, 246, 0.15);
- --color-toast-info-border: rgba(59, 130, 246, 0.3);
- --color-toast-close: #666666;
- --color-toast-close-hover: #A3A3A3;
-
- --color-skeleton-bg: #2A2722;
-
- --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.25);
- --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.25);
- --shadow-lg: 0 10px 20px rgba(0, 0, 0, 0.25);
- --shadow-xl: 0 20px 40px rgba(0, 0, 0, 0.3);
+ /* Section 间距 —— Mobile First,默认移动端 */
+ --section-gap: var(--spacing-3xl);
+ --section-gap-sm: var(--spacing-2xl);
+ --section-padding-y: var(--spacing-3xl);
}
@layer base {
@@ -281,6 +283,8 @@
position: relative;
overflow-x: hidden;
width: 100%;
+ transition: background-color var(--transition-normal) var(--ease-out),
+ color var(--transition-normal) var(--ease-out);
}
body::before {
@@ -290,28 +294,179 @@
left: 0;
right: 0;
bottom: 0;
- background:
- radial-gradient(ellipse at 20% 15%, rgba(var(--color-primary-rgb), 0.015) 0%, transparent 50%),
- radial-gradient(ellipse at 80% 85%, rgba(var(--color-brand-primary-rgb), 0.015) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
h1, h2, h3, h4, h5, h6 {
- font-weight: 600;
+ font-weight: 700;
line-height: var(--line-height-tight);
letter-spacing: var(--letter-spacing-tight);
color: var(--color-text-primary);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
- h1 { font-size: var(--font-size-5xl); }
- h2 { font-size: var(--font-size-4xl); }
- h3 { font-size: var(--font-size-2xl); }
- h4 { font-size: var(--font-size-xl); }
+ h1 {
+ font-size: var(--font-size-5xl);
+ letter-spacing: var(--letter-spacing-tighter);
+ font-weight: 800;
+ }
+ h2 {
+ font-size: var(--font-size-4xl);
+ letter-spacing: var(--letter-spacing-tight);
+ font-weight: 700;
+ }
+ h3 {
+ font-size: var(--font-size-2xl);
+ letter-spacing: var(--letter-spacing-tight);
+ font-weight: 700;
+ }
+ h4 {
+ font-size: var(--font-size-xl);
+ letter-spacing: var(--letter-spacing-normal);
+ font-weight: 600;
+ }
+ h5 {
+ font-size: var(--font-size-lg);
+ letter-spacing: var(--letter-spacing-normal);
+ font-weight: 600;
+ }
+ h6 {
+ font-size: var(--font-size-base);
+ letter-spacing: var(--letter-spacing-wide);
+ font-weight: 600;
+ text-transform: uppercase;
+ }
p {
color: var(--color-text-secondary);
line-height: var(--line-height-relaxed);
+ margin-bottom: var(--paragraph-spacing);
+ }
+
+ p:last-child {
+ margin-bottom: 0;
+ }
+
+ /* 列表样式优化 */
+ ul, ol {
+ color: var(--color-text-secondary);
+ line-height: var(--line-height-relaxed);
+ margin-bottom: var(--paragraph-spacing);
+ padding-left: 1.5em;
+ }
+
+ li {
+ margin-bottom: 0.5em;
+ }
+
+ li:last-child {
+ margin-bottom: 0;
+ }
+
+ ul {
+ list-style-type: none;
+ }
+
+ ul li::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0.75em;
+ width: 6px;
+ height: 6px;
+ border-radius: 1px;
+ background: var(--color-brand);
+ transform: rotate(45deg);
+ }
+
+ ul li {
+ position: relative;
+ padding-left: 0.5em;
+ }
+
+ /* 引用样式 */
+ blockquote {
+ border-left: 3px solid var(--color-brand);
+ padding-left: 1.5em;
+ margin: 2em 0;
+ font-size: var(--font-size-lg);
+ color: var(--color-text-primary);
+ font-style: normal;
+ line-height: var(--line-height-relaxed);
+ background: var(--color-bg-secondary);
+ padding: 1.5em 2em;
+ border-radius: 0 var(--radius-md) var(--radius-md) 0;
+ }
+
+ /* 强调文本 */
+ strong {
+ font-weight: 600;
+ color: var(--color-text-primary);
+ }
+
+ /* 代码样式 */
+ code {
+ font-family: var(--font-mono);
+ font-size: 0.9em;
+ background: var(--color-bg-tertiary);
+ padding: 0.15em 0.4em;
+ border-radius: var(--radius-sm);
+ color: var(--color-brand);
+ }
+
+ pre {
+ font-family: var(--font-mono);
+ font-size: var(--font-size-sm);
+ line-height: var(--line-height-snug);
+ background: var(--color-ink);
+ color: var(--color-dark-text-primary);
+ padding: 1.5em;
+ border-radius: var(--radius-lg);
+ overflow-x: auto;
+ margin: 2em 0;
+ }
+
+ pre code {
+ background: transparent;
+ padding: 0;
+ color: inherit;
+ font-size: inherit;
+ }
+
+ /* 水平分隔线 */
+ hr {
+ border: none;
+ height: 1px;
+ background: var(--color-border-primary);
+ margin: 3em 0;
+ }
+
+ /* 数字和符号的字体优化 */
+ .tabular-nums {
+ font-variant-numeric: tabular-nums;
+ }
+
+ .oldstyle-nums {
+ font-variant-numeric: oldstyle-nums;
+ }
+
+ /* 小字说明文字 */
+ .caption {
+ font-size: var(--font-size-caption);
+ line-height: var(--line-height-caption);
+ color: var(--color-text-muted);
+ letter-spacing: var(--letter-spacing-wide);
+ }
+
+ /* 眉毛标签(Eyebrow Label) */
+ .eyebrow {
+ font-size: var(--font-size-xs);
+ font-weight: 600;
+ letter-spacing: var(--letter-spacing-eyebrow);
+ text-transform: uppercase;
+ color: var(--color-brand);
}
a {
@@ -324,6 +479,24 @@
color: var(--color-link-hover);
}
+ a:focus-visible,
+ button:focus-visible,
+ input:focus-visible,
+ textarea:focus-visible,
+ select:focus-visible,
+ [tabindex]:focus-visible {
+ outline: 2px solid var(--color-brand);
+ outline-offset: 2px;
+ border-radius: 4px;
+ }
+
+ button:focus:not(:focus-visible),
+ a:focus:not(:focus-visible),
+ input:focus:not(:focus-visible),
+ textarea:focus:not(:focus-visible) {
+ outline: none;
+ }
+
button {
font-family: inherit;
cursor: pointer;
@@ -338,16 +511,27 @@
outline: none;
}
- .font-calligraphy {
- font-family: var(--font-ma-shan-zheng), 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif !important;
- font-weight: normal;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- text-rendering: optimizeLegibility;
+ @media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
+ }
+
+ @media (prefers-contrast: more) {
+ :root {
+ --color-text-primary: #000000;
+ --color-text-secondary: #1a1a1a;
+ --color-border-primary: #000000;
+ }
}
.font-brand {
- font-family: var(--font-aoyagi-reisho), 'Aoyagi Reisho', 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif !important;
+ font-family: var(--font-brand), "STKaiti", "KaiTi", serif !important;
font-weight: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -355,88 +539,56 @@
}
::selection {
- background-color: var(--color-text-primary);
- color: var(--color-bg-primary);
+ background-color: var(--color-brand);
+ color: white;
}
}
@layer utilities {
- .container-wide {
+ .container-x {
width: 100%;
- max-width: 1280px;
+ max-width: var(--container-max);
margin-left: auto;
margin-right: auto;
- padding-left: var(--spacing-lg);
- padding-right: var(--spacing-lg);
+ padding-left: var(--container-padding);
+ padding-right: var(--container-padding);
}
@media (min-width: 768px) {
- .container-wide {
- padding-left: var(--spacing-2xl);
- padding-right: var(--spacing-2xl);
+ .container-x {
+ --container-padding: 2.5rem;
}
}
@media (min-width: 1024px) {
- .container-wide {
- padding-left: 4rem;
- padding-right: 4rem;
- }
- }
-
- .container-full {
- width: 100%;
- padding-left: var(--spacing-lg);
- padding-right: var(--spacing-lg);
- }
-
- @media (min-width: 768px) {
- .container-full {
- padding-left: var(--spacing-2xl);
- padding-right: var(--spacing-2xl);
- }
- }
-
- @media (min-width: 1024px) {
- .container-full {
- padding-left: 4rem;
- padding-right: 4rem;
+ .container-x {
+ --container-padding: 3rem;
}
}
.section-padding {
- padding-top: var(--spacing-5xl);
- padding-bottom: var(--spacing-5xl);
+ padding-top: var(--section-padding-y);
+ padding-bottom: var(--section-padding-y);
}
- @media (min-width: 1024px) {
- .section-padding {
- padding-top: 10rem;
- padding-bottom: 10rem;
- }
+ .text-balance {
+ text-wrap: balance;
}
- /* ===== 背景纹理系统 ===== */
- .bg-texture-grid {
- background-image:
- linear-gradient(rgba(var(--color-border-primary-rgb, 229, 229, 229), 0.3) 1px, transparent 1px),
- linear-gradient(90deg, rgba(var(--color-border-primary-rgb, 229, 229, 229), 0.3) 1px, transparent 1px);
- background-size: 60px 60px;
- }
- .bg-texture-dots {
- background-image: radial-gradient(circle, rgba(var(--color-border-primary-rgb, 229, 229, 229), 0.4) 1px, transparent 1px);
+ .bg-dots {
+ background-image: radial-gradient(circle, var(--color-border-primary) 1px, transparent 1px);
background-size: 24px 24px;
}
- .bg-texture-noise {
+ .bg-grain {
position: relative;
}
- .bg-texture-noise::before {
+ .bg-grain::before {
content: '';
position: absolute;
inset: 0;
- opacity: 0.03;
+ opacity: 0.04;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
background-repeat: repeat;
background-size: 128px 128px;
@@ -444,49 +596,73 @@
z-index: 0;
}
- .bg-gradient-radial-brand {
- background:
- radial-gradient(ellipse at 30% 20%, rgba(var(--color-brand-primary-rgb), 0.04) 0%, transparent 50%),
- radial-gradient(ellipse at 70% 80%, rgba(var(--color-primary-rgb), 0.02) 0%, transparent 50%);
+ .bg-glow-brand {
+ background: radial-gradient(ellipse at 50% 0%, rgba(var(--color-brand-rgb), 0.08) 0%, transparent 60%);
}
- .bg-gradient-subtle-warm {
+ .bg-glow-hero {
background:
- radial-gradient(ellipse at 50% 0%, rgba(var(--color-brand-primary-rgb), 0.03) 0%, transparent 60%);
+ radial-gradient(ellipse at 20% 30%, rgba(var(--color-brand-rgb), 0.1) 0%, transparent 50%),
+ radial-gradient(ellipse at 80% 70%, rgba(var(--color-accent-blue-rgb), 0.06) 0%, transparent 50%);
}
- /* ===== 水墨雅致 · 宣纸纹理 ===== */
- .bg-paper-texture {
+ /* 斜切装饰(Accenture 风格) */
+ .clip-slant-right {
+ clip-path: polygon(0 0, 100% 0, 92% 100%, 0 100%);
+ }
+
+ .clip-slant-left {
+ clip-path: polygon(8% 0, 100% 0, 100% 100%, 0 100%);
+ }
+
+ /* 渐变分隔线 */
+ .divider-gradient {
+ height: 1px;
+ background: linear-gradient(90deg, transparent, var(--color-border-secondary), transparent);
+ }
+
+ /* L 形角框 */
+ .corner-frame {
position: relative;
}
- .bg-paper-texture::before {
+ .corner-frame::before,
+ .corner-frame::after {
content: '';
position: absolute;
- inset: 0;
- opacity: 0.025;
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='paper'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='5' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23paper)'/%3E%3C/svg%3E");
- background-repeat: repeat;
- background-size: 200px 200px;
+ width: 16px;
+ height: 16px;
+ border-color: var(--color-brand);
+ border-style: solid;
+ transition: all var(--transition-normal) var(--ease-out);
+ }
+ .corner-frame::before {
+ top: 0;
+ left: 0;
+ border-width: 2px 0 0 2px;
+ }
+ .corner-frame::after {
+ bottom: 0;
+ right: 0;
+ border-width: 0 2px 2px 0;
+ }
+
+ /* 编号样式(超大背景编号) */
+ .bg-number {
+ font-size: clamp(6rem, 20vw, 16rem);
+ font-weight: 900;
+ line-height: 0.8;
+ color: transparent;
+ -webkit-text-stroke: 1px var(--color-border-primary);
+ user-select: none;
pointer-events: none;
- z-index: 0;
- }
-
- /* 水墨渐淡分隔线 */
- .ink-divider {
- height: 1px;
- background: linear-gradient(90deg, transparent, var(--color-border-primary), transparent);
- }
-
- /* 水墨淡染背景块 */
- .ink-wash-block {
- background: radial-gradient(ellipse at 50% 50%, var(--color-ink-wash-light) 0%, transparent 70%);
}
}
+/* ===== 动画关键帧 ===== */
@keyframes fadeInUp {
from {
opacity: 0;
- transform: translateY(16px);
+ transform: translateY(24px);
}
to {
opacity: 1;
@@ -494,230 +670,75 @@
}
}
-@keyframes stampIn {
- 0% {
+@keyframes fadeInDown {
+ from {
opacity: 0;
- transform: scale(1.5) rotate(-15deg);
+ transform: translateY(-24px);
}
- 50% {
- opacity: 0.8;
- transform: scale(0.95) rotate(0deg);
- }
- 70% {
- transform: scale(1.02) rotate(0deg);
- }
- 100% {
+ to {
opacity: 1;
- transform: scale(1) rotate(0deg);
+ transform: translateY(0);
}
}
-.animate-fade-in-up {
- animation: fadeInUp 0.6s var(--ease-out) forwards;
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
}
-.animate-stamp-in {
- animation: stampIn 0.6s var(--ease-out) forwards;
-}
-
-.stagger-1 { animation-delay: 0.1s; }
-.stagger-2 { animation-delay: 0.2s; }
-
-::-webkit-scrollbar {
- width: 6px;
- height: 6px;
-}
-
-::-webkit-scrollbar-track {
- background: transparent;
-}
-
-::-webkit-scrollbar-thumb {
- background: var(--color-border-accent);
- border-radius: 3px;
-}
-
-::-webkit-scrollbar-thumb:hover {
- background: var(--color-text-tertiary);
-}
-
-@media (prefers-reduced-motion: reduce) {
- *,
- *::before,
- *::after {
- animation-duration: 0.01ms !important;
- animation-iteration-count: 1 !important;
- transition-duration: 0.01ms !important;
+@keyframes slideInLeft {
+ from {
+ opacity: 0;
+ transform: translateX(-32px);
}
-
- html {
- scroll-behavior: auto;
+ to {
+ opacity: 1;
+ transform: translateX(0);
}
}
-@media (max-width: 768px) {
- :root {
- --font-size-5xl: 2.25rem;
- --font-size-4xl: 1.875rem;
- --font-size-3xl: 1.5rem;
- --font-size-2xl: 1.25rem;
+@keyframes slideInRight {
+ from {
+ opacity: 0;
+ transform: translateX(32px);
}
-
- .section-padding {
- padding-top: var(--spacing-3xl);
- padding-bottom: var(--spacing-3xl);
+ to {
+ opacity: 1;
+ transform: translateX(0);
}
}
-.safe-area-inset-bottom {
- padding-bottom: env(safe-area-inset-bottom, 0px);
-}
-
-body {
- padding-bottom: 0;
- text-wrap: pretty;
-}
-
-@media (max-width: 767px) {
- body {
- padding-bottom: 64px;
+@keyframes scaleIn {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
}
-
- .container-wide,
- .container-full {
- padding-left: 1rem;
- padding-right: 1rem;
- max-width: 100%;
- overflow-x: hidden;
- }
-
- h1 { font-size: 2rem; }
- h2 { font-size: 1.75rem; }
- h3 { font-size: 1.375rem; }
-
- a:not(nav[aria-label="breadcrumb"] a), button {
- min-height: 44px;
- min-width: 44px;
- }
- nav[aria-label="breadcrumb"] a {
- min-height: 0;
- min-width: 0;
- }
-
- p, li, span {
- overflow-wrap: break-word;
- word-wrap: break-word;
+ to {
+ opacity: 1;
+ transform: scale(1);
}
}
-@property --border-angle {
- syntax: '
';
- initial-value: 0deg;
- inherits: false;
+@keyframes pulse-soft {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.6; }
}
-@keyframes rotateBorder {
- from { --border-angle: 0deg; }
- to { --border-angle: 360deg; }
-}
-
-.ink-glow-border {
- animation: rotateBorder 6s linear infinite paused;
-}
-
-.ink-glow-border::before {
- content: '';
- position: absolute;
- inset: -1px;
- border-radius: inherit;
- padding: 1px;
- background: conic-gradient(from var(--border-angle),
- var(--glow-start, var(--color-text-muted)),
- var(--glow-end, var(--color-border-primary)),
- var(--glow-start, var(--color-text-muted)));
- -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
- -webkit-mask-composite: xor;
- mask-composite: exclude;
- opacity: 0;
- transition: opacity 0.6s var(--ease-out);
-}
-
-.ink-glow-border:hover::before {
- opacity: 0.8;
-}
-
-.ink-glow-border::after {
- content: '';
- position: absolute;
- inset: -6px;
- border-radius: inherit;
- background: conic-gradient(from var(--border-angle),
- var(--glow-start, var(--color-text-muted)),
- var(--glow-end, var(--color-border-primary)),
- var(--glow-start, var(--color-text-muted)));
- filter: blur(16px);
- opacity: 0;
- transition: opacity 0.6s var(--ease-out);
- z-index: -1;
-}
-
-.ink-glow-border:hover::after {
- opacity: 0.2;
-}
-
-.ink-glow-border:hover {
- animation-play-state: running;
-}
-
-@media (min-width: 768px) and (max-width: 1023px) {
- .container-wide,
- .container-full {
- padding-left: 2rem;
- padding-right: 2rem;
- }
-
- h1 { font-size: 2.5rem; }
- h2 { font-size: 2rem; }
- h3 { font-size: 1.5rem; }
-
- .section-padding {
- padding-top: 4rem;
- padding-bottom: 4rem;
- }
-}
-
-.page-transition-loader {
- width: 120px;
- height: 3px;
- background: var(--color-bg-section);
- border-radius: 2px;
- overflow: hidden;
-}
-
-.page-transition-loader__bar {
- width: 40%;
- height: 100%;
- background: var(--color-brand-primary);
- border-radius: 2px;
- animation: page-loader-slide 1.2s ease-in-out infinite;
-}
-
-@keyframes page-loader-slide {
+@keyframes shimmer {
0% {
- transform: translateX(-100%);
- }
- 50% {
- transform: translateX(200%);
+ background-position: -200% 0;
}
100% {
- transform: translateX(-100%);
+ background-position: 200% 0;
}
}
-@media (prefers-reduced-motion: reduce) {
- .page-transition-loader__bar {
- animation: none;
- width: 100%;
+@keyframes float {
+ 0%, 100% {
+ transform: translateY(0);
+ }
+ 50% {
+ transform: translateY(-8px);
}
}
@@ -732,59 +753,327 @@ body {
}
}
-.animate-ripple {
- animation: ripple 0.6s ease-out forwards;
-}
-
@keyframes skeletonPulse {
0%, 100% {
- background-color: var(--color-skeleton-bg);
opacity: 1;
}
50% {
- background-color: color-mix(in srgb, var(--color-brand-primary) 8%, var(--color-skeleton-bg));
- opacity: 0.8;
+ opacity: 0.7;
}
}
-.skeleton-brand {
+@keyframes growWidth {
+ from { transform: scaleX(0); }
+ to { transform: scaleX(1); }
+}
+
+@keyframes spinSlow {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+
+.animate-fade-in-up {
+ animation: fadeInUp var(--transition-slower) var(--ease-out) forwards;
+}
+
+.animate-fade-in-down {
+ animation: fadeInDown var(--transition-slower) var(--ease-out) forwards;
+}
+
+.animate-fade-in {
+ animation: fadeIn var(--transition-normal) var(--ease-out) forwards;
+}
+
+.animate-slide-in-left {
+ animation: slideInLeft var(--transition-slow) var(--ease-out) forwards;
+}
+
+.animate-slide-in-right {
+ animation: slideInRight var(--transition-slow) var(--ease-out) forwards;
+}
+
+.animate-scale-in {
+ animation: scaleIn var(--transition-normal) var(--ease-out) forwards;
+}
+
+.animate-pulse-soft {
+ animation: pulse-soft 2s ease-in-out infinite;
+}
+
+.animate-float {
+ animation: float 3s ease-in-out infinite;
+}
+
+.animate-ripple {
+ animation: ripple var(--transition-normal) var(--ease-out) forwards;
+}
+
+.animate-grow-width {
+ animation: growWidth var(--transition-slow) var(--ease-out) forwards;
+ transform-origin: left center;
+}
+
+.animate-spin-slow {
+ animation: spinSlow 8s linear infinite;
+}
+
+.skeleton-pulse {
animation: skeletonPulse 2s ease-in-out infinite;
}
-/* ===== 背景纹理系统 - Dark Mode ===== */
-[data-theme="dark"] .bg-texture-grid {
- background-image:
- linear-gradient(rgba(var(--color-border-primary-rgb, 42, 39, 34), 0.5) 1px, transparent 1px),
- linear-gradient(90deg, rgba(var(--color-border-primary-rgb, 42, 39, 34), 0.5) 1px, transparent 1px);
+.skeleton-shimmer {
+ background: linear-gradient(
+ 90deg,
+ var(--color-bg-tertiary) 25%,
+ var(--color-bg-secondary) 50%,
+ var(--color-bg-tertiary) 75%
+ );
+ background-size: 200% 100%;
+ animation: shimmer 1.5s ease-in-out infinite;
}
-[data-theme="dark"] .bg-texture-dots {
- background-image: radial-gradient(circle, rgba(var(--color-border-primary-rgb, 42, 39, 34), 0.5) 1px, transparent 1px);
+.animation-delay-100 { animation-delay: 100ms; }
+.animation-delay-200 { animation-delay: 200ms; }
+.animation-delay-300 { animation-delay: 300ms; }
+.animation-delay-400 { animation-delay: 400ms; }
+.animation-delay-500 { animation-delay: 500ms; }
+.animation-delay-600 { animation-delay: 600ms; }
+.animation-delay-700 { animation-delay: 700ms; }
+.animation-delay-800 { animation-delay: 800ms; }
+
+/* ===== 滚动条样式 ===== */
+::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
}
-[data-theme="dark"] .bg-paper-texture::before {
- opacity: 0.04;
+::-webkit-scrollbar-track {
+ background: transparent;
}
-[data-theme="dark"] .bg-texture-noise::before {
- opacity: 0.05;
+::-webkit-scrollbar-thumb {
+ background: var(--color-border-secondary);
+ border-radius: 3px;
}
-[data-theme="dark"] .bg-gradient-radial-brand {
- background:
- radial-gradient(ellipse at 30% 20%, rgba(var(--color-brand-primary-rgb), 0.06) 0%, transparent 50%),
- radial-gradient(ellipse at 70% 80%, rgba(var(--color-primary-rgb), 0.03) 0%, transparent 50%);
+::-webkit-scrollbar-thumb:hover {
+ background: var(--color-text-muted);
}
-[data-theme="dark"] .bg-gradient-subtle-warm {
- background:
- radial-gradient(ellipse at 50% 0%, rgba(var(--color-brand-primary-rgb), 0.05) 0%, transparent 60%);
+
+/* ===== 响应式调整(Mobile First) ===== */
+
+body {
+ padding-bottom: 64px;
}
-[data-theme="dark"] .ink-divider {
- background: linear-gradient(90deg, transparent, var(--color-border-secondary), transparent);
+p, li, span {
+ overflow-wrap: break-word;
+ word-wrap: break-word;
}
-[data-theme="dark"] .ink-wash-block {
- background: radial-gradient(ellipse at 50% 50%, rgba(var(--color-primary-rgb), 0.03) 0%, transparent 70%);
+a:not(nav[aria-label="breadcrumb"] a),
+button {
+ min-height: 44px;
+ min-width: 44px;
+}
+
+nav[aria-label="breadcrumb"] a {
+ min-height: 0;
+ min-width: 0;
+}
+
+@media (min-width: 640px) {
+ :root {
+ --container-padding: 1.5rem;
+ }
+}
+
+@media (min-width: 768px) {
+ :root {
+ --font-size-lg: 1.125rem;
+ --font-size-xl: 1.25rem;
+ --font-size-2xl: 1.5rem;
+ --font-size-3xl: 1.875rem;
+ --font-size-4xl: 2.25rem;
+ --font-size-5xl: 3rem;
+ --font-size-6xl: 3.75rem;
+ --font-size-7xl: 4.5rem;
+ --font-size-display: 6rem;
+ --section-gap: var(--spacing-4xl);
+ --section-gap-sm: var(--spacing-3xl);
+ --section-padding-y: var(--spacing-4xl);
+ }
+
+ body {
+ padding-bottom: 0;
+ }
+}
+
+@media (min-width: 1024px) {
+ :root {
+ --font-size-2xl: 1.5rem;
+ --font-size-3xl: 2rem;
+ --font-size-4xl: 2.5rem;
+ --font-size-5xl: 3.5rem;
+ --font-size-6xl: 4.5rem;
+ --font-size-7xl: 6rem;
+ --font-size-display: 8rem;
+ --container-padding: 3rem;
+ --section-gap: var(--spacing-5xl);
+ --section-padding-y: var(--spacing-5xl);
+ }
+}
+
+/* ===== 安全区域 ===== */
+.safe-area-inset-bottom {
+ padding-bottom: env(safe-area-inset-bottom, 0px);
+}
+
+/* ===== 页面过渡加载条 ===== */
+.page-transition-loader {
+ width: 120px;
+ height: 3px;
+ background: var(--color-bg-tertiary);
+ border-radius: 2px;
+ overflow: hidden;
+}
+
+.page-transition-loader__bar {
+ width: 40%;
+ height: 100%;
+ background: var(--color-brand);
+ border-radius: 2px;
+ animation: page-loader-slide 1.2s ease-in-out infinite;
+}
+
+@keyframes page-loader-slide {
+ 0% { transform: translateX(-100%); }
+ 50% { transform: translateX(200%); }
+ 100% { transform: translateX(-100%); }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .page-transition-loader__bar {
+ animation: none;
+ width: 100%;
+ }
+}
+
+/* ===== 渐变边框动画 ===== */
+@property --border-angle {
+ syntax: '';
+ initial-value: 0deg;
+ inherits: false;
+}
+
+@keyframes rotateBorder {
+ from { --border-angle: 0deg; }
+ to { --border-angle: 360deg; }
+}
+
+.glow-border {
+ position: relative;
+ animation: rotateBorder 6s linear infinite paused;
+}
+
+.glow-border::before {
+ content: '';
+ position: absolute;
+ inset: -1px;
+ border-radius: inherit;
+ padding: 1px;
+ background: conic-gradient(from var(--border-angle),
+ var(--color-text-muted),
+ var(--color-border-primary),
+ var(--color-text-muted));
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
+ -webkit-mask-composite: xor;
+ mask-composite: exclude;
+ opacity: 0;
+ transition: opacity 0.6s var(--ease-out);
+ pointer-events: none;
+}
+
+.glow-border:hover::before {
+ opacity: 0.8;
+}
+
+.glow-border:hover {
+ animation-play-state: running;
+}
+
+/* === CMS RichTextEditor (TipTap) 样式 === */
+/* 仅用于 CMS Studio 暗色主题,选择器限定在 .rich-text-editor-content */
+.rich-text-editor-content {
+ min-height: 120px;
+ padding: 0.75rem;
+ color: white;
+ font-size: 0.875rem;
+ line-height: 1.6;
+ outline: none;
+}
+
+.rich-text-editor-content:empty::before {
+ content: attr(data-placeholder);
+ color: rgba(255, 255, 255, 0.3);
+ pointer-events: none;
+}
+
+.rich-text-editor-content h2 {
+ font-size: 1.125rem;
+ font-weight: 600;
+ margin: 0.75rem 0 0.5rem;
+ color: white;
+}
+
+.rich-text-editor-content h3 {
+ font-size: 1rem;
+ font-weight: 600;
+ margin: 0.5rem 0 0.375rem;
+ color: white;
+}
+
+.rich-text-editor-content p {
+ margin: 0.375rem 0;
+}
+
+.rich-text-editor-content ul,
+.rich-text-editor-content ol {
+ padding-left: 1.25rem;
+ margin: 0.5rem 0;
+}
+
+.rich-text-editor-content ul {
+ list-style: disc;
+}
+
+.rich-text-editor-content ol {
+ list-style: decimal;
+}
+
+.rich-text-editor-content li {
+ margin: 0.25rem 0;
+}
+
+.rich-text-editor-content blockquote {
+ border-left: 3px solid var(--color-brand);
+ padding-left: 0.75rem;
+ margin: 0.5rem 0;
+ color: rgba(255, 255, 255, 0.7);
+ font-style: italic;
+}
+
+.rich-text-editor-content a {
+ color: var(--color-brand);
+ text-decoration: underline;
+}
+
+.rich-text-editor-content strong {
+ font-weight: 600;
+ color: white;
+}
+
+.rich-text-editor-content em {
+ font-style: italic;
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index ba382ae..d024a25 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,9 +1,6 @@
import type { Metadata, Viewport } from "next";
-import localFont from "next/font/local";
-import { Ma_Shan_Zheng, Noto_Sans_SC } from "next/font/google";
import "./globals.css";
import { Suspense } from "react";
-import { ThemeProvider } from "@/contexts/theme-context";
import { GoogleAnalyticsWrapper } from "@/components/analytics/GoogleAnalyticsWrapper";
import { GlobalErrorTracker } from "@/components/analytics/GlobalErrorTracker";
import { CookieConsent } from "@/components/analytics/CookieConsent";
@@ -16,42 +13,8 @@ import { ErrorBoundary } from "@/components/ui/error-boundary";
import { ScrollProgress } from "@/components/ui/scroll-progress";
import { BackToTop } from "@/components/ui/back-to-top";
import { ClientLayout } from "@/components/layout/client-layout";
-
-const geistSans = localFont({
- src: "./fonts/geist-sans.woff2",
- variable: "--font-geist-sans",
- display: "swap",
- preload: false,
-});
-
-const geistMono = localFont({
- src: "./fonts/geist-mono.woff2",
- variable: "--font-geist-mono",
- display: "swap",
- preload: false,
-});
-
-const aoyagiReisho = localFont({
- src: "./fonts/AoyagiReisho-subset.ttf",
- variable: "--font-aoyagi-reisho",
- display: "swap",
- preload: true,
-});
-
-const maShanZheng = Ma_Shan_Zheng({
- weight: "400",
- variable: "--font-ma-shan-zheng",
- display: "swap",
- preload: false,
-});
-
-const notoSansSC = Noto_Sans_SC({
- weight: ["400", "500", "700"],
- variable: "--font-noto-sans-sc",
- display: "swap",
- subsets: ["latin"],
- preload: false,
-});
+import { getPublishedItems } from "@/lib/cms/data-server";
+import { SiteConfigProvider, type SiteConfig, type NavigationItem, type MegaDropdownGroup } from "@/lib/site-config";
export const metadata: Metadata = {
metadataBase: new URL("https://www.novalon.cn"),
@@ -109,25 +72,42 @@ export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
maximumScale: 5,
- themeColor: [
- { media: "(prefers-color-scheme: light)", color: "#FFFFFF" },
- { media: "(prefers-color-scheme: dark)", color: "#0A0A0A" },
- ],
+ themeColor: "#FFFFFF",
};
-export default function RootLayout({
+export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
+ // Fetch site configuration and navigation from CMS
+ const [navItems, configItems] = await Promise.all([
+ getPublishedItems('navigation').catch(() => []),
+ getPublishedItems('site-config').catch(() => []),
+ ]);
+
+ const navData = navItems[0]?.data as Record | undefined;
+ const configData = configItems[0]?.data as Record | undefined;
+
+ const siteConfig: SiteConfig = {
+ name: (configData?.name as string) || '',
+ shortName: (configData?.shortName as string) || '',
+ displayName: (configData?.displayName as string) || '',
+ slogan: (configData?.slogan as string) || '',
+ description: (configData?.description as string) || '',
+ founded: (configData?.founded as string) || '',
+ location: (configData?.location as string) || '',
+ email: (configData?.email as string) || '',
+ address: (configData?.address as string) || '',
+ icp: (configData?.icp as string) || '',
+ police: (configData?.police as string) || '',
+ mainNav: (navData?.mainNav as NavigationItem[]) || [],
+ megaDropdown: (navData?.megaDropdown as Record) || {},
+ };
+
return (
-
@@ -142,21 +122,28 @@ export default function RootLayout({
+
+ 跳转到主要内容
+
-
-
+
+
{children}
-
-
+
+
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
index 1e778cb..987422e 100644
--- a/src/app/not-found.tsx
+++ b/src/app/not-found.tsx
@@ -1,16 +1,21 @@
'use client';
+import { useEffect } from 'react';
import { StaticLink } from '@/components/ui/static-link';
import { Button } from '@/components/ui/button';
import { Home, ArrowLeft, Search } from 'lucide-react';
export default function NotFound() {
+ useEffect(() => {
+ document.title = '页面未找到 - 睿新致远';
+ }, []);
+
return (
-
+
404
@@ -54,8 +59,8 @@ export default function NotFound() {
href="/products"
className="flex flex-col items-center p-4 bg-[var(--color-bg-primary)] rounded-lg hover:shadow-md transition-shadow group"
>
-
-
+
+
产品
@@ -64,8 +69,8 @@ export default function NotFound() {
href="/solutions"
className="flex flex-col items-center p-4 bg-[var(--color-bg-primary)] rounded-lg hover:shadow-md transition-shadow group"
>
-
-
+
+
解决方案
@@ -74,8 +79,8 @@ export default function NotFound() {
href="/about"
className="flex flex-col items-center p-4 bg-[var(--color-bg-primary)] rounded-lg hover:shadow-md transition-shadow group"
>
-
-
+
+
关于我们
@@ -84,8 +89,8 @@ export default function NotFound() {
href="/contact"
className="flex flex-col items-center p-4 bg-[var(--color-bg-primary)] rounded-lg hover:shadow-md transition-shadow group"
>
-
-
+
+
联系我们
diff --git a/src/app/privacy/page.test.tsx b/src/app/privacy/page.test.tsx
index 56232e4..588ba3f 100644
--- a/src/app/privacy/page.test.tsx
+++ b/src/app/privacy/page.test.tsx
@@ -8,38 +8,43 @@ describe('PrivacyPolicyPage', () => {
});
describe('Rendering', () => {
- it('should render privacy policy page', () => {
- render(
);
+ it('should render privacy policy page', async () => {
+ const page = await PrivacyPolicyPage();
+ render(page);
const container = screen.getByText('隐私政策').closest('div');
expect(container).toBeInTheDocument();
});
- it('should render page title', () => {
- render(
);
+ it('should render page title', async () => {
+ const page = await PrivacyPolicyPage();
+ render(page);
const title = screen.getByRole('heading', { level: 1 });
expect(title).toBeInTheDocument();
expect(title).toHaveTextContent('隐私政策');
});
- it('should render introduction section', () => {
- render(
);
+ it('should render introduction section', async () => {
+ const page = await PrivacyPolicyPage();
+ render(page);
const intro = screen.getByText('引言');
expect(intro).toBeInTheDocument();
});
- it('should render information collection section', () => {
- render(
);
+ it('should render information collection section', async () => {
+ const page = await PrivacyPolicyPage();
+ render(page);
const section = screen.getByText(/我们如何收集和使用您的个人信息/i);
expect(section).toBeInTheDocument();
});
});
describe('Accessibility', () => {
- it('should have proper heading hierarchy', () => {
- render(
);
+ it('should have proper heading hierarchy', async () => {
+ const page = await PrivacyPolicyPage();
+ render(page);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
-
+
const h2s = screen.getAllByRole('heading', { level: 2 });
expect(h2s.length).toBeGreaterThan(0);
});
diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx
index b68c87d..1214d1a 100644
--- a/src/app/privacy/page.tsx
+++ b/src/app/privacy/page.tsx
@@ -1,4 +1,5 @@
import { Metadata } from 'next';
+import { getPublishedItems } from '@/lib/cms/data-server';
import { COMPANY_INFO } from '@/lib/constants';
export const metadata: Metadata = {
@@ -6,10 +7,237 @@ export const metadata: Metadata = {
description: `${COMPANY_INFO.name}隐私政策`,
};
-export default function PrivacyPolicyPage() {
+function PrivacyContent() {
+ return (
+
+
+ 引言
+
+ 四川睿新致远科技有限公司(以下简称“我们”、“公司”)深知个人信息对您的重要性,并会尽全力保护您的个人信息安全可靠。我们致力于维持您对我们的信任,恪守以下原则,保护您的个人信息:权责一致原则、目的明确原则、选择同意原则、最少够用原则、确保安全原则、主体参与原则、公开透明原则等。
+
+
+ 本隐私政策适用于您通过四川睿新致远科技有限公司官方网站、移动应用、产品服务等渠道访问和使用我们的产品和服务时,我们收集和使用您的个人信息的情形。
+
+
+
+
+ 一、我们如何收集和使用您的个人信息
+
+ 我们会遵循正当、合法、必要的原则,仅为实现产品功能,向您提供服务之目的,收集和使用您的个人信息。
+
+
+ 1.1 我们收集的个人信息
+
+ - 账户信息:当您注册账户时,我们可能收集您的姓名、电子邮箱地址、手机号码、公司名称等。
+ - 联系信息:当您通过联系表单或客服与我们沟通时,我们可能收集您的姓名、电子邮箱地址、手机号码、留言内容等。
+ - 使用信息:我们可能收集您使用我们产品和服务的信息,包括访问时间、浏览记录、操作日志等。
+ - 设备信息:我们可能收集您使用的设备信息,包括设备型号、操作系统、浏览器类型等。
+
+
+ 1.2 我们如何使用您的个人信息
+
+ - 提供产品和服务:向您提供我们的产品和服务,处理您的请求和订单。
+ - 客户服务:与您联系,提供客户支持,回复您的咨询和反馈。
+ - 改进产品和服务:分析使用情况,改进我们的产品和服务质量。
+ - 安全保障:检测、预防和处理安全事件,保护您和我们的合法权益。
+ - 法律合规:遵守法律法规要求,履行法律义务。
+
+
+
+
+ 二、我们如何共享、转让、公开披露您的个人信息
+
+ 2.1 共享
+
+ 我们不会向其他任何公司、组织和个人分享您的个人信息,但以下情况除外:
+
+
+ - 在获取明确同意的情况下分享:获得您的明确同意后,我们会与其他方共享您的个人信息。
+ - 根据法律法规、法律程序、强制性的行政或司法要求所必须的情况进行提供。
+ - 在涉及合并、收购或破产清算时,如涉及到个人信息转让,我们会要求新的持有您个人信息的公司、组织继续受本隐私政策的约束。
+
+
+ 2.2 转让
+
+ 我们不会将您的个人信息转让给任何公司、组织和个人,但以下情况除外:
+
+
+ - 在获取明确同意的情况下转让。
+ - 根据适用的法律法规、法律程序、强制性的行政或司法要求所必须的情况进行提供。
+ - 在涉及合并、收购或破产清算时,如涉及到个人信息转让,我们会要求新的持有您个人信息的公司、组织继续受本隐私政策的约束。
+
+
+ 2.3 公开披露
+
+ 我们仅会在以下情况下,公开披露您的个人信息:
+
+
+ - 获得您的明确同意。
+ - 基于法律法规或法律程序的要求。
+ - 在涉及合并、收购或破产清算时。
+ - 为保护我们或他人的合法权益免受损害。
+
+
+
+
+ 三、我们如何保护您的个人信息
+
+ 我们已使用符合业界标准的安全防护措施保护您提供的个人信息,防止数据遭到未经授权访问、公开披露、使用、修改、损坏或丢失。我们会采取一切合理可行的措施,保护您的个人信息。
+
+
+ - 使用加密技术确保数据传输和存储安全。
+ - 限制访问权限,仅授权人员可访问个人信息。
+ - 定期进行安全审计和风险评估。
+ - 建立数据备份和恢复机制。
+ - 制定应急响应预案,及时处理安全事件。
+
+
+
+
+ 四、您的权利
+
+ 按照中国相关的法律、法规、标准,以及其他国家、地区的通行做法,我们保障您对自己的个人信息行使以下权利:
+
+
+ - 访问您的个人信息。
+ - 更正您的个人信息。
+ - 删除您的个人信息。
+ - 改变您授权同意的范围。
+ - 注销您的账户。
+ - 获取您的个人信息副本。
+
+
+ 如您需要行使上述权利,请通过本隐私政策提供的联系方式与我们联系。
+
+
+
+
+ 五、未成年人保护
+
+ 我们非常重视对未成年人个人信息的保护。如果您是18周岁以下的未成年人,在使用我们的产品和服务前,应事先取得您家长或法定监护人的同意。如您是未成年人的监护人,当您对您所监护的未成年人的个人信息处理存在疑问时,请通过本隐私政策提供的联系方式与我们联系。
+
+
+
+
+ 六、隐私政策的更新
+
+ 我们可能适时更新本隐私政策的条款,该等更新构成本隐私政策的一部分。如该等更新造成您在本隐私政策下权利的实质减少,我们将在更新生效前通过在主页上显著位置提示或向您发送电子邮件或其他方式通知您,在该种情况下,若您继续使用我们的服务,即表示同意受经修订的本隐私政策的约束。
+
+
+
+
+ 七、Cookie 和网站分析工具
+
+ 7.1 Cookie 使用说明
+
+ 我们使用 Cookie 和类似技术来提供、保护和改进我们的服务。Cookie 是存储在您设备上的小型文本文件,帮助我们识别您的设备、记住您的偏好设置。
+
+
+
+
+
+ | Cookie 类型 |
+ 用途 |
+ 持续时间 |
+ 是否必需 |
+
+
+
+
+ | 必要 Cookie |
+ 网站基本功能运行 |
+ 会话期间 |
+ 是 |
+
+
+ | 分析 Cookie |
+ 了解网站使用情况,改进服务 |
+ 14个月 |
+ 否 |
+
+
+ | 营销 Cookie |
+ 个性化广告(当前未使用) |
+ - |
+ 否 |
+
+
+
+
+
+ 7.2 Google Analytics 使用说明
+
+ 我们使用 Google Analytics 4(由 Google LLC 提供)分析网站使用情况,帮助我们了解访客如何使用网站,从而改进用户体验。
+
+
+ 收集的数据包括:
+
+
+ - 访问的页面和停留时间
+ - 设备类型、浏览器类型
+ - 地理位置(国家/城市级别,IP 地址已匿名化)
+ - 访问来源(直接访问、搜索引擎、外部链接)
+
+
+ 我们已采取的保护措施:
+
+
+ - IP 地址匿名化
+ - 数据保留期限设为 14 个月
+ - 禁用广告个性化功能
+ - 禁用 Google 信号(不进行跨设备追踪)
+ - 不与 Google 其他服务共享数据用于广告目的
+
+
+ 7.3 您的选择
+
+ - 您可以在首次访问时选择接受或拒绝分析 Cookie
+ - 您可以点击页面右下角的“Cookie 设置”按钮随时更改偏好
+ - 您可以通过浏览器设置删除或阻止 Cookie(可能影响网站功能)
+
+
+ 7.4 数据删除请求
+
+ 如您希望删除我们持有的您的个人数据,或撤回您的同意,请通过以下方式联系我们:
+
+
+ - 隐私邮箱:privacy@novalon.cn
+ - 联系地址:中国四川省成都市龙泉驿区幸福路12号
+
+
+
+
+ 八、如何联系我们
+
+ 如果您对本隐私政策有任何疑问、意见或建议,或需要行使您的权利,请通过以下方式与我们联系:
+
+
+ - 公司名称:四川睿新致远科技有限公司
+ - 联系邮箱:contact@novalon.cn
+ - 隐私邮箱:privacy@novalon.cn
+ - 联系地址:中国四川省成都市龙泉驿区幸福路12号
+
+
+
+ );
+}
+
+export default async function PrivacyPolicyPage() {
+ let cmsContent: string | null = null;
+
+ try {
+ const items = await getPublishedItems('legal-page');
+ const privacyItem = items.find((item) => item.data.pageType === 'privacy');
+ if (privacyItem && typeof privacyItem.data.content === 'string' && privacyItem.data.content.length > 0) {
+ cmsContent = privacyItem.data.content as string;
+ }
+ } catch {
+ // CMS not available, fall back to inline content
+ }
+
return (
-
+
隐私政策
@@ -22,216 +250,14 @@ export default function PrivacyPolicyPage() {
-
-
- 引言
-
- 四川睿新致远科技有限公司(以下简称“我们”、“公司”)深知个人信息对您的重要性,并会尽全力保护您的个人信息安全可靠。我们致力于维持您对我们的信任,恪守以下原则,保护您的个人信息:权责一致原则、目的明确原则、选择同意原则、最少够用原则、确保安全原则、主体参与原则、公开透明原则等。
-
-
- 本隐私政策适用于您通过四川睿新致远科技有限公司官方网站、移动应用、产品服务等渠道访问和使用我们的产品和服务时,我们收集和使用您的个人信息的情形。
-
-
-
-
- 一、我们如何收集和使用您的个人信息
-
- 我们会遵循正当、合法、必要的原则,仅为实现产品功能,向您提供服务之目的,收集和使用您的个人信息。
-
-
- 1.1 我们收集的个人信息
-
- - 账户信息:当您注册账户时,我们可能收集您的姓名、电子邮箱地址、手机号码、公司名称等。
- - 联系信息:当您通过联系表单或客服与我们沟通时,我们可能收集您的姓名、电子邮箱地址、手机号码、留言内容等。
- - 使用信息:我们可能收集您使用我们产品和服务的信息,包括访问时间、浏览记录、操作日志等。
- - 设备信息:我们可能收集您使用的设备信息,包括设备型号、操作系统、浏览器类型等。
-
-
- 1.2 我们如何使用您的个人信息
-
- - 提供产品和服务:向您提供我们的产品和服务,处理您的请求和订单。
- - 客户服务:与您联系,提供客户支持,回复您的咨询和反馈。
- - 改进产品和服务:分析使用情况,改进我们的产品和服务质量。
- - 安全保障:检测、预防和处理安全事件,保护您和我们的合法权益。
- - 法律合规:遵守法律法规要求,履行法律义务。
-
-
-
-
- 二、我们如何共享、转让、公开披露您的个人信息
-
- 2.1 共享
-
- 我们不会向其他任何公司、组织和个人分享您的个人信息,但以下情况除外:
-
-
- - 在获取明确同意的情况下分享:获得您的明确同意后,我们会与其他方共享您的个人信息。
- - 根据法律法规、法律程序、强制性的行政或司法要求所必须的情况进行提供。
- - 在涉及合并、收购或破产清算时,如涉及到个人信息转让,我们会要求新的持有您个人信息的公司、组织继续受本隐私政策的约束。
-
-
- 2.2 转让
-
- 我们不会将您的个人信息转让给任何公司、组织和个人,但以下情况除外:
-
-
- - 在获取明确同意的情况下转让。
- - 根据适用的法律法规、法律程序、强制性的行政或司法要求所必须的情况进行提供。
- - 在涉及合并、收购或破产清算时,如涉及到个人信息转让,我们会要求新的持有您个人信息的公司、组织继续受本隐私政策的约束。
-
-
- 2.3 公开披露
-
- 我们仅会在以下情况下,公开披露您的个人信息:
-
-
- - 获得您的明确同意。
- - 基于法律法规或法律程序的要求。
- - 在涉及合并、收购或破产清算时。
- - 为保护我们或他人的合法权益免受损害。
-
-
-
-
- 三、我们如何保护您的个人信息
-
- 我们已使用符合业界标准的安全防护措施保护您提供的个人信息,防止数据遭到未经授权访问、公开披露、使用、修改、损坏或丢失。我们会采取一切合理可行的措施,保护您的个人信息。
-
-
- - 使用加密技术确保数据传输和存储安全。
- - 限制访问权限,仅授权人员可访问个人信息。
- - 定期进行安全审计和风险评估。
- - 建立数据备份和恢复机制。
- - 制定应急响应预案,及时处理安全事件。
-
-
-
-
- 四、您的权利
-
- 按照中国相关的法律、法规、标准,以及其他国家、地区的通行做法,我们保障您对自己的个人信息行使以下权利:
-
-
- - 访问您的个人信息。
- - 更正您的个人信息。
- - 删除您的个人信息。
- - 改变您授权同意的范围。
- - 注销您的账户。
- - 获取您的个人信息副本。
-
-
- 如您需要行使上述权利,请通过本隐私政策提供的联系方式与我们联系。
-
-
-
-
- 五、未成年人保护
-
- 我们非常重视对未成年人个人信息的保护。如果您是18周岁以下的未成年人,在使用我们的产品和服务前,应事先取得您家长或法定监护人的同意。如您是未成年人的监护人,当您对您所监护的未成年人的个人信息处理存在疑问时,请通过本隐私政策提供的联系方式与我们联系。
-
-
-
-
- 六、隐私政策的更新
-
- 我们可能适时更新本隐私政策的条款,该等更新构成本隐私政策的一部分。如该等更新造成您在本隐私政策下权利的实质减少,我们将在更新生效前通过在主页上显著位置提示或向您发送电子邮件或其他方式通知您,在该种情况下,若您继续使用我们的服务,即表示同意受经修订的本隐私政策的约束。
-
-
-
-
- 七、Cookie 和网站分析工具
-
- 7.1 Cookie 使用说明
-
- 我们使用 Cookie 和类似技术来提供、保护和改进我们的服务。Cookie 是存储在您设备上的小型文本文件,帮助我们识别您的设备、记住您的偏好设置。
-
-
-
-
-
- | Cookie 类型 |
- 用途 |
- 持续时间 |
- 是否必需 |
-
-
-
-
- | 必要 Cookie |
- 网站基本功能运行 |
- 会话期间 |
- 是 |
-
-
- | 分析 Cookie |
- 了解网站使用情况,改进服务 |
- 14个月 |
- 否 |
-
-
- | 营销 Cookie |
- 个性化广告(当前未使用) |
- - |
- 否 |
-
-
-
-
-
- 7.2 Google Analytics 使用说明
-
- 我们使用 Google Analytics 4(由 Google LLC 提供)分析网站使用情况,帮助我们了解访客如何使用网站,从而改进用户体验。
-
-
- 收集的数据包括:
-
-
- - 访问的页面和停留时间
- - 设备类型、浏览器类型
- - 地理位置(国家/城市级别,IP 地址已匿名化)
- - 访问来源(直接访问、搜索引擎、外部链接)
-
-
- 我们已采取的保护措施:
-
-
- - IP 地址匿名化
- - 数据保留期限设为 14 个月
- - 禁用广告个性化功能
- - 禁用 Google 信号(不进行跨设备追踪)
- - 不与 Google 其他服务共享数据用于广告目的
-
-
- 7.3 您的选择
-
- - 您可以在首次访问时选择接受或拒绝分析 Cookie
- - 您可以点击页面右下角的“Cookie 设置”按钮随时更改偏好
- - 您可以通过浏览器设置删除或阻止 Cookie(可能影响网站功能)
-
-
- 7.4 数据删除请求
-
- 如您希望删除我们持有的您的个人数据,或撤回您的同意,请通过以下方式联系我们:
-
-
- - 隐私邮箱:privacy@novalon.cn
- - 联系地址:中国四川省成都市龙泉驿区幸福路12号
-
-
-
-
- 八、如何联系我们
-
- 如果您对本隐私政策有任何疑问、意见或建议,或需要行使您的权利,请通过以下方式与我们联系:
-
-
- - 公司名称:四川睿新致远科技有限公司
- - 联系邮箱:contact@novalon.cn
- - 隐私邮箱:privacy@novalon.cn
- - 联系地址:中国四川省成都市龙泉驿区幸福路12号
-
-
-
+ {cmsContent ? (
+
+ ) : (
+
+ )}
diff --git a/src/app/robots.ts b/src/app/robots.ts
new file mode 100644
index 0000000..c21fc08
--- /dev/null
+++ b/src/app/robots.ts
@@ -0,0 +1,27 @@
+import type { MetadataRoute } from 'next';
+
+export default function robots(): MetadataRoute.Robots {
+ return {
+ rules: [
+ {
+ userAgent: '*',
+ allow: '/',
+ disallow: [
+ '/_next/',
+ '/api/',
+ '/admin/',
+ ],
+ },
+ {
+ userAgent: 'Googlebot',
+ allow: '/',
+ disallow: [
+ '/_next/',
+ '/api/',
+ ],
+ },
+ ],
+ sitemap: 'https://www.novalon.cn/sitemap.xml',
+ host: 'https://www.novalon.cn',
+ };
+}
diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts
new file mode 100644
index 0000000..ecbd3c3
--- /dev/null
+++ b/src/app/sitemap.ts
@@ -0,0 +1,76 @@
+import type { MetadataRoute } from 'next';
+import { getAllPublishedSlugs } from '@/lib/cms/data-server';
+
+const BASE_URL = 'https://www.novalon.cn';
+
+export default async function sitemap(): Promise
{
+ const staticPages: MetadataRoute.Sitemap = [
+ { url: `${BASE_URL}/`, lastModified: new Date(), changeFrequency: 'daily', priority: 1 },
+ { url: `${BASE_URL}/about`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.8 },
+ { url: `${BASE_URL}/services`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.9 },
+ { url: `${BASE_URL}/products`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.9 },
+ { url: `${BASE_URL}/solutions`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.9 },
+ { url: `${BASE_URL}/cases`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.8 },
+ { url: `${BASE_URL}/news`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.7 },
+ { url: `${BASE_URL}/team`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.6 },
+ { url: `${BASE_URL}/contact`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.7 },
+ ];
+
+ try {
+ // 从 CMS 获取动态页面 slug
+ const [caseSlugs, newsSlugs, serviceSlugs, productSlugs, solutionSlugs] = await Promise.all([
+ getAllPublishedSlugs('case-study'),
+ getAllPublishedSlugs('news'),
+ getAllPublishedSlugs('service'),
+ getAllPublishedSlugs('product'),
+ getAllPublishedSlugs('solution'),
+ ]);
+
+ const casePages: MetadataRoute.Sitemap = caseSlugs.map(({ slug }) => ({
+ url: `${BASE_URL}/cases/${slug}`,
+ lastModified: new Date(),
+ changeFrequency: 'monthly' as const,
+ priority: 0.8,
+ }));
+
+ const newsPages: MetadataRoute.Sitemap = newsSlugs.map(({ slug }) => ({
+ url: `${BASE_URL}/news/${slug}`,
+ lastModified: new Date(),
+ changeFrequency: 'monthly' as const,
+ priority: 0.6,
+ }));
+
+ const servicePages: MetadataRoute.Sitemap = serviceSlugs.map(({ slug }) => ({
+ url: `${BASE_URL}/services/${slug}`,
+ lastModified: new Date(),
+ changeFrequency: 'monthly' as const,
+ priority: 0.8,
+ }));
+
+ const productPages: MetadataRoute.Sitemap = productSlugs.map(({ slug }) => ({
+ url: `${BASE_URL}/products/${slug}`,
+ lastModified: new Date(),
+ changeFrequency: 'monthly' as const,
+ priority: 0.8,
+ }));
+
+ const solutionPages: MetadataRoute.Sitemap = solutionSlugs.map(({ slug }) => ({
+ url: `${BASE_URL}/solutions/${slug}`,
+ lastModified: new Date(),
+ changeFrequency: 'monthly' as const,
+ priority: 0.8,
+ }));
+
+ return [
+ ...staticPages,
+ ...casePages,
+ ...newsPages,
+ ...servicePages,
+ ...productPages,
+ ...solutionPages,
+ ];
+ } catch {
+ // 数据库不可用时返回静态页面
+ return staticPages;
+ }
+}
\ No newline at end of file
diff --git a/src/app/terms/page.test.tsx b/src/app/terms/page.test.tsx
index a942a2b..64f6205 100644
--- a/src/app/terms/page.test.tsx
+++ b/src/app/terms/page.test.tsx
@@ -8,38 +8,43 @@ describe('TermsOfServicePage', () => {
});
describe('Rendering', () => {
- it('should render terms of service page', () => {
- render();
+ it('should render terms of service page', async () => {
+ const page = await TermsOfServicePage();
+ render(page);
const container = screen.getByText('服务条款').closest('div');
expect(container).toBeInTheDocument();
});
- it('should render page title', () => {
- render();
+ it('should render page title', async () => {
+ const page = await TermsOfServicePage();
+ render(page);
const title = screen.getByRole('heading', { level: 1 });
expect(title).toBeInTheDocument();
expect(title).toHaveTextContent('服务条款');
});
- it('should render introduction section', () => {
- render();
+ it('should render introduction section', async () => {
+ const page = await TermsOfServicePage();
+ render(page);
const intro = screen.getByText('引言');
expect(intro).toBeInTheDocument();
});
- it('should render service content section', () => {
- render();
+ it('should render service content section', async () => {
+ const page = await TermsOfServicePage();
+ render(page);
const sections = screen.getAllByRole('heading', { level: 2 });
expect(sections.length).toBeGreaterThan(0);
});
});
describe('Accessibility', () => {
- it('should have proper heading hierarchy', () => {
- render();
+ it('should have proper heading hierarchy', async () => {
+ const page = await TermsOfServicePage();
+ render(page);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
-
+
const h2s = screen.getAllByRole('heading', { level: 2 });
expect(h2s.length).toBeGreaterThan(0);
});
diff --git a/src/app/terms/page.tsx b/src/app/terms/page.tsx
index 2c2e1a1..555e773 100644
--- a/src/app/terms/page.tsx
+++ b/src/app/terms/page.tsx
@@ -1,4 +1,5 @@
import { Metadata } from 'next';
+import { getPublishedItems } from '@/lib/cms/data-server';
import { COMPANY_INFO } from '@/lib/constants';
export const metadata: Metadata = {
@@ -6,10 +7,182 @@ export const metadata: Metadata = {
description: `${COMPANY_INFO.name}服务条款`,
};
-export default function TermsOfServicePage() {
+function TermsContent() {
+ return (
+
+
+ 引言
+
+ 欢迎使用四川睿新致远科技有限公司(以下简称“我们”、“公司”)提供的产品和服务。在使用我们的产品和服务之前,请您仔细阅读并理解本服务条款。如果您不同意本服务条款的任何内容,请停止使用我们的产品和服务。
+
+
+ 本服务条款是您与四川睿新致远科技有限公司之间就使用我们的产品和服务所订立的协议。我们有权根据需要不时修改本服务条款,修改后的条款一旦公布即代替原条款,恕不另行通知。
+
+
+
+
+ 一、服务内容
+
+ 我们提供的产品和服务包括但不限于:软件开发、云服务、数据分析、信息安全、企业级软件产品(如ERP、CRM、后台管理系统、BI等)等。具体服务内容以我们官方网站或产品文档为准。
+
+
+ 我们保留随时修改、暂停或终止部分或全部服务的权利,无需事先通知。对于因服务修改、暂停或终止而给您造成的任何损失,我们不承担任何责任,除非法律另有规定。
+
+
+
+
+ 二、用户注册与账户
+
+ 2.1 注册资格
+
+ 您确认,在您完成注册程序或以其他方式实际使用本服务时,您应当是具备完全民事权利能力和完全民事行为能力的自然人、法人或其他组织。若您不具备前述主体资格,则您及您的监护人应承担因此而导致的一切后果,且我们有权注销或永久冻结您的账户。
+
+
+ 2.2 账户安全
+
+ 您有责任维护您账户的安全性和保密性。您不得向任何第三方泄露您的账户信息,也不得与他人共享您的账户。如果您发现任何未经授权使用您账户的情况,应立即通知我们。
+
+
+ 您对您账户下发生的所有活动负责。我们对因您未能维护账户安全而造成的任何损失不承担责任。
+
+
+
+
+ 三、用户行为规范
+
+ 在使用我们的产品和服务时,您同意遵守以下行为规范:
+
+
+ - 遵守所有适用的法律法规和本服务条款。
+ - 不得利用我们的产品和服务进行任何违法或不当活动。
+ - 不得干扰或破坏我们的产品和服务或与我们的产品和服务相连的服务器和网络。
+ - 不得上传、传播或存储任何违法、有害、威胁、诽谤、骚扰、侵权或其他不当内容。
+ - 不得侵犯他人的知识产权、隐私权或其他合法权益。
+ - 不得进行任何形式的商业欺诈或诈骗活动。
+ - 不得恶意收集或获取其他用户的信息。
+
+
+ 如果我们认定您违反了本服务条款或任何适用法律,我们有权在不事先通知的情况下,暂停或终止您的账户,并拒绝您现在或将来使用我们的产品和服务。
+
+
+
+
+ 四、知识产权
+
+ 我们的产品和服务中包含的所有内容,包括但不限于软件、设计、文字、图片、音频、视频、商标、服务标识等,均受著作权法、商标法、专利法或其他适用法律的保护。
+
+
+ 除非另有明确说明,我们拥有或持有我们产品和服务中所有内容的所有知识产权。您不得以任何形式复制、修改、传播、展示、执行、创作衍生作品、转让、出售或以其他方式使用这些内容,除非获得我们的明确书面许可。
+
+
+ 您在使用我们的产品和服务时产生的任何内容,您仍保留其知识产权,但您授予我们全球性、非独占性、免版税的许可,以使用、复制、修改、传播、展示和执行这些内容,仅限于提供和改进我们的产品和服务。
+
+
+
+
+ 五、服务费用与支付
+
+ 5.1 费用标准
+
+ 我们的产品和服务可能需要支付费用。具体费用标准以我们官方网站或产品文档公布的价格为准。我们保留随时调整价格的权利,调整后的价格适用于调整后的新订单或续费。
+
+
+ 5.2 支付方式
+
+ 我们接受多种支付方式,包括但不限于银行转账、支付宝、微信支付等。具体支付方式以我们官方网站或产品文档为准。
+
+
+ 5.3 退款政策
+
+ 除非另有明确说明,我们提供的产品和服务一经售出,不予退款。如因我们的原因导致产品或服务无法正常使用,我们将根据实际情况提供相应的补偿或解决方案。
+
+
+
+
+ 六、免责声明
+
+ 我们的产品和服务按“现状”和“可用”基础提供,不提供任何明示或暗示的保证,包括但不限于对适销性、适用性、非侵权性或准确性、可靠性的保证。
+
+
+ 我们不对以下情况承担责任:
+
+
+ - 因您违反本服务条款或任何适用法律而导致的任何损失或损害。
+ - 因不可抗力、网络故障、设备故障等不可控因素导致的服务中断或数据丢失。
+ - 因第三方服务或内容导致的任何损失或损害。
+ - 因您使用或无法使用我们的产品和服务而导致的任何间接、附带、特殊或后果性损害。
+
+
+
+
+ 七、服务终止
+
+ 您可以随时停止使用我们的产品和服务,并注销您的账户。我们也有权在不事先通知的情况下,因以下原因暂停或终止您的账户:
+
+
+ - 您违反本服务条款或任何适用法律。
+ - 我们出于安全、法律或商业考虑,认为有必要终止您的账户。
+ - 您长时间未使用您的账户。
+ - 我们停止提供相关的产品和服务。
+
+
+
+
+ 八、争议解决
+
+ 本服务条款的订立、执行、解释及争议解决均适用中华人民共和国法律。如就本服务条款发生任何争议,双方应首先通过友好协商解决;协商不成的,任何一方均可向公司所在地有管辖权的人民法院提起诉讼。
+
+
+
+
+ 九、其他条款
+
+ - 本服务条款构成您与我们就使用我们的产品和服务所达成的完整协议,取代之前的所有口头或书面协议。
+ - 如本服务条款的任何条款被认定为无效或不可执行,该条款应被限制或排除,以使其有效和可执行,其余条款继续有效。
+ - 我们未行使或延迟行使本服务条款项下的任何权利或规定,不构成对该权利或规定的放弃。
+ - 本服务条款的标题仅为方便而设,不影响本服务条款的解释。
+
+
+
+
+ 十、联系我们
+
+ 如果您对本服务条款有任何疑问、意见或建议,请通过以下方式与我们联系:
+
+
+ - 公司名称:四川睿新致远科技有限公司
+ - 联系邮箱:contact@novalon.cn
+ - 联系地址:中国四川省成都市龙泉驿区幸福路12号
+
+
+
+
+
+ );
+}
+
+export default async function TermsOfServicePage() {
+ let cmsContent: string | null = null;
+
+ try {
+ const items = await getPublishedItems('legal-page');
+ const termsItem = items.find((item) => item.data.pageType === 'terms');
+ if (termsItem && typeof termsItem.data.content === 'string' && termsItem.data.content.length > 0) {
+ cmsContent = termsItem.data.content as string;
+ }
+ } catch {
+ // CMS not available, fall back to inline content
+ }
+
return (
-
+
服务条款
@@ -22,161 +195,14 @@ export default function TermsOfServicePage() {
-
-
- 引言
-
- 欢迎使用四川睿新致远科技有限公司(以下简称“我们”、“公司”)提供的产品和服务。在使用我们的产品和服务之前,请您仔细阅读并理解本服务条款。如果您不同意本服务条款的任何内容,请停止使用我们的产品和服务。
-
-
- 本服务条款是您与四川睿新致远科技有限公司之间就使用我们的产品和服务所订立的协议。我们有权根据需要不时修改本服务条款,修改后的条款一旦公布即代替原条款,恕不另行通知。
-
-
-
-
- 一、服务内容
-
- 我们提供的产品和服务包括但不限于:软件开发、云服务、数据分析、信息安全、企业级软件产品(如ERP、CRM、后台管理系统、BI等)等。具体服务内容以我们官方网站或产品文档为准。
-
-
- 我们保留随时修改、暂停或终止部分或全部服务的权利,无需事先通知。对于因服务修改、暂停或终止而给您造成的任何损失,我们不承担任何责任,除非法律另有规定。
-
-
-
-
- 二、用户注册与账户
-
- 2.1 注册资格
-
- 您确认,在您完成注册程序或以其他方式实际使用本服务时,您应当是具备完全民事权利能力和完全民事行为能力的自然人、法人或其他组织。若您不具备前述主体资格,则您及您的监护人应承担因此而导致的一切后果,且我们有权注销或永久冻结您的账户。
-
-
- 2.2 账户安全
-
- 您有责任维护您账户的安全性和保密性。您不得向任何第三方泄露您的账户信息,也不得与他人共享您的账户。如果您发现任何未经授权使用您账户的情况,应立即通知我们。
-
-
- 您对您账户下发生的所有活动负责。我们对因您未能维护账户安全而造成的任何损失不承担责任。
-
-
-
-
- 三、用户行为规范
-
- 在使用我们的产品和服务时,您同意遵守以下行为规范:
-
-
- - 遵守所有适用的法律法规和本服务条款。
- - 不得利用我们的产品和服务进行任何违法或不当活动。
- - 不得干扰或破坏我们的产品和服务或与我们的产品和服务相连的服务器和网络。
- - 不得上传、传播或存储任何违法、有害、威胁、诽谤、骚扰、侵权或其他不当内容。
- - 不得侵犯他人的知识产权、隐私权或其他合法权益。
- - 不得进行任何形式的商业欺诈或诈骗活动。
- - 不得恶意收集或获取其他用户的信息。
-
-
- 如果我们认定您违反了本服务条款或任何适用法律,我们有权在不事先通知的情况下,暂停或终止您的账户,并拒绝您现在或将来使用我们的产品和服务。
-
-
-
-
- 四、知识产权
-
- 我们的产品和服务中包含的所有内容,包括但不限于软件、设计、文字、图片、音频、视频、商标、服务标识等,均受著作权法、商标法、专利法或其他适用法律的保护。
-
-
- 除非另有明确说明,我们拥有或持有我们产品和服务中所有内容的所有知识产权。您不得以任何形式复制、修改、传播、展示、执行、创作衍生作品、转让、出售或以其他方式使用这些内容,除非获得我们的明确书面许可。
-
-
- 您在使用我们的产品和服务时产生的任何内容,您仍保留其知识产权,但您授予我们全球性、非独占性、免版税的许可,以使用、复制、修改、传播、展示和执行这些内容,仅限于提供和改进我们的产品和服务。
-
-
-
-
- 五、服务费用与支付
-
- 5.1 费用标准
-
- 我们的产品和服务可能需要支付费用。具体费用标准以我们官方网站或产品文档公布的价格为准。我们保留随时调整价格的权利,调整后的价格适用于调整后的新订单或续费。
-
-
- 5.2 支付方式
-
- 我们接受多种支付方式,包括但不限于银行转账、支付宝、微信支付等。具体支付方式以我们官方网站或产品文档为准。
-
-
- 5.3 退款政策
-
- 除非另有明确说明,我们提供的产品和服务一经售出,不予退款。如因我们的原因导致产品或服务无法正常使用,我们将根据实际情况提供相应的补偿或解决方案。
-
-
-
-
- 六、免责声明
-
- 我们的产品和服务按“现状”和“可用”基础提供,不提供任何明示或暗示的保证,包括但不限于对适销性、适用性、非侵权性或准确性、可靠性的保证。
-
-
- 我们不对以下情况承担责任:
-
-
- - 因您违反本服务条款或任何适用法律而导致的任何损失或损害。
- - 因不可抗力、网络故障、设备故障等不可控因素导致的服务中断或数据丢失。
- - 因第三方服务或内容导致的任何损失或损害。
- - 因您使用或无法使用我们的产品和服务而导致的任何间接、附带、特殊或后果性损害。
-
-
-
-
- 七、服务终止
-
- 您可以随时停止使用我们的产品和服务,并注销您的账户。我们也有权在不事先通知的情况下,因以下原因暂停或终止您的账户:
-
-
- - 您违反本服务条款或任何适用法律。
- - 我们出于安全、法律或商业考虑,认为有必要终止您的账户。
- - 您长时间未使用您的账户。
- - 我们停止提供相关的产品和服务。
-
-
-
-
- 八、争议解决
-
- 本服务条款的订立、执行、解释及争议解决均适用中华人民共和国法律。如就本服务条款发生任何争议,双方应首先通过友好协商解决;协商不成的,任何一方均可向公司所在地有管辖权的人民法院提起诉讼。
-
-
-
-
- 九、其他条款
-
- - 本服务条款构成您与我们就使用我们的产品和服务所达成的完整协议,取代之前的所有口头或书面协议。
- - 如本服务条款的任何条款被认定为无效或不可执行,该条款应被限制或排除,以使其有效和可执行,其余条款继续有效。
- - 我们未行使或延迟行使本服务条款项下的任何权利或规定,不构成对该权利或规定的放弃。
- - 本服务条款的标题仅为方便而设,不影响本服务条款的解释。
-
-
-
-
- 十、联系我们
-
- 如果您对本服务条款有任何疑问、意见或建议,请通过以下方式与我们联系:
-
-
- - 公司名称:四川睿新致远科技有限公司
- - 联系邮箱:contact@novalon.cn
- - 联系地址:中国四川省成都市龙泉驿区幸福路12号
-
-
-
-
-
+ {cmsContent ? (
+
+ ) : (
+
+ )}
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..22d7ce5
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,23 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+export function middleware(request: NextRequest) {
+ const { pathname } = request.nextUrl;
+
+ // 只处理 /admin 路由(除了登录页和 API 路由)
+ if (pathname.startsWith('/admin') && !pathname.startsWith('/admin/login') && !pathname.startsWith('/api/')) {
+ const token = request.cookies.get('novalon_token')?.value;
+ const authHeader = request.headers.get('authorization');
+
+ // 如果 cookie 和 header 都没有 token,重定向到登录页
+ if (!token && !authHeader) {
+ // 允许客户端组件处理认证(因为 AuthProvider 在客户端也会检查 localStorage)
+ return NextResponse.next();
+ }
+ }
+
+ return NextResponse.next();
+}
+
+export const config = {
+ matcher: ['/admin/:path*'],
+};
\ No newline at end of file