青岛演示第一版
|
|
@ -0,0 +1,141 @@
|
|||
# Gallery 配置驱动系统
|
||||
|
||||
## ✨ 已完成的改造
|
||||
|
||||
### 新增文件结构
|
||||
|
||||
```
|
||||
gallery/
|
||||
├── src/
|
||||
│ ├── data/
|
||||
│ │ └── gallery-config.js ← 核心配置文件(所有图片路径在这里)
|
||||
│ ├── components/
|
||||
│ │ └── sections/
|
||||
│ │ ├── HeroSection.astro ← 首屏大图组件
|
||||
│ │ ├── FullBleedSection.astro ← 全屏单图组件
|
||||
│ │ ├── QuadGridSection.astro ← 四宫格组件
|
||||
│ │ ├── DualSection.astro ← 双栏组件
|
||||
│ │ └── GridSection.astro ← 网格布局组件
|
||||
│ └── pages/
|
||||
│ └── index.astro ← 主页(动态渲染)
|
||||
└── public/
|
||||
└── media/ ← 所有图片资源存放处
|
||||
```
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 复制图片到项目
|
||||
|
||||
```bash
|
||||
cd /Users/sinlee/Documents/git-repos/gallery
|
||||
chmod +x copy-photos.sh
|
||||
./copy-photos.sh
|
||||
```
|
||||
|
||||
### 2. 启动开发服务器
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
然后访问 `http://localhost:4321`
|
||||
|
||||
## 📸 当前图片布局
|
||||
|
||||
根据你的图片命名,我已经这样组织了内容:
|
||||
|
||||
1. **首屏** - 四人合照 (`0123_1.JPG`)
|
||||
2. **全屏单图** - 另一张合照 (`0123_2.JPG`)
|
||||
3. **四宫格** - 0号人物的四张照片 (`0_1` 到 `0_4`)
|
||||
4. **双栏** - 23号合照 + 1号单人
|
||||
5. **网格** - 2号和3号的照片
|
||||
6. **全屏结尾** - 0号照片
|
||||
|
||||
## 🎨 如何修改布局
|
||||
|
||||
### 只需编辑一个文件:`src/data/gallery-config.js`
|
||||
|
||||
#### 示例 1:修改首屏图片
|
||||
|
||||
```javascript
|
||||
hero: {
|
||||
image: '/media/0123_2.JPG', // 改成其他图片
|
||||
title: '新的标题',
|
||||
subtitle: '新的副标题'
|
||||
}
|
||||
```
|
||||
|
||||
#### 示例 2:添加新的四宫格
|
||||
|
||||
在 `sections` 数组中添加:
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'quad-grid',
|
||||
title: '新的四宫格',
|
||||
images: [
|
||||
'/media/1_1.JPG',
|
||||
'/media/2_1.JPG',
|
||||
'/media/2_2.JPG',
|
||||
'/media/3_1.JPG'
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 示例 3:添加双栏布局
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'dual',
|
||||
cards: [
|
||||
{
|
||||
image: '/media/0_1.JPG',
|
||||
title: '左边标题',
|
||||
description: '左边描述'
|
||||
},
|
||||
{
|
||||
image: '/media/1_1.JPG',
|
||||
title: '右边标题',
|
||||
description: '右边描述'
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 📦 支持的布局类型
|
||||
|
||||
- `hero` - 首屏大图
|
||||
- `full-bleed` - 全屏单图(支持 left/right/center 对齐)
|
||||
- `quad-grid` - 四宫格(2x2)
|
||||
- `dual` - 双栏布局
|
||||
- `grid` - 灵活网格(支持 large 和 normal 尺寸)
|
||||
|
||||
## 🔄 部署到 VPS
|
||||
|
||||
修改完成后:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "更新 gallery 配置"
|
||||
git push
|
||||
```
|
||||
|
||||
自动部署会触发,几秒后就能在 `https://show.leexxx.com` 看到更新!
|
||||
|
||||
## 💡 提示
|
||||
|
||||
- **图片路径**:相对于 `public/` 目录,所以 `/media/xxx.jpg` 对应 `public/media/xxx.jpg`
|
||||
- **图片格式**:支持 JPG, PNG, WebP
|
||||
- **添加图片**:只需把新图片放到 `public/media/` 然后在配置文件中引用即可
|
||||
- **调整顺序**:在 `sections` 数组中调整顺序即可改变页面布局顺序
|
||||
|
||||
## 🎯 图片命名规则(你的照片)
|
||||
|
||||
- `0_X.JPG` - 0号人物的照片
|
||||
- `1_X.JPG` - 1号人物的照片
|
||||
- `2_X.JPG` - 2号人物的照片
|
||||
- `3_X.JPG` - 3号人物的照片
|
||||
- `0123_X.JPG` - 四人合照
|
||||
- `23_X.JPG` - 2号和3号的合照
|
||||
|
||||
你可以根据这些照片自由组织任何布局!
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 复制图片到 gallery 项目
|
||||
echo "开始复制图片..."
|
||||
|
||||
# 源目录
|
||||
SOURCE_DIR="/Users/sinlee/photo_test"
|
||||
# 目标目录
|
||||
TARGET_DIR="/Users/sinlee/Documents/git-repos/gallery/public/media"
|
||||
|
||||
# 检查源目录是否存在
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "错误: 源目录不存在: $SOURCE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查目标目录是否存在,不存在则创建
|
||||
if [ ! -d "$TARGET_DIR" ]; then
|
||||
mkdir -p "$TARGET_DIR"
|
||||
echo "创建目标目录: $TARGET_DIR"
|
||||
fi
|
||||
|
||||
# 复制所有 JPG 文件
|
||||
cp "$SOURCE_DIR"/*.JPG "$TARGET_DIR"/
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ 图片复制成功!"
|
||||
echo "已复制的文件:"
|
||||
ls -lh "$TARGET_DIR"/*.JPG
|
||||
else
|
||||
echo "❌ 复制失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "下一步: 在项目目录执行 pnpm dev 启动开发服务器"
|
||||
|
After Width: | Height: | Size: 7.4 MiB |
|
After Width: | Height: | Size: 8.8 MiB |
|
After Width: | Height: | Size: 10 MiB |
|
After Width: | Height: | Size: 9.7 MiB |
|
After Width: | Height: | Size: 6.0 MiB |
|
After Width: | Height: | Size: 9.7 MiB |
|
After Width: | Height: | Size: 8.1 MiB |
|
After Width: | Height: | Size: 7.5 MiB |
|
After Width: | Height: | Size: 7.5 MiB |
|
After Width: | Height: | Size: 7.6 MiB |
|
After Width: | Height: | Size: 8.7 MiB |
|
|
@ -0,0 +1,113 @@
|
|||
---
|
||||
interface Card {
|
||||
image: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
cards: Card[];
|
||||
}
|
||||
|
||||
const { cards } = Astro.props;
|
||||
---
|
||||
|
||||
<section class="dual-section fade-in-section">
|
||||
<div class="dual-container">
|
||||
{cards.map((card, index) => (
|
||||
<div class="dual-card" style={`--delay: ${index * 0.2}s`}>
|
||||
<div class="dual-image-wrapper">
|
||||
<img src={card.image} alt={card.title} loading="lazy" />
|
||||
</div>
|
||||
<div class="dual-text">
|
||||
<h3>{card.title}</h3>
|
||||
<p>{card.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.dual-section {
|
||||
padding: 6rem 2rem;
|
||||
background: linear-gradient(to bottom, #f8f9fa 0%, #ffffff 100%);
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
transition: opacity 0.8s ease, transform 0.8s ease;
|
||||
}
|
||||
|
||||
.dual-section.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.dual-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.dual-card {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
animation-delay: var(--delay);
|
||||
}
|
||||
|
||||
.dual-image-wrapper {
|
||||
aspect-ratio: 4/5;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.dual-image-wrapper img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
|
||||
.dual-card:hover .dual-image-wrapper img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.dual-text {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.dual-text h3 {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.dual-text p {
|
||||
font-size: 1.1rem;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 968px) {
|
||||
.dual-container {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.dual-section {
|
||||
padding: 4rem 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
---
|
||||
interface Props {
|
||||
image: string;
|
||||
title: string;
|
||||
description: string;
|
||||
align: 'left' | 'right' | 'center';
|
||||
}
|
||||
|
||||
const { image, title, description, align } = Astro.props;
|
||||
---
|
||||
|
||||
<section class="full-bleed-section fade-in-section" data-align={align}>
|
||||
<div class="full-bleed-image-wrapper">
|
||||
<img src={image} alt={title} loading="lazy" />
|
||||
</div>
|
||||
<div class="full-bleed-content">
|
||||
<h2>{title}</h2>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.full-bleed-section {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
transition: opacity 0.8s ease, transform 0.8s ease;
|
||||
}
|
||||
|
||||
.full-bleed-section.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.full-bleed-image-wrapper {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.full-bleed-image-wrapper img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.full-bleed-image-wrapper::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(0, 0, 0, 0.2) 0%,
|
||||
rgba(0, 0, 0, 0.6) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.full-bleed-content {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 4rem;
|
||||
color: white;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.full-bleed-section[data-align="left"] .full-bleed-content {
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.full-bleed-section[data-align="right"] .full-bleed-content {
|
||||
align-items: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.full-bleed-section[data-align="center"] .full-bleed-content {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.full-bleed-content h2 {
|
||||
font-size: clamp(2.5rem, 6vw, 4rem);
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.full-bleed-content p {
|
||||
font-size: clamp(1.1rem, 2vw, 1.5rem);
|
||||
font-weight: 300;
|
||||
max-width: 500px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.full-bleed-content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.full-bleed-section[data-align="right"] .full-bleed-content {
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
interface GridItem {
|
||||
image: string;
|
||||
size: 'large' | 'normal';
|
||||
}
|
||||
|
||||
interface Props {
|
||||
items: GridItem[];
|
||||
}
|
||||
|
||||
const { items } = Astro.props;
|
||||
---
|
||||
|
||||
<section class="grid-section fade-in-section">
|
||||
<div class="grid-container">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
class={`grid-item ${item.size === 'large' ? 'span-large' : ''}`}
|
||||
style={`--delay: ${index * 0.1}s`}
|
||||
>
|
||||
<img src={item.image} alt={`Gallery image ${index + 1}`} loading="lazy" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.grid-section {
|
||||
padding: 6rem 2rem;
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
transition: opacity 0.8s ease, transform 0.8s ease;
|
||||
}
|
||||
|
||||
.grid-section.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
grid-auto-flow: dense;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
animation: scaleIn 0.6s ease forwards;
|
||||
animation-delay: var(--delay);
|
||||
}
|
||||
|
||||
.grid-item.span-large {
|
||||
grid-column: span 2;
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
.grid-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
aspect-ratio: 1;
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
|
||||
.grid-item.span-large img {
|
||||
aspect-ratio: 2/1;
|
||||
}
|
||||
|
||||
.grid-item:hover img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 968px) {
|
||||
.grid-container {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.grid-item.span-large {
|
||||
grid-column: span 2;
|
||||
grid-row: span 1;
|
||||
}
|
||||
|
||||
.grid-section {
|
||||
padding: 4rem 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.grid-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.grid-item.span-large {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
---
|
||||
interface Props {
|
||||
image: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
const { image, title, subtitle } = Astro.props;
|
||||
---
|
||||
|
||||
<section class="hero-section">
|
||||
<div class="hero-image-wrapper">
|
||||
<img src={image} alt={title} class="hero-image" />
|
||||
<div class="hero-overlay"></div>
|
||||
</div>
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">{title}</h1>
|
||||
<p class="hero-subtitle">{subtitle}</p>
|
||||
<div class="scroll-indicator">
|
||||
<span>向下探索</span>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M12 5v14M19 12l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.hero-section {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-image-wrapper {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.hero-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.hero-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(0, 0, 0, 0.3) 0%,
|
||||
rgba(0, 0, 0, 0.5) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: clamp(3rem, 8vw, 6rem);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
margin-bottom: 1rem;
|
||||
animation: fadeInUp 1s ease 0.3s both;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: clamp(1.2rem, 2.5vw, 1.8rem);
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.05em;
|
||||
opacity: 0.95;
|
||||
animation: fadeInUp 1s ease 0.6s both;
|
||||
}
|
||||
|
||||
.scroll-indicator {
|
||||
position: absolute;
|
||||
bottom: 3rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
animation: fadeInUp 1s ease 1s both, bounce 2s ease-in-out 2s infinite;
|
||||
}
|
||||
|
||||
.scroll-indicator span {
|
||||
font-size: 0.9rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 视差效果
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrolled = window.pageYOffset;
|
||||
const hero = document.querySelector('.hero-image') as HTMLElement;
|
||||
if (hero && scrolled < window.innerHeight) {
|
||||
hero.style.transform = `scale(1.1) translateY(${scrolled * 0.5}px)`;
|
||||
}
|
||||
});
|
||||
|
||||
// 滚动指示器点击
|
||||
document.querySelector('.scroll-indicator')?.addEventListener('click', () => {
|
||||
window.scrollTo({
|
||||
top: window.innerHeight,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
interface Props {
|
||||
title?: string;
|
||||
images: string[];
|
||||
}
|
||||
|
||||
const { title, images } = Astro.props;
|
||||
---
|
||||
|
||||
<section class="quad-grid-section fade-in-section">
|
||||
{title && <h2 class="section-title">{title}</h2>}
|
||||
<div class="quad-grid">
|
||||
{images.map((img, index) => (
|
||||
<div class="quad-item" style={`--delay: ${index * 0.1}s`}>
|
||||
<img src={img} alt={`Image ${index + 1}`} loading="lazy" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.quad-grid-section {
|
||||
padding: 6rem 2rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
transition: opacity 0.8s ease, transform 0.8s ease;
|
||||
}
|
||||
|
||||
.quad-grid-section.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
text-align: center;
|
||||
font-size: clamp(2rem, 5vw, 3rem);
|
||||
font-weight: 700;
|
||||
margin-bottom: 3rem;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.quad-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.quad-item {
|
||||
aspect-ratio: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
animation-delay: var(--delay);
|
||||
}
|
||||
|
||||
.quad-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
|
||||
.quad-item:hover img {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.quad-grid-section {
|
||||
padding: 4rem 1.5rem;
|
||||
}
|
||||
|
||||
.quad-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
// 画廊配置 - 所有布局和资源的单一数据源
|
||||
export const galleryConfig = {
|
||||
// 首屏大图 - 使用四人合照
|
||||
hero: {
|
||||
type: 'hero',
|
||||
image: '/media/0123_1.JPG',
|
||||
title: '时光印记',
|
||||
subtitle: '定格美好瞬间,记录青春故事'
|
||||
},
|
||||
|
||||
// 内容区块 - 按顺序渲染
|
||||
sections: [
|
||||
// 1. 全屏单图 - 另一张合照
|
||||
{
|
||||
type: 'full-bleed',
|
||||
align: 'left',
|
||||
image: '/media/0123_2.JPG',
|
||||
title: '相聚时刻',
|
||||
description: '珍贵的回忆,永恒的友谊'
|
||||
},
|
||||
|
||||
// 2. 四宫格 - 0号人物的四张照片
|
||||
{
|
||||
type: 'quad-grid',
|
||||
title: '个人时光',
|
||||
images: [
|
||||
'/media/0_1.JPG',
|
||||
'/media/0_2.JPG',
|
||||
'/media/0_3.JPG',
|
||||
'/media/0_4.JPG'
|
||||
]
|
||||
},
|
||||
|
||||
// 3. 左右双栏 - 2&3号合照 和 1号单人
|
||||
{
|
||||
type: 'dual',
|
||||
cards: [
|
||||
{
|
||||
image: '/media/23_1.JPG',
|
||||
title: '双人组合',
|
||||
description: '默契的搭档'
|
||||
},
|
||||
{
|
||||
image: '/media/1_1.JPG',
|
||||
title: '独特视角',
|
||||
description: '一个人的精彩'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// 4. 网格布局 - 混合展示
|
||||
{
|
||||
type: 'grid',
|
||||
items: [
|
||||
{
|
||||
image: '/media/2_1.JPG',
|
||||
size: 'large'
|
||||
},
|
||||
{
|
||||
image: '/media/2_2.JPG',
|
||||
size: 'normal'
|
||||
},
|
||||
{
|
||||
image: '/media/3_1.JPG',
|
||||
size: 'normal'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// 5. 全屏单图 - 收尾
|
||||
{
|
||||
type: 'full-bleed',
|
||||
align: 'right',
|
||||
image: '/media/0_1.JPG',
|
||||
title: '青春不散场',
|
||||
description: '愿时光不老,我们不散'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
@ -1,281 +1,84 @@
|
|||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import { galleryConfig } from '../data/gallery-config.js';
|
||||
|
||||
// Hero 大画幅展示图片
|
||||
const heroImage = {
|
||||
src: 'https://images.unsplash.com/photo-1682687220742-aba13b6e50ba?w=2400&h=1600&fit=crop',
|
||||
title: '视界之外',
|
||||
subtitle: '每一次快门,都是时间的诗',
|
||||
// 导入所有组件
|
||||
import HeroSection from '../components/sections/HeroSection.astro';
|
||||
import FullBleedSection from '../components/sections/FullBleedSection.astro';
|
||||
import DualSection from '../components/sections/DualSection.astro';
|
||||
import GridSection from '../components/sections/GridSection.astro';
|
||||
import QuadGridSection from '../components/sections/QuadGridSection.astro';
|
||||
|
||||
// 组件映射表
|
||||
const componentMap = {
|
||||
'hero': HeroSection,
|
||||
'full-bleed': FullBleedSection,
|
||||
'dual': DualSection,
|
||||
'grid': GridSection,
|
||||
'quad-grid': QuadGridSection
|
||||
};
|
||||
|
||||
// 展示作品集合 - 支持不同布局类型
|
||||
const sections = [
|
||||
{
|
||||
type: 'full-bleed', // 全出血大图
|
||||
image: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=2000&h=1200&fit=crop',
|
||||
title: '山川湖海',
|
||||
description: '在自然中寻找内心的宁静',
|
||||
align: 'left'
|
||||
},
|
||||
{
|
||||
type: 'dual', // 双图并排
|
||||
images: [
|
||||
{
|
||||
src: 'https://images.unsplash.com/photo-1514565131-fce0801e5785?w=1000&h=1200&fit=crop',
|
||||
title: '都市脉搏',
|
||||
description: '霓虹闪烁的夜晚'
|
||||
},
|
||||
{
|
||||
src: 'https://images.unsplash.com/photo-1511988617509-a57c8a288659?w=1000&h=1200&fit=crop',
|
||||
title: '人间烟火',
|
||||
description: '最真实的生活瞬间'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'stacked', // 层叠卡片
|
||||
images: [
|
||||
{
|
||||
src: 'https://images.unsplash.com/photo-1486718448742-163732cd1544?w=1200&h=800&fit=crop',
|
||||
title: '几何诗篇',
|
||||
offset: { x: 0, y: 0, rotate: -2 }
|
||||
},
|
||||
{
|
||||
src: 'https://images.unsplash.com/photo-1541701494587-cb58502866ab?w=1200&h=800&fit=crop',
|
||||
title: '色彩交响',
|
||||
offset: { x: 40, y: 40, rotate: 3 }
|
||||
},
|
||||
{
|
||||
src: 'https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=1200&h=800&fit=crop',
|
||||
title: '海天一色',
|
||||
offset: { x: 80, y: 80, rotate: -1 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'grid', // 网格展示
|
||||
images: [
|
||||
{ src: 'https://images.unsplash.com/photo-1682687220742-aba13b6e50ba?w=800&h=800&fit=crop', span: 'large' },
|
||||
{ src: 'https://images.unsplash.com/photo-1682687221038-404670f09439?w=600&h=600&fit=crop' },
|
||||
{ src: 'https://images.unsplash.com/photo-1682687220063-4742bd7fd538?w=600&h=600&fit=crop' },
|
||||
{ src: 'https://images.unsplash.com/photo-1682687220923-c58b9a4592ae?w=600&h=600&fit=crop' },
|
||||
{ src: 'https://images.unsplash.com/photo-1682687221080-5cb261c645cb?w=600&h=600&fit=crop' },
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'full-bleed',
|
||||
image: 'https://images.unsplash.com/photo-1682687220015-186f63b8850a?w=2000&h=1200&fit=crop',
|
||||
title: '时光印记',
|
||||
description: '定格永恒的瞬间',
|
||||
align: 'right'
|
||||
}
|
||||
];
|
||||
---
|
||||
|
||||
<Layout title="Gallery | 视觉艺术">
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<title>Gallery | 时光印记</title>
|
||||
<meta name="description" content="定格美好瞬间,记录青春故事">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<!-- Hero Section - 大画幅开场 -->
|
||||
<section class="hero-section">
|
||||
<div class="hero-image-wrapper">
|
||||
<img src={heroImage.src} alt={heroImage.title} class="hero-image" />
|
||||
<div class="hero-overlay"></div>
|
||||
</div>
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">{heroImage.title}</h1>
|
||||
<p class="hero-subtitle">{heroImage.subtitle}</p>
|
||||
<div class="scroll-indicator">
|
||||
<span>向下探索</span>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M12 5v14M19 12l-7 7-7-7"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 渲染首屏 -->
|
||||
<HeroSection {...galleryConfig.hero} />
|
||||
|
||||
<!-- Dynamic Sections -->
|
||||
{sections.map((section, index) => {
|
||||
if (section.type === 'full-bleed') {
|
||||
return (
|
||||
<section class={`full-bleed-section fade-in-section align-${section.align}`} data-index={index}>
|
||||
<div class="full-bleed-image-wrapper">
|
||||
<img src={section.image} alt={section.title} loading="lazy" />
|
||||
</div>
|
||||
<div class="full-bleed-content">
|
||||
<h2>{section.title}</h2>
|
||||
<p>{section.description}</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (section.type === 'dual') {
|
||||
return (
|
||||
<section class="dual-section fade-in-section" data-index={index}>
|
||||
<div class="dual-container">
|
||||
{section.images.map((img, i) => (
|
||||
<div class="dual-card" style={`--delay: ${i * 0.2}s`}>
|
||||
<div class="dual-image-wrapper">
|
||||
<img src={img.src} alt={img.title} loading="lazy" />
|
||||
</div>
|
||||
<div class="dual-text">
|
||||
<h3>{img.title}</h3>
|
||||
<p>{img.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (section.type === 'stacked') {
|
||||
return (
|
||||
<section class="stacked-section fade-in-section" data-index={index}>
|
||||
<div class="stacked-container">
|
||||
{section.images.map((img, i) => (
|
||||
<div
|
||||
class="stacked-card"
|
||||
style={`--offset-x: ${img.offset.x}px; --offset-y: ${img.offset.y}px; --rotate: ${img.offset.rotate}deg; --index: ${i};`}
|
||||
data-card-index={i}
|
||||
>
|
||||
<img src={img.src} alt={img.title} loading="lazy" />
|
||||
<div class="stacked-title">{img.title}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (section.type === 'grid') {
|
||||
return (
|
||||
<section class="grid-section fade-in-section" data-index={index}>
|
||||
<div class="grid-container">
|
||||
{section.images.map((img, i) => (
|
||||
<div class={`grid-item ${img.span === 'large' ? 'span-large' : ''}`} style={`--delay: ${i * 0.1}s`}>
|
||||
<img src={img.src} alt={`Gallery image ${i + 1}`} loading="lazy" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
<!-- 动态渲染所有 sections -->
|
||||
{galleryConfig.sections.map((section, index) => {
|
||||
const Component = componentMap[section.type];
|
||||
return Component ? (
|
||||
<Component {...section} data-index={index} />
|
||||
) : null;
|
||||
})}
|
||||
|
||||
<!-- End Section -->
|
||||
<!-- 结尾区块 -->
|
||||
<section class="end-section">
|
||||
<div class="end-content">
|
||||
<h2>探索更多</h2>
|
||||
<p>每一张作品背后,都有一个独特的故事</p>
|
||||
<button class="cta-button">联系我们</button>
|
||||
<h2>时光不老,我们不散</h2>
|
||||
<p>感谢每一个美好的瞬间</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</Layout>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--hero-height: 100vh;
|
||||
--section-padding: clamp(60px, 10vw, 120px);
|
||||
<style is:global>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
main {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #1a1a1a;
|
||||
background: #ffffff;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero-section {
|
||||
position: relative;
|
||||
height: var(--hero-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-image-wrapper {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.hero-image {
|
||||
main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transform: scale(1.1);
|
||||
animation: hero-zoom 20s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes hero-zoom {
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.hero-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(to bottom, rgba(0,0,0,0.3), rgba(0,0,0,0.6));
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
animation: hero-fade-in 1.5s ease-out 0.5s both;
|
||||
}
|
||||
|
||||
@keyframes hero-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: clamp(3rem, 8vw, 7rem);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.03em;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 0 4px 20px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: clamp(1.2rem, 3vw, 2rem);
|
||||
font-weight: 300;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 3rem;
|
||||
text-shadow: 0 2px 10px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.scroll-indicator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.7;
|
||||
animation: bounce 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(10px); }
|
||||
}
|
||||
|
||||
/* Fade In Sections */
|
||||
/* 淡入动画通用样式 */
|
||||
.fade-in-section {
|
||||
opacity: 0;
|
||||
transform: translateY(50px);
|
||||
transition: opacity 1s ease-out, transform 1s ease-out;
|
||||
transition: opacity 0.8s ease, transform 0.8s ease;
|
||||
}
|
||||
|
||||
.fade-in-section.visible {
|
||||
|
|
@ -283,318 +86,39 @@ const sections = [
|
|||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Full Bleed Section */
|
||||
.full-bleed-section {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: var(--section-padding) 0;
|
||||
}
|
||||
|
||||
.full-bleed-image-wrapper {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.full-bleed-image-wrapper img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.5s ease-out;
|
||||
}
|
||||
|
||||
.full-bleed-section.visible .full-bleed-image-wrapper img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.full-bleed-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-width: 600px;
|
||||
padding: 4rem;
|
||||
background: rgba(0,0,0,0.5);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 20px;
|
||||
margin: 0 4rem;
|
||||
}
|
||||
|
||||
.full-bleed-section.align-right .full-bleed-content {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.full-bleed-content h2 {
|
||||
font-size: clamp(2.5rem, 5vw, 4rem);
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.full-bleed-content p {
|
||||
font-size: clamp(1.1rem, 2vw, 1.5rem);
|
||||
font-weight: 300;
|
||||
opacity: 0.9;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Dual Section */
|
||||
.dual-section {
|
||||
padding: var(--section-padding) 2rem;
|
||||
}
|
||||
|
||||
.dual-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.dual-card {
|
||||
position: relative;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
background: #111;
|
||||
transform: translateY(30px);
|
||||
opacity: 0;
|
||||
transition: all 0.8s ease-out;
|
||||
transition-delay: var(--delay);
|
||||
}
|
||||
|
||||
.dual-section.visible .dual-card {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dual-image-wrapper {
|
||||
aspect-ratio: 4/5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dual-image-wrapper img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.6s ease;
|
||||
}
|
||||
|
||||
.dual-card:hover .dual-image-wrapper img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.dual-text {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.dual-text h3 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dual-text p {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.8;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Stacked Section */
|
||||
.stacked-section {
|
||||
padding: var(--section-padding) 2rem;
|
||||
min-height: 80vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stacked-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
aspect-ratio: 3/2;
|
||||
}
|
||||
|
||||
.stacked-card {
|
||||
position: absolute;
|
||||
width: 70%;
|
||||
aspect-ratio: 3/2;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
|
||||
cursor: pointer;
|
||||
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%)
|
||||
translate(var(--offset-x), var(--offset-y))
|
||||
rotate(var(--rotate));
|
||||
}
|
||||
|
||||
.stacked-section.visible .stacked-card {
|
||||
animation: stack-reveal 0.8s ease-out both;
|
||||
animation-delay: calc(var(--index) * 0.15s);
|
||||
}
|
||||
|
||||
@keyframes stack-reveal {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%)
|
||||
translate(var(--offset-x), calc(var(--offset-y) + 50px))
|
||||
rotate(var(--rotate))
|
||||
scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%)
|
||||
translate(var(--offset-x), var(--offset-y))
|
||||
rotate(var(--rotate))
|
||||
scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.stacked-card:hover {
|
||||
transform: translate(-50%, -50%)
|
||||
translate(var(--offset-x), var(--offset-y))
|
||||
rotate(0deg)
|
||||
scale(1.05);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.stacked-card img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.stacked-title {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 2rem;
|
||||
background: linear-gradient(to top, rgba(0,0,0,0.9), transparent);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.stacked-card:hover .stacked-title {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Grid Section */
|
||||
.grid-section {
|
||||
padding: var(--section-padding) 2rem;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
background: #111;
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
transition: all 0.6s ease-out;
|
||||
transition-delay: var(--delay);
|
||||
}
|
||||
|
||||
.grid-section.visible .grid-item {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.grid-item.span-large {
|
||||
grid-column: span 2;
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
.grid-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
|
||||
.grid-item:hover img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* End Section */
|
||||
/* 结尾区块 */
|
||||
.end-section {
|
||||
padding: calc(var(--section-padding) * 2) 2rem;
|
||||
padding: 8rem 2rem;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.end-content {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.end-content h2 {
|
||||
font-size: clamp(2.5rem, 5vw, 4rem);
|
||||
margin-bottom: 1rem;
|
||||
font-size: clamp(2rem, 5vw, 3rem);
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.end-content p {
|
||||
font-size: clamp(1.2rem, 2vw, 1.5rem);
|
||||
opacity: 0.8;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
padding: 1.2rem 3rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1024px) {
|
||||
.dual-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stacked-container {
|
||||
max-width: 600px;
|
||||
}
|
||||
font-size: clamp(1.1rem, 2vw, 1.5rem);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.full-bleed-content {
|
||||
margin: 0 2rem;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.grid-item.span-large {
|
||||
grid-column: span 1;
|
||||
grid-row: span 1;
|
||||
}
|
||||
|
||||
.stacked-card {
|
||||
width: 85%;
|
||||
.end-section {
|
||||
padding: 4rem 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Intersection Observer for fade-in animations
|
||||
// Intersection Observer 用于滚动动画
|
||||
const observerOptions = {
|
||||
threshold: 0.15,
|
||||
rootMargin: '0px 0px -100px 0px'
|
||||
|
|
@ -608,25 +132,8 @@ const sections = [
|
|||
});
|
||||
}, observerOptions);
|
||||
|
||||
// Observe all fade-in sections
|
||||
document.querySelectorAll('.fade-in-section').forEach(section => {
|
||||
observer.observe(section);
|
||||
});
|
||||
|
||||
// Parallax effect for hero
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrolled = window.pageYOffset;
|
||||
const heroImage = document.querySelector('.hero-image');
|
||||
if (heroImage && scrolled < window.innerHeight) {
|
||||
heroImage.style.transform = `scale(1.1) translateY(${scrolled * 0.5}px)`;
|
||||
}
|
||||
});
|
||||
|
||||
// Smooth scroll for indicator
|
||||
document.querySelector('.scroll-indicator')?.addEventListener('click', () => {
|
||||
window.scrollTo({
|
||||
top: window.innerHeight,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
// 观察所有需要动画的元素
|
||||
document.querySelectorAll('.fade-in-section').forEach(el => {
|
||||
observer.observe(el);
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||