删除没用文件,修复所有筛选功能

This commit is contained in:
lsy 2025-04-08 20:14:37 +08:00
parent a45d393cee
commit d36c639ca4
27 changed files with 1607 additions and 3975 deletions

View File

@ -15,7 +15,18 @@
body * {
transition: background-color var(--transition-duration) ease,
color var(--transition-duration) ease,
box-shadow var(--transition-duration) ease;
box-shadow var(--transition-duration) ease,
border-color var(--transition-duration) ease,
opacity var(--transition-duration) ease,
transform var(--transition-duration) ease,
filter var(--transition-duration) ease;
}
/* 特别处理渐变背景过渡 */
[class*="bg-gradient"] {
transition: background-image var(--transition-duration) ease,
background-color var(--transition-duration) ease,
opacity var(--transition-duration) ease;
}
/* 防止某些元素过渡 */

View File

@ -1,109 +0,0 @@
---
interface Image {
src: string;
alt: string;
width?: number;
height?: number;
}
interface Props {
images: Image[];
className?: string;
gap?: number;
direction?: 'left' | 'right';
speed?: 'slow' | 'normal' | 'fast';
}
const {
images,
className = "",
gap = 16,
direction = 'left',
speed = 'normal'
} = Astro.props;
// 计算速度
const getSpeed = () => {
switch(speed) {
case 'slow': return '60s';
case 'fast': return '20s';
default: return '40s';
}
};
const speedValue = getSpeed();
---
<div class={`animated-gallery overflow-hidden ${className}`}>
<div class="flex items-center w-full" style={`gap: ${gap}px;`}>
<!-- 复制两组图片,确保无缝循环 -->
<div
class="flex animated-slide"
style={`gap: ${gap}px; animation: scroll-${direction} ${speedValue} linear infinite;`}
>
{images.map(image => (
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="bg-gray-300 dark:bg-gray-700 flex items-center justify-center"
style={`width: ${image.width || 300}px; height: ${image.height || 200}px;`}
>
<span class="text-gray-500 dark:text-gray-400">{image.alt}</span>
</div>
</div>
))}
</div>
<!-- 复制第二组,确保无缝循环 -->
<div
class="flex animated-slide"
style={`gap: ${gap}px; animation: scroll-${direction} ${speedValue} linear infinite;`}
>
{images.map(image => (
<div class="flex-shrink-0 rounded-lg overflow-hidden">
<div
class="bg-gray-300 dark:bg-gray-700 flex items-center justify-center"
style={`width: ${image.width || 300}px; height: ${image.height || 200}px;`}
>
<span class="text-gray-500 dark:text-gray-400">{image.alt}</span>
</div>
</div>
))}
</div>
</div>
</div>
<style>
@keyframes scroll-left {
0% {
transform: translateX(0);
}
100% {
transform: translateX(calc(-100% - var(--gap, 16px)));
}
}
@keyframes scroll-right {
0% {
transform: translateX(calc(-100% - var(--gap, 16px)));
}
100% {
transform: translateX(0);
}
}
.animated-gallery:hover .animated-slide {
animation-play-state: paused;
}
</style>
<script>
// 动态计算间隙变量
document.addEventListener('DOMContentLoaded', () => {
const galleries = document.querySelectorAll('.animated-gallery');
galleries.forEach(gallery => {
const gap = gallery.querySelector('.flex')?.getAttribute('style')?.match(/gap: (\d+)px/)?.[1] || '16';
(gallery as HTMLElement).style.setProperty('--gap', `${gap}px`);
});
});
</script>

View File

@ -1,63 +0,0 @@
---
interface Props {
frontTitle?: string;
backTitle?: string;
className?: string;
}
const {
frontTitle = "正面",
backTitle = "背面",
className = ""
} = Astro.props;
---
<div class={`flip-card-container ${className}`}>
<div class="flip-card">
<div class="flip-card-front p-6 flex flex-col items-center justify-center rounded-xl bg-white dark:bg-color-dark-card shadow-md">
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">{frontTitle}</h3>
<div class="front-content">
<slot name="front" />
</div>
</div>
<div class="flip-card-back p-6 flex flex-col items-center justify-center rounded-xl bg-color-primary-500 dark:bg-color-dark-primary-600 text-white shadow-md">
<h3 class="text-xl font-bold mb-3">{backTitle}</h3>
<div class="back-content">
<slot name="back" />
</div>
</div>
</div>
</div>
<style>
.flip-card-container {
perspective: 1000px;
width: 100%;
height: 300px;
}
.flip-card {
position: relative;
width: 100%;
height: 100%;
transition: transform 0.8s;
transform-style: preserve-3d;
}
.flip-card-container:hover .flip-card {
transform: rotateY(180deg);
}
.flip-card-front, .flip-card-back {
position: absolute;
width: 100%;
height: 100%;
-webkit-backface-visibility: hidden; /* Safari */
backface-visibility: hidden;
overflow: auto;
}
.flip-card-back {
transform: rotateY(180deg);
}
</style>

View File

@ -1,54 +0,0 @@
---
interface Props {
links: Array<{
label: string;
href: string;
isActive?: boolean;
}>;
className?: string;
}
const { links, className = "" } = Astro.props;
---
<div class={`fixed top-10 inset-x-0 max-w-2xl mx-auto z-50 ${className}`}>
<div class="relative backdrop-blur-sm">
<div class="absolute inset-0 bg-white dark:bg-gray-900/70 shadow-lg rounded-full"></div>
<nav class="relative">
<ul class="flex items-center justify-center p-2 gap-2">
{
links.map((link) => (
<li>
<a
href={link.href}
class={`px-4 py-2 rounded-full font-medium transition-colors ${
link.isActive
? "bg-color-primary-600 text-white dark:bg-color-dark-primary-600"
: "hover:bg-gray-100 text-gray-900 dark:text-gray-100 dark:hover:bg-gray-800"
}`}
>
{link.label}
</a>
</li>
))
}
</ul>
</nav>
</div>
</div>
<script>
// 简单的滚动动画效果
document.addEventListener('scroll', () => {
const navbar = document.querySelector('.fixed.top-10');
if (!navbar) return;
if (window.scrollY > 100) {
navbar.classList.add('top-4');
navbar.classList.remove('top-10');
} else {
navbar.classList.add('top-10');
navbar.classList.remove('top-4');
}
});
</script>

View File

@ -1,63 +0,0 @@
---
interface Props {
words: string[];
baseText?: string;
className?: string;
duration?: number; // 每个词的显示时间(毫秒)
}
const {
words,
baseText = "河北",
className = "",
duration = 2000
} = Astro.props;
const id = `gradient-text-${Math.random().toString(36).substring(2, 11)}`;
---
<div class={`gradient-text-wrapper ${className}`}>
<div class="relative inline-block">
<span class="inline-block">{baseText}</span>
<div id={id} class="gradient-text absolute top-0 left-0 right-0 bottom-0 text-transparent bg-clip-text bg-gradient-to-r from-color-primary-500 to-color-primary-700 dark:from-color-dark-primary-400 dark:to-color-dark-primary-600">
<span class="gradient-word inline-block">{words[0]}</span>
</div>
</div>
</div>
<script define:vars={{ id, words, duration }}>
document.addEventListener('DOMContentLoaded', () => {
const gradientText = document.getElementById(id);
if (!gradientText) return;
const wordElement = gradientText.querySelector('.gradient-word');
if (!wordElement) return;
let currentIndex = 0;
const updateWord = () => {
// 淡出当前词
wordElement.classList.add('opacity-0');
// 更新词并淡入
setTimeout(() => {
currentIndex = (currentIndex + 1) % words.length;
wordElement.textContent = words[currentIndex];
wordElement.classList.remove('opacity-0');
}, 300); // 300ms淡出时间
};
// 设置定时器更新词语
setInterval(updateWord, duration);
});
</script>
<style>
.gradient-word {
transition: opacity 0.3s ease;
}
.opacity-0 {
opacity: 0;
}
</style>

View File

@ -1,70 +0,0 @@
---
interface Props {
color?: string;
intensity?: 'subtle' | 'medium' | 'strong';
className?: string;
}
const {
color = "rgba(75, 107, 255, 0.5)",
intensity = 'medium',
className = ""
} = Astro.props;
// 根据强度调整发光范围
const getGlowSize = () => {
switch(intensity) {
case 'subtle': return '100px';
case 'strong': return '180px';
default: return '140px'; // medium
}
};
const glowSize = getGlowSize();
const id = `hover-glow-${Math.random().toString(36).substring(2, 11)}`;
---
<div id={id} class={`hover-glow-container relative overflow-hidden ${className}`}>
<div class="hover-glow-content relative z-10">
<slot />
</div>
<div class="hover-glow-effect absolute pointer-events-none opacity-0 transition-opacity duration-300"></div>
</div>
<style define:vars={{ glowColor: color, glowSize: glowSize }}>
.hover-glow-effect {
background: radial-gradient(
circle at var(--x, 50%) var(--y, 50%),
var(--glowColor) 0%,
transparent calc(var(--glowSize))
);
inset: -100px;
}
.hover-glow-container:hover .hover-glow-effect {
opacity: 1;
}
</style>
<script define:vars={{ id }}>
document.addEventListener('DOMContentLoaded', () => {
const container = document.getElementById(id);
if (!container) return;
const glowEffect = container.querySelector('.hover-glow-effect');
if (!glowEffect) return;
// 鼠标移动时更新发光位置
container.addEventListener('mousemove', (e) => {
const rect = container.getBoundingClientRect();
// 计算鼠标在元素内的相对位置(百分比)
const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)) * 100;
const y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height)) * 100;
// 设置自定义属性用于CSS变量
glowEffect.style.setProperty('--x', `${x}%`);
glowEffect.style.setProperty('--y', `${y}%`);
});
});
</script>

View File

@ -1,144 +0,0 @@
---
interface Props {
strength?: number; // 磁性强度1-10之间
className?: string;
radius?: number; // 影响半径(像素)
smoothing?: number; // 平滑程度 (0-1)
}
const {
strength = 5,
className = "",
radius = 150,
smoothing = 0.15
} = Astro.props;
const magnetId = `magnetic-${Math.random().toString(36).substring(2, 11)}`;
---
<div id={magnetId} class={`magnetic-element relative ${className}`}>
<div class="magnetic-content relative inline-block z-10">
<slot />
</div>
</div>
<style>
.magnetic-element {
display: inline-block;
transform-style: preserve-3d;
position: relative;
}
.magnetic-content {
position: relative;
display: inline-block;
transition: transform 0.1s cubic-bezier(0.165, 0.84, 0.44, 1);
}
</style>
<script define:vars={{ magnetId, strength, radius, smoothing }}>
document.addEventListener('DOMContentLoaded', () => {
const magneticElement = document.getElementById(magnetId);
if (!magneticElement) return;
const magneticContent = magneticElement.querySelector('.magnetic-content');
if (!magneticContent) return;
// 计算最大移动距离(基于强度)
const maxDistance = strength * 3;
let isHovered = false;
let rafId = null;
// 鼠标移动距离
let mouseX = 0;
let mouseY = 0;
// 当前位置
let currentX = 0;
let currentY = 0;
const lerp = (start, end, t) => {
return start * (1 - t) + end * t;
};
const updatePosition = () => {
if (!isHovered) {
// 如果没有悬停,逐渐回到原位
currentX = lerp(currentX, 0, smoothing);
currentY = lerp(currentY, 0, smoothing);
// 如果足够接近原点,就停止动画
if (Math.abs(currentX) < 0.1 && Math.abs(currentY) < 0.1) {
currentX = 0;
currentY = 0;
cancelAnimationFrame(rafId);
rafId = null;
magneticContent.style.transform = '';
return;
}
} else {
// 如果悬停中,平滑过渡到目标位置
currentX = lerp(currentX, mouseX, smoothing);
currentY = lerp(currentY, mouseY, smoothing);
}
// 应用变换
magneticContent.style.transform = `translate3D(${currentX}px, ${currentY}px, 0)`;
// 继续动画
rafId = requestAnimationFrame(updatePosition);
};
// 鼠标进入
magneticElement.addEventListener('mouseenter', () => {
isHovered = true;
if (!rafId) {
rafId = requestAnimationFrame(updatePosition);
}
});
// 鼠标移动
magneticElement.addEventListener('mousemove', (e) => {
if (!isHovered) return;
const rect = magneticElement.getBoundingClientRect();
// 计算鼠标相对元素中心的位置
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
// 鼠标距离中心的距离
const distX = e.clientX - centerX;
const distY = e.clientY - centerY;
// 计算总距离
const distance = Math.sqrt(distX * distX + distY * distY);
// 如果鼠标在影响半径内
if (distance < radius) {
// 计算移动百分比(越接近中心,移动越大)
const percent = (radius - distance) / radius;
// 应用强度系数,计算最终移动距离
mouseX = distX * percent * (maxDistance / radius);
mouseY = distY * percent * (maxDistance / radius);
} else {
mouseX = 0;
mouseY = 0;
}
});
// 鼠标离开
magneticElement.addEventListener('mouseleave', () => {
isHovered = false;
});
// 页面卸载时清除动画帧
window.addEventListener('beforeunload', () => {
if (rafId) {
cancelAnimationFrame(rafId);
}
});
});
</script>

View File

