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:
345
apps/frontend/src/pages/web-portfolios/[slug].astro
Normal file
345
apps/frontend/src/pages/web-portfolios/[slug].astro
Normal 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>
|
||||
Reference in New Issue
Block a user