feat: liquid glass UI, blob background, redesign home/portfolio/about pages
- Liquid glass effect on navbar/cards with backdrop-filter invert - Animated blob gradient background (SVG-based) - Portfolio section: scene-dark invert, show 5 items on home - How Work section: step flow with numbers + connecting lines - Hero: Decision snapshot replacing problem selector - About page: inverted background with contrast fixes - Fix parallax JS bundling via Astro - Fix navbar fixed positioning after liquid glass CSS - Submenu hover fix - Clean up removed legacy files/assets
This commit is contained in:
@@ -1,307 +1,53 @@
|
||||
---
|
||||
import Base from '../../layouts/Base.astro';
|
||||
import Hero from '../../components/Hero.astro';
|
||||
import PageShell from '../../components/PageShell.astro';
|
||||
|
||||
import { getCollection, render } from 'astro:content';
|
||||
|
||||
const { slug } = Astro.params;
|
||||
const allPosts = await getCollection('blog');
|
||||
const post = allPosts.find(p => p.id === slug);
|
||||
|
||||
if (!post) {
|
||||
return Astro.redirect('/404');
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const allPosts = await getCollection('blog');
|
||||
return allPosts.map(post => ({
|
||||
const posts = await getCollection('blog', ({ data }) => !data.draft);
|
||||
|
||||
return posts.map((post) => ({
|
||||
params: { slug: post.id },
|
||||
props: { post },
|
||||
}));
|
||||
}
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { Content } = await render(post);
|
||||
|
||||
const related = allPosts
|
||||
.filter(p => p.id !== post.id)
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
|
||||
.slice(0, 3);
|
||||
|
||||
const formattedDate = post.data.date.toLocaleDateString('th-TH', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
|
||||
// Surface rotation for related cards
|
||||
const surfaces = ['soft', 'yellow', 'mint'] as const;
|
||||
function surfaceFor(i: number) {
|
||||
return surfaces[i % surfaces.length];
|
||||
}
|
||||
const formattedDate = new Intl.DateTimeFormat('th-TH', {
|
||||
dateStyle: 'long',
|
||||
}).format(post.data.pubDate);
|
||||
---
|
||||
|
||||
<Base title={`${post.data.title} | MoreminiMore`}>
|
||||
<Hero
|
||||
eyebrow={post.data.category}
|
||||
title={post.data.title}
|
||||
lede={formattedDate}
|
||||
showStats={false} />
|
||||
|
||||
{post.data.image && (
|
||||
<section class="section section-bento article-image-section">
|
||||
|
||||
|
||||
<div class="container" style="position: relative; z-index: 1;">
|
||||
<div class="article-image">
|
||||
<img src={post.data.image} alt={post.data.title} />
|
||||
</div>
|
||||
<PageShell title={`${post.data.title} | MoreminiMore`} description={post.data.description}>
|
||||
<article class="blog-article">
|
||||
<header class="blog-article-hero scene scene-light" data-scene="light">
|
||||
<div class="blog-article-heading">
|
||||
<a class="text-link" href="/blog/">บทความทั้งหมด</a>
|
||||
<p class="eyebrow">{post.data.category} · {formattedDate}</p>
|
||||
<h1>{post.data.title}</h1>
|
||||
<p>{post.data.description}</p>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<section class="section section-bento article-section">
|
||||
|
||||
|
||||
<div class="container" style="position: relative; z-index: 1;">
|
||||
<div class="bento-grid">
|
||||
<div class="blog-article-shell">
|
||||
<div class="blog-prose liquid-glass liquidGlass-wrapper">
|
||||
<div class="liquidGlass-effect" aria-hidden="true"></div>
|
||||
<div class="liquidGlass-tint" aria-hidden="true"></div>
|
||||
<div class="liquidGlass-shine" aria-hidden="true"></div>
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Main article body — full width bento tile -->
|
||||
<div class="bento-tile span-8 surface-white">
|
||||
|
||||
<div class="article-meta">
|
||||
<span class="article-date">{formattedDate}</span>
|
||||
</div>
|
||||
<div class="article-body">
|
||||
<Content />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sidebar tile: about + contact stack -->
|
||||
<div class="bento-tile span-4 surface-soft">
|
||||
|
||||
<p>ดิจิทัลเอเจนซี่ที่ช่วยให้ธุรกิจไทยเติบโตด้วยเทคโนโลยีสมัยใหม่</p>
|
||||
<a href="/about" class="btn btn-outline-dark btn-sm">ดูเพิ่มเติม</a>
|
||||
|
||||
<div class="sidebar-divider"></div>
|
||||
|
||||
<span class="tile-eyebrow-sm">สนใจบริการ?</span>
|
||||
<p style="margin-top: 8px;">ติดต่อเราได้เลย ปรึกษาฟรี!</p>
|
||||
<div class="sidebar-actions">
|
||||
<a href="/contact" class="btn btn-primary btn-sm">ติดต่อเรา</a>
|
||||
<a href="tel:0809955945" class="btn btn-outline-dark btn-sm">080-995-5945</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<section class="final-cta">
|
||||
<div class="glass-panel liquid-glass liquidGlass-wrapper">
|
||||
<div class="liquidGlass-effect" aria-hidden="true"></div>
|
||||
<div class="liquidGlass-tint" aria-hidden="true"></div>
|
||||
<div class="liquidGlass-shine" aria-hidden="true"></div>
|
||||
<p class="eyebrow">Next step</p>
|
||||
<h2>อ่านแล้วเจอโจทย์คล้ายกัน ส่งปัญหามาให้เราช่วยดูก่อนได้</h2>
|
||||
<button class="button button-primary" type="button" data-open-lead>ส่งโจทย์ให้เราดู</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{related.length > 0 && (
|
||||
<section class="section section-bento related-section">
|
||||
|
||||
|
||||
<div class="container" style="position: relative; z-index: 1;">
|
||||
<div class="section-header reveal">
|
||||
<span class="section-badge">บทความที่เกี่ยวข้อง</span>
|
||||
<h2 class="section-title">อ่านต่อ <span class="highlight">เนื้อหาใกล้เคียง</span></h2>
|
||||
</div>
|
||||
|
||||
<div class="bento-grid">
|
||||
|
||||
{related.map((r, i) => (
|
||||
<a href={`/blog/${r.id}`} class="related-link">
|
||||
<div class="bento-tile span-4">
|
||||
|
||||
<div class="related-card-body">
|
||||
{r.data.image && (
|
||||
<div class="related-image">
|
||||
<img src={r.data.image} alt={r.data.title} loading="lazy" />
|
||||
</div>
|
||||
)}
|
||||
<span class="related-date">
|
||||
{new Date(r.data.date).toLocaleDateString('th-TH', { year: 'numeric', month: 'long', day: 'numeric' })}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</Base>
|
||||
|
||||
<style>
|
||||
.article-image-section { background: var(--color-white); padding-top: 40px; padding-bottom: 40px; }
|
||||
.article-section { background: var(--color-white); }
|
||||
.related-section { background: var(--color-bg-alt); }
|
||||
.section-bento { position: relative; overflow: hidden; }
|
||||
|
||||
/* Article image (hero) */
|
||||
.article-image {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--color-bg-soft);
|
||||
}
|
||||
.article-image img {
|
||||
width: 100%;
|
||||
max-height: 500px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Main article body */
|
||||
.article-meta {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
}
|
||||
.article-date {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--color-gray-600);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.article-body {
|
||||
font-size: 17px;
|
||||
line-height: 1.8;
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
.article-body :global(h2) {
|
||||
font-family: var(--font-display);
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: var(--color-black);
|
||||
margin: 36px 0 18px;
|
||||
}
|
||||
.article-body :global(h3) {
|
||||
font-family: var(--font-display);
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
color: var(--color-black);
|
||||
margin: 28px 0 14px;
|
||||
}
|
||||
.article-body :global(p) { margin-bottom: 18px; }
|
||||
.article-body :global(ul), .article-body :global(ol) {
|
||||
margin: 16px 0;
|
||||
padding-left: 24px;
|
||||
}
|
||||
.article-body :global(li) { margin-bottom: 10px; }
|
||||
.article-body :global(a) {
|
||||
color: var(--color-primary-dark);
|
||||
font-weight: 600;
|
||||
}
|
||||
.article-body :global(a:hover) { text-decoration: underline; }
|
||||
.article-body :global(blockquote) {
|
||||
border-left: 4px solid var(--color-primary);
|
||||
padding-left: 20px;
|
||||
margin: 28px 0;
|
||||
font-style: italic;
|
||||
color: var(--color-gray-700);
|
||||
}
|
||||
.article-body :global(img) {
|
||||
border-radius: var(--radius-md);
|
||||
margin: 20px 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.article-body :global(strong) { color: var(--color-black); font-weight: 800; }
|
||||
|
||||
/* Sidebar (soft tile) */
|
||||
.sidebar-divider {
|
||||
height: 1px;
|
||||
background: var(--color-gray-200);
|
||||
margin: 24px 0;
|
||||
}
|
||||
.tile-eyebrow-sm {
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
opacity: 0.7;
|
||||
display: block;
|
||||
}
|
||||
.sidebar-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.btn-sm {
|
||||
padding: 10px 20px;
|
||||
font-size: 13px;
|
||||
border-radius: var(--radius-md);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Section header */
|
||||
.section-header { text-align: center; margin-bottom: 48px; }
|
||||
.section-badge {
|
||||
display: inline-block;
|
||||
background: var(--color-primary);
|
||||
color: var(--color-black);
|
||||
padding: 8px 20px;
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.section-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(28px, 4vw, 40px);
|
||||
font-weight: 900;
|
||||
color: var(--color-black);
|
||||
}
|
||||
.section-title .highlight { color: var(--color-primary-dark); }
|
||||
|
||||
/* Related cards */
|
||||
.related-link {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
.related-card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.related-image {
|
||||
aspect-ratio: 16/10;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-bg-soft);
|
||||
margin: -8px -8px 4px;
|
||||
}
|
||||
.related-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
.related-link:hover .related-image img { transform: scale(1.06); }
|
||||
.related-date {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.article-body { font-size: 16px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</PageShell>
|
||||
|
||||
Reference in New Issue
Block a user