diff --git a/deploy-full.sh b/deploy-full.sh index 46976be..6235fe3 100755 --- a/deploy-full.sh +++ b/deploy-full.sh @@ -55,6 +55,15 @@ build_project() { echo "❌ 错误: 构建失败,dist 目录不存在" exit 1 fi + + # 确保全局样式被包含到部署包中 + if [ -f "src/styles/global.css" ]; then + mkdir -p dist/src/styles + cp src/styles/global.css dist/src/styles/global.css + echo "🎨 已复制 global.css 到 dist/src/styles/" + else + echo "⚠️ 未找到 src/styles/global.css,跳过复制" + fi echo "✅ 构建完成" } diff --git a/COMPONENTS_GUIDE.md b/docs/COMPONENTS_GUIDE.md similarity index 100% rename from COMPONENTS_GUIDE.md rename to docs/COMPONENTS_GUIDE.md diff --git a/TYPESCRIPT_WARNINGS_FIXES.md b/docs/TYPESCRIPT_WARNINGS_FIXES.md similarity index 100% rename from TYPESCRIPT_WARNINGS_FIXES.md rename to docs/TYPESCRIPT_WARNINGS_FIXES.md diff --git a/package-lock.json b/package-lock.json index 45034ec..fcb6602 100644 --- a/package-lock.json +++ b/package-lock.json @@ -212,6 +212,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -5425,23 +5426,6 @@ "node": ">=18.12" } }, - "node_modules/prettier": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", - "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", - "dev": true, - "license": "MIT", - "optional": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -5842,6 +5826,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz", "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -7180,6 +7165,23 @@ "prettier": "2.8.7" } }, + "node_modules/yaml-language-server/node_modules/prettier": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", + "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/yaml-language-server/node_modules/request-light": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz", diff --git a/src/components/Container.astro b/src/components/Container.astro index 073d928..4bddd96 100644 --- a/src/components/Container.astro +++ b/src/components/Container.astro @@ -8,6 +8,13 @@ export interface Props { animated?: boolean; hover?: boolean; className?: string; + reveal?: boolean; + revealDirection?: 'up' | 'down' | 'left' | 'right' | 'fade' | 'none'; + revealDistance?: string; + revealDelay?: string; + revealBlur?: string; + revealScale?: string; + revealOnce?: boolean; } const { @@ -18,7 +25,14 @@ const { shadow = 'md', animated = true, hover = false, - className = '' + className = '', + reveal = true, + revealDirection, + revealDistance, + revealDelay, + revealBlur, + revealScale, + revealOnce } = Astro.props; const variantClasses = { @@ -63,6 +77,17 @@ const shadowClasses = { xl: 'shadow-xl', '2xl': 'shadow-2xl' }; + +const revealAttributes = reveal + ? { + 'data-reveal': revealDirection ?? '', + ...(revealDistance ? { 'data-reveal-distance': revealDistance } : {}), + ...(revealDelay ? { 'data-reveal-delay': revealDelay } : { 'data-reveal-delay': 'var(--container-delay, 0s)' }), + ...(revealBlur ? { 'data-reveal-blur': revealBlur } : {}), + ...(revealScale ? { 'data-reveal-scale': revealScale } : {}), + ...(revealOnce === false ? { 'data-reveal-once': 'false' } : {}) + } + : {}; ---
diff --git a/src/components/Header.astro b/src/components/Header.astro index 04bdbee..b02a8ee 100644 --- a/src/components/Header.astro +++ b/src/components/Header.astro @@ -6,106 +6,152 @@ export interface Props { className?: string; } +import GlowButton from './common/GlowButton.astro'; + const { - title = 'Jiao77 - AI & Technology Explorer' + title = 'Jiao77 - AI & Technology Explorer', + description, + navigationItems, + className = '' } = Astro.props; + +const defaultNavigation = [ + { label: '首页', href: '/', icon: 'fas fa-home' }, + { label: '报告导航', href: '/report', icon: 'fas fa-chart-line' }, + { label: '关于我', href: '/about', icon: 'fas fa-user-astronaut' }, + { label: '组件测试', href: '/components-demo', icon: 'fas fa-user-astronaut' }, +]; + +const links = navigationItems && navigationItems.length > 0 ? navigationItems : defaultNavigation; --- -
- -
-
- - - -
-
-
-

{title}

-
- -
- - + + {description && ( +

{description}

+ )}
+
+ + diff --git a/src/components/common/ImageViewer.astro b/src/components/common/ImageViewer.astro new file mode 100644 index 0000000..502c5b5 --- /dev/null +++ b/src/components/common/ImageViewer.astro @@ -0,0 +1,629 @@ +--- +export interface Props { + src: string; + alt: string; + caption?: string; + className?: string; + width?: string | number; + height?: string | number; + aspectRatio?: string; + lazy?: boolean; +} + +const { + src, + alt, + caption, + className = '', + width, + height, + aspectRatio, + lazy = true +} = Astro.props; + +// 生成唯一ID +const imageId = `image-${Math.random().toString(36).substring(2, 11)}`; +--- + +
+
+ {alt} +
+ {caption && ( +
{caption}
+ )} +
+ + + + + + \ No newline at end of file diff --git a/src/components/common/ScrollProgress.astro b/src/components/common/ScrollProgress.astro new file mode 100644 index 0000000..f5bc281 --- /dev/null +++ b/src/components/common/ScrollProgress.astro @@ -0,0 +1,43 @@ +--- +--- + + + + + + diff --git a/src/components/navigation/NavigationCard.astro b/src/components/navigation/NavigationCard.astro index 7db90be..9c6bada 100644 --- a/src/components/navigation/NavigationCard.astro +++ b/src/components/navigation/NavigationCard.astro @@ -1,4 +1,6 @@ --- +import GlowButton from '../common/GlowButton.astro'; + export interface Props { title: string; description?: string; @@ -6,6 +8,14 @@ export interface Props { icon?: string; color?: 'primary' | 'secondary' | 'accent'; size?: 'small' | 'medium' | 'large'; + buttonLabel?: string; + reveal?: boolean; + revealDirection?: 'up' | 'down' | 'left' | 'right' | 'fade' | 'none'; + revealDistance?: string; + revealDelay?: string; + revealBlur?: string; + revealScale?: string; + revealOnce?: boolean; } const { @@ -14,7 +24,15 @@ const { href, icon, color = 'primary', - size = 'medium' + size = 'medium', + buttonLabel, + reveal = true, + revealDirection, + revealDistance, + revealDelay, + revealBlur, + revealScale, + revealOnce } = Astro.props; // 判断是否为外部链接 @@ -26,16 +44,44 @@ const colorClasses = { primary: 'nav-card-primary', secondary: 'nav-card-secondary', accent: 'nav-card-accent' -}; +} as const; const sizeClasses = { small: 'nav-card-small', medium: 'nav-card-medium', large: 'nav-card-large' -}; +} as const; + +const buttonVariantMap = { + primary: 'primary', + secondary: 'secondary', + accent: 'outline' +} as const; + +const buttonSizeMap = { + small: 'sm', + medium: 'md', + large: 'lg' +} as const; + +const ctaLabel = buttonLabel ?? '访问服务'; + +const revealAttributes = reveal + ? { + 'data-reveal': revealDirection ?? '', + ...(revealDistance ? { 'data-reveal-distance': revealDistance } : { 'data-reveal-distance': '32px' }), + ...(revealDelay ? { 'data-reveal-delay': revealDelay } : { 'data-reveal-delay': 'var(--card-delay, 0s)' }), + ...(revealBlur ? { 'data-reveal-blur': revealBlur } : {}), + ...(revealScale ? { 'data-reveal-scale': revealScale } : {}), + ...(revealOnce === false ? { 'data-reveal-once': 'false' } : {}) + } + : {}; --- - +
\ 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' } : {}) + } + : {}; --- -
+
{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; +
+ +