@ -1,159 +0,0 @@
---
interface Props {
text?: string;
href?: string;
className?: string;
color?: string;
glowSize?: 'small' | 'medium' | 'large';
glowIntensity?: 'low' | 'medium' | 'high';
pulseAnimation?: boolean;
}
const {
text = "霓虹按钮",
href,
className = "",
color = "rgb(101, 111, 247)", // 紫色
glowSize = 'medium',
glowIntensity = 'medium',
pulseAnimation = true
} = Astro.props;
// 计算发光尺寸
const getGlowSizePx = (size: string) => {
switch (size) {
case 'small': return 10;
case 'large': return 30;
default: return 20;
}
};
// 计算发光强度值
const getIntensityValue = (intensity: string) => {
switch (intensity) {
case 'low': return 0.6;
case 'high': return 1;
default: return 0.8;
}
};
const glowSizePx = getGlowSizePx(glowSize);
const intensityValue = getIntensityValue(glowIntensity);
const buttonId = `neon-btn-${Math.random().toString(36).substring(2, 11)}`;
---
<div class={`neon-button-wrapper ${pulseAnimation ? 'pulse-enabled' : ''} ${className}`}>
{href ? (
<a
id={buttonId}
href={href}
class="neon-button relative inline-block px-6 py-3 rounded-md font-medium transition-all duration-300 overflow-hidden"
>
<span class="relative z-10">{text}</span>
</a>
) : (
<button
id={buttonId}
class="neon-button relative inline-block px-6 py-3 rounded-md font-medium transition-all duration-300 overflow-hidden"
>
<span class="relative z-10">{text}</span>
</button>
)}
</div>
<style define:vars={{ color, glowSizePx, intensityValue }}>
.neon-button-wrapper {
position: relative;
display: inline-block;
}
.neon-button {
background-color: transparent;
color: var(--color);
border: 2px solid var(--color);
transition: all 0.3s ease;
transform: translateY(0);
text-shadow: 0 0 5px rgba(255, 255, 255, 0.3);
}
.neon-button:hover {
color: white;
background-color: var(--color);
box-shadow:
0 0 calc(var(--glowSizePx) * 1px) 0 var(--color),
0 0 calc(var(--glowSizePx) * 2px) 0 var(--color),
0 0 calc(var(--glowSizePx) * 3px) 0 rgba(var(--rgb-values), calc(var(--intensityValue) * 0.5));
transform: translateY(-3px);
text-shadow: 0 0 8px rgba(255, 255, 255, 0.8);
}
.pulse-enabled .neon-button:hover::before {
content: '';
position: absolute;
inset: -5px;
border-radius: inherit;
z-index: -1;
animation: neon-pulse 1.5s ease-in-out infinite;
background: transparent;
border: 2px solid var(--color);
opacity: 0;
}
@keyframes neon-pulse {
0% {
transform: scale(1);
opacity: 0.7;
}
70% {
opacity: 0;
}
100% {
transform: scale(1.5);
opacity: 0;
}
}
</style>
<script define:vars={{ buttonId, color }}>
document.addEventListener('DOMContentLoaded', () => {
const button = document.getElementById(buttonId);
if (!button) return;
// 计算RGB值以便在CSS中使用rgba
const getRGB = (colorStr) => {
// 检查是否为hex格式
if (colorStr.startsWith('#')) {
const hex = colorStr.substring(1);
return [
parseInt(hex.substring(0, 2), 16),
parseInt(hex.substring(2, 4), 16),
parseInt(hex.substring(4, 6), 16)
].join(', ');
}
// 检查是否已经是rgb格式
const rgbMatch = colorStr.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
return `${rgbMatch[1]}, ${rgbMatch[2]}, ${rgbMatch[3]}`;
}
// 默认回退颜色
return '101, 111, 247';
};
const rgbValues = getRGB(color);
button.style.setProperty('--rgb-values', rgbValues);
// 鼠标移动时的额外发光效果
button.addEventListener('mousemove', (e) => {
const rect = button.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 设置发光的中心点
button.style.setProperty('--x', `${x}px`);
button.style.setProperty('--y', `${y}px`);
});
});
</script>

View File

@ -1,237 +0,0 @@
---
interface Props {
className?: string;
glareEnabled?: boolean;
maxGlare?: number; // 0-1 之间
depth?: number; // 视差深度1-10之间
backgroundImage?: string;
}
const {
className = "",
glareEnabled = true,
maxGlare = 0.5,
depth = 5,
backgroundImage
} = Astro.props;
const cardId = `parallax-card-${Math.random().toString(36).substring(2, 11)}`;
---
<div id={cardId} class={`parallax-card ${className}`}>
{backgroundImage && (
<div class="parallax-card-background absolute inset-0 z-0" style={`background-image: url(${backgroundImage})`}></div>
)}
<div class="parallax-card-content relative z-10">
<slot />
</div>
{glareEnabled && <div class="parallax-card-glare absolute top-0 left-0 w-full h-full z-20"></div>}
</div>
<style>
.parallax-card {
position: relative;
width: 100%;
height: auto;
padding: 2rem;
border-radius: 1rem;
overflow: hidden;
background-color: rgba(255, 255, 255, 0.1);
transform-style: preserve-3d;
transform: perspective(1000px);
box-shadow: 0 10px 30px -15px rgba(0, 0, 0, 0.25);
transition: transform 0.1s ease, box-shadow 0.3s ease;
will-change: transform, box-shadow;
}
.parallax-card:hover {
box-shadow: 0 20px 40px -25px rgba(0, 0, 0, 0.3);
}
.parallax-card-background {
background-size: cover;
background-position: center;
transform-style: preserve-3d;
transition: transform 0.2s ease;
filter: blur(0);
}
.parallax-card-content {
transform-style: preserve-3d;
}
.parallax-card-glare {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(105deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.05) 80%);
transform: translateZ(1px);
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease;
}
[data-theme='dark'] .parallax-card {
background-color: rgba(30, 30, 30, 0.5);
box-shadow: 0 10px 30px -15px rgba(0, 0, 0, 0.5);
}
[data-theme='dark'] .parallax-card:hover {
box-shadow: 0 20px 40px -25px rgba(0, 0, 0, 0.7);
}
[data-theme='dark'] .parallax-card-glare {
background: linear-gradient(105deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.03) 80%);
}
</style>
<script define:vars={{ cardId, glareEnabled, maxGlare, depth }}>
document.addEventListener('DOMContentLoaded', () => {
const card = document.getElementById(cardId);
if (!card) return;
const contentItems = Array.from(card.querySelectorAll('.parallax-item'));
const background = card.querySelector('.parallax-card-background');
const glare = card.querySelector('.parallax-card-glare');
// 视差灵敏度系数
const depthFactor = depth * 0.2;
// 是否正在悬停
let isHovered = false;
// 处理鼠标移动
const handleMouseMove = (e) => {
if (!isHovered) return;
const rect = card.getBoundingClientRect();
// 计算鼠标在卡片上的相对位置 (0~1)
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
// 旋转角度(-15 ~ 15度
const rotateX = (0.5 - y) * 30 * depthFactor * 0.1;
const rotateY = (x - 0.5) * 30 * depthFactor * 0.1;
// 应用旋转
card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
// 背景视差效果
if (background) {
const bgX = (x - 0.5) * 40 * depthFactor;
const bgY = (y - 0.5) * 40 * depthFactor;
background.style.transform = `translateX(${bgX}px) translateY(${bgY}px) scale(1.05)`;
}
// 内部元素视差效果
contentItems.forEach(item => {
const depth = parseInt(item.getAttribute('data-depth') || '5') / 10;
const itemX = (x - 0.5) * 30 * depth * depthFactor;
const itemY = (y - 0.5) * 30 * depth * depthFactor;
item.style.transform = `translateX(${itemX}px) translateY(${itemY}px) translateZ(${depth * 50}px)`;
});
// 光晕效果
if (glare && glareEnabled) {
// 调整光晕角度
const glareX = x * 100;
const glareY = y * 100;
// 计算光晕透明度
const glareOpacity = Math.min(
Math.max(
Math.sqrt((x - 0.5) * (x - 0.5) + (y - 0.5) * (y - 0.5)) * 2,
0
),
maxGlare
);
glare.style.opacity = glareOpacity.toString();
glare.style.backgroundImage = `radial-gradient(circle at ${glareX}% ${glareY}%, rgba(255,255,255,${glareOpacity}) 0%, rgba(255,255,255,0) 80%)`;
}
};
// 鼠标进入
const handleMouseEnter = () => {
isHovered = true;
// 平滑过渡到3D效果
card.style.transition = 'transform 0.3s ease';
if (background) {
background.style.transition = 'transform 0.3s ease';
background.style.transformOrigin = 'center center';
}
contentItems.forEach(item => {
item.style.transition = 'transform 0.3s ease';
});
// 一定时间后移除过渡效果,实现更流畅的追踪
setTimeout(() => {
card.style.transition = '';
if (background) {
background.style.transition = '';
}
contentItems.forEach(item => {
item.style.transition = '';
});
}, 300);
};
// 鼠标离开
const handleMouseLeave = () => {
isHovered = false;
// 添加过渡效果
card.style.transition = 'transform 0.5s ease';
if (background) {
background.style.transition = 'transform 0.5s ease';
}
contentItems.forEach(item => {
item.style.transition = 'transform 0.5s ease';
});
// 重置旋转
card.style.transform = 'perspective(1000px) rotateX(0) rotateY(0)';
if (background) {
background.style.transform = 'translateX(0) translateY(0) scale(1)';
}
contentItems.forEach(item => {
item.style.transform = 'translateX(0) translateY(0) translateZ(0)';
});
if (glare) {
glare.style.opacity = '0';
}
};
// 移动端触摸处理
const handleTouchMove = (e) => {
if (e.touches.length !== 1) return;
const touch = e.touches[0];
handleMouseMove({
clientX: touch.clientX,
clientY: touch.clientY
});
};
// 添加事件监听
card.addEventListener('mouseenter', handleMouseEnter);
card.addEventListener('mousemove', handleMouseMove);
card.addEventListener('mouseleave', handleMouseLeave);
card.addEventListener('touchmove', handleTouchMove);
card.addEventListener('touchstart', handleMouseEnter);
card.addEventListener('touchend', handleMouseLeave);
});
</script>

View File

@ -1,91 +0,0 @@
---
interface Props {
images: Array<{
src: string;
alt: string;
speed?: number; // 滚动速度因子,正数向下,负数向上
}>;
heading: string;
subheading?: string;
className?: string;
}
const { images, heading, subheading, className = "" } = Astro.props;
---
<section class={`relative overflow-hidden ${className}`} id="parallax-section">
<!-- 背景图层 -->
<div class="absolute inset-0">
<div class="absolute inset-0 bg-black/60 z-10"></div>
{
images.map((image, index) => (
<div
class="absolute inset-0 parallax-layer transition-transform duration-100"
data-speed={image.speed || 0.2}
style={{
zIndex: index,
transform: "translateY(0px)",
}}
>
<div class="absolute inset-0 bg-gray-500 flex items-center justify-center">
<span class="text-white text-lg">{image.alt}</span>
</div>
</div>
))
}
</div>
<!-- 内容 -->
<div class="relative z-20 min-h-[60vh] flex items-center justify-center text-center py-20 px-4">
<div class="max-w-4xl mx-auto">
<h2 class="text-3xl sm:text-5xl font-bold text-white mb-4 tracking-tight">
{heading}
</h2>
{
subheading && (
<p class="text-xl text-gray-200 max-w-2xl mx-auto">{subheading}</p>
)
}
<slot />
</div>
</div>
</section>
<script>
// 简单的视差滚动效果
document.addEventListener('DOMContentLoaded', () => {
const parallaxLayers = document.querySelectorAll('.parallax-layer');
function updateParallax() {
const scrollY = window.scrollY;
const section = document.getElementById('parallax-section');
if (!section) return;
const sectionRect = section.getBoundingClientRect();
const sectionTop = sectionRect.top + scrollY;
const sectionCenter = sectionTop + sectionRect.height / 2;
// 仅当部分在视口中时更新视差效果
if (
scrollY + window.innerHeight >= sectionTop &&
scrollY <= sectionTop + sectionRect.height
) {
const relativeScroll = scrollY - sectionTop;
parallaxLayers.forEach(layer => {
const speed = parseFloat(layer.getAttribute('data-speed') || '0.2');
const yOffset = relativeScroll * speed;
(layer as HTMLElement).style.transform = `translateY(${yOffset}px)`;
});
}
}
// 初始更新
updateParallax();
// 滚动事件监听
window.addEventListener('scroll', updateParallax);
window.addEventListener('resize', updateParallax);
});
</script>

View File

@ -1,129 +0,0 @@
---
interface Props {
text?: string;
href?: string;
className?: string;
color?: string;
particleCount?: number;
}
const {
text = "点击我",
href,
className = "",
color = "#6366f1",
particleCount = 30
} = Astro.props;
const buttonId = `particle-btn-${Math.random().toString(36).substring(2, 11)}`;
---
<div class={`particle-button-container ${className}`}>
{href ? (
<a
href={href}
id={buttonId}
class="particle-button relative inline-block px-6 py-3 rounded-md bg-color-primary-600 hover:bg-color-primary-700 text-white font-medium transition-all duration-300 overflow-hidden"
>
<span class="relative z-10">{text}</span>
</a>
) : (
<button
id={buttonId}
class="particle-button relative inline-block px-6 py-3 rounded-md bg-color-primary-600 hover:bg-color-primary-700 text-white font-medium transition-all duration-300 overflow-hidden"
>
<span class="relative z-10">{text}</span>
</button>
)}
<div class="particles-container"></div>
</div>
<style>
.particle-button-container {
position: relative;
display: inline-block;
}
.particle-button {
transform: translateY(0);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.particle-button:hover {
transform: translateY(-3px);
box-shadow: 0 7px 14px rgba(0, 0, 0, 0.15);
}
.particle {
position: absolute;
border-radius: 50%;
pointer-events: none;
opacity: 0;
}
</style>
<script define:vars={{ buttonId, color, particleCount }}>
document.addEventListener('DOMContentLoaded', () => {
const button = document.getElementById(buttonId);
if (!button) return;
const container = button.closest('.particle-button-container');
if (!container) return;
const particles = [];
// 创建粒子元素
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
particle.classList.add('particle');
particle.style.backgroundColor = color;
container.appendChild(particle);
particles.push(particle);
}
button.addEventListener('mouseenter', (e) => {
const buttonRect = button.getBoundingClientRect();
const centerX = buttonRect.left + buttonRect.width / 2;
const centerY = buttonRect.top + buttonRect.height / 2;
particles.forEach((particle, index) => {
// 随机大小
const size = Math.random() * 8 + 4;
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
// 初始位置为按钮中心
particle.style.left = `${buttonRect.width / 2}px`;
particle.style.top = `${buttonRect.height / 2}px`;
// 随机方向和距离
const angle = Math.random() * Math.PI * 2;
const distance = Math.random() * 90 + 30;
const destinationX = Math.cos(angle) * distance;
const destinationY = Math.sin(angle) * distance;
// 随机动画时间
const duration = Math.random() * 1000 + 500;
const delay = Math.random() * 200;
// 设置动画
particle.style.transition = `all ${duration}ms ease-out ${delay}ms`;
// 设置透明度
particle.style.opacity = '0';
// 触发重绘,然后应用动画
setTimeout(() => {
particle.style.opacity = '1';
particle.style.transform = `translate(${destinationX}px, ${destinationY}px)`;
// 动画结束后重置
setTimeout(() => {
particle.style.opacity = '0';
particle.style.transform = `translate(${buttonRect.width / 2}px, ${buttonRect.height / 2}px)`;
}, duration + delay);
}, 10);
});
});
});
</script>

View File

