feat(pages): Bento Grid redesign across all pages

Apply Bento Grid + decorative parallax orb system from about.astro
to all remaining pages (home, services, portfolio, faq, contact,
terms, privacy, blog list, blog detail, services detail).

Layout changes (consistent across all pages):
- Main content sections use <BentoGrid> + <BentoTile>
- 2-3 <DecoOrb> per page for decorative parallax (no parallax blobs
  behind text — orbs are pure decoration, z-index: 0, pointer-events: none)
- Surface variants: white, soft, yellow, purple-soft, teal, mint, dark
  (rotated across pages for visual variety)
- Asymmetric span strategy (8+4, 7+5, 6+6) instead of flat grids
- Process sections use clean 4x1 grid
- Pull quote + yellow CTA kept as-is (standalone sections)

Content rules preserved:
- All Thai content kept verbatim
- No fabricated statistics
- No emoji icons (use text numerals 01 02 03 04)
- All design tokens from global.css (no hardcoded hex)
- Existing global classes (.container, .section, .btn, .section-badge,
  .section-title, .cta-section, etc.) reused — no design system break

Build verified: 22 pages built in 1.80s, no errors.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Kunthawat Greethong
2026-06-08 23:30:48 +07:00
parent 789473271e
commit b5be45bcd6
11 changed files with 1767 additions and 1984 deletions

View File

