照片廊首次提交

This commit is contained in:
Sin Lee 2025-11-29 00:38:26 +08:00
parent 21c819a3d5
commit 0e67a2f254
3 changed files with 675 additions and 53 deletions

View File

@ -1,11 +1,13 @@
// @ts-check
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
site: 'https://example.com',
integrations: [mdx(), sitemap()],
site: 'https://gallery.yourdomain.com',
// 或者如果是主域名:'https://yourdomain.com'
// 图片优化
image: {
service: {
entrypoint: 'astro/assets/services/sharp'
}
}
});

37
src/layouts/Layout.astro Normal file
View File

@ -0,0 +1,37 @@
---
interface Props {
title: string;
}
const { title } = Astro.props;
---
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>
<style is:global>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
background: #f5f5f5;
}
</style>

View File

@ -1,49 +1,632 @@
---
import BaseHead from '../components/BaseHead.astro';
import Footer from '../components/Footer.astro';
import Header from '../components/Header.astro';
import { SITE_DESCRIPTION, SITE_TITLE } from '../consts';
import Layout from '../layouts/Layout.astro';
// Hero 大画幅展示图片
const heroImage = {
src: 'https://images.unsplash.com/photo-1682687220742-aba13b6e50ba?w=2400&h=1600&fit=crop',
title: '视界之外',
subtitle: '每一次快门,都是时间的诗',
};
// 展示作品集合 - 支持不同布局类型
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'
}
];
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
</head>
<body>
<Header />
<main>
<h1>🧑‍🚀 Hello, Astronaut!</h1>
<p>
Welcome to the official <a href="https://astro.build/">Astro</a> blog starter template. This
template serves as a lightweight, minimally-styled starting point for anyone looking to build
a personal website, blog, or portfolio with Astro.
</p>
<p>
This template comes with a few integrations already configured in your
<code>astro.config.mjs</code> file. You can customize your setup with
<a href="https://astro.build/integrations">Astro Integrations</a> to add tools like Tailwind,
React, or Vue to your project.
</p>
<p>Here are a few ideas on how to get started with the template:</p>
<ul>
<li>Edit this page in <code>src/pages/index.astro</code></li>
<li>Edit the site header items in <code>src/components/Header.astro</code></li>
<li>Add your name to the footer in <code>src/components/Footer.astro</code></li>
<li>Check out the included blog posts in <code>src/content/blog/</code></li>
<li>Customize the blog post page layout in <code>src/layouts/BlogPost.astro</code></li>
</ul>
<p>
Have fun! If you get stuck, remember to
<a href="https://docs.astro.build/">read the docs</a>
or <a href="https://astro.build/chat">join us on Discord</a> to ask questions.
</p>
<p>
Looking for a blog template with a bit more personality? Check out
<a href="https://github.com/Charca/astro-blog-template">astro-blog-template</a>
by <a href="https://twitter.com/Charca">Maxi Ferreira</a>.
</p>
</main>
<Footer />
</body>
</html>
<Layout title="Gallery | 视觉艺术">
<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>
<!-- 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>
);
}
})}
<!-- End Section -->
<section class="end-section">
<div class="end-content">
<h2>探索更多</h2>
<p>每一张作品背后,都有一个独特的故事</p>
<button class="cta-button">联系我们</button>
</div>
</section>
</main>
</Layout>
<style>
:root {
--hero-height: 100vh;
--section-padding: clamp(60px, 10vw, 120px);
}
main {
background: #000;
color: #fff;
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 {
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;
}
.fade-in-section.visible {
opacity: 1;
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;
text-align: center;
}
.end-content h2 {
font-size: clamp(2.5rem, 5vw, 4rem);
margin-bottom: 1rem;
font-weight: 700;
}
.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;
}
}
@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%;
}
}
</style>
<script>
// Intersection Observer for fade-in animations
const observerOptions = {
threshold: 0.15,
rootMargin: '0px 0px -100px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, 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'
});
});
</script>