删除没用文件,修复所有筛选功能
This commit is contained in:
parent
a45d393cee
commit
d36c639ca4
@ -15,7 +15,18 @@
|
|||||||
body * {
|
body * {
|
||||||
transition: background-color var(--transition-duration) ease,
|
transition: background-color var(--transition-duration) ease,
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 防止某些元素过渡 */
|
/* 防止某些元素过渡 */
|
||||||
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -59,6 +59,30 @@ const initialStyles = getInitialStyles();
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const revealElements = document.querySelectorAll('.scroll-reveal');
|
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 = {
|
const observerOptions = {
|
||||||
root: null, // 使用视口作为根元素
|
root: null, // 使用视口作为根元素
|
||||||
rootMargin: '0px',
|
rootMargin: '0px',
|
||||||
@ -116,6 +140,16 @@ const initialStyles = getInitialStyles();
|
|||||||
});
|
});
|
||||||
}, observerOptions);
|
}, observerOptions);
|
||||||
|
|
||||||
|
// 为每个元素添加超时保障,确保元素最终会显示
|
||||||
|
revealElements.forEach((element) => {
|
||||||
|
const el = element as HTMLElement;
|
||||||
|
// 设置一个3秒的超时,确保即使IntersectionObserver失效,元素也会显示
|
||||||
|
setTimeout(() => {
|
||||||
|
el.style.opacity = '1';
|
||||||
|
el.style.transform = 'none';
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
|
||||||
// 开始观察所有滚动显示元素
|
// 开始观察所有滚动显示元素
|
||||||
revealElements.forEach(element => {
|
revealElements.forEach(element => {
|
||||||
// 设置自定义阈值
|
// 设置自定义阈值
|
||||||
|
@ -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"
|
|
||||||
>
|
|
||||||
查看详情 →
|
|
||||||
</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>
|
|
@ -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} →
|
|
||||||
</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>
|
|
@ -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} →
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -7,7 +7,7 @@ interface Props {
|
|||||||
const { title, subtitle } = Astro.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="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-4xl mx-auto text-center">
|
<div class="max-w-4xl mx-auto text-center">
|
||||||
<h1 class="text-4xl font-bold mb-4">{title}</h1>
|
<h1 class="text-4xl font-bold mb-4">{title}</h1>
|
||||||
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -11,16 +11,11 @@ interface Props {
|
|||||||
|
|
||||||
const { title, description = "河北游礼宣传网站 - 探索河北的文化与魅力" } = Astro.props;
|
const { title, description = "河北游礼宣传网站 - 探索河北的文化与魅力" } = Astro.props;
|
||||||
|
|
||||||
// 社交媒体数据
|
|
||||||
const socialLinks = [
|
|
||||||
{ name: "微信", url: "#" },
|
|
||||||
{ name: "微博", url: "#" },
|
|
||||||
{ name: "抖音", url: "#" }
|
|
||||||
];
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN" class="no-js">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
@ -29,94 +24,64 @@ const socialLinks = [
|
|||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<DarkModeTransition />
|
<DarkModeTransition />
|
||||||
|
<script>
|
||||||
|
document.documentElement.classList.remove('no-js');
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<SmoothScroll />
|
<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="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="flex justify-between items-center h-16">
|
<div class="flex justify-between items-center h-16">
|
||||||
<!-- 网站Logo和名称 -->
|
<!-- 网站Logo和名称 -->
|
||||||
<div class="flex items-center">
|
<div class="flex items-center pointer-events-auto">
|
||||||
<a href="/" class="flex items-center">
|
<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>
|
</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>
|
|
||||||
|
|
||||||
<!-- 主题切换按钮和移动端菜单 -->
|
|
||||||
<div class="flex items-center">
|
|
||||||
<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">
|
|
||||||
<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>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 移动端菜单 -->
|
<!-- 导航菜单 -->
|
||||||
<div id="mobileMenu" class="md:hidden hidden pb-3">
|
<nav class="hidden md:flex space-x-8 pointer-events-auto">
|
||||||
<div class="flex flex-col space-y-2">
|
<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="/" 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="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="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="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="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="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="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="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="px-3 py-2 rounded-md text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-color-dark-card">旅游攻略</a>
|
</nav>
|
||||||
</div>
|
|
||||||
|
<!-- 主题切换按钮和移动端菜单 -->
|
||||||
|
<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-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>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
|
||||||
|
|
||||||
|
<!-- 移动端菜单 -->
|
||||||
|
<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-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">
|
<main class="flex-grow">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</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">© {new Date().getFullYear()} 河北游礼网站. 保留所有权利.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -144,4 +109,63 @@ const socialLinks = [
|
|||||||
.social-icon:hover {
|
.social-icon:hover {
|
||||||
color: var(--theme-primary);
|
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>
|
</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>
|
@ -64,20 +64,10 @@ sortedAttractions.forEach((attraction: CollectionEntry<"attractions">) => {
|
|||||||
// 按照城市出现次数排序
|
// 按照城市出现次数排序
|
||||||
cities.sort((a, b) => b.count - a.count);
|
cities.sort((a, b) => b.count - a.count);
|
||||||
|
|
||||||
// 从URL获取查询参数 - 在静态站点中只用于预填充输入框
|
|
||||||
const searchQuery = ''; // 对于静态生成的站点,我们在客户端处理搜索
|
|
||||||
const tagQuery = ''; // 标签筛选参数
|
|
||||||
const cityQuery = ''; // 城市筛选参数
|
|
||||||
|
|
||||||
// 分页逻辑
|
// 分页逻辑
|
||||||
const itemsPerPage = 9;
|
const itemsPerPage = 9;
|
||||||
const page = 1; // 对于静态生成的页面,我们默认显示第一页,客户端再处理分页
|
const page = 1; // 对于静态生成的页面,我们默认显示第一页,客户端再处理分页
|
||||||
|
|
||||||
// 搜索和筛选逻辑 - 在静态站点中,所有筛选都在客户端完成
|
|
||||||
const selectedCategory = '';
|
|
||||||
const selectedCity = '';
|
|
||||||
const selectedTags: string[] = [];
|
|
||||||
const sortBy: 'date' | 'name' = 'date';
|
|
||||||
|
|
||||||
// 对于静态生成的页面,我们提供所有景点数据,客户端再进行筛选
|
// 对于静态生成的页面,我们提供所有景点数据,客户端再进行筛选
|
||||||
let filteredAttractions = sortedAttractions;
|
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] : '其他景点';
|
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="景点 - 河北游礼">
|
<MainLayout title="景点 - 河北游礼">
|
||||||
<!-- 摄影探索风格头部 - 更鲜艳的色彩方案 -->
|
<!-- 摄影探索风格头部 - 更鲜艳的色彩方案 -->
|
||||||
<div class="relative overflow-hidden">
|
<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="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="max-w-5xl mx-auto text-center">
|
||||||
<!-- 彩色相机取景框效果 -->
|
<!-- 彩色相机取景框效果 -->
|
||||||
<div class="inline-block relative">
|
<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="container mx-auto px-4">
|
||||||
<!-- 移动端筛选按钮 -->
|
<!-- 移动端筛选按钮 -->
|
||||||
<div class="lg:hidden mb-4 flex justify-between items-center">
|
<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 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 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 dark:bg-gray-900 shadow-xl overflow-y-auto">
|
<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-gray-800 flex justify-between items-center">
|
<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>
|
<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">
|
<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" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
@ -232,7 +217,7 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
|
|||||||
<!-- 左侧滤镜区域 - 彩色摄影参数风格 - 在桌面端显示 -->
|
<!-- 左侧滤镜区域 - 彩色摄影参数风格 - 在桌面端显示 -->
|
||||||
<div class="hidden lg:block lg:col-span-3">
|
<div class="hidden lg:block lg:col-span-3">
|
||||||
<div class="sticky top-16 space-y-3 sm:space-y-6">
|
<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">
|
<form id="search-form" class="mb-4 sm:mb-6 max-w-xl mx-auto" method="get" action="/attractions">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@ -241,7 +226,7 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
|
|||||||
type="text"
|
type="text"
|
||||||
name="search"
|
name="search"
|
||||||
placeholder="搜索景点名称、描述、标签..."
|
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">
|
<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">
|
<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>
|
||||||
|
|
||||||
<!-- 自定义筛选器提示 - 彩色相机操作引导 -->
|
<!-- 自定义筛选器提示 - 彩色相机操作引导 -->
|
||||||
<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="flex items-start space-x-3">
|
||||||
<div class="text-primary-500 dark:text-primary-400">
|
<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">
|
<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 (
|
return (
|
||||||
<div class="w-full sm:w-[calc(50%-8px)] md:w-[calc(50%-12px)] lg:w-[calc(33.333%-16px)]">
|
<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">
|
<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="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">
|
<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,92 +821,65 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
|
|||||||
// 获取所有卡片容器
|
// 获取所有卡片容器
|
||||||
const cardContainers = attractionsGrid ? Array.from(attractionsGrid.children) as HTMLElement[] : [];
|
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) => {
|
cardContainers.forEach((container) => {
|
||||||
const cardTitle = container.querySelector('.attraction-title')?.textContent?.toLowerCase() || '';
|
const card = container.querySelector('.attraction-card') as HTMLElement;
|
||||||
const cardDesc = container.querySelector('.attraction-desc')?.textContent?.toLowerCase() || '';
|
if (!card) return;
|
||||||
const cardTags = container.querySelector('.attraction-tags')?.textContent?.toLowerCase() || '';
|
|
||||||
|
|
||||||
// 获取所有城市标签(现在city是数组)
|
try {
|
||||||
const cityElements = container.querySelectorAll('.attraction-city');
|
// 从data属性获取完整数据
|
||||||
let cardCities: string[] = [];
|
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())
|
||||||
|
};
|
||||||
|
|
||||||
cityElements.forEach((element) => {
|
// 匹配逻辑:满足所有已提供的筛选条件
|
||||||
const cityText = element?.textContent?.toLowerCase() || '';
|
let isMatch = true;
|
||||||
if (cityText) {
|
|
||||||
cardCities.push(cityText);
|
// 搜索词筛选
|
||||||
|
if (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 (cardCities.length === 0) {
|
if (tagValue && !cardData.tags.some((tag: string) => tag.includes(tagValue))) {
|
||||||
const singleCityText = container.querySelector('.attraction-city')?.textContent?.toLowerCase() || '';
|
isMatch = false;
|
||||||
if (singleCityText) {
|
|
||||||
cardCities.push(singleCityText);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 匹配逻辑:满足所有已提供的筛选条件
|
// 城市筛选
|
||||||
let isMatch = true;
|
if (cityValue && !cardData.city.some((city: string) => city.includes(cityValue))) {
|
||||||
|
isMatch = false;
|
||||||
|
}
|
||||||
|
|
||||||
// 搜索词筛选
|
if (isMatch) {
|
||||||
if (searchValue && !(
|
matchCount++;
|
||||||
cardTitle.includes(searchValue) ||
|
matchedCards.push(container);
|
||||||
cardDesc.includes(searchValue) ||
|
container.classList.remove('hidden-card');
|
||||||
cardTags.includes(searchValue) ||
|
} else {
|
||||||
cardCities.some(city => city.includes(searchValue))
|
container.classList.add('hidden-card');
|
||||||
)) {
|
}
|
||||||
isMatch = false;
|
} catch (error) {
|
||||||
}
|
console.error('Error filtering card:', error);
|
||||||
|
|
||||||
// 标签筛选
|
|
||||||
if (tagValue && !cardTags.includes(tagValue)) {
|
|
||||||
isMatch = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 城市筛选 - 检查cityValue是否包含在任何一个城市中
|
|
||||||
if (cityValue && !cardCities.some(city => city.includes(cityValue))) {
|
|
||||||
isMatch = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isMatch) {
|
|
||||||
matchCount++;
|
|
||||||
matchedCards.push(container);
|
|
||||||
container.classList.remove('hidden-card');
|
|
||||||
} else {
|
|
||||||
container.classList.add('hidden-card');
|
container.classList.add('hidden-card');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 重新排序匹配的卡片,使它们从第一个位置开始依次排列
|
// 重新排序匹配的卡片
|
||||||
matchedCards.forEach((container, index) => {
|
matchedCards.forEach((container, index) => {
|
||||||
container.style.order = index.toString();
|
container.style.order = index.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新无结果消息显示
|
// 更新无结果提示显示
|
||||||
if (matchCount === 0) {
|
if (matchCount === 0) {
|
||||||
// 显示无结果消息
|
|
||||||
if (noResultsMessage) {
|
if (noResultsMessage) {
|
||||||
noResultsMessage.classList.remove('hidden');
|
noResultsMessage.classList.remove('hidden');
|
||||||
// 更新无结果消息中的搜索词
|
|
||||||
if (searchTermMessage) {
|
if (searchTermMessage) {
|
||||||
let messageText = '抱歉,未找到相关景点。';
|
let messageText = '抱歉,未找到相关景点。';
|
||||||
|
|
||||||
@ -935,16 +898,13 @@ const getCity = (attraction: CollectionEntry<"attractions">) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏景点网格
|
|
||||||
if (attractionsGrid) {
|
if (attractionsGrid) {
|
||||||
attractionsGrid.classList.add('hidden');
|
attractionsGrid.classList.add('hidden');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 隐藏无结果消息
|
|
||||||
if (noResultsMessage) {
|
if (noResultsMessage) {
|
||||||
noResultsMessage.classList.add('hidden');
|
noResultsMessage.classList.add('hidden');
|
||||||
}
|
}
|
||||||
// 显示景点网格
|
|
||||||
if (attractionsGrid) {
|
if (attractionsGrid) {
|
||||||
attractionsGrid.classList.remove('hidden');
|
attractionsGrid.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
@ -184,14 +184,14 @@ if (queryParams) queryParams = '?' + queryParams.substring(1);
|
|||||||
|
|
||||||
<MainLayout title="河北美食食谱 - 河北游礼">
|
<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 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="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="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">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
<span>收集于 2023 年</span>
|
<span>收集于 2025 年</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -33,25 +33,44 @@ const relatedCultures = allCultures
|
|||||||
---
|
---
|
||||||
|
|
||||||
<MainLayout title={`${entry.data.title} - 河北游礼`}>
|
<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="md:hidden fixed bottom-6 left-1/2 transform -translate-x-1/2 z-50">
|
||||||
<div class="absolute inset-0 bg-black/30"></div>
|
<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="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<ScrollReveal animation="fade">
|
<ScrollReveal animation="fade">
|
||||||
<div class="flex flex-wrap items-center gap-2 mb-4">
|
<div class="flex flex-wrap items-center gap-2 mb-4">
|
||||||
<a href="/" class="text-white/80 hover:text-white transition-colors">首页</a>
|
<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-white/60">/</span>
|
<span class="text-ancient-black/60 dark:text-ancient-white/60 font-ancient-small">/</span>
|
||||||
<a href="/culture" class="text-white/80 hover:text-white transition-colors">文化</a>
|
<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-white/60">/</span>
|
<span class="text-ancient-black/60 dark:text-ancient-white/60 font-ancient-small">/</span>
|
||||||
<span class="text-white/60">{entry.data.title}</span>
|
<span class="text-ancient-black/60 dark:text-ancient-white/60 font-ancient-small">{entry.data.title}</span>
|
||||||
</div>
|
</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">
|
<div class="flex flex-wrap items-center gap-4 mb-4">
|
||||||
{entry.data.city && entry.data.city.length > 0 && (
|
{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>
|
<span class="mr-1">📍</span>
|
||||||
{entry.data.city.map((cityName, index) => (
|
{entry.data.city.map((cityName, index) => (
|
||||||
<>
|
<>
|
||||||
@ -63,64 +82,69 @@ const relatedCultures = allCultures
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{entry.data.pubDate && (
|
{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')}
|
<span class="mr-1">📅</span> {new Date(entry.data.pubDate).toLocaleDateString('zh-CN')}
|
||||||
</div>
|
</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}
|
<span class="mr-1">🏷️</span> {entry.data.category}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2 mb-6">
|
<div class="flex flex-wrap gap-2 mb-6">
|
||||||
{entry.data.tags.map((tag) => (
|
{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}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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="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="grid grid-cols-1 lg:grid-cols-3 gap-12">
|
||||||
<!-- 左侧内容 -->
|
<!-- 左侧内容 - 古籍风格 -->
|
||||||
<div class="lg:col-span-2">
|
<div class="lg:col-span-2">
|
||||||
<ScrollReveal animation="fade">
|
<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 />
|
<Content />
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧边栏 -->
|
<!-- 右侧边栏 - 古籍风格 -->
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<!-- 文化图片 -->
|
<!-- 文化图片 - 古籍风格 -->
|
||||||
<ScrollReveal animation="slide-up">
|
<ScrollReveal animation="slide-up">
|
||||||
<div class="rounded-lg overflow-hidden shadow-md">
|
<div class="border border-ancient-accent/40 dark:border-ancient-accent-dark/40 overflow-hidden shadow-md">
|
||||||
<div class="h-64 bg-gray-300 dark:bg-gray-700 flex items-center justify-center">
|
<div class="h-64 bg-ancient-paper-light/70 dark:bg-ancient-paper-dark/70 flex items-center justify-center relative">
|
||||||
<span class="text-gray-500 dark:text-gray-400">{entry.data.title} 图片</span>
|
<span class="text-ancient-black/40 dark:text-ancient-white/40 font-ancient">{entry.data.title} 图片</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
||||||
<!-- 文化信息卡片 -->
|
<!-- 文化信息卡片 - 古籍风格 -->
|
||||||
<ScrollReveal animation="slide-up" delay={100}>
|
<ScrollReveal animation="slide-up" delay={100}>
|
||||||
<div class="bg-gray-50 dark:bg-color-dark-card rounded-lg shadow-md p-6">
|
<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-semibold text-gray-900 dark:text-white mb-4">文化信息</h3>
|
<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 && (
|
{entry.data.city && entry.data.city.length > 0 && (
|
||||||
<div class="flex">
|
<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="text-gray-900 dark:text-white">
|
<div class="text-ancient-black dark:text-ancient-white">
|
||||||
{entry.data.city.map((cityName, index) => (
|
{entry.data.city.map((cityName, index) => (
|
||||||
<>
|
<>
|
||||||
<span>{cityName}</span>
|
<span>{cityName}</span>
|
||||||
@ -132,15 +156,15 @@ const relatedCultures = allCultures
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div class="flex">
|
<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>
|
||||||
<span class="text-gray-900 dark:text-white">{entry.data.category}</span>
|
<span class="text-ancient-black dark:text-ancient-white">{entry.data.category}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex">
|
<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">
|
<div class="flex flex-wrap gap-1">
|
||||||
{entry.data.tags.map((tag) => (
|
{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}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
@ -149,8 +173,8 @@ const relatedCultures = allCultures
|
|||||||
|
|
||||||
{entry.data.pubDate && (
|
{entry.data.pubDate && (
|
||||||
<div class="flex">
|
<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>
|
||||||
<span class="text-gray-900 dark:text-white">{new Date(entry.data.pubDate).toLocaleDateString('zh-CN')}</span>
|
<span class="text-ancient-black dark:text-ancient-white">{new Date(entry.data.pubDate).toLocaleDateString('zh-CN')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -158,11 +182,16 @@ const relatedCultures = allCultures
|
|||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
||||||
<!-- 相关文化 -->
|
<!-- 相关文化 - 古籍风格 -->
|
||||||
{relatedCultures.length > 0 && (
|
{relatedCultures.length > 0 && (
|
||||||
<ScrollReveal animation="slide-up" delay={200}>
|
<ScrollReveal animation="slide-up" delay={200}>
|
||||||
<div class="bg-gray-50 dark:bg-color-dark-card rounded-lg shadow-md p-6">
|
<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-semibold text-gray-900 dark:text-white mb-4">相关文化</h3>
|
<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">
|
<div class="space-y-4">
|
||||||
{relatedCultures.map((culture) => (
|
{relatedCultures.map((culture) => (
|
||||||
@ -171,14 +200,14 @@ const relatedCultures = allCultures
|
|||||||
class="block group"
|
class="block group"
|
||||||
>
|
>
|
||||||
<div class="flex items-start space-x-3">
|
<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">
|
<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-gray-500 dark:text-gray-400">图片</span>
|
<span class="text-xs text-ancient-black/40 dark:text-ancient-white/40">图片</span>
|
||||||
</div>
|
</div>
|
||||||
<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}
|
{culture.data.title}
|
||||||
</h4>
|
</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)}...
|
{culture.data.description.substring(0, 60)}...
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -190,17 +219,117 @@ const relatedCultures = allCultures
|
|||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<!-- 返回按钮 -->
|
<!-- 返回按钮 - 仅在非移动端显示 - 古籍风格 -->
|
||||||
<ScrollReveal animation="slide-up" delay={300}>
|
<div class="hidden md:block">
|
||||||
<a
|
<ScrollReveal animation="slide-up" delay={300}>
|
||||||
href="/culture"
|
<a
|
||||||
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"
|
href="/culture"
|
||||||
>
|
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>
|
返回所有文化
|
||||||
</ScrollReveal>
|
</a>
|
||||||
|
</ScrollReveal>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MainLayout>
|
</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
@ -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">了解更多 →</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>
|
|
@ -89,9 +89,9 @@ const visibleTravels = sortedTravels.slice(
|
|||||||
|
|
||||||
<MainLayout title="旅行攻略 - 河北游礼">
|
<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="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">
|
<div class="container mx-auto px-4 relative">
|
||||||
@ -349,7 +349,14 @@ const visibleTravels = sortedTravels.slice(
|
|||||||
href={`/travel/${travel.slug}`}
|
href={`/travel/${travel.slug}`}
|
||||||
class="block group"
|
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>
|
<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 searchInput = document.getElementById('search-input') as HTMLInputElement;
|
||||||
const searchButton = document.getElementById('search-button');
|
const searchButton = document.getElementById('search-button');
|
||||||
const testNoResultsBtn = document.getElementById('test-no-results-btn');
|
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 noResultsMessage = document.getElementById('no-results-message');
|
||||||
const searchTermMessage = document.getElementById('search-term-message');
|
const searchTermMessage = document.getElementById('search-term-message');
|
||||||
const travelList = document.getElementById('travel-list');
|
const travelList = document.getElementById('travel-list');
|
||||||
@ -1603,164 +1610,88 @@ const visibleTravels = sortedTravels.slice(
|
|||||||
|
|
||||||
let matchCount = 0;
|
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) => {
|
travelCards.forEach((card) => {
|
||||||
const cardTitle = card.querySelector('h3')?.textContent?.toLowerCase() || '';
|
const cardContainer = card.closest('a')?.parentElement;
|
||||||
const cardDesc = card.querySelector('p.font-handwriting.text-slate-600')?.textContent?.toLowerCase() || '';
|
if (!cardContainer) return;
|
||||||
const cardTags = card.querySelectorAll('span.px-2.py-0\\.5.text-xs');
|
|
||||||
const cardInfo = card.querySelectorAll('.flex.flex-wrap.gap-4 .flex.items-center');
|
|
||||||
|
|
||||||
// 获取卡片的季节、类型和城市信息
|
try {
|
||||||
let cardSeason = '';
|
// 直接从card获取数据属性
|
||||||
let cardType = '';
|
const cardData = {
|
||||||
let cardCities: string[] = [];
|
title: card.getAttribute('data-title')?.toLowerCase() || '',
|
||||||
let tagsText = '';
|
description: card.getAttribute('data-description')?.toLowerCase() || '',
|
||||||
let cardTagsList: string[] = [];
|
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() || '';
|
const matchesSearch = !searchValue ||
|
||||||
if (infoText.includes('难度')) {
|
cardData.title.includes(searchValue) ||
|
||||||
// 跳过难度信息
|
cardData.description.includes(searchValue) ||
|
||||||
} else if (infoText.includes('天')) {
|
cardData.tags.some((tag: string) => tag.includes(searchValue)) ||
|
||||||
// 跳过天数信息
|
cardData.cities.some((city: string) => city.includes(searchValue));
|
||||||
} else if (cardSeason === '') {
|
|
||||||
cardSeason = infoText;
|
|
||||||
} else if (cardType === '') {
|
|
||||||
cardType = infoText;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 提取卡片标签文本
|
// 匹配季节
|
||||||
cardTags.forEach(tag => {
|
const matchesSeasons = selectedSeasons.length === 0 ||
|
||||||
const tagText = tag.textContent?.toLowerCase().replace('#', '').trim() || '';
|
selectedSeasons.some(season => cardData.season.includes(season));
|
||||||
if (tagText) {
|
|
||||||
tagsText += tagText + ' ';
|
|
||||||
cardTagsList.push(tagText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 寻找城市信息 - 从DOM中寻找城市标签
|
// 匹配类型
|
||||||
// 首先尝试查找城市专用标签
|
const matchesTypes = selectedTypes.length === 0 ||
|
||||||
const cityElements = card.querySelectorAll('.city-tag, .location-tag');
|
selectedTypes.some(type => cardData.type.includes(type));
|
||||||
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 matchesCities = selectedCities.length === 0 ||
|
||||||
// 检查是否有标记了城市信息的元素
|
selectedCities.some(city => cardData.cities.some((cardCity: string) => cardCity.includes(city)));
|
||||||
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) {
|
const matchesTags = selectedTags.length === 0 ||
|
||||||
// 检查标题和描述中是否包含已知城市
|
selectedTags.some(tag => cardData.tags.some((cardTag: string) => cardTag.includes(tag)));
|
||||||
selectedCities.forEach(city => {
|
|
||||||
if (cardTitle.includes(city) || cardDesc.includes(city)) {
|
|
||||||
cardCities.push(city);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 匹配条件
|
// 所有条件都匹配才显示
|
||||||
const matchesSearch = !searchValue ||
|
const isMatch = matchesSearch && matchesSeasons && matchesTypes && matchesCities && matchesTags;
|
||||||
cardTitle.includes(searchValue) ||
|
|
||||||
cardDesc.includes(searchValue) ||
|
|
||||||
tagsText.includes(searchValue);
|
|
||||||
|
|
||||||
const matchesSeasons = selectedSeasons.length === 0 || selectedSeasons.some(season => cardSeason.includes(season));
|
if (isMatch) {
|
||||||
const matchesTypes = selectedTypes.length === 0 || selectedTypes.some(type => cardType.includes(type));
|
matchCount++;
|
||||||
|
cardContainer.style.display = '';
|
||||||
// 检查是否有任何选中的城市存在于卡片城市中
|
|
||||||
// 如果没有找到城市标签,但存在城市筛选条件,尝试在标题和描述中寻找
|
|
||||||
let matchesCities = selectedCities.length === 0;
|
|
||||||
if (!matchesCities) {
|
|
||||||
if (cardCities.length > 0) {
|
|
||||||
// 如果找到了城市标签,检查是否匹配
|
|
||||||
matchesCities = cardCities.some(cardCity =>
|
|
||||||
selectedCities.some(selectedCity => cardCity.includes(selectedCity))
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// 如果没有找到城市标签,尝试在标题和描述中寻找
|
cardContainer.style.display = 'none';
|
||||||
matchesCities = selectedCities.some(city =>
|
|
||||||
cardTitle.includes(city) || cardDesc.includes(city)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error('Error filtering card:', error);
|
||||||
// 标签匹配:如果有选中的标签,检查卡片标签中是否包含它们中的任何一个
|
cardContainer.style.display = 'none';
|
||||||
const matchesTags = selectedTags.length === 0 || selectedTags.some(tag =>
|
|
||||||
cardTagsList.some(cardTag => cardTag.includes(tag))
|
|
||||||
);
|
|
||||||
|
|
||||||
// 所有条件都匹配才显示
|
|
||||||
const isMatch = matchesSearch && matchesSeasons && matchesTypes && matchesCities && matchesTags;
|
|
||||||
|
|
||||||
if (isMatch) {
|
|
||||||
matchCount++;
|
|
||||||
card.classList.remove('hidden');
|
|
||||||
} else {
|
|
||||||
card.classList.add('hidden');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新无结果提示显示
|
// 更新无结果提示显示
|
||||||
if (matchCount === 0) {
|
if (matchCount === 0) {
|
||||||
|
if (travelList) travelList.classList.add('hidden');
|
||||||
if (noResultsMessage) {
|
if (noResultsMessage) {
|
||||||
noResultsMessage.classList.remove('hidden');
|
noResultsMessage.classList.remove('hidden');
|
||||||
|
|
||||||
// 更新无结果消息中的搜索词
|
|
||||||
if (searchTermMessage) {
|
if (searchTermMessage) {
|
||||||
|
let message = '抱歉,未找到相关旅行笔记。';
|
||||||
if (searchValue) {
|
if (searchValue) {
|
||||||
searchTermMessage.textContent = `抱歉,未找到与 "${searchValue}" 相关的旅行笔记。请尝试其他关键词或浏览所有旅行记录。`;
|
message += `没有找到包含"${searchValue}"的内容。`;
|
||||||
} else if (selectedSeasons.length > 0 || selectedTypes.length > 0 || selectedCities.length > 0 || selectedTags.length > 0) {
|
|
||||||
searchTermMessage.textContent = `抱歉,未找到符合当前筛选条件的旅行笔记。请尝试调整筛选条件或浏览所有旅行记录。`;
|
|
||||||
} else {
|
|
||||||
searchTermMessage.textContent = `抱歉,未找到相关旅行笔记。请尝试其他关键词或浏览所有旅行记录。`;
|
|
||||||
}
|
}
|
||||||
|
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 {
|
} else {
|
||||||
// 隐藏无结果提示
|
if (travelList) travelList.classList.remove('hidden');
|
||||||
if (noResultsMessage) {
|
if (noResultsMessage) noResultsMessage.classList.add('hidden');
|
||||||
noResultsMessage.classList.add('hidden');
|
|
||||||
}
|
|
||||||
// 显示旅行列表
|
|
||||||
if (travelList) {
|
|
||||||
travelList.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return matchCount;
|
return matchCount;
|
||||||
|
@ -90,6 +90,19 @@
|
|||||||
--color-dark-primary-900: #faefdb;
|
--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 {
|
:root {
|
||||||
/* 基础色调 */
|
/* 基础色调 */
|
||||||
--bg-primary: var(--color-gray-50);
|
--bg-primary: var(--color-gray-50);
|
||||||
@ -177,6 +190,37 @@
|
|||||||
background-color: var(--color-dark-bg);
|
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,
|
input,
|
||||||
select,
|
select,
|
||||||
@ -220,6 +264,37 @@
|
|||||||
.bg-amber-100 {
|
.bg-amber-100 {
|
||||||
background-color: var(--color-primary-50);
|
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-light { border-color: var(--theme-primary-light); }
|
||||||
.border-theme-primary-dark { border-color: var(--theme-primary-dark); }
|
.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-scroll-bg { background-color: var(--bg-scroll); }
|
||||||
.bg-recipe-paper { background-color: var(--bg-recipe); }
|
.bg-recipe-paper { background-color: var(--bg-recipe); }
|
||||||
|
Loading…
Reference in New Issue
Block a user