@@ -2,18 +2,11 @@
import Base from '../../layouts/Base.astro';
import Navigation from '../../components/Navigation.astro';
import Footer from '../../components/Footer.astro';
import PageHero from '../../components/PageHero.astro';
import Icon from '../../components/Icon.astro';
import BentoGrid from '../../components/BentoGrid.astro';
import BentoTile from '../../components/BentoTile.astro';
import DecoOrb from '../../components/DecoOrb.astro';
import { getCollection, render } from 'astro:content';
const { slug } = Astro.params;
const services = await getCollection('services');
const service = services.find(s => s.id === slug);
if (!service) {
return Astro.redirect('/404');
}
export async function getStaticPaths() {
const services = await getCollection('services');
return services.map(service => ({
@@ -22,15 +15,17 @@ export async function getStaticPaths() {
}));
}
const { service } = Astro.props;
const { Content } = await render(service);
const slug = service.id;
const isWebDev = slug === 'webdev';
const isMarketing = slug === 'marketing';
const isAutomation = slug === 'automation';
const isTechConsult = slug === 'ai-consult';
// Service-specific data
const serviceData = {
// Service-specific data — keyed by slug, with safe fallback to webdev.
const serviceData: Record<string, any> = {
'webdev': {
badge: 'บริการรับทำเว็บไซต์สำหรับ SME ไทย',
title: 'สร้างเว็บไซต์',
@@ -150,26 +145,24 @@ const serviceData = {
};
const data = serviceData[slug] || serviceData['webdev'];
const featureList = data.features || data.services || [];
---
<Base title={`${service.data.title} | MoreminiMore`}>
<Navigation />
<!-- HERO (light theme, was dark) -->
<section class="service-hero">
<div class="hero-bg">
<div class="bg-dots"></div>
<div class="bg-glow"></div>
</div>
<div class="container">
<!-- HERO -->
<section class="service-hero section-bento">
<DecoOrb color="yellow" size="500px" speed={0.4} position={{ top: '-150px', right: '-100px' }} opacity={0.3} blur="80px" />
<DecoOrb color="soft" size="400px" speed={0.3} position={{ bottom: '-100px', left: '-100px' }} opacity={0.4} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
<div class="hero-grid">
<div class="hero-content">
<span class="hero-badge">{data.badge}</span>
<h1 class="hero-title kinetic-title">
<span class="word-wrapper"><span class="word" style="--delay: 0.1s">{data.title}</span></span>
<span class="word-wrapper"><span class="word" style="--delay: 0.2s">{data.titleAccent}</span></span>
{data.titleAccent2 && <span class="word-wrapper"><span class="word" style="--delay: 0.3s">{data.titleAccent2}</span></span>}
<h1 class="hero-title">
<span class="hero-line">{data.title}</span>
<span class="hero-line hero-accent">{data.titleAccent}</span>
{data.titleAccent2 && <span class="hero-line">{data.titleAccent2}</span>}
</h1>
<p class="hero-desc">{data.desc}</p>
@@ -181,7 +174,7 @@ const data = serviceData[slug] || serviceData['webdev'];
โทรหาเรา
</a>
<a href="https://line.me/ti/p/~@539hdlul" target="_blank" rel="noopener" class="btn btn-outline-dark">
<svg viewBox="0 0 24 24" fill="currentColor" viewBox="0 0 24 24"><path d="M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63H17.61v1.125h1.755c.349 0 .63.283.63.63 0 .344-.281.629-.63.629h-2.386c-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.627-.63h2.386c.349 0 .63.285.63.63 0 .349-.281.63-.63.63H17.61v1.125h1.755zm-3.855 3.016c0 .27-.174.51-.432.596-.064.021-.133.031-.199.031-.211 0-.391-.09-.51-.25l-2.443-3.317v2.94c0 .344-.279.629-.631.629-.346 0-.626-.285-.626-.629V8.108c0-.27.173-.51.43-.595.06-.023.136-.033.194-.033.195 0 .375.104.495.254l2.462 3.33V8.108c0-.345.282-.63.63-.63.345 0 .63.285.63.63v4.771zm-5.741 0c0 .344-.282.629-.631.629-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.627-.63.349 0 .631.285.631.63v4.771zm-2.466.629H4.917c-.345 0-.63-.285-.63-.629V8.108c0-.345.285-.63.63-.63.348 0 .63.285.63.63v4.141h1.756c.348 0 .629.283.629.63 0 .344-.282.629-.629.629M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608.391.082.923.258 1.058.59.12.301.079.766.038 1.08l-.164 1.02c-.045.301-.24 1.186 1.049.645 1.291-.539 6.916-4.078 9.436-9.491.371-.661.56-1.388.56-2.159"/></svg>
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63H17.61v1.125h1.755c.349 0 .63.283.63.63 0 .344-.281.629-.63.629h-2.386c-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.627-.63h2.386c.349 0 .63.285.63.63 0 .349-.281.63-.63.63H17.61v1.125h1.755zm-3.855 3.016c0 .27-.174.51-.432.596-.064.021-.133.031-.199.031-.211 0-.391-.09-.51-.25l-2.443-3.317v2.94c0 .344-.279.629-.631.629-.346 0-.626-.285-.626-.629V8.108c0-.27.173-.51.43-.595.06-.023.136-.033.194-.033.195 0 .375.104.495.254l2.462 3.33V8.108c0-.345.282-.63.63-.63.345 0 .63.285.63.63v4.771zm-5.741 0c0 .344-.282.629-.631.629-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.627-.63.349 0 .631.285.631.63v4.771zm-2.466.629H4.917c-.345 0-.63-.285-.63-.629V8.108c0-.345.285-.63.63-.63.348 0 .63.285.63.63v4.141h1.756c.348 0 .629.283.629.63 0 .344-.282.629-.629.629M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608.391.082.923.258 1.058.59.12.301.079.766.038 1.08l-.164 1.02c-.045.301-.24 1.186 1.049.645 1.291-.539 6.916-4.078 9.436-9.491.371-.661.56-1.388.56-2.159"/></svg>
ทัก LINE
</a>
</div>
@@ -196,29 +189,33 @@ const data = serviceData[slug] || serviceData['webdev'];
</div>
</div>
<!-- Side card — first 4 features as a single tile -->
<div class="hero-visual">
<div class="features-card">
<div class="card-header">
<span class="card-title">บริการหลัก</span>
</div>
{(data.features || data.services || []).slice(0, 4).map((f) => (
<div class="feature-item">
<div class="feature-icon"><Icon name={f.icon as any} /></div>
<div class="feature-content">
<span class="feature-title">{f.title}</span>
<span class="feature-desc">{f.desc || f.items?.[0]}</span>
</div>
</div>
))}
</div>
<BentoGrid>
<BentoTile span={12} surface="yellow" eyebrow="บริการหลัก" title={`${featureList.length} ความสามารถหลัก`}>
<ul class="hero-feature-list">
{featureList.slice(0, 4).map((f) => (
<li class="hero-feature-item">
<div class="hero-feature-index">{String(featureList.indexOf(f) + 1).padStart(2, '0')}</div>
<div class="hero-feature-content">
<span class="hero-feature-title">{f.title}</span>
<span class="hero-feature-desc">{f.desc || f.items?.[0]}</span>
</div>
</li>
))}
</ul>
</BentoTile>
</BentoGrid>
</div>
</div>
</div>
</section>
<!-- FEATURES -->
<section class="section features-section">
<div class="container">
<!-- FEATURES (BENTO) -->
<section class="section section-bento">
<DecoOrb color="purple" size="400px" speed={0.3} position={{ top: '-100px', left: '10%' }} opacity={0.2} blur="80px" />
<DecoOrb color="yellow" size="300px" speed={0.4} position={{ bottom: '-100px', right: '5%' }} opacity={0.25} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
<div class="section-header reveal">
<span class="section-badge">{isTechConsult ? 'บริการของเรา' : 'ความสามารถ'}</span>
<h2 class="section-title">
@@ -226,180 +223,219 @@ const data = serviceData[slug] || serviceData['webdev'];
</h2>
</div>
<div class="features-grid stagger-children">
{(data.features || data.services || []).map((f) => (
<div class="feature-card">
<div class="feature-icon"><Icon name={f.icon as any} /></div>
<h3 class="feature-title">{f.title}</h3>
<p class="feature-desc">{f.desc || f.items?.join(' · ')}</p>
</div>
))}
</div>
<BentoGrid>
{featureList.map((f: any, i: number) => {
// Asymmetric span + surface rotation per the bento design system
const span = i % 3 === 0 ? 8 : 4;
const surfaces = ['yellow', 'purple-soft', 'mint', 'soft', 'teal', 'soft'] as const;
const surface = surfaces[i % surfaces.length];
return (
<BentoTile span={span} surface={surface} eyebrow={`0${i + 1}`} title={f.title}>
<p>{f.desc || f.items?.join(' · ')}</p>
</BentoTile>
);
})}
</BentoGrid>
</div>
</section>
<!-- TARGET AUDIENCE -->
<!-- TARGET AUDIENCE (BENTO) -->
{data.targets && (
<section class="section section-soft target-section">
<div class="container">
<section class="section section-soft section-bento">
<DecoOrb color="teal" size="400px" speed={0.3} position={{ top: '-100px', right: '-100px' }} opacity={0.2} blur="80px" />
<DecoOrb color="soft" size="300px" speed={0.4} position={{ bottom: '-100px', left: '-50px' }} opacity={0.35} blur="80px" />
<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="target-grid stagger-children">
{data.targets.map((t) => (
<div class="target-card">
<div class="target-icon"><Icon name={t.icon as any} /></div>
<h3 class="target-title">{t.title}</h3>
<p class="target-desc">{t.desc}</p>
</div>
))}
</div>
<BentoGrid>
{data.targets.map((t: any, i: number) => {
// First target takes 12 to lead, then 6+6 for remaining pair
const span = i === 0 ? 12 : 6;
const surfaces = ['yellow', 'soft', 'purple-soft'] as const;
const surface = surfaces[i % surfaces.length];
return (
<BentoTile span={span} surface={surface} eyebrow={`0${i + 1}`} title={t.title}>
<p>{t.desc}</p>
</BentoTile>
);
})}
</BentoGrid>
</div>
</section>
)}
<!-- INCLUDED -->
{data.included && (
<section class="section included-section">
<div class="container">
<div class="section-header reveal">
<span class="section-badge">สิ่งที่ได้รับ</span>
<h2 class="section-title">
ทุกเว็บไซต์มาพร้อม<span class="highlight">ให้ครบ</span>
</h2>
</div>
<div class="included-grid stagger-children">
{data.included.map((item) => (
<div class="included-item">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>{item}</span>
<!-- INCLUDED + TECH OPTIONS (BENTO) -->
{(data.included || data.techOptions) && (
<section class="section section-bento">
<DecoOrb color="mint" size="400px" speed={0.3} position={{ top: '-100px', left: '20%' }} opacity={0.2} blur="80px" />
<DecoOrb color="soft" size="300px" speed={0.4} position={{ bottom: '-100px', right: '10%' }} opacity={0.35} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
{data.included && (
<>
<div class="section-header reveal">
<span class="section-badge">สิ่งที่ได้รับ</span>
<h2 class="section-title">ทุกเว็บไซต์มาพร้อม<span class="highlight">ให้ครบ</span></h2>
</div>
))}
</div>
<BentoGrid>
{data.included.map((item: string, i: number) => (
<BentoTile span={4} surface="white" eyebrow="✓" title={item}>
<p class="tile-meta">รวมอยู่ในแพ็คเกจ — ไม่มีค่าใช้จ่ายเพิ่ม</p>
</BentoTile>
))}
<BentoTile span={12} surface="yellow" eyebrow="ราคารวม" title="เว็บไซต์ + Server 1 ปี + Domain .com">
<p>เริ่มต้น <strong>฿15,500*</strong></p>
<p class="tile-meta-dark">*ราคาขึ้นอยู่กับความซับซ้อนของเว็บไซต์</p>
</BentoTile>
</BentoGrid>
</>
)}
<div class="pricing-highlight">
<p><strong>รวม:</strong> เว็บไซต์ + Server 1 ปี + Domain .com = เริ่มต้น ฿15,500*</p>
<span class="pricing-note">*ราคาขึ้นอยู่กับความซับซ้อนของเว็บไซต์</span>
</div>
</div>
</section>
)}
{data.techOptions && (
<section class="section section-soft tech-section">
<div class="container">
<div class="section-header reveal">
<span class="section-badge">เลือกระบบ</span>
<h2 class="section-title">
เลือกระบบที่<span class="highlight">เหมาะกับคุณ</span>
</h2>
</div>
<div class="tech-grid stagger-children">
{data.techOptions.map((t) => (
<div class="tech-card">
<div class="tech-badge">{t.badge}</div>
<h3 class="tech-name">{t.name}</h3>
<p class="tech-desc">{t.desc}</p>
<span class="tech-duration">{t.duration}</span>
</div>
))}
</div>
{data.techOptions && (
<>
<div class="section-header reveal" style="margin-top: 64px;">
<span class="section-badge">เลือกระบบ</span>
<h2 class="section-title">เลือกระบบที่<span class="highlight">เหมาะกับคุณ</span></h2>
</div>
<BentoGrid>
{data.techOptions.map((t: any, i: number) => (
<BentoTile span={6} surface={i === 0 ? 'yellow' : 'soft'} eyebrow={t.badge} title={t.name}>
<p>{t.desc}</p>
<p class="tile-meta-strong">{t.duration}</p>
</BentoTile>
))}
</BentoGrid>
</>
)}
</div>
</section>
)}
<!-- PRICING (BENTO) -->
{data.pricing && (
<section class="section pricing-section">
<div class="container">
<section class="section section-soft section-bento">
<DecoOrb color="yellow" size="500px" speed={0.4} position={{ top: '-150px', right: '-100px' }} opacity={0.25} blur="80px" />
<DecoOrb color="soft" size="400px" speed={0.3} position={{ bottom: '-100px', left: '-100px' }} opacity={0.4} blur="80px" />
<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>
<p class="section-desc">ชัดเจน ไม่มีค่าใช้จ่ายซ่อนเร้น</p>
</div>
<div class="pricing-list stagger-children">
{data.pricing.map((p) => (
<div class="pricing-item">
<span class="pricing-label">{p.label}</span>
<span class="pricing-value">{p.price}</span>
</div>
))}
</div>
<BentoGrid>
{data.pricing.map((p: any, i: number) => {
const surfaces = ['white', 'soft', 'yellow', 'mint', 'purple-soft'] as const;
const surface = surfaces[i % surfaces.length];
return (
<BentoTile span={4} surface={surface} eyebrow={p.label} title={p.price}>
</BentoTile>
);
})}
</BentoGrid>
</div>
</section>
)}
<!-- AI FEATURES (BENTO) -->
{data.aiFeatures && (
<section class="section section-soft ai-section">
<div class="container">
<section class="section section-bento">
<DecoOrb color="purple" size="500px" speed={0.3} position={{ top: '-100px', left: '10%' }} opacity={0.2} blur="100px" />
<DecoOrb color="yellow" size="300px" speed={0.4} position={{ bottom: '10%', right: '-50px' }} opacity={0.25} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
<div class="section-header reveal">
<span class="section-badge">AI Analytics</span>
<h2 class="section-title">
AI วิเคราะห์<span class="highlight">ทุกความเคลื่อนไหว</span>
</h2>
<h2 class="section-title">AI วิเคราะห์<span class="highlight">ทุกความเคลื่อนไหว</span></h2>
</div>
<div class="ai-grid stagger-children">
{data.aiFeatures.map((f) => (
<div class="ai-item">
<svg viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
</svg>
<span>{f}</span>
</div>
))}
</div>
<BentoGrid>
{data.aiFeatures.map((f: string, i: number) => {
// Asymmetric: first item 12, then 6+6 paired
const span = i === 0 ? 12 : 6;
const surfaces = ['yellow', 'soft', 'mint', 'purple-soft'] as const;
const surface = surfaces[i % surfaces.length];
return (
<BentoTile span={span} surface={surface} eyebrow={`✓ 0${i + 1}`} title={f}>
</BentoTile>
);
})}
</BentoGrid>
</div>
</section>
)}
<!-- WHY US (BENTO) -->
{data.whyUs && (
<section class="section why-section">
<div class="container">
<section class="section section-bento">
<DecoOrb color="yellow" size="500px" speed={0.4} position={{ top: '-150px', right: '-100px' }} opacity={0.25} blur="80px" />
<DecoOrb color="teal" size="400px" speed={0.3} position={{ bottom: '-100px', left: '-50px' }} opacity={0.2} blur="80px" />
<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="why-grid stagger-children">
{data.whyUs.map((w) => (
<div class="why-card">
<h3 class="why-title">{w.title}</h3>
<p class="why-desc">{w.desc}</p>
</div>
))}
</div>
<BentoGrid>
{data.whyUs.map((w: any, i: number) => {
const surfaces = ['yellow', 'soft', 'purple-soft'] as const;
const surface = surfaces[i % surfaces.length];
return (
<BentoTile span={4} surface={surface} eyebrow={`0${i + 1}`} title={w.title}>
<p>{w.desc}</p>
</BentoTile>
);
})}
</BentoGrid>
</div>
</section>
)}
<!-- FAQ -->
<section class="section section-soft faq-section">
<div class="container">
<!-- DYNAMIC CONTENT FROM MDX (full-width bento tile) -->
<section class="section section-soft section-bento">
<DecoOrb color="soft" size="500px" speed={0.3} position={{ top: '-100px', left: '20%' }} opacity={0.4} blur="80px" />
<DecoOrb color="yellow" size="300px" speed={0.4} position={{ bottom: '-50px', right: '-50px' }} opacity={0.2} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
<BentoGrid>
<BentoTile span={12} surface="white" eyebrow="รายละเอียดเพิ่มเติม" title="เนื้อหาจากเอกสารบริการ">
<div class="mdx-content">
<Content />
</div>
</BentoTile>
</BentoGrid>
</div>
</section>
<!-- FAQ (BENTO) -->
<section class="section section-bento">
<DecoOrb color="purple" size="400px" speed={0.3} position={{ top: '-100px', left: '20%' }} opacity={0.2} blur="80px" />
<DecoOrb color="yellow" size="300px" speed={0.4} position={{ bottom: '-50px', right: '-50px' }} opacity={0.25} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
<div class="section-header reveal">
<span class="section-badge">FAQ</span>
<h2 class="section-title">คำถาม<span class="highlight">ที่พบบ่อย</span></h2>
</div>
<div class="faq-list">
{data.faqs.map((faq) => (
<details class="faq-item">
<summary class="faq-question">
{faq.q}
<span class="faq-toggle">+</span>
</summary>
<div class="faq-answer">
<p>{faq.a}</p>
</div>
</details>
))}
</div>
<BentoGrid>
{data.faqs.map((faq: any, i: number) => {
// Hero: first question 12-span, then alternating 8+4 for visual tension
let span: 12 | 8 | 4 = 6;
if (i === 0) span = 12;
else if (i % 3 === 1) span = 8;
else if (i % 3 === 2) span = 4;
else span = 6;
const surfaces = ['yellow', 'soft', 'purple-soft', 'mint', 'teal', 'soft'] as const;
const surface = surfaces[i % surfaces.length];
return (
<BentoTile span={span} surface={surface} eyebrow={`Q${i + 1}`} title={faq.q}>
<div class="faq-answer-inline">
<p>{faq.a}</p>
</div>
</BentoTile>
);
})}
</BentoGrid>
</div>
</section>
@@ -411,7 +447,9 @@ const data = serviceData[slug] || serviceData['webdev'];
<p class="cta-desc">ติดต่อเราเพื่อคุยกันและให้คำปรึกษาฟรี! เราพร้อมช่วยคุณสร้าง{data.title}ที่ตอบโจทย์ธุรกิจ</p>
<div class="cta-actions">
<a href="tel:0809955945" class="btn btn-dark btn-lg">
<Icon name="phone" size={18} class="btn-icon" />
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18">
<path d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>
</svg>
080-995-5945
</a>
<a href="https://line.me/ti/p/~@539hdlul" target="_blank" rel="noopener" class="btn btn-outline-dark btn-lg">สอบถามราคา</a>
@@ -424,29 +462,26 @@ const data = serviceData[slug] || serviceData['webdev'];
</Base>
<style>
/* Section wrappers */
.section-bento {
position: relative;
overflow: hidden;
}
.section-soft { background: var(--color-bg-alt); }
.section-yellow { background: var(--color-primary); }
/* HERO */
.service-hero {
background: var(--color-white);
padding: 140px 0 80px;
position: relative;
overflow: hidden;
}
.hero-bg { position: absolute; inset: 0; overflow: hidden; }
.bg-dots {
position: absolute;
inset: 0;
background-image: radial-gradient(circle at 1px 1px, rgba(254, 212, 0, 0.15) 1px, transparent 0);
background-size: 40px 40px;
}
.bg-glow {
position: absolute;
inset: 0;
background: radial-gradient(ellipse at 50% 0%, rgba(254, 212, 0, 0.15) 0%, transparent 60%);
}
.hero-grid {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: 1.3fr 0.7fr;
grid-template-columns: 1.3fr 1fr;
gap: 60px;
align-items: center;
}
@@ -466,12 +501,12 @@ const data = serviceData[slug] || serviceData['webdev'];
font-family: var(--font-display);
font-size: clamp(36px, 5vw, 56px);
font-weight: 900;
line-height: 1.3; /* Thai-safe */
line-height: 1.3;
color: var(--color-black);
margin-bottom: 20px;
}
.hero-title.kinetic-title .word-wrapper { display: block; }
.hero-title.kinetic-title .word-wrapper:nth-child(2) .word { color: var(--color-primary-dark); }
.hero-line { display: block; }
.hero-accent { color: var(--color-primary-dark); }
.hero-desc {
font-size: 18px;
color: var(--color-gray-700);
@@ -494,28 +529,42 @@ const data = serviceData[slug] || serviceData['webdev'];
}
.trust-item { font-weight: 600; }
.hero-visual { display: flex; }
.features-card {
background: var(--color-white);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-xl);
padding: 32px;
width: 100%;
box-shadow: var(--shadow-md);
/* Hero feature list (inside yellow tile) — uses numeral index, no icons */
.hero-feature-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 12px;
}
.card-header { margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid var(--color-gray-200); }
.card-title { font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: var(--color-gray-500); }
.feature-item { display: flex; gap: 12px; align-items: flex-start; padding: 12px 0; }
.feature-item + .feature-item { border-top: 1px solid var(--color-gray-100); }
.feature-icon { flex-shrink: 0; }
.feature-content { display: flex; flex-direction: column; }
.feature-title { font-size: 15px; font-weight: 700; color: var(--color-black); }
.feature-desc { font-size: 13px; color: var(--color-gray-600); }
/* Generic section styles */
.section-soft { background: var(--color-bg-alt); }
.section-yellow { background: var(--color-primary); }
.hero-feature-item {
display: flex;
gap: 12px;
align-items: flex-start;
padding: 12px 0;
}
.hero-feature-item + .hero-feature-item { border-top: 1px solid rgba(0, 0, 0, 0.1); }
.hero-feature-index {
flex-shrink: 0;
width: 36px;
height: 36px;
border: 2px solid var(--color-black);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-display);
font-size: 14px;
font-weight: 900;
color: var(--color-black);
letter-spacing: -0.5px;
}
.hero-feature-content { display: flex; flex-direction: column; }
.hero-feature-title { font-size: 15px; font-weight: 700; color: var(--color-black); }
.hero-feature-desc { font-size: 13px; color: rgba(0, 0, 0, 0.7); }
/* Section header */
.section-header { text-align: center; margin-bottom: 48px; }
.section-badge {
display: inline-block;
@@ -539,193 +588,68 @@ const data = serviceData[slug] || serviceData['webdev'];
.section-title .highlight { color: var(--color-primary-dark); }
.section-desc { font-size: 16px; color: var(--color-gray-600); margin-top: 8px; }
.features-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
/* Inline meta paragraphs inside tiles */
.tile-meta {
font-size: 14px;
color: var(--color-gray-600);
}
.feature-card {
background: var(--color-white);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-xl);
padding: 32px;
transition: all 0.3s ease;
}
.feature-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); border-color: var(--color-primary); }
.features-grid > .feature-card .feature-icon { width: 64px; height: 64px; }
.feature-card .feature-title { font-size: 18px; font-weight: 800; color: var(--color-black); margin-bottom: 8px; font-family: var(--font-display); }
.feature-card .feature-desc { font-size: 14px; color: var(--color-gray-600); line-height: 1.6; }
.target-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.target-card {
background: var(--color-white);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-xl);
padding: 32px;
}
.target-icon { margin-bottom: 12px; }
.target-title { font-family: var(--font-display); font-size: 18px; font-weight: 800; color: var(--color-black); margin-bottom: 8px; }
.target-desc { font-size: 14px; color: var(--color-gray-600); line-height: 1.6; }
.included-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
max-width: 700px;
margin: 0 auto 32px;
}
.included-item {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: var(--color-bg-alt);
border-radius: var(--radius-md);
}
.included-item svg { width: 20px; height: 20px; color: var(--color-primary-dark); flex-shrink: 0; }
.included-item span { font-size: 15px; font-weight: 500; color: var(--color-black); }
.pricing-highlight {
text-align: center;
padding: 24px;
background: var(--color-primary);
border-radius: var(--radius-md);
color: var(--color-black);
}
.pricing-highlight p { font-size: 16px; margin-bottom: 4px; }
.pricing-note { font-size: 13px; }
.tech-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
max-width: 800px;
margin: 0 auto;
}
.tech-card {
background: var(--color-white);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-xl);
padding: 32px;
text-align: center;
}
.tech-badge {
display: inline-block;
width: 56px;
height: 56px;
background: var(--color-primary);
color: var(--color-black);
border-radius: 50%;
line-height: 56px;
font-family: var(--font-display);
font-size: 20px;
font-weight: 900;
margin-bottom: 16px;
}
.tech-name { font-family: var(--font-display); font-size: 20px; font-weight: 800; color: var(--color-black); margin-bottom: 8px; }
.tech-desc { font-size: 14px; color: var(--color-gray-600); line-height: 1.6; margin-bottom: 12px; }
.tech-duration { display: inline-block; padding: 4px 12px; background: var(--color-bg-soft); color: var(--color-gray-700); border-radius: var(--radius-sm); font-size: 13px; font-weight: 600; }
.pricing-list {
max-width: 700px;
margin: 0 auto;
background: var(--color-white);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-xl);
overflow: hidden;
}
.pricing-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 28px;
border-top: 1px solid var(--color-gray-200);
}
.pricing-item:first-child { border-top: none; }
.pricing-label { font-size: 15px; color: var(--color-gray-700); }
.pricing-value { font-family: var(--font-display); font-size: 16px; font-weight: 800; color: var(--color-primary-dark); }
.ai-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
max-width: 700px;
margin: 0 auto;
}
.ai-item {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: var(--color-white);
border-radius: var(--radius-md);
}
.ai-item svg { width: 20px; height: 20px; color: var(--color-primary-dark); flex-shrink: 0; }
.ai-item span { font-size: 15px; color: var(--color-black); }
.why-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.why-card {
background: var(--color-white);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-xl);
padding: 32px;
}
.why-title { font-family: var(--font-display); font-size: 20px; font-weight: 800; color: var(--color-black); margin-bottom: 12px; }
.why-desc { font-size: 14px; color: var(--color-gray-600); line-height: 1.6; }
.faq-list { max-width: 800px; margin: 0 auto; }
.faq-item {
background: var(--color-white);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-md);
margin-bottom: 12px;
overflow: hidden;
}
.faq-item[open] { border-color: var(--color-primary); }
.faq-question {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
cursor: pointer;
list-style: none;
font-family: var(--font-display);
font-size: 16px;
.tile-meta-strong {
margin-top: 12px;
font-size: 15px;
font-weight: 700;
color: var(--color-black);
}
.faq-question::-webkit-details-marker { display: none; }
.faq-toggle { color: var(--color-primary); font-size: 24px; line-height: 1; }
.faq-item[open] .faq-toggle { transform: rotate(45deg); }
.faq-answer { padding: 0 24px 24px; }
.faq-answer p { font-size: 15px; line-height: 1.8; color: var(--color-gray-700); }
.tile-meta-dark {
font-size: 14px;
opacity: 0.7;
}
/* Inline FAQ (single answer per tile) */
.faq-answer-inline p {
font-size: 15px;
line-height: 1.7;
color: var(--color-gray-700);
}
.surface-yellow .faq-answer-inline p { color: rgba(0, 0, 0, 0.85); }
.surface-purple .faq-answer-inline p,
.surface-teal .faq-answer-inline p,
.surface-coral .faq-answer-inline p,
.surface-dark .faq-answer-inline p { color: rgba(255, 255, 255, 0.95); }
/* MDX content wrapper */
.mdx-content { font-size: 16px; line-height: 1.7; color: var(--color-gray-700); }
.mdx-content :global(h2) { font-family: var(--font-display); font-size: 22px; font-weight: 800; margin: 24px 0 12px; color: var(--color-black); }
.mdx-content :global(h3) { font-family: var(--font-display); font-size: 18px; font-weight: 700; margin: 16px 0 8px; color: var(--color-black); }
.mdx-content :global(p) { margin-bottom: 12px; }
.mdx-content :global(ul) { padding-left: 20px; margin-bottom: 12px; }
.mdx-content :global(li) { margin-bottom: 4px; }
.mdx-content :global(strong) { color: var(--color-black); }
/* CTA */
.cta-content { text-align: center; max-width: 700px; margin: 0 auto; }
.cta-title { font-family: var(--font-display); font-size: clamp(28px, 4vw, 44px); font-weight: 900; color: var(--color-black); margin-bottom: 16px; }
.cta-desc { font-size: 18px; color: rgba(0, 0, 0, 0.7); margin-bottom: 32px; }
.cta-actions { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; }
.cta-actions .btn svg { display: inline-block; vertical-align: middle; margin-right: 6px; }
@media (max-width: 1024px) {
.hero-grid { grid-template-columns: 1fr; gap: 40px; }
.features-grid { grid-template-columns: 1fr; }
.target-grid { grid-template-columns: 1fr; }
.why-grid { grid-template-columns: 1fr; }
.included-grid { grid-template-columns: 1fr; }
.tech-grid { grid-template-columns: 1fr; }
.ai-grid { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
.hero-actions { flex-direction: column; }
.hero-actions .btn { width: 100%; justify-content: center; }
.cta-actions { flex-direction: column; }
.cta-actions .btn { width: 100%; justify-content: center; }
.hero-actions, .cta-actions { flex-direction: column; }
.hero-actions .btn, .cta-actions .btn { width: 100%; justify-content: center; }
}
</style>
<script>
// Parallax orbs (use data-parallax-speed from DecoOrb)
const parallaxEls = document.querySelectorAll('[data-parallax-speed]');
function updateParallax() {
const scrolled = window.scrollY;
parallaxEls.forEach(el => {
const speed = parseFloat(el.getAttribute('data-parallax-speed') || '0.4');
const ty = scrolled * speed * -0.3;
el.style.transform = `translate3d(0, ${ty}px, 0)`;
});
}
window.addEventListener('scroll', () => requestAnimationFrame(updateParallax), { passive: true });
</script>