@ -59,6 +59,30 @@ const initialStyles = getInitialStyles();
document.addEventListener('DOMContentLoaded', () => {
const revealElements = document.querySelectorAll('.scroll-reveal');
// 检查是否为移动端设备
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
// 如果是移动端,直接显示所有元素,不使用动画
if (isMobile) {
revealElements.forEach((element) => {
const el = element as HTMLElement;
el.style.opacity = '1';
el.style.transform = 'none';
});
return;
}
// 检查是否支持 IntersectionObserver
if (!('IntersectionObserver' in window)) {
// 如果不支持,也直接显示所有元素
revealElements.forEach((element) => {
const el = element as HTMLElement;
el.style.opacity = '1';
el.style.transform = 'none';
});
return;
}
const observerOptions = {
root: null, // 使用视口作为根元素
rootMargin: '0px',
@ -116,6 +140,16 @@ const initialStyles = getInitialStyles();
});
}, observerOptions);
// 为每个元素添加超时保障,确保元素最终会显示
revealElements.forEach((element) => {
const el = element as HTMLElement;
// 设置一个3秒的超时确保即使IntersectionObserver失效元素也会显示
setTimeout(() => {
el.style.opacity = '1';
el.style.transform = 'none';
}, 3000);
});
// 开始观察所有滚动显示元素
revealElements.forEach(element => {
// 设置自定义阈值

View File

@ -1,265 +0,0 @@
---
interface Props {
title: string;
description?: string;
image?: string;
link?: string;
className?: string;
color?: string;
spotlightSize?: 'small' | 'medium' | 'large';
spotlightIntensity?: 'subtle' | 'medium' | 'strong';
}
const {
title,
description,
image,
link,
className = "",
color = "rgba(75, 107, 255, 0.3)", // 默认是蓝色光晕
spotlightSize = 'medium',
spotlightIntensity = 'medium'
} = Astro.props;
const id = `spotlight-card-${Math.random().toString(36).substring(2, 11)}`;
// 计算光晕大小
const sizeMap = {
small: '70%',
medium: '100%',
large: '130%'
};
// 计算光晕强度
const intensityMap = {
subtle: '0.15',
medium: '0.25',
strong: '0.4'
};
const spotlightSizeValue = sizeMap[spotlightSize];
const spotlightIntensityValue = intensityMap[spotlightIntensity];
---
<div
id={id}
class={`spotlight-card group relative overflow-hidden rounded-xl border border-white/10 dark:bg-color-dark-card bg-white p-6 shadow-lg transition-all hover:shadow-xl ${className}`}
style="transform-style: preserve-3d; transform: perspective(1000px);"
data-spotlight-size={spotlightSizeValue}
data-spotlight-intensity={spotlightIntensityValue}
>
<div class="spotlight-primary absolute pointer-events-none inset-0 z-0 transition duration-300 opacity-0"></div>
<div class="spotlight-secondary absolute pointer-events-none inset-0 z-0 transition duration-300 opacity-0"></div>
<div class="spotlight-border absolute pointer-events-none inset-0 z-0 transition duration-300 opacity-0"></div>
{image && (
<div class="mb-4 overflow-hidden rounded-lg">
<div class="h-40 bg-gray-300 dark:bg-gray-700 flex items-center justify-center">
<span class="text-gray-500 dark:text-gray-400">{title} 图片</span>
</div>
</div>
)}
<h3 class="group-hover:text-color-primary-600 dark:group-hover:text-color-primary-400 text-lg font-semibold text-gray-900 dark:text-white mb-2 transition-colors">
{title}
</h3>
{description && (
<p class="text-gray-600 dark:text-gray-400 mb-4 text-sm">
{description}
</p>
)}
{link && (
<a
href={link}
class="text-color-primary-600 hover:text-color-primary-700 dark:text-color-primary-400 dark:hover:text-color-primary-300 font-medium text-sm"
>
查看详情 &rarr;
</a>
)}
<slot />
</div>
<style define:vars={{
spotlightColor: color,
spotlightSize: spotlightSizeValue,
spotlightIntensity: spotlightIntensityValue
}}>
.spotlight-primary {
background: radial-gradient(
circle at center,
var(--spotlightColor) 0%,
transparent 70%
);
filter: blur(5px);
opacity: 0;
/* 确保径向渐变是一个完美的圆 */
border-radius: 50%;
}
.spotlight-secondary {
background: radial-gradient(
circle at center,
rgba(255, 255, 255, 0.1) 0%,
transparent 60%
);
opacity: 0;
/* 确保径向渐变是一个完美的圆 */
border-radius: 50%;
}
.spotlight-border {
background: none;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: inherit;
opacity: 0;
}
[data-theme='dark'] .spotlight-primary {
background: radial-gradient(
circle at center,
var(--spotlightColor) 0%,
transparent 70%
);
}
[data-theme='dark'] .spotlight-secondary {
background: radial-gradient(
circle at center,
rgba(255, 255, 255, 0.07) 0%,
transparent 60%
);
}
</style>
<script>
// 等待 DOM 加载完成
document.addEventListener('DOMContentLoaded', () => {
// 获取所有光标效果卡片
const cards = document.querySelectorAll('.spotlight-card');
cards.forEach(card => {
const primaryEffect = card.querySelector('.spotlight-primary');
const secondaryEffect = card.querySelector('.spotlight-secondary');
const borderEffect = card.querySelector('.spotlight-border');
if (!primaryEffect || !secondaryEffect || !borderEffect) return;
// 获取配置
const size = card.getAttribute('data-spotlight-size') || '100%';
const intensity = parseFloat(card.getAttribute('data-spotlight-intensity') || '0.25');
// 上一次鼠标位置
let lastX = 0;
let lastY = 0;
// 平滑因子 (值越小越平滑)
const smoothFactor = 0.15;
// 用于动画的requestAnimationFrame ID
let animationFrameId: number | null = null;
// 鼠标移入时添加效果
card.addEventListener('mouseenter', () => {
// 计算光效尺寸 - 使用卡片对角线长度确保覆盖整个卡片区域
const rect = card.getBoundingClientRect();
const diagonalLength = Math.sqrt(rect.width * rect.width + rect.height * rect.height);
const effectSize = diagonalLength * 1.5 + 'px'; // 增加50%确保覆盖
// 设置光效元素尺寸
(primaryEffect as HTMLElement).style.width = effectSize;
(primaryEffect as HTMLElement).style.height = effectSize;
(secondaryEffect as HTMLElement).style.width = effectSize;
(secondaryEffect as HTMLElement).style.height = effectSize;
// 设置可见度
(primaryEffect as HTMLElement).style.opacity = intensity.toString();
(secondaryEffect as HTMLElement).style.opacity = (intensity * 0.7).toString();
(borderEffect as HTMLElement).style.opacity = (intensity * 0.5).toString();
// 启动平滑动画
if (animationFrameId === null) {
animateSpotlight();
}
});
// 鼠标移出时移除效果
card.addEventListener('mouseleave', () => {
(primaryEffect as HTMLElement).style.opacity = '0';
(secondaryEffect as HTMLElement).style.opacity = '0';
(borderEffect as HTMLElement).style.opacity = '0';
// 重置卡片的 3D 效果
(card as HTMLElement).style.transform = 'perspective(1000px)';
// 取消动画
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
});
// 鼠标移动时更新光标位置和 3D 效果
card.addEventListener('mousemove', (e) => {
const mouseEvent = e as MouseEvent;
const rect = card.getBoundingClientRect();
lastX = mouseEvent.clientX - rect.left;
lastY = mouseEvent.clientY - rect.top;
// 计算 3D 旋转效果
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const rotateY = (lastX - centerX) / 20;
const rotateX = (centerY - lastY) / 20;
// 应用 3D 效果
(card as HTMLElement).style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
});
// 平滑动画函数
function animateSpotlight() {
const rect = card.getBoundingClientRect();
// 获取当前位置
const currentPrimaryStyle = window.getComputedStyle(primaryEffect as Element);
const currentSecondaryStyle = window.getComputedStyle(secondaryEffect as Element);
// 解析当前位置
let currentX = parseInt(currentPrimaryStyle.left || '0');
let currentY = parseInt(currentPrimaryStyle.top || '0');
if (isNaN(currentX)) currentX = rect.width / 2;
if (isNaN(currentY)) currentY = rect.height / 2;
// 计算新位置 (平滑过渡)
const newX = currentX + (lastX - currentX) * smoothFactor;
const newY = currentY + (lastY - currentY) * smoothFactor;
// 应用位置
// 不再在这里设置宽高而是在mouseenter时设置一次
(primaryEffect as HTMLElement).style.left = `${newX}px`;
(primaryEffect as HTMLElement).style.top = `${newY}px`;
(primaryEffect as HTMLElement).style.transform = `translate(-50%, -50%)`;
(secondaryEffect as HTMLElement).style.left = `${newX}px`;
(secondaryEffect as HTMLElement).style.top = `${newY}px`;
(secondaryEffect as HTMLElement).style.transform = `translate(-50%, -50%) scale(1.2)`;
// 边框位置不需要移动,但可以随鼠标位置变化透明度
const distanceFromCenter = Math.sqrt(
Math.pow((newX / rect.width - 0.5) * 2, 2) +
Math.pow((newY / rect.height - 0.5) * 2, 2)
);
// 越靠近边缘,边框越明显
const borderOpacity = Math.min(distanceFromCenter * intensity, intensity * 0.5);
(borderEffect as HTMLElement).style.opacity = borderOpacity.toString();
// 继续动画循环
animationFrameId = requestAnimationFrame(animateSpotlight);
}
});
});
</script>

View File

@ -1,117 +0,0 @@
---
interface Props {
title: string;
description?: string;
image?: string;
imageAlt?: string;
link?: string;
linkText?: string;
tags?: string[];
className?: string;
}
const {
title,
description,
image,
imageAlt = "",
link,
linkText = "详细信息",
tags = [],
className = ""
} = Astro.props;
---
<div class={`carousel-card flex-shrink-0 w-full md:w-80 bg-white dark:bg-color-dark-card rounded-xl overflow-hidden shadow-md hover:shadow-xl transition-all duration-300 ${className}`}>
{image && (
<div class="h-48 overflow-hidden relative">
<div class="w-full h-full bg-gray-300 dark:bg-gray-700 flex items-center justify-center card-image-container">
<span class="text-gray-500 dark:text-gray-400">{imageAlt || title}</span>
</div>
<div class="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent opacity-0 transition-opacity duration-300 card-overlay"></div>
</div>
)}
<div class="p-5">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2 line-clamp-1 transition-colors duration-300">{title}</h3>
{description && (
<p class="text-gray-600 dark:text-gray-400 mb-3 text-sm line-clamp-2">{description}</p>
)}
{tags.length > 0 && (
<div class="flex flex-wrap gap-1 mb-3">
{tags.map(tag => (
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 text-xs rounded-full dark:bg-color-dark-surface dark:text-gray-400 transition-colors duration-300 card-tag">
{tag}
</span>
))}
</div>
)}
<slot />
{link && (
<a
href={link}
class="inline-block mt-2 text-color-primary-600 hover:text-color-primary-700 dark:text-color-primary-400 dark:hover:text-color-primary-300 text-sm font-medium transition-transform duration-300 card-link"
>
{linkText} &rarr;
</a>
)}
</div>
</div>
<style>
.carousel-card {
transform: translateZ(0); /* 启用硬件加速,使动画更流畅 */
will-change: transform, opacity; /* 提前告知浏览器哪些属性会变化 */
transition: transform 0.3s ease, opacity 0.3s ease, box-shadow 0.3s ease;
border: 1px solid transparent;
}
.carousel-card:hover {
transform: translateY(-5px) scale(1.02);
border-color: rgba(99, 102, 241, 0.3);
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1);
}
[data-theme='dark'] .carousel-card:hover {
border-color: rgba(99, 102, 241, 0.3);
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3);
}
.carousel-card:hover .card-overlay {
opacity: 1;
}
.carousel-card:hover .card-image-container {
transform: scale(1.05);
transition: transform 0.6s ease;
}
.card-image-container {
transition: transform 0.3s ease;
transform-origin: center;
}
.carousel-card:hover h3 {
color: rgb(99, 102, 241);
}
[data-theme='dark'] .carousel-card:hover h3 {
color: rgb(129, 140, 248);
}
.carousel-card:hover .card-link {
transform: translateX(4px);
}
.carousel-card:hover .card-tag {
background-color: rgba(99, 102, 241, 0.1);
}
[data-theme='dark'] .carousel-card:hover .card-tag {
background-color: rgba(99, 102, 241, 0.2);
}
</style>

View File

@ -1,69 +0,0 @@
---
interface Props {
title: string;
description?: string;
image?: string;
imageAlt?: string;
link?: string;
linkText?: string;
tags?: string[];
location?: string;
className?: string;
}
const {
title,
description,
image,
imageAlt = "",
link,
linkText = "详细信息",
tags = [],
location,
className = ""
} = Astro.props;
---
<div class={`bg-white dark:bg-color-dark-card rounded-lg overflow-hidden shadow-md hover:shadow-lg transition-shadow ${className}`}>
{image && (
<div class="h-56 overflow-hidden relative">
<div class="w-full h-full bg-gray-300 dark:bg-gray-700 flex items-center justify-center">
<span class="text-gray-500 dark:text-gray-400">{imageAlt || title}</span>
</div>
{location && (
<div class="absolute top-3 left-3 bg-white dark:bg-color-dark-bg px-2 py-1 rounded text-sm font-medium text-gray-700 dark:text-gray-300">
{location}
</div>
)}
</div>
)}
<div class="p-6">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">{title}</h3>
{description && (
<p class="text-gray-600 dark:text-gray-400 mb-4 line-clamp-3">{description}</p>
)}
{tags.length > 0 && (
<div class="flex flex-wrap gap-2 mb-4">
{tags.map(tag => (
<span class="px-2 py-1 bg-gray-100 text-gray-600 text-xs rounded-full dark:bg-color-dark-surface dark:text-gray-400">
{tag}
</span>
))}
</div>
)}
<slot />
{link && (
<a
href={link}
class="inline-block mt-2 text-color-primary-600 hover:text-color-primary-700 dark:text-color-primary-400 dark:hover:text-color-primary-300 font-medium"
>
{linkText} &rarr;
</a>
)}
</div>
</div>

View File

