{icon && (
+
@@ -48,14 +94,23 @@ const sizeClasses = {
{description && (
+ {ctaLabel}
+
-
+
\ No newline at end of file
diff --git a/src/components/report/ReportSection.astro b/src/components/report/ReportSection.astro
index 8096118..c7eda39 100644
--- a/src/components/report/ReportSection.astro
+++ b/src/components/report/ReportSection.astro
@@ -4,13 +4,27 @@ export interface Props {
subtitle?: string;
level?: 1 | 2 | 3 | 4;
divider?: boolean;
+ reveal?: boolean;
+ revealDirection?: 'up' | 'down' | 'left' | 'right' | 'fade' | 'none';
+ revealDistance?: string;
+ revealDelay?: string;
+ revealBlur?: string;
+ revealScale?: string;
+ revealOnce?: boolean;
}
const {
title,
subtitle,
level = 1,
- divider = true
+ divider = true,
+ reveal = true,
+ revealDirection,
+ revealDistance,
+ revealDelay,
+ revealBlur,
+ revealScale,
+ revealOnce
} = Astro.props;
const Tag = `h${level}` as any;
@@ -20,13 +34,40 @@ const sizeClasses = {
3: 'text-xl md:text-2xl',
4: 'text-lg md:text-xl'
};
+
+let emojiPart: string | null = null;
+let titleBody = title;
+
+if (typeof title === 'string') {
+ const trimmedTitle = title.trimStart();
+ const emojiMatch = trimmedTitle.match(/^([\p{Extended_Pictographic}\u2600-\u27BF][\p{Extended_Pictographic}\u2600-\u27BF\uFE0F\u200D]*)/u);
+
+ if (emojiMatch && emojiMatch[1]) {
+ emojiPart = emojiMatch[1];
+ titleBody = trimmedTitle.slice(emojiMatch[0].length).trimStart();
+ } else {
+ titleBody = trimmedTitle;
+ }
+}
+
+const revealAttributes = reveal
+ ? {
+ 'data-reveal': revealDirection ?? '',
+ ...(revealDistance ? { 'data-reveal-distance': revealDistance } : { 'data-reveal-distance': '40px' }),
+ ...(revealDelay ? { 'data-reveal-delay': revealDelay } : { 'data-reveal-delay': 'var(--section-delay, 0s)' }),
+ ...(revealBlur ? { 'data-reveal-blur': revealBlur } : {}),
+ ...(revealScale ? { 'data-reveal-scale': revealScale } : {}),
+ ...(revealOnce === false ? { 'data-reveal-once': 'false' } : {})
+ }
+ : {};
---
-{description}
)} - -
-
-
+
+
{title && (
-
- {title}
+
+ {emojiPart && {emojiPart}}
+ {typeof titleBody === 'string' ? titleBody : title}
)}
@@ -52,7 +93,6 @@ const sizeClasses = {
margin-bottom: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
- animation: fadeIn 0.6s ease-out;
}
.report-section-container:hover {
@@ -71,6 +111,27 @@ const sizeClasses = {
color: #011a2d;
margin: 0 0 0.5rem 0;
line-height: 1.2;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+ }
+
+ .section-title.section-title--with-emoji {
+ gap: 0.65rem;
+ }
+
+ .section-title__emoji {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.35em;
+ line-height: 1;
+ filter: drop-shadow(0 4px 10px rgba(15, 23, 42, 0.18));
+ }
+
+ .section-title__text {
+ display: inline-block;
background: linear-gradient(135deg, #011a2d, #2c4a6b, #5b778e);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
@@ -163,15 +224,4 @@ const sizeClasses = {
font-size: 0.9rem;
}
}
-
- @keyframes fadeIn {
- from {
- opacity: 0;
- transform: translateY(20px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
- }
\ No newline at end of file
diff --git a/src/components/report/ReportSidebar.astro b/src/components/report/ReportSidebar.astro
new file mode 100644
index 0000000..77af546
--- /dev/null
+++ b/src/components/report/ReportSidebar.astro
@@ -0,0 +1,522 @@
+---
+export interface Props {
+ title?: string;
+ toggleLabel?: string;
+ targetSelector?: string;
+ headingSelector?: string;
+ minLevel?: number;
+ maxLevel?: number;
+ defaultOpenBreakpoint?: string;
+}
+
+const {
+ title = '页面导航',
+ toggleLabel = '导航',
+ targetSelector = '[data-report-content]',
+ headingSelector,
+ minLevel = 2,
+ maxLevel = 3,
+ defaultOpenBreakpoint = '(min-width: 1280px)'
+} = Astro.props;
+
+const sidebarId = `report-sidebar-${Math.random().toString(36).slice(2, 10)}`;
+const navListId = `${sidebarId}-list`;
+const config = {
+ sidebarId,
+ navListId,
+ title,
+ toggleLabel,
+ targetSelector,
+ headingSelector: headingSelector ?? '',
+ minLevel,
+ maxLevel,
+ breakpoint: defaultOpenBreakpoint
+};
+---
+
+
+
+
+
+
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro
index e46cac6..92b0363 100644
--- a/src/layouts/BaseLayout.astro
+++ b/src/layouts/BaseLayout.astro
@@ -1,4 +1,6 @@
---
+import ScrollProgress from '../components/common/ScrollProgress.astro';
+
export interface Props {
title: string;
description?: string;
@@ -28,10 +30,15 @@ const { title, description = '', type = 'navigation' } = Astro.props;
+
+
+