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>
325 lines
12 KiB
Plaintext
325 lines
12 KiB
Plaintext
---
|
||
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 BentoGrid from '../components/BentoGrid.astro';
|
||
import BentoTile from '../components/BentoTile.astro';
|
||
import DecoOrb from '../components/DecoOrb.astro';
|
||
import { getCollection } from 'astro:content';
|
||
|
||
const faqItems = await getCollection('faq');
|
||
const categories = ['บริการ', 'ราคา', 'ระยะเวลา', 'AI & เทคนิค', 'หลังการขาย'];
|
||
|
||
// Group FAQ items by category (preserve original order within each category)
|
||
const groupedFaq = categories
|
||
.map(cat => ({
|
||
category: cat,
|
||
items: faqItems.filter(f => f.data.category === cat),
|
||
}))
|
||
.filter(g => g.items.length > 0);
|
||
|
||
// Split each category's items into chunks of 2 (or 3 for the last chunk if odd count)
|
||
function chunkItems<T>(arr: T[], size: number): T[][] {
|
||
const chunks: T[][] = [];
|
||
for (let i = 0; i < arr.length; i += size) {
|
||
chunks.push(arr.slice(i, i + size));
|
||
}
|
||
return chunks;
|
||
}
|
||
|
||
// Pre-compute all FAQ tiles with assigned spans + surfaces for visual rhythm
|
||
// Rotation: yellow, soft, purple-soft, mint, teal, dark, coral, purple, mint
|
||
const surfaceRotation = ['yellow', 'soft', 'purple-soft', 'mint', 'teal', 'dark', 'coral', 'purple', 'mint'] as const;
|
||
|
||
// Span strategy for category tile pairs: 7+5, 5+7, 8+4, 4+8, 7+5 ... avoid 6+6
|
||
const spanPairs = [[7, 5], [5, 7], [8, 4], [4, 8], [7, 5]] as const;
|
||
|
||
interface FaqTile {
|
||
category: string;
|
||
items: typeof faqItems;
|
||
surface: typeof surfaceRotation[number];
|
||
span: 4 | 5 | 7 | 8;
|
||
tileIndex: number; // 0, 1 within category
|
||
isFirst: boolean;
|
||
isLast: boolean;
|
||
}
|
||
|
||
const faqTiles: FaqTile[] = [];
|
||
groupedFaq.forEach((group, gIdx) => {
|
||
const chunks = chunkItems(group.items, 2);
|
||
const pair = spanPairs[gIdx % spanPairs.length];
|
||
chunks.forEach((chunk, cIdx) => {
|
||
const tileSurface = surfaceRotation[faqTiles.length % surfaceRotation.length];
|
||
faqTiles.push({
|
||
category: group.category,
|
||
items: chunk,
|
||
surface: tileSurface,
|
||
span: (pair[cIdx] ?? 6) as 4 | 5 | 7 | 8,
|
||
tileIndex: cIdx,
|
||
isFirst: cIdx === 0,
|
||
isLast: cIdx === chunks.length - 1,
|
||
});
|
||
});
|
||
});
|
||
---
|
||
|
||
<Base title="คำถามที่พบบ่อย | MoreminiMore | รับทำเว็บไซต์ SEO AI Chatbot">
|
||
<Navigation />
|
||
|
||
<PageHero
|
||
badge="FAQ"
|
||
title="คำถามที่ลูกค้าถามบ่อยที่สุด"
|
||
subtitle="30+ คำถามที่รวบรวมจากแชต LINE จริง ๆ ไม่ใช่แต่งขึ้นเอง"
|
||
/>
|
||
|
||
<!-- FAQ CATEGORIES (BENTO) -->
|
||
<section class="section section-bento">
|
||
<DecoOrb color="yellow" size="500px" speed={0.4} position={{ top: '-150px', left: '-100px' }} opacity={0.25} blur="80px" />
|
||
<DecoOrb color="soft" size="400px" speed={0.3} position={{ bottom: '-100px', right: '-100px' }} opacity={0.4} blur="80px" />
|
||
<div class="container" style="position: relative; z-index: 1;">
|
||
<BentoGrid>
|
||
{faqTiles.map((tile) => (
|
||
<BentoTile
|
||
span={tile.span}
|
||
surface={tile.surface}
|
||
eyebrow={tile.isFirst ? tile.category : `${tile.category} · ต่อ`}
|
||
>
|
||
<div class="faq-list">
|
||
{tile.items.map((item) => (
|
||
<details class="faq-item">
|
||
<summary class="faq-question">
|
||
<span class="question-text">{item.data.question}</span>
|
||
<span class="faq-toggle">+</span>
|
||
</summary>
|
||
<div class="faq-answer">
|
||
<p>{item.data.answer}</p>
|
||
</div>
|
||
</details>
|
||
))}
|
||
</div>
|
||
</BentoTile>
|
||
))}
|
||
|
||
<!-- OTHER TOPICS — full-width tile with tag cloud -->
|
||
<BentoTile span={12} surface="soft" eyebrow="เรื่องอื่น ๆ" title="คำถามอื่น ๆ ที่ลูกค้าถามบ่อย">
|
||
<div class="tag-cloud">
|
||
<span class="topic-tag">โฮสติ้ง</span>
|
||
<span class="topic-tag">โดเมน</span>
|
||
<span class="topic-tag">SSL</span>
|
||
<span class="topic-tag">ใบเสนอราคา</span>
|
||
<span class="topic-tag">ใบกำกับภาษี</span>
|
||
<span class="topic-tag">สัญญา</span>
|
||
<span class="topic-tag">NDA</span>
|
||
<span class="topic-tag">ลิขสิทธิ์งาน</span>
|
||
<span class="topic-tag">ทีมงาน</span>
|
||
<span class="topic-tag">ขนาดทีม</span>
|
||
<span class="topic-tag">ที่ตั้งบริษัท</span>
|
||
<span class="topic-tag">ตัวอย่างงาน</span>
|
||
<span class="topic-tag">ขอดูเว็บจริง</span>
|
||
<span class="topic-tag">นัดคุยนอกสถานที่</span>
|
||
</div>
|
||
</BentoTile>
|
||
</BentoGrid>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- QUICK CHANNELS (BENTO) -->
|
||
<section class="section section-bento">
|
||
<DecoOrb color="purple" size="400px" speed={0.3} position={{ top: '-100px', right: '20%' }} opacity={0.2} blur="80px" />
|
||
<DecoOrb color="yellow" size="300px" speed={0.4} position={{ bottom: '-100px', left: '-100px' }} opacity={0.3} 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>
|
||
<BentoGrid>
|
||
<BentoTile span={4} surface="yellow" eyebrow="LINE" title="@moreminimore">
|
||
<p>คนที่อยากคุยเร็ว ๆ แบบเป็นกันเอง</p>
|
||
<p><strong>ตอบใน 30 นาที (เวลาทำการ)</strong></p>
|
||
<a href="https://line.me/ti/p/~@539hdlul" target="_blank" rel="noopener" class="btn btn-dark" style="margin-top: 16px;">ทักเลย →</a>
|
||
</BentoTile>
|
||
<BentoTile span={4} surface="soft" eyebrow="โทร" title="080-995-5945">
|
||
<p>คนที่อยากคุยยาว ๆ 5–10 นาที ถามตอบสด</p>
|
||
<p><strong>จ-ศ 09:00-18:00</strong></p>
|
||
<a href="tel:0809955945" class="btn btn-dark" style="margin-top: 16px;">โทรเลย →</a>
|
||
</BentoTile>
|
||
<BentoTile span={4} surface="purple-soft" eyebrow="Email" title="contact@moreminimore.com">
|
||
<p>คนที่อยากส่งรายละเอียดโปรเจกต์ + ไฟล์แนบ</p>
|
||
<p><strong>ตอบภายใน 1 วัน</strong></p>
|
||
<a href="mailto:contact@moreminimore.com" class="btn btn-dark" style="margin-top: 16px;">ส่งอีเมล →</a>
|
||
</BentoTile>
|
||
</BentoGrid>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section section-yellow cta-section">
|
||
<div class="container">
|
||
<div class="cta-content reveal">
|
||
<h2 class="cta-title">พร้อมคุยรายละเอียด?</h2>
|
||
<p class="cta-desc">นัดปรึกษาฟรี 30 นาที ผ่าน Zoom หรือนัดเจอที่ออฟฟิศ (กรุงเทพ/สมุทรสาคร)</p>
|
||
<div class="cta-actions">
|
||
<a href="/contact" class="btn btn-dark btn-lg">นัดปรึกษา →</a>
|
||
<a href="https://line.me/ti/p/~@539hdlul" target="_blank" rel="noopener" class="btn btn-outline-dark btn-lg">ทัก LINE ตอนนี้</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<Footer />
|
||
</Base>
|
||
|
||
<style>
|
||
.section-bento {
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* FAQ inside BentoTile */
|
||
.faq-list { display: flex; flex-direction: column; gap: 10px; margin-top: 4px; }
|
||
|
||
.faq-item {
|
||
background: var(--color-white);
|
||
border-radius: var(--radius-md);
|
||
border: 1px solid var(--color-gray-200);
|
||
overflow: hidden;
|
||
transition: all 0.3s ease;
|
||
}
|
||
.faq-item[open] { border-color: var(--color-primary); }
|
||
.faq-item:hover { box-shadow: var(--shadow-sm); }
|
||
|
||
/* When inside a yellow surface, change item background to be visible */
|
||
.surface-yellow .faq-item { background: var(--color-white); }
|
||
.surface-yellow .faq-item[open] { border-color: var(--color-black); }
|
||
|
||
.faq-question {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 14px 18px;
|
||
cursor: pointer;
|
||
list-style: none;
|
||
transition: background 0.2s ease;
|
||
}
|
||
.faq-question::-webkit-details-marker { display: none; }
|
||
.faq-question:hover { background: var(--color-bg-alt); }
|
||
|
||
.question-text {
|
||
flex: 1;
|
||
font-family: var(--font-display);
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
color: var(--color-black);
|
||
padding-right: 16px;
|
||
}
|
||
.faq-toggle {
|
||
font-size: 22px;
|
||
font-weight: 300;
|
||
color: var(--color-primary-dark);
|
||
flex-shrink: 0;
|
||
line-height: 1;
|
||
}
|
||
.faq-item[open] .faq-toggle { transform: rotate(45deg); }
|
||
|
||
.faq-answer {
|
||
padding: 0 18px 16px;
|
||
}
|
||
.faq-answer p {
|
||
font-size: 14px;
|
||
line-height: 1.7;
|
||
color: var(--color-gray-700);
|
||
white-space: pre-line;
|
||
}
|
||
|
||
/* Tag cloud */
|
||
.tag-cloud {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
.topic-tag {
|
||
display: inline-block;
|
||
padding: 8px 16px;
|
||
background: var(--color-white);
|
||
color: var(--color-gray-700);
|
||
border: 1px solid var(--color-gray-200);
|
||
border-radius: var(--radius-full);
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
transition: all 0.2s ease;
|
||
}
|
||
.topic-tag:hover {
|
||
background: var(--color-primary);
|
||
border-color: var(--color-primary);
|
||
color: var(--color-black);
|
||
}
|
||
|
||
/* 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, 44px);
|
||
font-weight: 900;
|
||
line-height: 1.15;
|
||
color: var(--color-black);
|
||
margin-bottom: 16px;
|
||
}
|
||
.section-title .highlight { color: var(--color-primary-dark); }
|
||
.section-soft { background: var(--color-bg-alt); }
|
||
.section-yellow { background: var(--color-primary); }
|
||
|
||
/* 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;
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.cta-actions { flex-direction: column; }
|
||
.cta-actions .btn { width: 100%; justify-content: center; }
|
||
.faq-question { padding: 14px 16px; }
|
||
.question-text { font-size: 14px; }
|
||
}
|
||
</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>
|