@ -7,7 +7,7 @@ interface Props {
const { title, subtitle } = Astro.props;
---
<section class="py-12 bg-gradient-to-r from-color-primary-500 to-color-primary-700 text-white dark:from-color-dark-primary-800 dark:to-color-dark-primary-950">
<section class="py-12 bg-gradient-to-r from-amber-500 to-amber-700 text-white dark:from-amber-800/70 dark:to-amber-950/80">
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
<div class="max-w-4xl mx-auto text-center">
<h1 class="text-4xl font-bold mb-4">{title}</h1>

View File

@ -1,61 +0,0 @@
---
interface Props {
bgColor?: 'white' | 'gray' | 'primary' | 'none';
padding?: 'none' | 'sm' | 'md' | 'lg' | 'xl';
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | 'full' | 'none';
className?: string;
}
const {
bgColor = 'white',
padding = 'lg',
maxWidth = 'lg',
className = ""
} = Astro.props;
// 设置背景颜色
const getBgColorClass = () => {
switch(bgColor) {
case 'white': return 'bg-white dark:bg-color-dark-bg';
case 'gray': return 'bg-gray-100 dark:bg-color-dark-surface';
case 'primary': return 'bg-color-primary-50 dark:bg-color-dark-primary-50';
case 'none': return '';
default: return 'bg-white dark:bg-color-dark-bg';
}
};
// 设置内边距
const getPaddingClass = () => {
switch(padding) {
case 'none': return '';
case 'sm': return 'py-4 px-4';
case 'md': return 'py-8 px-4 sm:px-6';
case 'lg': return 'py-12 px-4 sm:px-6 lg:px-8';
case 'xl': return 'py-16 px-4 sm:px-6 lg:px-8';
default: return 'py-12 px-4 sm:px-6 lg:px-8';
}
};
// 设置最大宽度
const getMaxWidthClass = () => {
switch(maxWidth) {
case 'none': return '';
case 'sm': return 'max-w-2xl mx-auto';
case 'md': return 'max-w-4xl mx-auto';
case 'lg': return 'max-w-7xl mx-auto';
case 'xl': return 'max-w-screen-2xl mx-auto';
case 'full': return 'w-full';
default: return 'max-w-7xl mx-auto';
}
};
const bgColorClass = getBgColorClass();
const paddingClass = getPaddingClass();
const maxWidthClass = getMaxWidthClass();
---
<section class={`${bgColorClass} ${paddingClass} ${className}`}>
<div class={maxWidthClass}>
<slot />
</div>
</section>

View File

@ -1,39 +0,0 @@
---
interface Props {
title: string;
subtitle?: string;
alignment?: 'left' | 'center' | 'right';
marginBottom?: string;
className?: string;
}
const {
title,
subtitle,
alignment = 'center',
marginBottom = 'mb-8',
className = ""
} = Astro.props;
// 设置对齐方式
const getAlignmentClass = () => {
switch(alignment) {
case 'left': return 'text-left';
case 'right': return 'text-right';
case 'center': return 'text-center';
default: return 'text-center';
}
};
const alignmentClass = getAlignmentClass();
---
<div class={`${alignmentClass} ${marginBottom} ${className}`}>
<h2 class="text-3xl font-bold text-gray-900 dark:text-white">{title}</h2>
{subtitle && (
<p class="mt-3 text-lg text-gray-600 dark:text-gray-400 max-w-3xl mx-auto">
{subtitle}
</p>
)}
<slot />
</div>

View File

@ -1,331 +0,0 @@
---
interface Props {
autoplay?: boolean;
autoplaySpeed?: number; // 毫秒
showArrows?: boolean;
showDots?: boolean;
cardGap?: number; // px
className?: string;
}
const {
autoplay = true,
autoplaySpeed = 5000,
showArrows = true,
showDots = true,
cardGap = 16,
className = ""
} = Astro.props;
const carouselId = `carousel-${Math.random().toString(36).substring(2, 11)}`;
---
<div class={`smooth-card-carousel relative overflow-hidden group ${className}`} id={carouselId}>
<!-- 轮播容器 -->
<div class="carousel-container relative">
<!-- 轮播轨道 -->
<div class="carousel-track flex transition-transform duration-500 ease-out cursor-grab">
<slot />
</div>
<!-- 控制按钮 -->
{showArrows && (
<div class="carousel-controls">
<button class="carousel-prev absolute left-4 top-1/2 -translate-y-1/2 z-10 bg-white bg-opacity-70 dark:bg-color-dark-card dark:bg-opacity-70 rounded-full p-2 shadow-md opacity-40 group-hover:opacity-100 transition-opacity hover:bg-opacity-90 dark:hover:bg-opacity-90 focus:outline-none hover:scale-110">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-700 dark:text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
<span class="sr-only">上一个</span>
</button>
<button class="carousel-next absolute right-4 top-1/2 -translate-y-1/2 z-10 bg-white bg-opacity-70 dark:bg-color-dark-card dark:bg-opacity-70 rounded-full p-2 shadow-md opacity-40 group-hover:opacity-100 transition-opacity hover:bg-opacity-90 dark:hover:bg-opacity-90 focus:outline-none hover:scale-110">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-700 dark:text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
<span class="sr-only">下一个</span>
</button>
</div>
)}
<!-- 指示点 -->
{showDots && (
<div class="carousel-dots absolute bottom-3 left-0 right-0 flex justify-center space-x-2 z-10">
<!-- 指示点将通过JS动态生成 -->
</div>
)}
</div>
</div>
<style define:vars={{ cardGap: `${cardGap}px` }}>
.smooth-card-carousel {
position: relative;
overflow: hidden;
}
.carousel-track {
display: flex;
transition: transform 0.5s ease-out;
gap: var(--cardGap);
padding: 8px 4px; /* 添加内边距,让阴影效果完全显示 */
}
.carousel-track.grabbing {
cursor: grabbing;
transition: none;
}
.carousel-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.5);
transition: all 0.3s ease;
}
.carousel-dot.active {
width: 10px;
height: 10px;
background-color: white;
}
/* 淡入淡出效果 */
.carousel-track > * {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.carousel-track .card-faded {
opacity: 0.6;
transform: scale(0.95);
}
/* 导航按钮悬停效果 */
.carousel-prev,
.carousel-next {
transition: opacity 0.3s ease, transform 0.2s ease, background-color 0.3s ease;
}
.carousel-container::before,
.carousel-container::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 60px;
z-index: 5;
pointer-events: none;
}
.carousel-container::before {
left: 0;
background: linear-gradient(to right, rgba(255,255,255,0.5), transparent);
}
.carousel-container::after {
right: 0;
background: linear-gradient(to left, rgba(255,255,255,0.5), transparent);
}
/* 暗黑模式下的渐变适配 */
[data-theme='dark'] .carousel-container::before {
background: linear-gradient(to right, rgba(0,0,0,0.3), transparent);
}
[data-theme='dark'] .carousel-container::after {
background: linear-gradient(to left, rgba(0,0,0,0.3), transparent);
}
</style>
<script define:vars={{ carouselId, autoplay, autoplaySpeed }}>
document.addEventListener('DOMContentLoaded', () => {
const carousel = document.getElementById(carouselId);
if (!carousel) return;
const track = carousel.querySelector('.carousel-track');
const cards = Array.from(track.children);
const carouselContainer = carousel.querySelector('.carousel-container');
const prevBtn = carousel.querySelector('.carousel-prev');
const nextBtn = carousel.querySelector('.carousel-next');
const dotsContainer = carousel.querySelector('.carousel-dots');
let currentIndex = 0;
let startX, startScrollLeft, cardWidth, totalCards, visibleCards;
let autoplayInterval;
let isDragging = false;
// 初始化
const initialize = () => {
if (cards.length === 0) return;
// 计算一张卡片的宽度(包括间距)
cardWidth = cards[0].offsetWidth + parseInt(getComputedStyle(track).gap);
// 计算可见卡片数量和总卡片数
visibleCards = Math.floor(carouselContainer.offsetWidth / cardWidth);
totalCards = cards.length;
// 克隆卡片以实现无限滚动
if (totalCards < visibleCards * 2) {
for (let i = 0; i < totalCards; i++) {
const clone = cards[i].cloneNode(true);
track.appendChild(clone);
}
// 更新卡片数组
cards.push(...Array.from(track.children).slice(totalCards));
totalCards = cards.length;
}
// 创建指示点
if (dotsContainer) {
dotsContainer.innerHTML = '';
const numDots = Math.ceil(totalCards / visibleCards);
for (let i = 0; i < numDots; i++) {
const dot = document.createElement('button');
dot.classList.add('carousel-dot');
dot.setAttribute('aria-label', `滑动到第${i + 1}组卡片`);
if (i === 0) dot.classList.add('active');
dot.addEventListener('click', () => {
goToSlide(i * visibleCards);
});
dotsContainer.appendChild(dot);
}
}
// 开始自动播放
if (autoplay) {
startAutoplay();
}
applyCardStyles();
};
// 设置卡片样式(淡入淡出效果)
const applyCardStyles = () => {
cards.forEach((card, index) => {
if (index < currentIndex || index >= currentIndex + visibleCards) {
card.classList.add('card-faded');
} else {
card.classList.remove('card-faded');
}
});
};
// 滚动到指定滑块
const goToSlide = (index) => {
// 防止越界
if (index < 0) {
index = totalCards - visibleCards;
} else if (index >= totalCards) {
index = 0;
}
currentIndex = index;
track.style.transform = `translateX(-${currentIndex * cardWidth}px)`;
// 更新指示点
if (dotsContainer) {
const dots = Array.from(dotsContainer.children);
const activeDotIndex = Math.floor(currentIndex / visibleCards);
dots.forEach((dot, i) => {
if (i === activeDotIndex) {
dot.classList.add('active');
} else {
dot.classList.remove('active');
}
});
}
applyCardStyles();
};
// 下一张
const nextSlide = () => {
goToSlide(currentIndex + visibleCards);
};
// 上一张
const prevSlide = () => {
goToSlide(currentIndex - visibleCards);
};
// 鼠标/触摸事件处理
const dragStart = (e) => {
isDragging = true;
startX = e.type.includes('mouse') ? e.pageX : e.touches[0].pageX;
startScrollLeft = currentIndex * cardWidth;
track.classList.add('grabbing');
// 暂停自动播放
if (autoplay) {
clearInterval(autoplayInterval);
}
};
const dragging = (e) => {
if (!isDragging) return;
e.preventDefault();
const x = e.type.includes('mouse') ? e.pageX : e.touches[0].pageX;
const walk = startX - x;
track.style.transform = `translateX(-${startScrollLeft + walk}px)`;
};
const dragEnd = () => {
if (!isDragging) return;
isDragging = false;
track.classList.remove('grabbing');
const threshold = cardWidth / 3;
const walk = parseInt(track.style.transform.match(/-?\d+/) || 0);
const targetIndex = Math.round(walk / cardWidth);
goToSlide(targetIndex);
// 重新开始自动播放
if (autoplay) {
startAutoplay();
}
};
// 自动播放
const startAutoplay = () => {
if (autoplayInterval) clearInterval(autoplayInterval);
autoplayInterval = setInterval(nextSlide, autoplaySpeed);
};
// 事件监听
if (prevBtn) prevBtn.addEventListener('click', prevSlide);
if (nextBtn) nextBtn.addEventListener('click', nextSlide);
// 鼠标拖动
track.addEventListener('mousedown', dragStart);
window.addEventListener('mousemove', dragging);
window.addEventListener('mouseup', dragEnd);
// 触摸拖动
track.addEventListener('touchstart', dragStart);
window.addEventListener('touchmove', dragging);
window.addEventListener('touchend', dragEnd);
// 鼠标悬停时暂停自动播放
carousel.addEventListener('mouseenter', () => {
if (autoplay) clearInterval(autoplayInterval);
});
carousel.addEventListener('mouseleave', () => {
if (autoplay) startAutoplay();
});
// 窗口调整大小时重新初始化
window.addEventListener('resize', initialize);
// 初始化
initialize();
});
</script>

View File

