feat(frontend): update pages, components and branding

Refresh Astro frontend implementation including new pages (Portfolio, Teams, Services), components, and styling updates.
This commit is contained in:
2026-02-11 11:50:42 +08:00
parent be7fc902fb
commit 9c2181f743
49 changed files with 9699 additions and 899 deletions

View File

@@ -0,0 +1,345 @@
---
/**
* Portfolio Detail Page - 作品詳情頁
* Displays full project information with live website link
* Pixel-perfect implementation based on Webflow design
*/
import Layout from '../../layouts/Layout.astro'
import { fetchPortfolioBySlug, getWebsiteTypeLabel } from '../../lib/api/portfolio'
// Get slug from params
const { slug } = Astro.params
// Validate slug
if (!slug) {
return Astro.redirect('/404')
}
// Fetch portfolio item
const portfolio = await fetchPortfolioBySlug(slug)
// Handle 404 for non-existent portfolio
if (!portfolio) {
return Astro.redirect('/404')
}
// Get website type label
const typeLabel = getWebsiteTypeLabel(portfolio.websiteType)
// Get tags
const tags = portfolio.tags?.map(t => t.tag) || []
// SEO metadata
const title = `${portfolio.title} | 恩群數位案例分享`
const description = portfolio.description || `瀏覽 ${portfolio.title} 專案詳情`
---
<Layout title={title} description={description}>
<article class="portfolio-detail">
<div class="container">
<!-- Back Link -->
<a href="/portfolio" class="back-link">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/>
</svg>
返回作品列表
</a>
<!-- Project Header -->
<header class="project-header">
<div class="project-meta">
<span class="project-type-badge">{typeLabel}</span>
</div>
<h1 class="project-title">{portfolio.title}</h1>
{
portfolio.description && (
<p class="project-description">{portfolio.description}</p>
)
}
<!-- Tags -->
{
tags.length > 0 && (
<div class="project-tags">
{tags.map((tag) => (
<span class="tag">{tag}</span>
))}
</div>
)
}
</header>
<!-- Hero Image -->
{
portfolio.image?.url && (
<div class="project-hero-image">
<img
src={portfolio.image.url}
alt={portfolio.image.alt || portfolio.title}
loading="eager"
width="1200"
height="675"
/>
</div>
)
}
<!-- Project Content -->
<div class="project-content">
<div class="content-section">
<h2>專案介紹</h2>
<p>
此專案展示了我們在{typeLabel}領域的專業能力。
我們致力於為客戶打造符合品牌定位、使用體驗優良的數位產品。
</p>
</div>
<div class="content-section">
<h2>專案連結</h2>
{
portfolio.url ? (
<a
href={portfolio.url}
target="_blank"
rel="noopener noreferrer"
class="btn-primary"
>
前往網站
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/>
</svg>
</a>
) : (
<p class="text-muted">此專案暫無公開連結</p>
)
}
</div>
</div>
<!-- CTA Section -->
<div class="detail-cta">
<h3>喜歡這個作品嗎?</h3>
<p>讓我們一起為您的品牌打造獨特的數位體驗</p>
<a href="/contact-us" class="cta-button">
聯絡我們
</a>
</div>
</div>
</article>
</Layout>
<style>
/* Portfolio Detail */
.portfolio-detail {
padding: 60px 20px;
background-color: #ffffff;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
/* Back Link */
.back-link {
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 2rem;
color: var(--color-enchunblue, #23608c);
text-decoration: none;
font-weight: 500;
transition: color 0.25s ease;
}
.back-link:hover {
color: var(--color-enchunblue-hover, #1a4d6e);
}
/* Project Header */
.project-header {
text-align: center;
margin-bottom: 3rem;
}
.project-meta {
margin-bottom: 1rem;
}
.project-type-badge {
display: inline-block;
background: var(--color-enchunblue, #23608c);
color: white;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 500;
}
.project-title {
font-family: "Noto Sans TC", sans-serif;
font-size: 2.5rem;
font-weight: 700;
color: var(--color-dark-blue, #1a1a1a);
margin-bottom: 1rem;
line-height: 1.3;
}
.project-description {
font-family: "Noto Sans TC", sans-serif;
font-size: 1.125rem;
color: var(--color-gray-600, #666);
max-width: 700px;
margin: 0 auto 1.5rem;
line-height: 1.7;
}
.project-tags {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.75rem;
}
.tag {
background: var(--color-gray-100, #f5f5f5);
color: var(--color-gray-700, #4a5568);
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 500;
}
/* Hero Image */
.project-hero-image {
margin-bottom: 3rem;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
.project-hero-image img {
width: 100%;
height: auto;
display: block;
}
/* Project Content */
.project-content {
display: grid;
gap: 2.5rem;
margin-bottom: 3rem;
}
.content-section h2 {
font-family: "Noto Sans TC", sans-serif;
font-size: 1.5rem;
font-weight: 600;
color: var(--color-dark-blue, #1a1a1a);
margin-bottom: 1rem;
}
.content-section p {
color: var(--color-gray-600, #666);
line-height: 1.8;
}
.btn-primary {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--color-enchunblue, #23608c);
color: white;
padding: 0.875rem 1.75rem;
border-radius: 8px;
text-decoration: none;
font-weight: 500;
transition: all 0.3s ease;
}
.btn-primary:hover {
background: var(--color-enchunblue-hover, #1a4d6e);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(35, 96, 140, 0.3);
}
.text-muted {
color: var(--color-gray-500, #999);
}
/* Detail CTA */
.detail-cta {
text-align: center;
padding: 3rem 2rem;
background: var(--color-gray-50, #f9fafb);
border-radius: 16px;
}
.detail-cta h3 {
font-family: "Noto Sans TC", sans-serif;
font-size: 1.5rem;
font-weight: 600;
color: var(--color-enchunblue, #23608c);
margin-bottom: 0.75rem;
}
.detail-cta p {
color: var(--color-gray-600, #666);
margin-bottom: 1.5rem;
}
.cta-button {
display: inline-block;
background: var(--color-enchunblue, #23608c);
color: white;
padding: 14px 28px;
border-radius: 8px;
font-weight: 600;
text-decoration: none;
transition: all 0.3s ease;
}
.cta-button:hover {
background: var(--color-enchunblue-hover, #1a4d6e);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(35, 96, 140, 0.3);
}
/* Responsive Adjustments */
@media (max-width: 991px) {
.portfolio-detail {
padding: 50px 20px;
}
.project-title {
font-size: 2rem;
}
}
@media (max-width: 767px) {
.portfolio-detail {
padding: 40px 16px;
}
.project-title {
font-size: 1.75rem;
}
.project-description {
font-size: 1rem;
}
.content-section h2 {
font-size: 1.25rem;
}
.detail-cta {
padding: 2rem 1.5rem;
}
.detail-cta h3 {
font-size: 1.25rem;
}
}
</style>