@ -11,16 +11,11 @@ interface Props {
const { title, description = "河北游礼宣传网站 - 探索河北的文化与魅力" } = Astro.props;
// 社交媒体数据
const socialLinks = [
{ name: "微信", url: "#" },
{ name: "微博", url: "#" },
{ name: "抖音", url: "#" }
];
---
<!DOCTYPE html>
<html lang="zh-CN">
<html lang="zh-CN" class="no-js">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@ -29,36 +24,39 @@ const socialLinks = [
<meta name="description" content={description} />
<title>{title}</title>
<DarkModeTransition />
<script>
document.documentElement.classList.remove('no-js');
</script>
</head>
<body>
<SmoothScroll />
<div class="min-h-screen flex flex-col">
<header class="sticky top-0 z-50 bg-white dark:bg-color-dark-surface">
<!-- 导航栏 - 完全脱离文档流 -->
<header class="fixed top-0 left-0 right-0 z-50 pointer-events-none">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<!-- 网站Logo和名称 -->
<div class="flex items-center">
<div class="flex items-center pointer-events-auto">
<a href="/" class="flex items-center">
<span class="text-xl font-bold text-color-primary-600 dark:text-color-primary-400">河北游礼</span>
<span class="text-xl font-bold text-color-primary-600 dark:text-color-primary-400 text-shadow-sm">河北游礼</span>
</a>
</div>
<!-- 导航菜单 -->
<nav class="hidden md:flex space-x-8">
<a href="/" class="text-gray-700 hover:text-color-primary-600 dark:text-gray-300 dark:hover:text-color-primary-400">首页</a>
<a href="/attractions" class="text-gray-700 hover:text-color-primary-600 dark:text-gray-300 dark:hover:text-color-primary-400">景点</a>
<a href="/culture" class="text-gray-700 hover:text-color-primary-600 dark:text-gray-300 dark:hover:text-color-primary-400">文化</a>
<a href="/cuisine" class="text-gray-700 hover:text-color-primary-600 dark:text-gray-300 dark:hover:text-color-primary-400">美食</a>
<a href="/travel" class="text-gray-700 hover:text-color-primary-600 dark:text-gray-300 dark:hover:text-color-primary-400">旅游攻略</a>
<nav class="hidden md:flex space-x-8 pointer-events-auto">
<a href="/" class="text-gray-800 hover:text-color-primary-600 dark:text-gray-200 dark:hover:text-color-primary-400 text-shadow-sm">首页</a>
<a href="/attractions" class="text-gray-800 hover:text-color-primary-600 dark:text-gray-200 dark:hover:text-color-primary-400 text-shadow-sm">景点</a>
<a href="/culture" class="text-gray-800 hover:text-color-primary-600 dark:text-gray-200 dark:hover:text-color-primary-400 text-shadow-sm">文化</a>
<a href="/cuisine" class="text-gray-800 hover:text-color-primary-600 dark:text-gray-200 dark:hover:text-color-primary-400 text-shadow-sm">美食</a>
<a href="/travel" class="text-gray-800 hover:text-color-primary-600 dark:text-gray-200 dark:hover:text-color-primary-400 text-shadow-sm">旅游攻略</a>
</nav>
<!-- 主题切换按钮和移动端菜单 -->
<div class="flex items-center">
<div class="flex items-center pointer-events-auto">
<ThemeToggle />
<!-- 移动端菜单按钮 -->
<button id="mobileMenuToggle" aria-label="打开菜单" class="md:hidden ml-2 p-2 rounded-full hover:bg-gray-200 dark:hover:bg-color-dark-card">
<button id="mobileMenuToggle" aria-label="打开菜单" class="md:hidden ml-2 p-2 rounded-full hover:bg-white/20 dark:hover:bg-black/20">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-800 dark:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
@ -67,56 +65,23 @@ const socialLinks = [
</div>
<!-- 移动端菜单 -->
<div id="mobileMenu" class="md:hidden hidden pb-3">
<div id="mobileMenu" class="md:hidden hidden pb-3 bg-white/90 dark:bg-black/90 backdrop-blur-md mt-1 rounded-lg shadow-lg pointer-events-auto">
<div class="flex flex-col space-y-2">
<a href="/" class="px-3 py-2 rounded-md text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-color-dark-card">首页</a>
<a href="/attractions" class="px-3 py-2 rounded-md text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-color-dark-card">景点</a>
<a href="/culture" class="px-3 py-2 rounded-md text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-color-dark-card">文化</a>
<a href="/cuisine" class="px-3 py-2 rounded-md text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-color-dark-card">美食</a>
<a href="/travel" class="px-3 py-2 rounded-md text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-color-dark-card">旅游攻略</a>
<a href="/" class="px-3 py-2 rounded-md text-gray-700 hover:bg-white/20 dark:text-gray-300 dark:hover:bg-white/10">首页</a>
<a href="/attractions" class="px-3 py-2 rounded-md text-gray-700 hover:bg-white/20 dark:text-gray-300 dark:hover:bg-white/10">景点</a>
<a href="/culture" class="px-3 py-2 rounded-md text-gray-700 hover:bg-white/20 dark:text-gray-300 dark:hover:bg-white/10">文化</a>
<a href="/cuisine" class="px-3 py-2 rounded-md text-gray-700 hover:bg-white/20 dark:text-gray-300 dark:hover:bg-white/10">美食</a>
<a href="/travel" class="px-3 py-2 rounded-md text-gray-700 hover:bg-white/20 dark:text-gray-300 dark:hover:bg-white/10">旅游攻略</a>
</div>
</div>
</div>
</header>
<!-- 页面主体内容 - 完全不受导航栏影响 -->
<div class="min-h-screen flex flex-col">
<main class="flex-grow">
<slot />
</main>
<footer class="bg-gray-100 dark:bg-color-dark-surface border-t border-border-color">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div>
<h3 class="text-lg font-semibold mb-4 text-gray-900 dark:text-white">关于我们</h3>
<p class="text-gray-600 dark:text-gray-400">河北游礼网站致力于宣传河北省的旅游资源、文化遗产和地方特色,为游客提供全面的河北旅游信息。</p>
</div>
<div>
<h3 class="text-lg font-semibold mb-4 text-gray-900 dark:text-white">快速链接</h3>
<ul class="space-y-2">
<li><a href="/about" class="text-color-primary-600 hover:text-color-primary-800 dark:text-color-primary-400 dark:hover:text-color-primary-300">关于河北</a></li>
<li><a href="/contact" class="text-color-primary-600 hover:text-color-primary-800 dark:text-color-primary-400 dark:hover:text-color-primary-300">联系我们</a></li>
<li><a href="/sitemap" class="text-color-primary-600 hover:text-color-primary-800 dark:text-color-primary-400 dark:hover:text-color-primary-300">网站地图</a></li>
</ul>
</div>
<div>
<h3 class="text-lg font-semibold mb-4 text-gray-900 dark:text-white">关注我们</h3>
<div class="flex space-x-4">
{socialLinks.map(({name, url}) => (
<a href={url} class="social-icon text-gray-600 hover:text-color-primary-600 dark:text-gray-400 dark:hover:text-color-primary-400">
<span class="sr-only">{name}</span>
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path fill-rule="evenodd" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
</svg>
</a>
))}
</div>
</div>
</div>
<div class="mt-8 pt-8 border-t border-gray-300 dark:border-color-dark-border text-center">
<p class="text-gray-600 dark:text-gray-400">&copy; {new Date().getFullYear()} 河北游礼网站. 保留所有权利.</p>
</div>
</div>
</footer>
</div>
<script>
@ -144,4 +109,63 @@ const socialLinks = [
.social-icon:hover {
color: var(--theme-primary);
}
/* 文字阴影样式 */
.text-shadow-sm {
text-shadow: 0 1px 2px rgba(0,0,0,0.15), 0 1px 4px rgba(0,0,0,0.1);
}
:global(.dark) .text-shadow-sm {
text-shadow: 0 1px 3px rgba(0,0,0,0.3), 0 2px 6px rgba(0,0,0,0.2);
}
/* 导航栏样式 */
header {
transition: all 0.3s ease;
}
/* 滚动时的背景效果 */
header.scrolled nav a,
header.scrolled .text-shadow-sm {
text-shadow: none;
}
header.scrolled::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 100%;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: -1;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
:global([data-theme='dark']) header.scrolled::before {
background-color: var(--color-dark-card);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
</style>
<script>
// 监听滚动事件,当页面滚动时给导航栏添加背景
document.addEventListener('DOMContentLoaded', () => {
const header = document.querySelector('header');
const checkScroll = () => {
if (window.scrollY > 10) {
header?.classList.add('scrolled');
} else {
header?.classList.remove('scrolled');
}
};
// 初始检查
checkScroll();
// 滚动时检查
window.addEventListener('scroll', checkScroll);
});
</script>

View File

@ -64,20 +64,10 @@ sortedAttractions.forEach((attraction: CollectionEntry<"attractions">) => {
// 按照城市出现次数排序
cities.sort((a, b) => b.count - a.count);
// 从URL获取查询参数 - 在静态站点中只用于预填充输入框
const searchQuery = ''; // 对于静态生成的站点,我们在客户端处理搜索
const tagQuery = ''; // 标签筛选参数
const cityQuery = ''; // 城市筛选参数
// 分页逻辑
const itemsPerPage = 9;
const page = 1; // 对于静态生成的页面,我们默认显示第一页,客户端再处理分页
// 搜索和筛选逻辑 - 在静态站点中,所有筛选都在客户端完成
const selectedCategory = '';
const selectedCity = '';
const selectedTags: string[] = [];
const sortBy: 'date' | 'name' = 'date';
// 对于静态生成的页面,我们提供所有景点数据,客户端再进行筛选
let filteredAttractions = sortedAttractions;
@ -90,21 +80,16 @@ const getCategory = (attraction: CollectionEntry<"attractions">) => {
return attraction.data.city && attraction.data.city.length > 0 ? attraction.data.city[0] : '其他景点';
};
// 辅助函数用于获取景点的城市从city提取或默认值
const getCity = (attraction: CollectionEntry<"attractions">) => {
return attraction.data.city && attraction.data.city.length > 0 ?
attraction.data.city[attraction.data.city.length - 1] : '其他地区';
};
---
<MainLayout title="景点 - 河北游礼">
<!-- 摄影探索风格头部 - 更鲜艳的色彩方案 -->
<div class="relative overflow-hidden">
<!-- 背景效果 - 景观照片效果和彩色渐变叠加 -->
<div class="absolute inset-0 bg-gradient-to-br from-primary-400/50 via-primary-500/30 to-accent-400/40 dark:from-primary-900/60 dark:via-primary-900/50 dark:to-accent-900/60 opacity-70 dark:opacity-60"></div>
<div class="absolute inset-0 bg-gradient-to-b from-primary-400/50 via-primary-500/30 to-white dark:from-primary-900/60 dark:via-primary-900/40 dark:to-black opacity-70 dark:opacity-60"></div>
<div class="absolute inset-0 bg-[url('/images/texture-light.jpg')] dark:bg-[url('/images/texture-dark.jpg')] mix-blend-overlay opacity-30 bg-cover bg-center"></div>
<div class="container mx-auto px-4 py-20 relative z-10">
<div class="container mx-auto px-4 pt-32 pb-20 md:pt-36 md:pb-24 relative z-10">
<div class="max-w-5xl mx-auto text-center">
<!-- 彩色相机取景框效果 -->
<div class="inline-block relative">
@ -151,7 +136,7 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
<!-- 主内容区 - 摄影展览风格 -->
<div class="bg-gradient-to-b from-white to-primary-50 dark:from-black dark:to-primary-950/30 text-gray-900 dark:text-white py-6 sm:py-10">
<div class="bg-white dark:bg-black text-gray-900 dark:text-white py-6 sm:py-10">
<div class="container mx-auto px-4">
<!-- 移动端筛选按钮 -->
<div class="lg:hidden mb-4 flex justify-between items-center">
@ -180,11 +165,11 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
<!-- 移动端筛选抽屉 - 默认隐藏 -->
<div id="mobile-filter-drawer" class="lg:hidden fixed inset-0 z-50 transform translate-x-full transition-transform duration-300 ease-in-out">
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" id="mobile-filter-backdrop"></div>
<div class="absolute right-0 top-0 bottom-0 w-4/5 max-w-sm bg-white dark:bg-gray-900 shadow-xl overflow-y-auto">
<div class="p-4 border-b border-gray-200 dark:border-gray-800 flex justify-between items-center">
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" id="mobile-filter-backdrop"></div>
<div class="absolute right-0 top-0 bottom-0 w-4/5 max-w-sm bg-white/95 dark:bg-primary-950/90 shadow-xl overflow-y-auto">
<div class="p-4 border-b border-gray-200 dark:border-primary-800/50 flex justify-between items-center">
<h3 class="text-lg font-medium text-gray-800 dark:text-white">筛选</h3>
<button id="mobile-filter-close" class="rounded-full p-2 text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800">
<button id="mobile-filter-close" class="rounded-full p-2 text-gray-500 hover:bg-gray-100 dark:hover:bg-primary-900/50">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
@ -232,7 +217,7 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
<!-- 左侧滤镜区域 - 彩色摄影参数风格 - 在桌面端显示 -->
<div class="hidden lg:block lg:col-span-3">
<div class="sticky top-16 space-y-3 sm:space-y-6">
<div class="bg-white/80 dark:bg-primary-950/40 backdrop-blur-sm p-4 sm:p-5 border border-primary-100 dark:border-primary-800/50 rounded-lg shadow-lg sm:shadow-xl shadow-primary-100/50 dark:shadow-primary-900/20">
<div class="bg-white/90 dark:bg-primary-950/80 backdrop-blur-sm p-4 sm:p-5 border border-primary-100 dark:border-primary-800/50 rounded-lg shadow-lg sm:shadow-xl shadow-primary-100/50 dark:shadow-primary-900/20">
<!-- 彩色搜索框 -->
<form id="search-form" class="mb-4 sm:mb-6 max-w-xl mx-auto" method="get" action="/attractions">
<div class="flex items-center">
@ -241,7 +226,7 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
type="text"
name="search"
placeholder="搜索景点名称、描述、标签..."
class="block w-full text-slate-700 dark:text-gray-200 bg-white/90 dark:bg-primary-900/50 border border-primary-300 dark:border-primary-700 rounded-full py-1.5 sm:py-2 pl-3 sm:pl-4 pr-10 focus:outline-none focus:border-primary-500 placeholder-gray-400/80 dark:placeholder-gray-500/80 text-sm"
class="block w-full text-slate-700 dark:text-gray-200 bg-white/90 dark:bg-primary-900/70 border border-primary-300 dark:border-primary-700 rounded-full py-1.5 sm:py-2 pl-3 sm:pl-4 pr-10 focus:outline-none focus:border-primary-500 placeholder-gray-400/80 dark:placeholder-gray-500/80 text-sm"
/>
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 sm:h-5 sm:w-5 text-primary-500 dark:text-primary-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -282,7 +267,7 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
</div>
<!-- 自定义筛选器提示 - 彩色相机操作引导 -->
<div class="p-4 border border-primary-100 dark:border-primary-900/50 bg-gradient-to-br from-white to-primary-50 dark:from-primary-950/20 dark:to-primary-950/20 mt-4 rounded-lg">
<div class="p-4 border border-primary-100 dark:border-primary-800/50 bg-white/90 dark:bg-primary-950/80 backdrop-blur-sm mt-4 rounded-lg">
<div class="flex items-start space-x-3">
<div class="text-primary-500 dark:text-primary-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -355,7 +340,12 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
return (
<div class="w-full sm:w-[calc(50%-8px)] md:w-[calc(50%-12px)] lg:w-[calc(33.333%-16px)]">
<a href={`/attractions/${attraction.slug}`} class="group">
<div class={`attraction-card relative bg-white dark:bg-gray-900 overflow-hidden border ${colorScheme.border} ${colorScheme.hover} transition-all duration-300 h-full shadow-lg hover:shadow-xl dark:shadow-none rounded-lg`}>
<div class={`attraction-card relative bg-white dark:bg-gray-900 overflow-hidden border ${colorScheme.border} ${colorScheme.hover} transition-all duration-300 h-full shadow-lg hover:shadow-xl dark:shadow-none rounded-lg`}
data-tags={JSON.stringify(attraction.data.tags)}
data-title={attraction.data.title}
data-description={attraction.data.description}
data-city={JSON.stringify(attraction.data.city)}
>
<!-- 景点图片 - 彩色摄影展览风格 -->
<div class="aspect-[4/5] relative overflow-hidden rounded-t-lg">
<div class="absolute inset-0 bg-gradient-to-br from-primary-100 via-primary-100 to-secondary-100 dark:from-primary-900/30 dark:via-primary-900/30 dark:to-secondary-900/30 flex items-center justify-center">
@ -831,69 +821,40 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
// 获取所有卡片容器
const cardContainers = attractionsGrid ? Array.from(attractionsGrid.children) as HTMLElement[] : [];
// 如果没有筛选条件,显示所有卡片
if (!searchValue && !tagValue && !cityValue) {
if (noResultsMessage) {
noResultsMessage.classList.add('hidden');
}
if (attractionsGrid) {
attractionsGrid.classList.remove('hidden');
}
// 显示所有卡片并重置排序
cardContainers.forEach((container) => {
container.classList.remove('hidden-card');
container.style.order = '';
});
return cardContainers.length;
}
// 遍历所有景点卡片进行筛选
cardContainers.forEach((container) => {
const cardTitle = container.querySelector('.attraction-title')?.textContent?.toLowerCase() || '';
const cardDesc = container.querySelector('.attraction-desc')?.textContent?.toLowerCase() || '';
const cardTags = container.querySelector('.attraction-tags')?.textContent?.toLowerCase() || '';
const card = container.querySelector('.attraction-card') as HTMLElement;
if (!card) return;
// 获取所有城市标签现在city是数组
const cityElements = container.querySelectorAll('.attraction-city');
let cardCities: string[] = [];
cityElements.forEach((element) => {
const cityText = element?.textContent?.toLowerCase() || '';
if (cityText) {
cardCities.push(cityText);
}
});
// 如果没有找到城市元素,使用旧的单一城市元素方式作为兼容
if (cardCities.length === 0) {
const singleCityText = container.querySelector('.attraction-city')?.textContent?.toLowerCase() || '';
if (singleCityText) {
cardCities.push(singleCityText);
}
}
try {
// 从data属性获取完整数据
const cardData = {
title: card.getAttribute('data-title')?.toLowerCase() || '',
description: card.getAttribute('data-description')?.toLowerCase() || '',
tags: JSON.parse(card.getAttribute('data-tags') || '[]').map((tag: string) => tag.toLowerCase()),
city: JSON.parse(card.getAttribute('data-city') || '[]').map((city: string) => city.toLowerCase())
};
// 匹配逻辑:满足所有已提供的筛选条件
let isMatch = true;
// 搜索词筛选
if (searchValue && !(
cardTitle.includes(searchValue) ||
cardDesc.includes(searchValue) ||
cardTags.includes(searchValue) ||
cardCities.some(city => city.includes(searchValue))
cardData.title.includes(searchValue) ||
cardData.description.includes(searchValue) ||
cardData.tags.some((tag: string) => tag.includes(searchValue)) ||
cardData.city.some((city: string) => city.includes(searchValue))
)) {
isMatch = false;
}
// 标签筛选
if (tagValue && !cardTags.includes(tagValue)) {
if (tagValue && !cardData.tags.some((tag: string) => tag.includes(tagValue))) {
isMatch = false;
}
// 城市筛选 - 检查cityValue是否包含在任何一个城市中
if (cityValue && !cardCities.some(city => city.includes(cityValue))) {
// 城市筛选
if (cityValue && !cardData.city.some((city: string) => city.includes(cityValue))) {
isMatch = false;
}
@ -904,19 +865,21 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
} else {
container.classList.add('hidden-card');
}
} catch (error) {
console.error('Error filtering card:', error);
container.classList.add('hidden-card');
}
});
// 重新排序匹配的卡片,使它们从第一个位置开始依次排列
// 重新排序匹配的卡片
matchedCards.forEach((container, index) => {
container.style.order = index.toString();
});
// 更新无结果消息显示
// 更新无结果提示显示
if (matchCount === 0) {
// 显示无结果消息
if (noResultsMessage) {
noResultsMessage.classList.remove('hidden');
// 更新无结果消息中的搜索词
if (searchTermMessage) {
let messageText = '抱歉,未找到相关景点。';
@ -935,16 +898,13 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
}
}
// 隐藏景点网格
if (attractionsGrid) {
attractionsGrid.classList.add('hidden');
}
} else {
// 隐藏无结果消息
if (noResultsMessage) {
noResultsMessage.classList.add('hidden');
}
// 显示景点网格
if (attractionsGrid) {
attractionsGrid.classList.remove('hidden');
}

View File

@ -184,14 +184,14 @@ if (queryParams) queryParams = '?' + queryParams.substring(1);
<MainLayout title="河北美食食谱 - 河北游礼">
<!-- 食谱风格头部 -->
<div class="relative overflow-hidden bg-recipe-paper dark:bg-recipe-paper-dark min-h-[400px] flex items-center">
<div class="relative overflow-hidden bg-recipe-paper dark:bg-recipe-paper-dark min-h-[480px] flex items-center">
<!-- 纸张纹理和装饰 -->
<div class="absolute inset-0 bg-[url('/images/recipe-paper-texture.png')] opacity-10"></div>
<div class="absolute top-0 left-0 w-32 h-32 bg-[url('/images/spoon-fork.png')] bg-no-repeat bg-contain opacity-15 -rotate-12"></div>
<div class="absolute top-16 left-0 w-32 h-32 bg-[url('/images/spoon-fork.png')] bg-no-repeat bg-contain opacity-15 -rotate-12"></div>
<div class="absolute bottom-0 right-0 w-32 h-32 bg-[url('/images/chef-hat.png')] bg-no-repeat bg-contain opacity-15 rotate-12"></div>
<!-- 食谱标题区域 -->
<div class="container mx-auto px-4 relative z-10">
<div class="container mx-auto px-4 relative z-10 pt-16">
<div class="max-w-4xl mx-auto text-center recipe-card">
<!-- 食谱卡片装饰 -->
<div class="recipe-card-pins absolute -top-3 left-1/2 transform -translate-x-1/2">
@ -228,7 +228,7 @@ if (queryParams) queryParams = '?' + queryParams.substring(1);
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
</svg>
<span>收集于 2023 年</span>
<span>收集于 2025 年</span>
</div>
</div>

View File

@ -33,25 +33,44 @@ const relatedCultures = allCultures
---
<MainLayout title={`${entry.data.title} - 河北游礼`}>
<!-- 页面标题区域 -->
<div class="relative py-16 bg-gradient-to-br from-color-primary-700 via-color-primary-600 to-color-primary-800 text-white dark:from-color-dark-primary-900 dark:via-color-dark-primary-800 dark:to-color-dark-primary-950">
<div class="absolute inset-0 bg-black/30"></div>
<!-- 移动端浮动返回按钮 -->
<div class="md:hidden fixed bottom-6 left-1/2 transform -translate-x-1/2 z-50">
<a
href="/culture"
class="flex items-center space-x-2 px-5 py-3 bg-ancient-accent dark:bg-ancient-accent-dark text-ancient-white rounded-full shadow-lg hover:bg-ancient-accent/90 dark:hover:bg-ancient-accent-dark/90 transition-colors border border-ancient-accent/30 dark:border-ancient-accent-dark/30"
aria-label="返回所有文化"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 17l-5-5m0 0l5-5m-5 5h12" />
</svg>
<span>返回文化列表</span>
</a>
</div>
<!-- 页面标题区域 - 古籍风格 -->
<div class="relative py-16 bg-ancient-paper dark:bg-ancient-paper-dark overflow-hidden">
<div class="absolute inset-0 bg-opacity-20 bg-amber-100 dark:bg-opacity-20 dark:bg-amber-900"></div>
<div class="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="max-w-4xl mx-auto">
<ScrollReveal animation="fade">
<div class="flex flex-wrap items-center gap-2 mb-4">
<a href="/" class="text-white/80 hover:text-white transition-colors">首页</a>
<span class="text-white/60">/</span>
<a href="/culture" class="text-white/80 hover:text-white transition-colors">文化</a>
<span class="text-white/60">/</span>
<span class="text-white/60">{entry.data.title}</span>
<a href="/" class="text-ancient-black/80 dark:text-ancient-white/80 hover:text-ancient-accent dark:hover:text-ancient-accent-dark transition-colors font-ancient-small">首页</a>
<span class="text-ancient-black/60 dark:text-ancient-white/60 font-ancient-small">/</span>
<a href="/culture" class="text-ancient-black/80 dark:text-ancient-white/80 hover:text-ancient-accent dark:hover:text-ancient-accent-dark transition-colors font-ancient-small">文化</a>
<span class="text-ancient-black/60 dark:text-ancient-white/60 font-ancient-small">/</span>
<span class="text-ancient-black/60 dark:text-ancient-white/60 font-ancient-small">{entry.data.title}</span>
</div>
<h1 class="text-4xl md:text-5xl font-bold mb-4">{entry.data.title}</h1>
<!-- 典籍风格标题 -->
<div class="official-title">
<h1 class="text-4xl md:text-5xl font-ancient text-ancient-black dark:text-ancient-white mb-4 leading-snug">{entry.data.title}</h1>
<div class="w-40 h-0.5 my-6 bg-ancient-accent dark:bg-ancient-accent-dark"></div>
</div>
<div class="flex flex-wrap items-center gap-4 mb-4">
{entry.data.city && entry.data.city.length > 0 && (
<div class="flex items-center text-white/90">
<div class="flex items-center text-ancient-black/90 dark:text-ancient-white/90 font-ancient-body">
<span class="mr-1">📍</span>
{entry.data.city.map((cityName, index) => (
<>
@ -63,64 +82,69 @@ const relatedCultures = allCultures
)}
{entry.data.pubDate && (
<div class="flex items-center text-white/90">
<div class="flex items-center text-ancient-black/90 dark:text-ancient-white/90 font-ancient-body">
<span class="mr-1">📅</span> {new Date(entry.data.pubDate).toLocaleDateString('zh-CN')}
</div>
)}
<div class="flex items-center text-white/90">
<div class="flex items-center text-ancient-black/90 dark:text-ancient-white/90 font-ancient-body">
<span class="mr-1">🏷️</span> {entry.data.category}
</div>
</div>
<div class="flex flex-wrap gap-2 mb-6">
{entry.data.tags.map((tag) => (
<span class="px-3 py-1 bg-white/20 backdrop-blur-sm text-white text-sm rounded-full">
<span class="px-3 py-1 bg-ancient-paper/70 dark:bg-ancient-paper-dark/70 text-ancient-black dark:text-ancient-white text-sm font-ancient-small border border-ancient-accent/30 dark:border-ancient-accent-dark/30 hover:border-ancient-accent/50 dark:hover:border-ancient-accent-dark/50 rounded-full">
{tag}
</span>
))}
</div>
<p class="text-xl text-white/90 max-w-3xl">{entry.data.description}</p>
<p class="text-xl text-ancient-black/90 dark:text-ancient-white/90 max-w-3xl font-ancient-body">{entry.data.description}</p>
</ScrollReveal>
</div>
</div>
</div>
<!-- 主要内容区域 -->
<div class="py-12 bg-white dark:bg-color-dark-bg">
<!-- 主要内容区域 - 古籍风格 -->
<div class="py-12 bg-ancient-paper dark:bg-ancient-paper-dark">
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-12">
<!-- 左侧内容 -->
<!-- 左侧内容 - 古籍风格 -->
<div class="lg:col-span-2">
<ScrollReveal animation="fade">
<div class="prose prose-lg dark:prose-invert max-w-none">
<div class="prose prose-lg dark:prose-invert max-w-none font-ancient-body bg-ancient-paper-light/50 dark:bg-ancient-paper-dark/50 p-8 border-ancient-accent/30 dark:border-ancient-accent-dark/30 relative">
<Content />
</div>
</ScrollReveal>
</div>
<!-- 右侧边栏 -->
<!-- 右侧边栏 - 古籍风格 -->
<div class="space-y-8">
<!-- 文化图片 -->
<!-- 文化图片 - 古籍风格 -->
<ScrollReveal animation="slide-up">
<div class="rounded-lg overflow-hidden shadow-md">
<div class="h-64 bg-gray-300 dark:bg-gray-700 flex items-center justify-center">
<span class="text-gray-500 dark:text-gray-400">{entry.data.title} 图片</span>
<div class="border border-ancient-accent/40 dark:border-ancient-accent-dark/40 overflow-hidden shadow-md">
<div class="h-64 bg-ancient-paper-light/70 dark:bg-ancient-paper-dark/70 flex items-center justify-center relative">
<span class="text-ancient-black/40 dark:text-ancient-white/40 font-ancient">{entry.data.title} 图片</span>
</div>
</div>
</ScrollReveal>
<!-- 文化信息卡片 -->
<!-- 文化信息卡片 - 古籍风格 -->
<ScrollReveal animation="slide-up" delay={100}>
<div class="bg-gray-50 dark:bg-color-dark-card rounded-lg shadow-md p-6">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">文化信息</h3>
<div class="bg-ancient-paper-light dark:bg-ancient-paper-dark-light p-6 border-2 border-ancient-accent/30 dark:border-ancient-accent-dark/30 shadow-md">
<h3 class="text-xl font-ancient-heading text-ancient-black dark:text-ancient-white mb-4 flex items-center">
<svg class="w-5 h-5 mr-2 text-ancient-accent dark:text-ancient-accent-dark" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
文化信息
</h3>
<div class="space-y-3">
<div class="space-y-3 font-ancient-body">
{entry.data.city && entry.data.city.length > 0 && (
<div class="flex">
<span class="w-24 flex-shrink-0 text-gray-600 dark:text-gray-400">分布地区:</span>
<div class="text-gray-900 dark:text-white">
<span class="w-24 flex-shrink-0 text-ancient-black/60 dark:text-ancient-white/60">分布地区:</span>
<div class="text-ancient-black dark:text-ancient-white">
{entry.data.city.map((cityName, index) => (
<>
<span>{cityName}</span>
@ -132,15 +156,15 @@ const relatedCultures = allCultures
)}
<div class="flex">
<span class="w-24 flex-shrink-0 text-gray-600 dark:text-gray-400">文化类型:</span>
<span class="text-gray-900 dark:text-white">{entry.data.category}</span>
<span class="w-24 flex-shrink-0 text-ancient-black/60 dark:text-ancient-white/60">文化类型:</span>
<span class="text-ancient-black dark:text-ancient-white">{entry.data.category}</span>
</div>
<div class="flex">
<span class="w-24 flex-shrink-0 text-gray-600 dark:text-gray-400">特色标签:</span>
<span class="w-24 flex-shrink-0 text-ancient-black/60 dark:text-ancient-white/60">特色标签:</span>
<div class="flex flex-wrap gap-1">
{entry.data.tags.map((tag) => (
<span class="px-2 py-0.5 bg-color-primary-100 text-color-primary-800 text-xs rounded-full dark:bg-color-dark-primary-900/70 dark:text-color-primary-300">
<span class="px-2 py-0.5 bg-ancient-paper/70 dark:bg-ancient-paper-dark/70 text-sm font-ancient-small border border-ancient-accent/30 dark:border-ancient-accent-dark/30 text-ancient-black dark:text-ancient-white text-xs rounded-full">
{tag}
</span>
))}
@ -149,8 +173,8 @@ const relatedCultures = allCultures
{entry.data.pubDate && (
<div class="flex">
<span class="w-24 flex-shrink-0 text-gray-600 dark:text-gray-400">发布时间:</span>
<span class="text-gray-900 dark:text-white">{new Date(entry.data.pubDate).toLocaleDateString('zh-CN')}</span>
<span class="w-24 flex-shrink-0 text-ancient-black/60 dark:text-ancient-white/60">发布时间:</span>
<span class="text-ancient-black dark:text-ancient-white">{new Date(entry.data.pubDate).toLocaleDateString('zh-CN')}</span>
</div>
)}
@ -158,11 +182,16 @@ const relatedCultures = allCultures
</div>
</ScrollReveal>
<!-- 相关文化 -->
<!-- 相关文化 - 古籍风格 -->
{relatedCultures.length > 0 && (
<ScrollReveal animation="slide-up" delay={200}>
<div class="bg-gray-50 dark:bg-color-dark-card rounded-lg shadow-md p-6">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">相关文化</h3>
<div class="bg-ancient-paper-light dark:bg-ancient-paper-dark-light p-6 border-2 border-ancient-accent/30 dark:border-ancient-accent-dark/30 shadow-md">
<h3 class="text-xl font-ancient-heading text-ancient-black dark:text-ancient-white mb-4 flex items-center">
<svg class="w-5 h-5 mr-2 text-ancient-accent dark:text-ancient-accent-dark" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
相关文化
</h3>
<div class="space-y-4">
{relatedCultures.map((culture) => (
@ -171,14 +200,14 @@ const relatedCultures = allCultures
class="block group"
>
<div class="flex items-start space-x-3">
<div class="w-16 h-16 flex-shrink-0 bg-gray-300 dark:bg-gray-700 rounded flex items-center justify-center">
<span class="text-xs text-gray-500 dark:text-gray-400">图片</span>
<div class="w-16 h-16 flex-shrink-0 bg-ancient-paper/70 dark:bg-ancient-paper-dark/70 border border-ancient-accent/20 dark:border-ancient-accent-dark/20 rounded flex items-center justify-center">
<span class="text-xs text-ancient-black/40 dark:text-ancient-white/40">图片</span>
</div>
<div>
<h4 class="text-base font-medium text-gray-900 dark:text-white group-hover:text-color-primary-600 dark:group-hover:text-color-primary-400 transition-colors">
<h4 class="text-base font-ancient-heading text-ancient-black dark:text-ancient-white group-hover:text-ancient-accent dark:group-hover:text-ancient-accent-dark transition-colors">
{culture.data.title}
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
<p class="text-sm text-ancient-black/70 dark:text-ancient-white/70 line-clamp-2 font-ancient-body">
{culture.data.description.substring(0, 60)}...
</p>
</div>
@ -190,11 +219,12 @@ const relatedCultures = allCultures
</ScrollReveal>
)}
<!-- 返回按钮 -->
<!-- 返回按钮 - 仅在非移动端显示 - 古籍风格 -->
<div class="hidden md:block">
<ScrollReveal animation="slide-up" delay={300}>
<a
href="/culture"
class="block w-full py-3 text-center bg-color-primary-600 text-white rounded-md hover:bg-color-primary-700 transition-colors dark:bg-color-dark-primary-600 dark:hover:bg-color-dark-primary-500"
class="block w-full py-4 mt-8 mb-4 text-center bg-ancient-accent dark:bg-ancient-accent-dark text-ancient-white rounded-md hover:bg-ancient-accent/90 dark:hover:bg-ancient-accent-dark/90 transition-colors font-ancient shadow-md border border-ancient-accent/50 dark:border-ancient-accent-dark/50 text-lg"
>
返回所有文化
</a>
@ -203,4 +233,103 @@ const relatedCultures = allCultures
</div>
</div>
</div>
</div>
</MainLayout>
<style>
/* 确保这些CSS类对此页面有效 */
.prose {
color: var(--ancient-black) !important;
}
.dark .prose {
color: var(--ancient-white) !important;
}
.prose h1, .prose h2, .prose h3, .prose h4 {
color: var(--ancient-black) !important;
font-family: var(--font-ancient-heading);
}
.dark .prose h1, .dark .prose h2, .dark .prose h3, .dark .prose h4 {
color: var(--ancient-white) !important;
}
.prose p, .prose ul, .prose ol {
font-family: var(--font-ancient-body);
}
.prose a {
color: var(--ancient-accent) !important;
text-decoration: none;
border-bottom: 1px dashed var(--ancient-accent);
transition: all 0.2s ease;
}
.dark .prose a {
color: var(--ancient-accent-dark) !important;
border-bottom-color: var(--ancient-accent-dark);
}
.prose a:hover {
border-bottom-style: solid;
color: var(--ancient-red) !important;
}
.dark .prose a:hover {
color: var(--ancient-red-dark) !important;
}
.prose blockquote {
border-left-color: var(--ancient-accent) !important;
background-color: rgba(var(--ancient-paper-light-rgb), 0.3) !important;
font-style: italic;
}
.dark .prose blockquote {
border-left-color: var(--ancient-accent-dark) !important;
background-color: rgba(var(--ancient-paper-dark-rgb), 0.3) !important;
}
.prose code {
background-color: rgba(var(--ancient-paper-light-rgb), 0.5) !important;
color: var(--ancient-black) !important;
padding: 0.2em 0.4em;
border-radius: 0.25em;
font-size: 0.875em;
}
.dark .prose code {
background-color: rgba(var(--ancient-paper-dark-rgb), 0.5) !important;
color: var(--ancient-white) !important;
}
.prose pre {
background-color: rgba(var(--ancient-paper-light-rgb), 0.8) !important;
border: 1px solid rgba(var(--ancient-accent-rgb), 0.2) !important;
}
.dark .prose pre {
background-color: rgba(var(--ancient-paper-dark-rgb), 0.8) !important;
border-color: rgba(var(--ancient-accent-dark-rgb), 0.2) !important;
}
.prose img {
border: 1px solid rgba(var(--ancient-accent-rgb), 0.2);
border-radius: 0.25rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.dark .prose img {
border-color: rgba(var(--ancient-accent-dark-rgb), 0.2);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.prose hr {
border-color: rgba(var(--ancient-accent-rgb), 0.2) !important;
}
.dark .prose hr {
border-color: rgba(var(--ancient-accent-dark-rgb), 0.2) !important;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,227 +0,0 @@
---
import MainLayout from "../layouts/MainLayout.astro";
import FlipCard from "../components/aceternity/FlipCard.astro";
import ParticleButton from "../components/aceternity/ParticleButton.astro";
import MagneticElement from "../components/aceternity/MagneticElement.astro";
import ParallaxCard from "../components/aceternity/ParallaxCard.astro";
import NeonButton from "../components/aceternity/NeonButton.astro";
import SpotlightCard from "../components/aceternity/SpotlightCard.astro";
import HoverGlow from "../components/aceternity/HoverGlow.astro";
import ScrollReveal from "../components/aceternity/ScrollReveal.astro";
---
<MainLayout title="交互效果展示 - 河北游礼">
<div class="py-16 bg-gray-50 dark:bg-color-dark-bg">
<div class="container mx-auto px-4">
<div class="max-w-4xl mx-auto mb-16 text-center">
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-6">酷炫交互效果展示</h1>
<p class="text-lg text-gray-600 dark:text-gray-400">探索我们网站上使用的各种交互和悬停效果</p>
</div>
<!-- 各种效果分组 -->
<div class="space-y-24">
<!-- 3D翻转卡片 -->
<section>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-8 text-center">3D翻转卡片</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<FlipCard
frontTitle="长城"
backTitle="世界文化遗产"
>
<div slot="front" class="text-center">
<img src="https://picsum.photos/seed/great-wall-flip/300/200" alt="长城" class="rounded-lg mb-4 mx-auto"/>
<p class="text-gray-600 dark:text-gray-400">中国最具代表性的文化象征</p>
</div>
<div slot="back" class="text-center">
<p class="mb-4 text-white">河北拥有全国最长的长城线,包括八达岭、金山岭等著名景点。</p>
<a href="/attractions/great-wall" class="inline-block px-4 py-2 bg-white text-color-primary-600 rounded-md hover:bg-gray-100">了解更多</a>
</div>
</FlipCard>
<FlipCard
frontTitle="蔚县剪纸"
backTitle="非遗文化"
>
<div slot="front" class="text-center">
<img src="https://picsum.photos/seed/paper-cutting-flip/300/200" alt="蔚县剪纸" class="rounded-lg mb-4 mx-auto"/>
<p class="text-gray-600 dark:text-gray-400">传统民间艺术</p>
</div>
<div slot="back" class="text-center">
<p class="mb-4 text-white">蔚县剪纸以其独特的造型和鲜明的色彩闻名,展现了浓郁的民俗风情。</p>
<a href="/culture/paper-cutting" class="inline-block px-4 py-2 bg-white text-color-primary-600 rounded-md hover:bg-gray-100">了解更多</a>
</div>
</FlipCard>
<FlipCard
frontTitle="保定莲花酥"
backTitle="传统美食"
>
<div slot="front" class="text-center">
<img src="https://picsum.photos/seed/lotus-pastry-flip/300/200" alt="保定莲花酥" class="rounded-lg mb-4 mx-auto"/>
<p class="text-gray-600 dark:text-gray-400">清代宫廷点心</p>
</div>
<div slot="back" class="text-center">
<p class="mb-4 text-white">酥脆可口,形如莲花,是保定地区传统的伴手礼。</p>
<a href="/cuisine/lotus-pastry" class="inline-block px-4 py-2 bg-white text-color-primary-600 rounded-md hover:bg-gray-100">了解更多</a>
</div>
</FlipCard>
</div>
</section>
<!-- 粒子按钮 -->
<section>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-8 text-center">粒子特效按钮</h2>
<div class="flex flex-wrap justify-center gap-8">
<ParticleButton
text="探索河北景点"
href="/attractions"
color="#4f46e5"
/>
<ParticleButton
text="了解文化遗产"
href="/culture"
color="#e11d48"
/>
<ParticleButton
text="品尝地方美食"
href="/cuisine"
color="#059669"
particleCount={40}
/>
</div>
</section>
<!-- 磁性元素 -->
<section>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-8 text-center">磁性吸附效果</h2>
<div class="flex flex-wrap justify-center gap-12">
<MagneticElement strength={3} className="text-center">
<div class="w-32 h-32 bg-color-primary-500 dark:bg-color-dark-primary-600 rounded-full flex items-center justify-center text-white font-bold shadow-lg">
弱吸附效果
</div>
</MagneticElement>
<MagneticElement strength={7} className="text-center">
<div class="w-32 h-32 bg-color-primary-600 dark:bg-color-dark-primary-700 rounded-full flex items-center justify-center text-white font-bold shadow-lg">
中吸附效果
</div>
</MagneticElement>
<MagneticElement strength={10} className="text-center">
<div class="w-32 h-32 bg-color-primary-700 dark:bg-color-dark-primary-800 rounded-full flex items-center justify-center text-white font-bold shadow-lg">
强吸附效果
</div>
</MagneticElement>
</div>
</section>
<!-- 视差卡片 -->
<section>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-8 text-center">3D视差卡片</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-4xl mx-auto">
<ParallaxCard
backgroundImage="https://picsum.photos/seed/parallax-card-1/800/600"
depth={7}
>
<h3 class="text-xl font-bold text-white mb-3 parallax-item" data-depth="8">承德避暑山庄</h3>
<p class="text-white mb-4 parallax-item" data-depth="5">中国清代皇家园林,世界文化遗产</p>
<a href="/attractions/summer-resort" class="inline-block px-4 py-2 bg-white text-color-primary-600 rounded-md hover:bg-gray-100 parallax-item" data-depth="3">了解更多</a>
</ParallaxCard>
<ParallaxCard
backgroundImage="https://picsum.photos/seed/parallax-card-2/800/600"
depth={5}
>
<h3 class="text-xl font-bold text-white mb-3 parallax-item" data-depth="8">秦皇岛海滨</h3>
<p class="text-white mb-4 parallax-item" data-depth="5">碧海蓝天,金色沙滩</p>
<a href="/attractions/qinhuangdao" class="inline-block px-4 py-2 bg-white text-color-primary-600 rounded-md hover:bg-gray-100 parallax-item" data-depth="3">了解更多</a>
</ParallaxCard>
</div>
</section>
<!-- 霓虹灯按钮 -->
<section>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-8 text-center">霓虹灯按钮</h2>
<div class="flex flex-wrap justify-center gap-8">
<NeonButton
text="蓝色按钮"
color="rgb(59, 130, 246)"
glowSize="medium"
glowIntensity="medium"
/>
<NeonButton
text="绿色按钮"
color="rgb(16, 185, 129)"
glowSize="large"
glowIntensity="high"
/>
<NeonButton
text="红色按钮"
color="rgb(239, 68, 68)"
glowSize="small"
glowIntensity="low"
pulseAnimation={false}
/>
</div>
</section>
<!-- 聚光灯卡片 -->
<section>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-8 text-center">聚光灯卡片</h2>
<div class="max-w-2xl mx-auto">
<SpotlightCard
title="正定古城"
description="河北省历史文化名城,保存有大量的历史文物古迹。建于战国时期,是全国首批历史文化名城之一,素有'北方雄镇'之称。城内现存有隆兴寺、临济寺、广惠寺三塔等众多历史遗迹和文物古迹,是中国保存最为完好的古城之一。"
className="p-10 bg-white dark:bg-color-dark-card rounded-lg shadow-md"
color="rgba(16, 185, 129, 0.35)"
spotlightSize="large"
spotlightIntensity="strong"
>
<div class="mt-4 mb-4">
<div class="flex items-center gap-2 text-gray-600 dark:text-gray-400 text-sm">
<span>旅游景点类型:</span>
<span class="px-2 py-1 bg-gray-100 text-gray-600 text-xs rounded-full dark:bg-color-dark-surface dark:text-gray-400">历史文化</span>
<span class="px-2 py-1 bg-gray-100 text-gray-600 text-xs rounded-full dark:bg-color-dark-surface dark:text-gray-400">古建筑</span>
<span class="px-2 py-1 bg-gray-100 text-gray-600 text-xs rounded-full dark:bg-color-dark-surface dark:text-gray-400">名城古镇</span>
</div>
</div>
<a href="/attractions/zhengding" class="inline-block mt-2 text-color-primary-600 hover:text-color-primary-700 dark:text-color-primary-400 dark:hover:text-color-primary-300 font-medium">了解更多 &rarr;</a>
</SpotlightCard>
</div>
</section>
<!-- 悬停发光效果 -->
<section>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-8 text-center">悬停发光效果</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-4xl mx-auto">
<HoverGlow intensity="subtle" className="relative">
<div class="p-8 bg-white dark:bg-color-dark-card rounded-lg shadow-md">
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">内画鼻烟壶</h3>
<p class="text-gray-600 dark:text-gray-400">河北衡水传统工艺品,在小小的鼻烟壶内壁上绘制精美图案。</p>
</div>
</HoverGlow>
<HoverGlow intensity="medium" color="#4f46e5" className="relative">
<div class="p-8 bg-white dark:bg-color-dark-card rounded-lg shadow-md">
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">曲阳石雕</h3>
<p class="text-gray-600 dark:text-gray-400">历史悠久的传统工艺,技艺精湛,形态逼真。</p>
</div>
</HoverGlow>
<HoverGlow intensity="strong" color="#e11d48" className="relative">
<div class="p-8 bg-white dark:bg-color-dark-card rounded-lg shadow-md">
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">景泰蓝</h3>
<p class="text-gray-600 dark:text-gray-400">一种金属胎掐丝珐琅工艺品,色彩华丽绚烂。</p>
</div>
</HoverGlow>
</div>
</section>
</div>
</div>
</div>
</MainLayout>

View File

@ -89,9 +89,9 @@ const visibleTravels = sortedTravels.slice(
<MainLayout title="旅行攻略 - 河北游礼">
<!-- 手绘旅行日记本风格的头部 -->
<div class="bg-theme-primary-bg dark:bg-slate-900 py-12 relative overflow-hidden">
<div class="bg-theme-primary-bg dark:bg-slate-900 pt-28 pb-16 md:pt-32 md:pb-20 relative overflow-hidden">
<!-- 装饰性笔触元素 -->
<div class="absolute top-0 right-0 w-40 h-40 bg-[url('/images/ink-splash.png')] bg-no-repeat opacity-10"></div>
<div class="absolute top-16 right-0 w-40 h-40 bg-[url('/images/ink-splash.png')] bg-no-repeat opacity-10"></div>
<div class="absolute bottom-0 left-0 w-32 h-32 bg-[url('/images/coffee-stain.png')] bg-no-repeat opacity-5"></div>
<div class="container mx-auto px-4 relative">
@ -349,7 +349,14 @@ const visibleTravels = sortedTravels.slice(
href={`/travel/${travel.slug}`}
class="block group"
>
<div class={`bg-white dark:bg-slate-800 rounded-sm border border-slate-200 dark:border-slate-700 overflow-hidden shadow-md transform ${index % 2 === 0 ? 'rotate-1' : '-rotate-1'} hover:shadow-lg transition-shadow relative`}>
<div class={`bg-white dark:bg-slate-800 rounded-sm border border-slate-200 dark:border-slate-700 overflow-hidden shadow-md transform ${index % 2 === 0 ? 'rotate-1' : '-rotate-1'} hover:shadow-lg transition-shadow relative`}
data-tags={JSON.stringify(travel.data.tags)}
data-season={travel.data.season}
data-type={travel.data.type}
data-cities={JSON.stringify(travel.data.city)}
data-title={travel.data.title}
data-description={travel.data.description}
>
<!-- 装饰性元素 -->
<div class="absolute top-3 right-3 w-16 h-16 bg-[url('/images/polaroid-corner.png')] bg-contain bg-no-repeat opacity-20"></div>
@ -1178,7 +1185,7 @@ const visibleTravels = sortedTravels.slice(
const searchInput = document.getElementById('search-input') as HTMLInputElement;
const searchButton = document.getElementById('search-button');
const testNoResultsBtn = document.getElementById('test-no-results-btn');
const travelCards = document.querySelectorAll('.space-y-10 a');
const travelCards = document.querySelectorAll('#travel-list div[data-tags]');
const noResultsMessage = document.getElementById('no-results-message');
const searchTermMessage = document.getElementById('search-term-message');
const travelList = document.getElementById('travel-list');
@ -1603,164 +1610,88 @@ const visibleTravels = sortedTravels.slice(
let matchCount = 0;
// 如果没有筛选条件,显示所有卡片
if (!searchValue && selectedSeasons.length === 0 && selectedTypes.length === 0 && selectedCities.length === 0 && selectedTags.length === 0) {
if (noResultsMessage) {
noResultsMessage.classList.add('hidden');
}
if (travelList) {
travelList.classList.remove('hidden');
}
travelCards.forEach((card) => {
card.classList.remove('hidden');
});
return travelCards.length;
}
// 遍历所有旅行卡片进行筛选
travelCards.forEach((card) => {
const cardTitle = card.querySelector('h3')?.textContent?.toLowerCase() || '';
const cardDesc = card.querySelector('p.font-handwriting.text-slate-600')?.textContent?.toLowerCase() || '';
const cardTags = card.querySelectorAll('span.px-2.py-0\\.5.text-xs');
const cardInfo = card.querySelectorAll('.flex.flex-wrap.gap-4 .flex.items-center');
const cardContainer = card.closest('a')?.parentElement;
if (!cardContainer) return;
// 获取卡片的季节、类型和城市信息
let cardSeason = '';
let cardType = '';
let cardCities: string[] = [];
let tagsText = '';
let cardTagsList: string[] = [];
try {
// 直接从card获取数据属性
const cardData = {
title: card.getAttribute('data-title')?.toLowerCase() || '',
description: card.getAttribute('data-description')?.toLowerCase() || '',
tags: JSON.parse(card.getAttribute('data-tags') || '[]').map((tag: string) => tag.toLowerCase()),
season: card.getAttribute('data-season')?.toLowerCase() || '',
type: card.getAttribute('data-type')?.toLowerCase() || '',
cities: JSON.parse(card.getAttribute('data-cities') || '[]').map((city: string) => city.toLowerCase())
};
cardInfo.forEach(info => {
const infoText = info.textContent?.toLowerCase() || '';
if (infoText.includes('难度')) {
// 跳过难度信息
} else if (infoText.includes('天')) {
// 跳过天数信息
} else if (cardSeason === '') {
cardSeason = infoText;
} else if (cardType === '') {
cardType = infoText;
}
});
// 提取卡片标签文本
cardTags.forEach(tag => {
const tagText = tag.textContent?.toLowerCase().replace('#', '').trim() || '';
if (tagText) {
tagsText += tagText + ' ';
cardTagsList.push(tagText);
}
});
// 寻找城市信息 - 从DOM中寻找城市标签
// 首先尝试查找城市专用标签
const cityElements = card.querySelectorAll('.city-tag, .location-tag');
if (cityElements && cityElements.length > 0) {
cityElements.forEach(cityElem => {
const cityText = cityElem.textContent?.toLowerCase().trim() || '';
if (cityText) {
cardCities.push(cityText);
}
});
}
// 如果没有找到专用城市标签尝试从cardInfo中提取
if (cardCities.length === 0) {
// 检查是否有标记了城市信息的元素
const cityInfoElements = card.querySelectorAll('[data-city], .city-info');
if (cityInfoElements && cityInfoElements.length > 0) {
cityInfoElements.forEach(cityElem => {
const cityText = cityElem.textContent?.toLowerCase().trim() || '';
cardCities.push(cityText);
});
}
}
// 如果依然没有找到,尝试从描述和标题中提取
if (cardCities.length === 0) {
// 检查标题和描述中是否包含已知城市
selectedCities.forEach(city => {
if (cardTitle.includes(city) || cardDesc.includes(city)) {
cardCities.push(city);
}
});
}
// 匹配条件
// 匹配搜索词
const matchesSearch = !searchValue ||
cardTitle.includes(searchValue) ||
cardDesc.includes(searchValue) ||
tagsText.includes(searchValue);
cardData.title.includes(searchValue) ||
cardData.description.includes(searchValue) ||
cardData.tags.some((tag: string) => tag.includes(searchValue)) ||
cardData.cities.some((city: string) => city.includes(searchValue));
const matchesSeasons = selectedSeasons.length === 0 || selectedSeasons.some(season => cardSeason.includes(season));
const matchesTypes = selectedTypes.length === 0 || selectedTypes.some(type => cardType.includes(type));
// 匹配季节
const matchesSeasons = selectedSeasons.length === 0 ||
selectedSeasons.some(season => cardData.season.includes(season));
// 检查是否有任何选中的城市存在于卡片城市中
// 如果没有找到城市标签,但存在城市筛选条件,尝试在标题和描述中寻找
let matchesCities = selectedCities.length === 0;
if (!matchesCities) {
if (cardCities.length > 0) {
// 如果找到了城市标签,检查是否匹配
matchesCities = cardCities.some(cardCity =>
selectedCities.some(selectedCity => cardCity.includes(selectedCity))
);
} else {
// 如果没有找到城市标签,尝试在标题和描述中寻找
matchesCities = selectedCities.some(city =>
cardTitle.includes(city) || cardDesc.includes(city)
);
}
}
// 匹配类型
const matchesTypes = selectedTypes.length === 0 ||
selectedTypes.some(type => cardData.type.includes(type));
// 标签匹配:如果有选中的标签,检查卡片标签中是否包含它们中的任何一个
const matchesTags = selectedTags.length === 0 || selectedTags.some(tag =>
cardTagsList.some(cardTag => cardTag.includes(tag))
);
// 匹配城市
const matchesCities = selectedCities.length === 0 ||
selectedCities.some(city => cardData.cities.some((cardCity: string) => cardCity.includes(city)));
// 匹配标签
const matchesTags = selectedTags.length === 0 ||
selectedTags.some(tag => cardData.tags.some((cardTag: string) => cardTag.includes(tag)));
// 所有条件都匹配才显示
const isMatch = matchesSearch && matchesSeasons && matchesTypes && matchesCities && matchesTags;
if (isMatch) {
matchCount++;
card.classList.remove('hidden');
cardContainer.style.display = '';
} else {
card.classList.add('hidden');
cardContainer.style.display = 'none';
}
} catch (error) {
console.error('Error filtering card:', error);
cardContainer.style.display = 'none';
}
});
// 更新无结果提示显示
if (matchCount === 0) {
if (travelList) travelList.classList.add('hidden');
if (noResultsMessage) {
noResultsMessage.classList.remove('hidden');
// 更新无结果消息中的搜索词
if (searchTermMessage) {
let message = '抱歉,未找到相关旅行笔记。';
if (searchValue) {
searchTermMessage.textContent = `抱歉,未找到与 "${searchValue}" 相关的旅行笔记。请尝试其他关键词或浏览所有旅行记录。`;
} else if (selectedSeasons.length > 0 || selectedTypes.length > 0 || selectedCities.length > 0 || selectedTags.length > 0) {
searchTermMessage.textContent = `抱歉,未找到符合当前筛选条件的旅行笔记。请尝试调整筛选条件或浏览所有旅行记录。`;
} else {
searchTermMessage.textContent = `抱歉,未找到相关旅行笔记。请尝试其他关键词或浏览所有旅行记录。`;
message += `没有找到包含"${searchValue}"的内容。`;
}
if (selectedSeasons.length > 0) {
message += `没有找到${selectedSeasons.join('、')}季节的内容。`;
}
if (selectedTypes.length > 0) {
message += `没有找到${selectedTypes.join('、')}类型的内容。`;
}
if (selectedCities.length > 0) {
message += `没有找到位于${selectedCities.join('、')}的内容。`;
}
if (selectedTags.length > 0) {
message += `没有找到标签为${selectedTags.join('、')}的内容。`;
}
searchTermMessage.textContent = message;
}
// 隐藏旅行列表
if (travelList) {
travelList.classList.add('hidden');
}
} else {
// 隐藏无结果提示
if (noResultsMessage) {
noResultsMessage.classList.add('hidden');
}
// 显示旅行列表
if (travelList) {
travelList.classList.remove('hidden');
}
if (travelList) travelList.classList.remove('hidden');
if (noResultsMessage) noResultsMessage.classList.add('hidden');
}
return matchCount;

View File

@ -90,6 +90,19 @@
--color-dark-primary-900: #faefdb;
}
/* 确保在无JavaScript环境下ScrollReveal元素仍然可见 */
.no-js .scroll-reveal {
opacity: 1 !important;
transform: none !important;
}
/* 当JS加载完成时移除no-js类 */
html {
&.no-js {
opacity: 1;
}
}
:root {
/* 基础色调 */
--bg-primary: var(--color-gray-50);
@ -177,6 +190,37 @@
background-color: var(--color-dark-bg);
}
/* 处理颜色命名不一致的问题 */
.text-color-primary-600,
.text-primary-600 {
color: var(--color-dark-primary-600);
}
.text-color-primary-400,
.text-primary-400 {
color: var(--color-dark-primary-400);
}
.bg-color-primary-600,
.bg-primary-600 {
background-color: var(--color-dark-primary-600);
}
.bg-color-primary-400,
.bg-primary-400 {
background-color: var(--color-dark-primary-400);
}
.border-color-primary-600,
.border-primary-600 {
border-color: var(--color-dark-primary-600);
}
.border-color-primary-400,
.border-primary-400 {
border-color: var(--color-dark-primary-400);
}
/* 表单元素适配 */
input,
select,
@ -220,6 +264,37 @@
.bg-amber-100 {
background-color: var(--color-primary-50);
}
/* 处理颜色命名不一致的问题 */
.text-color-primary-600,
.text-primary-600 {
color: var(--color-primary-600);
}
.text-color-primary-400,
.text-primary-400 {
color: var(--color-primary-400);
}
.bg-color-primary-600,
.bg-primary-600 {
background-color: var(--color-primary-600);
}
.bg-color-primary-400,
.bg-primary-400 {
background-color: var(--color-primary-400);
}
.border-color-primary-600,
.border-primary-600 {
border-color: var(--color-primary-600);
}
.border-color-primary-400,
.border-primary-400 {
border-color: var(--color-primary-400);
}
}
/* 背景渐变动画 */
@ -243,6 +318,25 @@
.border-theme-primary-light { border-color: var(--theme-primary-light); }
.border-theme-primary-dark { border-color: var(--theme-primary-dark); }
/* 添加color-primary和primary颜色类的统一映射 */
.text-primary-500, .text-color-primary-500 { color: var(--theme-primary); }
.text-primary-400, .text-color-primary-400 { color: var(--theme-primary-light); }
.text-primary-600, .text-color-primary-600 { color: var(--theme-primary-dark); }
.text-primary-300, .text-color-primary-300 { color: var(--color-primary-300); }
.text-primary-700, .text-color-primary-700 { color: var(--color-primary-700); }
.bg-primary-500, .bg-color-primary-500 { background-color: var(--theme-primary); }
.bg-primary-400, .bg-color-primary-400 { background-color: var(--theme-primary-light); }
.bg-primary-600, .bg-color-primary-600 { background-color: var(--theme-primary-dark); }
.bg-primary-50, .bg-color-primary-50 { background-color: var(--theme-primary-bg); }
.bg-primary-100, .bg-color-primary-100 { background-color: var(--theme-primary-bg-hover); }
.border-primary-500, .border-color-primary-500 { border-color: var(--theme-primary); }
.border-primary-400, .border-color-primary-400 { border-color: var(--theme-primary-light); }
.border-primary-600, .border-color-primary-600 { border-color: var(--theme-primary-dark); }
.border-primary-300, .border-color-primary-300 { border-color: var(--color-primary-300); }
.border-primary-700, .border-color-primary-700 { border-color: var(--color-primary-700); }
/* 主题特殊背景 - 使用统一命名并移除重复定义 */
.bg-scroll-bg { background-color: var(--bg-scroll); }
.bg-recipe-paper { background-color: var(--bg-recipe); }