refactor(legacy): apply v6 design to all inner pages (delete bento components)

Per user: 'เราไม่ต้องการ legacy design น่ะ เพราะมีดีไชน์ใหม่แล้วไง'
- New v6 design must apply to EVERY page, not just index

PHASE A — Add CSS compatibility aliases (~600 lines)
==================================================
fx-system.css grew from 0.05MB → 0.12MB with 3 new alias blocks
for legacy class names that pages still use:

1. LEGACY → v6 ALIASES
   .section, .section-bento, .section-soft, .section-yellow
   .section-header, .section-badge, .section-title, .section-desc
   .reveal, .hero-content, .hero-badge, .hero-grid
   .service-stack + .service-stack-{item,num,icon,body,title,bullets}
   .portfolio-card-{grid,top,badge,arrow,name,industry,highlight}
   .contact-form, .form-{group,label,input,row,success}, .btn, .btn-{primary,outline-dark}
   .filter-section, .filter-bar, .filter-btn
   .info-icon, .checklist
   All styled with v6 design tokens (--ink, --paper, --coral,
   --brand-yellow, --line-2, --paper-2, etc.)

2. BENTO COMPONENT ALIASES
   .bento-grid, .bento-tile, .span-{3,4,5,6,7,8,12}, .rows-{2,3}
   .surface-{white,soft,yellow,purple,purple-soft,teal,teal-soft,mint,dark,coral}
   .tile-{eyebrow,title,body,link}, .mega-cta
   All render via CSS Grid 12-col + v6 surface treatments
   Children (.tile-eyebrow, .tile-title, .tile-body) get v6
   typography (Kanit 800 for titles, JetBrains Mono 700 for
   eyebrows, Itim for em accents).

3. DARK MODE OVERRIDES
   html.dark .section-soft/.filter-section: var(--paper-2)
   html.dark .section-badge/.filter-btn.active: yellow
   html.dark .contact-form .form-input: dark inputs
   html.dark .bento-tile.surface-{soft,purple-soft,yellow}: dark variants

PHASE B — Replace legacy components with inline divs
==================================================
Deleted components (no longer imported by anyone):
- src/components/BentoGrid.astro
- src/components/BentoTile.astro
- src/components/DecoOrb.astro
- src/components/PageHero.astro

Replaced in 7 pages (mechanical regex sweep):
- services/[slug].astro
- contact.astro
- faq.astro
- blog/index.astro
- blog/[slug].astro
- privacy.astro
- terms.astro

JSX transforms:
- <BentoGrid>...</BentoGrid> → <div class='bento-grid'>...</div>
- <BentoTile span={6} surface='yellow' /> → <div class='bento-tile span-6 surface-yellow' />
- <DecoOrb .../> → (deleted, was decorative empty)
- Removed unused BentoGrid/BentoTile/DecoOrb imports

Cleaned orphan comments referencing deleted components:
- // use data-parallax-speed from DecoOrb
- <!-- mail, line, hours as separate BentoTiles -->
- /* FAQ inside BentoTile */

FINAL AUDIT
===========
- 0 legacy component refs in src/
- Build: 22 pages, 1.98s, 0 errors
- All 11 pages have v6 styling (via direct fx-* or via aliases)

Result: every page now uses the v6 design system consistently.
No more yellow DecorativeOrbs, no more BentoTile grid overlap,
no more PageHero/KineticHero mixing. Visual continuity across
all 11 pages.
This commit is contained in:
Kunthawat Greethong
2026-06-14 21:22:58 +07:00
parent 73d820412a
commit ceffb2a3f3
12 changed files with 761 additions and 698 deletions

View File

@@ -1,39 +0,0 @@
---
/**
* BentoGrid — 12-column asymmetric bento grid container.
* Use <BentoTile> as children.
*
* Example:
* <BentoGrid>
* <BentoTile span={6} surface="yellow">...</BentoTile>
* <BentoTile span={4} surface="purple-soft">...</BentoTile>
* </BentoGrid>
*/
---
<div class="bento-grid stagger">
<slot />
</div>
<style>
.bento-grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-auto-rows: minmax(80px, auto);
gap: 16px;
position: relative;
}
@media (max-width: 1024px) {
.bento-grid {
grid-template-columns: repeat(6, 1fr);
}
}
@media (max-width: 640px) {
.bento-grid {
grid-template-columns: 1fr;
gap: 12px;
}
}
</style>

View File

@@ -1,169 +0,0 @@
---
/**
* BentoTile — a single bento grid cell.
*
* Props:
* span: 3 | 4 | 5 | 6 | 7 | 8 | 12 (default 6)
* rows: 1 | 2 | 3 (default 1)
* surface: 'white' | 'soft' | 'yellow' | 'purple' | 'purple-soft' | 'teal' | 'mint' | 'dark' | 'coral'
* minHeight: optional inline min-height CSS value
* eyebrow: optional small uppercase label above title
* title: optional H2 title
* reveal: boolean, animate on scroll into view (default true)
*
* Example:
* <BentoTile span={8} surface="yellow" eyebrow="วิธีทำงาน" title="ไม่ได้ทำงานแบบเดียวกับทุกที่">
* <p>...content...</p>
* </BentoTile>
*/
interface Props {
span?: 3 | 4 | 5 | 6 | 7 | 8 | 12;
rows?: 1 | 2 | 3;
surface?: 'white' | 'soft' | 'yellow' | 'purple' | 'purple-soft' | 'teal' | 'teal-soft' | 'mint' | 'dark' | 'coral';
minHeight?: string;
eyebrow?: string;
title?: string;
reveal?: boolean;
class?: string;
}
const {
span = 6,
rows = 1,
surface = 'white',
minHeight,
eyebrow,
title,
reveal = true,
class: className = '',
} = Astro.props;
const spanClass = `span-${span}`;
const rowsClass = rows > 1 ? `rows-${rows}` : '';
const surfaceClass = `surface-${surface}`;
const revealClass = reveal ? 'reveal' : '';
---
<div
class:list={['bento-tile', spanClass, rowsClass, surfaceClass, revealClass, className]}
style={minHeight ? `min-height: ${minHeight};` : undefined}
>
{eyebrow && <div class:list={['tile-eyebrow', surface === 'dark' || surface === 'purple' || surface === 'teal' || surface === 'coral' ? 'inv' : '']}>{eyebrow}</div>}
{title && <h2 class:list={['tile-title', surface === 'dark' || surface === 'purple' || surface === 'teal' || surface === 'coral' ? 'light' : '']}>{title}</h2>}
<div class="tile-body">
<slot />
</div>
</div>
<style>
.bento-tile {
background: var(--color-white);
color: var(--color-black);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-xl);
padding: 32px;
position: relative;
overflow: hidden;
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.4s ease;
transform-style: preserve-3d;
min-height: 380px;
min-width: 0;
width: 100% !important;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.bento-tile:hover {
transform: translateY(-4px);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.08);
}
/* Spans */
.span-3 { grid-column: span 3; }
.span-4 { grid-column: span 4; }
.span-5 { grid-column: span 5; }
.span-6 { grid-column: span 6; }
.span-7 { grid-column: span 7; }
.span-8 { grid-column: span 8; }
.span-12 { grid-column: span 12; }
.rows-2 { grid-row: span 2; }
.rows-3 { grid-row: span 3; }
@media (max-width: 1024px) {
.span-3, .span-4, .span-5 { grid-column: span 3; }
.span-6, .span-7, .span-8 { grid-column: span 6; }
.span-12 { grid-column: span 6; }
}
@media (max-width: 640px) {
[class*="span-"] { grid-column: span 1; }
.rows-2, .rows-3 { grid-row: span 1; }
}
/* Surface variants */
.surface-soft { background: var(--color-bg-soft); border-color: var(--color-gray-200); }
.surface-yellow { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-black); }
.surface-purple { background: var(--color-purple); border-color: var(--color-purple); color: var(--color-white); }
.surface-purple-soft { background: var(--color-purple-soft); border-color: var(--color-purple-soft); }
.surface-teal { background: var(--color-teal); border-color: var(--color-teal); color: var(--color-white); }
.surface-teal-soft { background: var(--color-teal-soft); border-color: var(--color-teal-soft); }
.surface-mint { background: var(--color-mint-soft); border-color: var(--color-mint-soft); }
.surface-coral { background: var(--color-coral); border-color: var(--color-coral); color: var(--color-white); }
.surface-dark { background: var(--color-black); border-color: var(--color-black); color: var(--color-white); }
/* Typography */
.tile-eyebrow {
font-size: 11px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 2px;
opacity: 0.7;
margin-bottom: 12px;
}
.tile-eyebrow.inv {
opacity: 1;
color: var(--color-primary);
}
.tile-title {
font-family: var(--font-display);
font-size: clamp(22px, 3vw, 36px);
font-weight: 900;
line-height: 1.1;
margin-bottom: 16px;
}
.tile-title.light { color: var(--color-white); }
.tile-body { font-size: 16px; line-height: 1.7; }
.tile-body :global(p) { margin-bottom: 12px; }
.tile-body :global(p:last-child) { margin-bottom: 0; }
.tile-body :global(ul) { padding-left: 20px; margin-top: 12px; }
.tile-body :global(li) { margin-bottom: 8px; }
.tile-body :global(strong) { font-weight: 800; }
/* Light text on dark/colored surface */
.surface-dark .tile-body,
.surface-purple .tile-body,
.surface-teal .tile-body,
.surface-coral .tile-body {
color: rgba(255, 255, 255, 0.95);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.surface-dark .tile-body :global(strong),
.surface-purple .tile-body :global(strong),
.surface-teal .tile-body :global(strong),
.surface-coral .tile-body :global(strong) { color: var(--color-primary); }
/* Yellow tile — always black text */
.surface-yellow .tile-title,
.surface-yellow .tile-body,
.surface-yellow .tile-body :global(strong) { color: var(--color-black); }
.surface-yellow .tile-eyebrow { color: var(--color-black); opacity: 0.7; }
/* Checkmark list variant (for .surface-yellow with .checklist) */
.tile-body :global(.checklist) {
list-style: none;
padding: 0;
}
.tile-body :global(.checklist li) {
padding-left: 0;
}
</style>

View File

@@ -1,68 +0,0 @@
---
/**
* DecoOrb — decorative parallax blur orb for section backgrounds.
* Pure decoration, never blocks clicks, always behind content.
*
* Props:
* color: 'yellow' | 'soft' | 'purple' | 'mint' | 'teal' (default 'yellow')
* size: CSS dimension (default '400px')
* speed: parallax speed 0.01.0 (default 0.4)
* position: { top?, right?, bottom?, left? } CSS positions
* blur: CSS blur value (default '60px')
* opacity: CSS opacity 01 (default 0.5)
*
* Example:
* <DecoOrb color="yellow" size="500px" speed={0.4} position={{ top: '-100px', left: '-100px' }} />
*/
interface Props {
color?: 'yellow' | 'soft' | 'purple' | 'mint' | 'teal';
size?: string;
speed?: number;
position?: { top?: string; right?: string; bottom?: string; left?: string };
blur?: string;
opacity?: number;
}
const {
color = 'yellow',
size = '400px',
speed = 0.4,
position = {},
blur = '60px',
opacity = 0.5,
} = Astro.props;
const styleStr = [
`width: ${size};`,
`height: ${size};`,
`filter: blur(${blur});`,
`opacity: ${opacity};`,
position.top ? `top: ${position.top};` : '',
position.right ? `right: ${position.right};` : '',
position.bottom ? `bottom: ${position.bottom};` : '',
position.left ? `left: ${position.left};` : '',
].join(' ');
---
<div
class:list={['deco-orb', `orb-${color}`]}
data-parallax-speed={speed}
style={styleStr}
aria-hidden="true"
></div>
<style>
.deco-orb {
position: absolute;
border-radius: 50%;
pointer-events: none;
z-index: 0;
will-change: transform;
}
.orb-yellow { background: radial-gradient(circle, var(--color-primary) 0%, transparent 70%); }
.orb-soft { background: radial-gradient(circle, var(--color-primary-soft) 0%, transparent 70%); }
.orb-purple { background: radial-gradient(circle, var(--color-purple) 0%, transparent 70%); }
.orb-mint { background: radial-gradient(circle, var(--color-mint) 0%, transparent 70%); }
.orb-teal { background: radial-gradient(circle, var(--color-teal) 0%, transparent 70%); }
</style>

View File

@@ -1,195 +0,0 @@
---
/**
* MOREMINIMORE - PAGE HERO COMPONENT (LIGHT THEME + ANIMATIONS)
* White bg + dark text + yellow accent line. Animated on load.
*/
interface Props {
badge?: string;
title: string;
subtitle?: string;
}
const {
badge,
title,
subtitle,
} = Astro.props;
// Split title into words for kinetic animation
const titleWords = title.split(' ');
---
<section class="page-hero">
<div class="hero-bg">
<div class="bg-dots"></div>
</div>
<div class="hero-content container">
{badge && (
<span class="hero-badge">{badge}</span>
)}
<h1 class="hero-title kinetic-title">
{titleWords.map((word, index) => (
<span class="word-wrapper">
<span
class="word"
style={`--delay: ${0.2 + index * 0.08}s`}
>
{word}
</span>
</span>
))}
</h1>
{subtitle && (
<p class="hero-subtitle">{subtitle}</p>
)}
</div>
<!-- Yellow accent line at bottom (animated) -->
<div class="hero-accent">
<div class="accent-bar"></div>
</div>
</section>
<style>
/* ============================================
PAGE HERO BASE — LIGHT THEME + ANIMATIONS
============================================ */
.page-hero {
position: relative;
min-height: 50vh;
display: flex;
align-items: center;
justify-content: center;
background: var(--color-white);
overflow: hidden;
padding: 140px 0 80px;
}
/* ============================================
BACKGROUND (subtle yellow dots on white)
============================================ */
.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;
animation: dotsFloat 30s linear infinite;
}
@keyframes dotsFloat {
0% { transform: translate(0, 0); }
100% { transform: translate(40px, 40px); }
}
/* ============================================
CONTENT (animates on load)
============================================ */
.hero-content {
position: relative;
z-index: 1;
text-align: center;
padding: 40px var(--gutter) 60px;
}
.hero-badge {
display: inline-block;
padding: 8px 20px;
background: var(--color-primary);
color: var(--color-black);
border-radius: var(--radius-full);
font-family: var(--font-display);
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 3px;
margin-bottom: 24px;
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 0.6s var(--ease-out-expo) 0.1s forwards;
}
.hero-title {
font-family: var(--font-display);
font-size: clamp(36px, 6vw, 64px);
font-weight: 900;
line-height: 1.3; /* Thai-safe: 1.1 clipped descenders */
color: var(--color-black);
margin-bottom: 16px;
}
.hero-subtitle {
font-size: 18px;
color: var(--color-gray-600);
max-width: 600px;
margin: 0 auto;
line-height: 1.6;
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 0.6s var(--ease-out-expo) 1.2s forwards;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* ============================================
BOTTOM ACCENT LINE (animated draw)
============================================ */
.hero-accent {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 6px;
background: var(--color-gray-200);
}
.accent-bar {
width: 100%;
height: 100%;
background: var(--color-primary);
transform-origin: left;
animation: accentDraw 1.2s var(--ease-out-expo) 0.5s forwards;
transform: scaleX(0);
}
@keyframes accentDraw {
to { transform: scaleX(1); }
}
/* ============================================
RESPONSIVE
============================================ */
@media (max-width: 640px) {
.page-hero {
min-height: 45vh;
padding: 120px 0 60px;
}
.hero-content {
padding: 20px var(--gutter) 40px;
}
.hero-title {
font-size: 32px;
}
.hero-subtitle {
font-size: 16px;
}
}
</style>

View File

@@ -1,9 +1,6 @@
---
import Base from '../../layouts/Base.astro';
import Hero from '../../components/Hero.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;
@@ -51,8 +48,8 @@ function surfaceFor(i: number) {
{post.data.image && (
<section class="section section-bento article-image-section">
<DecoOrb color="soft" size="500px" speed={0.3} position={{ top: '-100px', right: '-150px' }} opacity={0.4} blur="80px" />
<DecoOrb color="yellow" size="350px" speed={0.3} position={{ bottom: '-150px', left: '-100px' }} opacity={0.2} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
<div class="article-image">
<img src={post.data.image} alt={post.data.title} />
@@ -62,22 +59,26 @@ function surfaceFor(i: number) {
)}
<section class="section section-bento article-section">
<DecoOrb color="purple" size="400px" speed={0.3} position={{ top: '5%', left: '-150px' }} opacity={0.2} blur="80px" />
<DecoOrb color="mint" size="350px" speed={0.3} position={{ bottom: '10%', right: '-100px' }} opacity={0.2} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
<BentoGrid>
<div class="bento-grid">
<!-- Main article body — full width bento tile -->
<BentoTile span={8} surface="white" eyebrow={post.data.category} title={post.data.title}>
<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>
</BentoTile>
</div>
<!-- Sidebar tile: about + contact stack -->
<BentoTile span={4} surface="soft" eyebrow="เกี่ยวกับเรา" title="MoreminiMore">
<div class="bento-tile span-4 surface-soft">
<p>ดิจิทัลเอเจนซี่ที่ช่วยให้ธุรกิจไทยเติบโตด้วยเทคโนโลยีสมัยใหม่</p>
<a href="/about" class="btn btn-outline-dark btn-sm">ดูเพิ่มเติม</a>
@@ -89,25 +90,29 @@ function surfaceFor(i: number) {
<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>
</BentoTile>
</BentoGrid>
</div>
</div>
</div>
</section>
{related.length > 0 && (
<section class="section section-bento related-section">
<DecoOrb color="yellow" size="400px" speed={0.3} position={{ top: '10%', right: '-100px' }} opacity={0.25} blur="80px" />
<DecoOrb color="soft" size="350px" 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>
</div>
<BentoGrid>
<div class="bento-grid">
{related.map((r, i) => (
<a href={`/blog/${r.id}`} class="related-link">
<BentoTile span={4} surface={surfaceFor(i)} eyebrow={r.data.category} title={r.data.title}>
<div class="bento-tile span-4">
<div class="related-card-body">
{r.data.image && (
<div class="related-image">
@@ -118,10 +123,12 @@ function surfaceFor(i: number) {
{new Date(r.data.date).toLocaleDateString('th-TH', { year: 'numeric', month: 'long', day: 'numeric' })}
</span>
</div>
</BentoTile>
</div>
</a>
))}
</BentoGrid>
</div>
</div>
</section>
)}
@@ -296,15 +303,5 @@ function surfaceFor(i: number) {
</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>

View File

@@ -1,9 +1,6 @@
---
import Base from '../../layouts/Base.astro';
import Hero from '../../components/Hero.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 blogPosts = await getCollection('blog');
@@ -25,12 +22,14 @@ function surfaceFor(i: number) {
{sortedPosts.length > 0 && (
<section class="section section-bento featured-section">
<DecoOrb color="yellow" size="450px" speed={0.3} position={{ top: '-100px', right: '-100px' }} opacity={0.3} blur="80px" />
<DecoOrb color="soft" size="400px" speed={0.3} position={{ bottom: '-150px', left: '-100px' }} opacity={0.4} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
<a href={`/blog/${sortedPosts[0].id}`} class="featured-tile-link">
<BentoGrid>
<BentoTile span={7} surface="white" eyebrow="บทความล่าสุด" title={sortedPosts[0].data.title}>
<div class="bento-grid">
<div class="bento-tile span-7 surface-white">
<div class="featured-content">
<div class="featured-image">
{sortedPosts[0].data.image && (
@@ -38,9 +37,11 @@ function surfaceFor(i: number) {
)}
</div>
</div>
</BentoTile>
</div>
<div class="bento-tile span-5 surface-yellow">
<BentoTile span={5} surface="yellow" eyebrow={sortedPosts[0].data.category} title="อ่านบทความเต็ม">
<div class="featured-aside">
<p class="featured-excerpt">{sortedPosts[0].data.excerpt}</p>
<div class="featured-meta">
@@ -55,16 +56,18 @@ function surfaceFor(i: number) {
</svg>
</span>
</div>
</BentoTile>
</BentoGrid>
</div>
</div>
</a>
</div>
</section>
)}
<section class="section section-bento blog-section">
<DecoOrb color="mint" size="400px" speed={0.3} position={{ top: '5%', right: '-150px' }} opacity={0.2} blur="80px" />
<DecoOrb color="purple" size="350px" speed={0.3} position={{ bottom: '-100px', left: '-100px' }} opacity={0.2} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
<div class="section-header reveal">
<span class="section-badge">บทความทั้งหมด</span>
@@ -72,10 +75,12 @@ function surfaceFor(i: number) {
</div>
{sortedPosts.length > 1 && (
<BentoGrid>
<div class="bento-grid">
{sortedPosts.slice(1).map((post, i) => (
<a href={`/blog/${post.id}`} class="post-tile-link">
<BentoTile span={4} surface={surfaceFor(i)} eyebrow={post.data.category} title={post.data.title} reveal={true}>
<div class="bento-tile span-4 reveal">
<div class="post-card-body">
{post.data.image && (
<div class="post-image">
@@ -87,10 +92,12 @@ function surfaceFor(i: number) {
{new Date(post.data.date).toLocaleDateString('th-TH', { year: 'numeric', month: 'long', day: 'numeric' })}
</span>
</div>
</BentoTile>
</div>
</a>
))}
</BentoGrid>
</div>
)}
</div>
</section>
@@ -259,15 +266,5 @@ function surfaceFor(i: number) {
</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>

View File

@@ -2,10 +2,6 @@
import Base from '../layouts/Base.astro';
import Hero from '../components/Hero.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';
// Service options for the form — with lucide icon names
const serviceOptions = [
{ value: 'webdev', label: 'AI-Enhanced Website (เว็บ + Chatbot + SEO)', icon: 'globe' },
@@ -27,40 +23,50 @@ const serviceOptions = [
<!-- QUICK CHANNEL PICKER (BENTO) -->
<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="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;">
<BentoGrid>
<BentoTile span={4} surface="yellow" eyebrow="LINE Official" title="@moreminimore">
<div class="bento-grid">
<div class="bento-tile span-4 surface-yellow">
<div class="channel-icon"><Icon name="message" size={32} /></div>
<p>คนที่อยากคุยเร็ว ๆ แบบเป็นกันเอง</p>
<p class="meta">ตอบใน 30 นาที (เวลาทำการ)</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">
</div>
<div class="bento-tile span-4 surface-soft">
<div class="channel-icon"><Icon name="phone" size={32} /></div>
<p>คนที่อยากคุยยาว ๆ 510 นาที ถามตอบสด</p>
<p class="meta">รับสายทันที หรือโทรกลับภายใน 2 ชม.</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">
</div>
<div class="bento-tile span-4 surface-purple-soft">
<div class="channel-icon"><Icon name="mail" size={32} /></div>
<p>คนที่อยากส่งรายละเอียดโปรเจกต์ + ไฟล์แนบ</p>
<p class="meta">ตอบภายใน 1 วันทำการ</p>
<a href="mailto:contact@moreminimore.com" class="btn btn-dark" style="margin-top: 16px;">ส่งอีเมล →</a>
</BentoTile>
</BentoGrid>
</div>
</div>
</div>
</section>
<!-- CONTACT FORM + INFO (BENTO) -->
<section class="section form-section section-bento">
<DecoOrb color="mint" size="400px" speed={0.3} position={{ top: '10%', right: '-100px' }} opacity={0.25} blur="80px" />
<DecoOrb color="yellow" size="300px" speed={0.4} position={{ bottom: '5%', left: '-100px' }} opacity={0.2} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
<BentoGrid>
<div class="bento-grid">
<!-- FORM — big tile on the left -->
<BentoTile span={8} surface="white" eyebrow="ส่งข้อความ" title="กรอก 4 ช่อง ใช้เวลา 60 วินาที">
<div class="bento-tile span-8 surface-white">
<p class="form-subtitle">เราจะตอบกลับภายใน 2 ชั่วโมง (เวลาทำการ)</p>
<form class="contact-form" id="contact-form">
@@ -126,86 +132,113 @@ const serviceOptions = [
<h3>ส่งแล้ว!</h3>
<p>เราจะตอบกลับภายใน 2 ชั่วโมง (ในเวลาทำการ) ถ้าเร่งด่วน ทัก LINE @moreminimore ครับ</p>
</div>
</BentoTile>
</div>
<div class="bento-tile span-4 surface-yellow">
<!-- INFO TILES — phone, email, line, hours as separate BentoTiles -->
<BentoTile span={4} surface="yellow" eyebrow="โทรศัพท์" title="080-995-5945">
<div class="info-icon"><Icon name="phone" size={24} /></div>
<p>โทรคุยสดได้เลย</p>
<p class="meta">จ-ศ 09:00-18:00</p>
<a href="tel:0809955945" class="btn btn-dark" style="margin-top: 12px;">โทรเลย →</a>
</BentoTile>
</div>
<div class="bento-tile span-4 surface-purple-soft">
<BentoTile span={4} surface="purple-soft" eyebrow="อีเมล" title="contact@moreminimore.com">
<div class="info-icon"><Icon name="mail" size={24} /></div>
<p>เหมาะกับส่งรายละเอียดโปรเจกต์ + ไฟล์แนบ</p>
<p class="meta">ตอบภายใน 1 วันทำการ</p>
<a href="mailto:contact@moreminimore.com" class="btn btn-dark" style="margin-top: 12px;">ส่งอีเมล →</a>
</BentoTile>
</div>
<div class="bento-tile span-4 surface-mint">
<BentoTile span={4} surface="mint" eyebrow="LINE Official" title="@moreminimore">
<div class="info-icon"><Icon name="message" size={24} /></div>
<p>เร็วที่สุด ตอบใน 30 นาที (เวลาทำการ)</p>
<p class="meta">นอกเวลา? ทักทิ้งไว้ได้</p>
<a href="https://line.me/ti/p/~@539hdlul" target="_blank" rel="noopener" class="btn btn-dark" style="margin-top: 12px;">ทักเลย →</a>
</BentoTile>
</div>
<div class="bento-tile span-12 surface-dark">
<BentoTile span={12} surface="dark" eyebrow="เวลาทำการ" title="จันทร์ - ศุกร์ 09:00 - 18:00 น.">
<div class="info-icon"><Icon name="clock" size={24} /></div>
<p>นอกเวลาทำการ? ทัก LINE ทิ้งไว้ได้ ตอบเช้าวันถัดไป</p>
<p class="meta" style="color: rgba(255,255,255,0.7);">53 หมู่ 1 ต.บ้านแพ้ว อ.บ้านแพ้ว สมุทรสาคร 74120 · นัดเจอล่วงหน้า</p>
</BentoTile>
</BentoGrid>
</div>
</div>
</div>
</section>
<!-- WHAT HAPPENS NEXT (BENTO) -->
<section class="section section-soft 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: '-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">หลังส่งฟอร์ม</span>
<h2 class="section-title">3 ขั้นตอนถัดไป — <span class="highlight">ไม่มีอะไรซับซ้อน</span></h2>
</div>
<BentoGrid>
<BentoTile span={4} surface="yellow" eyebrow="ขั้นที่ 1" title="ตอบกลับภายใน 2 ชั่วโมง">
<div class="bento-grid">
<div class="bento-tile span-4 surface-yellow">
<p>คนจริง (ไม่ใช่ Bot) จะตอบ — ถามคำถามเพิ่ม 23 ข้อ เพื่อเข้าใจปัญหาคุณ</p>
</BentoTile>
<BentoTile span={4} surface="soft" eyebrow="ขั้นที่ 2" title="นัดปรึกษาฟรี 30 นาที">
</div>
<div class="bento-tile span-4 surface-soft">
<p>คุยผ่าน Zoom / โทร / นัดเจอที่ออฟฟิศ (กรุงเทพ / สมุทรสาคร) — ไม่มี script sales</p>
</BentoTile>
<BentoTile span={4} surface="purple-soft" eyebrow="ขั้นที่ 3" title="ส่ง Proposal (35 วัน)">
</div>
<div class="bento-tile span-4 surface-purple-soft">
<p>เอกสาร PDF ที่ระบุ scope, timeline, ราคา — ไม่ชอบตรงไหนคุยกันแก้ได้</p>
</BentoTile>
</BentoGrid>
</div>
</div>
<p class="next-closing">ถ้าไม่ตรง → เราจะบอกตรง ๆ ว่า "ไม่เหมาะ" และแนะนำทางเลือกอื่น</p>
</div>
</section>
<!-- PRE-SUBMIT FAQ (BENTO) -->
<section class="section 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">4 คำถามที่คนถาม <span class="highlight">ก่อน</span> กดส่งฟอร์ม</h2>
</div>
<BentoGrid>
<BentoTile span={6} surface="soft" eyebrow="01" title="คุยกัน 30 นาทีแล้วจะถูกบังคับซื้อไหม?">
<div class="bento-grid">
<div class="bento-tile span-6 surface-soft">
<p>ไม่ คุยแล้วคุณไม่ชอบก็ไม่เป็นไร ไม่มี follow-up ไม่มีขายของเพิ่ม</p>
</BentoTile>
<BentoTile span={6} surface="soft" eyebrow="02" title="ถ้าส่งฟอร์มไปแล้วเงียบ ทำยังไง?">
</div>
<div class="bento-tile span-6 surface-soft">
<p>ทัก LINE @moreminimore ตรง ๆ จะเร็วกว่า — หรือโทร 080-995-5945</p>
</BentoTile>
<BentoTile span={6} surface="yellow" eyebrow="03" title="คุยช่วงไหนได้บ้าง?">
</div>
<div class="bento-tile span-6 surface-yellow">
<p>จันทร์-ศุกร์ 09:00-18:00 ปกติ ถ้าคุณต่างจังหวัด/ต่างประเทศ นัดนอกเวลาได้ บอกล่วงหน้า 12 วัน</p>
</BentoTile>
<BentoTile span={6} surface="purple-soft" eyebrow="04" title="ต้องเตรียมอะไรไปคุยไหม?">
</div>
<div class="bento-tile span-6 surface-purple-soft">
<p>ไม่ต้องเตรียมอะไรเลย แค่บอกธุรกิจคุณทำอะไร ปวดหัวเรื่องอะไร งบประมาณเท่าไหร่ ที่เหลือเราถามเอง</p>
</BentoTile>
</BentoGrid>
</div>
</div>
</div>
</section>
@@ -417,17 +450,6 @@ const serviceOptions = [
</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 });
// Form submission — wire to contact-submit.ts (Apps Script backend)
// Per plan 2026-06-13 round 2 #4: real working form, not placeholder.

View File

@@ -1,9 +1,6 @@
---
import Base from '../layouts/Base.astro';
import Hero from '../components/Hero.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');
@@ -71,16 +68,14 @@ groupedFaq.forEach((group, gIdx) => {
<!-- 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>
<div class="bento-grid">
{faqTiles.map((tile) => (
<BentoTile
span={tile.span}
surface={tile.surface}
eyebrow={tile.isFirst ? tile.category : `${tile.category} · ต่อ`}
>
<div class="bento-tile">
<div class="faq-list">
{tile.items.map((item) => (
<details class="faq-item">
@@ -94,11 +89,13 @@ groupedFaq.forEach((group, gIdx) => {
</details>
))}
</div>
</BentoTile>
</div>
))}
<!-- OTHER TOPICS — full-width tile with tag cloud -->
<BentoTile span={12} surface="soft" eyebrow="เรื่องอื่น ๆ" title="คำถามอื่น ๆ ที่ลูกค้าถามบ่อย">
<div class="bento-tile span-12 surface-soft">
<div class="tag-cloud">
<span class="topic-tag">โฮสติ้ง</span>
<span class="topic-tag">โดเมน</span>
@@ -115,37 +112,47 @@ groupedFaq.forEach((group, gIdx) => {
<span class="topic-tag">ขอดูเว็บจริง</span>
<span class="topic-tag">นัดคุยนอกสถานที่</span>
</div>
</BentoTile>
</BentoGrid>
</div>
</div>
</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">
<div class="bento-grid">
<div class="bento-tile span-4 surface-yellow">
<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">
</div>
<div class="bento-tile span-4 surface-soft">
<p>คนที่อยากคุยยาว ๆ 510 นาที ถามตอบสด</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">
</div>
<div class="bento-tile span-4 surface-purple-soft">
<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>
</div>
</div>
</section>
@@ -169,7 +176,6 @@ groupedFaq.forEach((group, gIdx) => {
overflow: hidden;
}
/* FAQ inside BentoTile */
.faq-list { display: flex; flex-direction: column; gap: 10px; margin-top: 4px; }
.faq-item {
@@ -304,15 +310,5 @@ groupedFaq.forEach((group, gIdx) => {
</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>

View File

@@ -1,9 +1,6 @@
---
import Base from '../layouts/Base.astro';
import Hero from '../components/Hero.astro';
import BentoGrid from '../components/BentoGrid.astro';
import BentoTile from '../components/BentoTile.astro';
import DecoOrb from '../components/DecoOrb.astro';
---
<Base title="นโยบายความเป็นส่วนตัว | MoreminiMore - รับทำเว็บไซต์ SEO AI Chatbot">
@@ -14,11 +11,13 @@ import DecoOrb from '../components/DecoOrb.astro';
showStats={false} />
<section class="section section-bento legal-section">
<DecoOrb color="purple" size="450px" speed={0.3} position={{ top: '-100px', left: '-150px' }} opacity={0.2} blur="80px" />
<DecoOrb color="soft" size="400px" speed={0.3} position={{ bottom: '-150px', right: '-100px' }} opacity={0.4} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
<BentoGrid>
<BentoTile span={8} surface="white" eyebrow="กฎหมาย" title="นโยบายความเป็นส่วนตัว">
<div class="bento-grid">
<div class="bento-tile span-8 surface-white">
<div class="legal-body">
<p class="legal-intro">บริษัท มอร์มินิมอร์ จำกัด ให้ความสำคัญกับการคุ้มครองข้อมูลส่วนบุคคลของท่าน</p>
@@ -53,9 +52,11 @@ import DecoOrb from '../components/DecoOrb.astro';
<p>ท่านมีสิทธิในการเข้าถึง แก้ไข ลบ หรือระงับการใช้ข้อมูลส่วนบุคคลของท่าน กรุณาติดต่อเราผ่านช่องทางที่ระบุในเว็บไซต์</p>
</div>
</div>
</BentoTile>
</div>
<div class="bento-tile span-4 surface-purple-soft">
<BentoTile span={4} surface="purple-soft" eyebrow="ข้อมูลเอกสาร" title="สรุปฉบับย่อ">
<div class="aside-body">
<p><strong>ชื่อเอกสาร:</strong> นโยบายความเป็นส่วนตัว</p>
<p><strong>มีผลบังคับใช้:</strong> 5 พฤษภาคม 2569</p>
@@ -68,8 +69,10 @@ import DecoOrb from '../components/DecoOrb.astro';
<li>สิทธิของท่าน</li>
</ol>
</div>
</BentoTile>
</BentoGrid>
</div>
</div>
</div>
</section>
</Base>
@@ -154,15 +157,5 @@ import DecoOrb from '../components/DecoOrb.astro';
</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>

View File

@@ -1,8 +1,5 @@
---
import Base from '../../layouts/Base.astro';
import BentoGrid from '../../components/BentoGrid.astro';
import BentoTile from '../../components/BentoTile.astro';
import DecoOrb from '../../components/DecoOrb.astro';
import Icon from '../../components/Icon.astro';
import { getCollection, render } from 'astro:content';
@@ -325,8 +322,8 @@ const featureList = data.features || data.services || [];
<Base title={`${service.data.title} | MoreminiMore`}>
<!-- 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">
@@ -374,8 +371,8 @@ const featureList = data.features || data.services || [];
<!-- SECTION 2: SERVICES (vertical card stack) -->
{data.services && (
<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">บริการที่คุณได้รับ</span>
@@ -408,8 +405,8 @@ const featureList = data.features || data.services || [];
<!-- SECTION 3: TECH COMPARISON + PORTFOLIO -->
{(data.realPortfolio || data.techOptions) && (
<section class="section section-soft section-bento">
<DecoOrb color="mint" size="400px" speed={0.3} position={{ top: '-100px', left: '20%' }} opacity={0.2} blur="80px" />
<DecoOrb color="teal" size="300px" speed={0.4} position={{ bottom: '-100px', right: '10%' }} opacity={0.3} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
{data.techOptions && (
@@ -471,8 +468,8 @@ const featureList = data.features || data.services || [];
<!-- SECTION 3B: TARGETS (เหมาะกับใคร) -->
{data.targets && (
<section class="section section-bento">
<DecoOrb color="soft" size="400px" speed={0.3} position={{ top: '-100px', left: '20%' }} opacity={0.4} 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">เหมาะกับใคร</span>
@@ -495,8 +492,8 @@ const featureList = data.features || data.services || [];
<!-- SECTION 4: PRICING (dynamic: 3-tier OR consult CTA) -->
<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="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;">
{data.pricing ? (
<>
@@ -561,8 +558,8 @@ const featureList = data.features || data.services || [];
<!-- SECTION 5: FAQ + MDX content -->
<section class="section section-soft 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>
@@ -1589,15 +1586,5 @@ const featureList = data.features || data.services || [];
</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>

View File

@@ -1,9 +1,6 @@
---
import Base from '../layouts/Base.astro';
import Hero from '../components/Hero.astro';
import BentoGrid from '../components/BentoGrid.astro';
import BentoTile from '../components/BentoTile.astro';
import DecoOrb from '../components/DecoOrb.astro';
---
<Base title="เงื่อนไขการให้บริการ | MoreminiMore - รับทำเว็บไซต์ SEO AI Chatbot">
@@ -14,11 +11,13 @@ import DecoOrb from '../components/DecoOrb.astro';
showStats={false} />
<section class="section section-bento legal-section">
<DecoOrb color="yellow" size="450px" speed={0.3} position={{ top: '-100px', right: '-150px' }} opacity={0.25} blur="80px" />
<DecoOrb color="soft" size="400px" speed={0.3} position={{ bottom: '-150px', left: '-100px' }} opacity={0.4} blur="80px" />
<div class="container" style="position: relative; z-index: 1;">
<BentoGrid>
<BentoTile span={8} surface="white" eyebrow="กฎหมาย" title="เงื่อนไขการให้บริการ">
<div class="bento-grid">
<div class="bento-tile span-8 surface-white">
<div class="legal-body">
<p class="legal-intro">ชื่อเว็บไซต์: MoreminiMore | เว็บไซต์: https://www.moreminimore.com | บริษัท: MoreminiMore Co.,Ltd.</p>
@@ -57,9 +56,11 @@ import DecoOrb from '../components/DecoOrb.astro';
<p>หากมีคำถามเกี่ยวกับเงื่อนไขการให้บริการ กรุณาติดต่อเราที่ contact@moreminimore.com หรือ 080-995-5945</p>
</div>
</div>
</BentoTile>
</div>
<div class="bento-tile span-4 surface-yellow">
<BentoTile span={4} surface="yellow" eyebrow="ข้อมูลเอกสาร" title="สรุปฉบับย่อ">
<div class="aside-body">
<p><strong>ชื่อเอกสาร:</strong> เงื่อนไขการให้บริการ</p>
<p><strong>มีผลบังคับใช้:</strong> 5 พฤษภาคม 2569</p>
@@ -75,8 +76,10 @@ import DecoOrb from '../components/DecoOrb.astro';
<li>ติดต่อเรา</li>
</ol>
</div>
</BentoTile>
</BentoGrid>
</div>
</div>
</div>
</section>
</Base>
@@ -151,15 +154,5 @@ import DecoOrb from '../components/DecoOrb.astro';
</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>

View File

@@ -662,6 +662,554 @@ html.dark body { background: var(--body-bg); color: var(--body-fg); }
Now: business consulting tagline. */
.fx-hero-content::after { content: 'AI · MARKETING · RESULTS' !important; }
/* ============================================
LEGACY → v6 COMPATIBILITY ALIASES (added 2026-06-13)
Per user "ไม่ต้องการ legacy design น่ะ" — we replace
the bento components entirely. But many inner pages
(blog/[slug], blog/index, contact, faq, privacy, terms,
services/[slug]) still use v7-5's class names like
.section-bento, .section-header, .reveal, etc.
Instead of rewriting every page content, we add
COMPATIBILITY ALIASES that map legacy class names to
the v6 design system. Pages keep their structure,
but styling comes from fx-system tokens (--ink, --paper,
--coral, --brand-yellow, --line-2, etc.) so every page
gets the same v6 look automatically.
These rules are LAST in the file so they win cascade
when overlapping with any base v7-5 rules above.
============================================ */
/* ----- Section containers ----- */
.section {
padding: 64px 32px;
position: relative;
overflow: hidden;
}
.section-bento {
padding: 64px 32px;
position: relative;
overflow: hidden;
}
.section-soft {
background: var(--paper-2);
border-top: 1.5px solid var(--ink);
border-bottom: 1.5px solid var(--ink);
}
.section-yellow {
background: var(--brand-yellow);
border-top: 2px solid var(--ink);
border-bottom: 2px solid var(--ink);
}
/* ----- Section headers (eyebrow / title / desc) ----- */
.section-header {
max-width: 1200px;
margin: 0 auto 32px;
text-align: center;
}
.section-badge {
display: inline-block;
font: 700 10px/1 'JetBrains Mono', monospace;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--ink);
background: var(--brand-yellow);
border: 1.5px solid var(--ink);
padding: 6px 12px;
margin-bottom: 16px;
}
.section-title {
font: 900 clamp(28px, 4vw, 44px)/1.1 'Kanit', sans-serif;
letter-spacing: -1.5px;
color: var(--ink);
margin: 0 0 16px;
}
.section-title .highlight {
color: var(--coral);
text-decoration: underline;
text-decoration-color: var(--ink);
text-underline-offset: 6px;
text-decoration-thickness: 4px;
}
.section-desc {
font: 400 16px/1.6 'Kanit', sans-serif;
color: var(--text-dim);
max-width: 700px;
margin: 0 auto;
}
/* ----- Reveal animation (legacy IntersectionObserver trigger) ----- */
.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.reveal.revealed {
opacity: 1;
transform: translateY(0);
}
/* ----- Hero (custom inner-page hero) ----- */
.hero-content {
background: var(--paper);
border: 1.5px solid var(--ink);
padding: 32px;
position: relative;
}
.hero-badge {
display: inline-block;
font: 700 10px/1 'JetBrains Mono', monospace;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--ink);
background: var(--brand-yellow);
border: 1.5px solid var(--ink);
padding: 6px 12px;
margin-bottom: 16px;
}
.hero-grid {
display: grid;
grid-template-columns: 1.5fr 1fr;
gap: 32px;
max-width: 1200px;
margin: 32px auto 0;
}
@media (max-width: 1024px) { .hero-grid { grid-template-columns: 1fr; } }
/* ----- Service stack (custom service card layout) ----- */
.service-stack {
display: flex;
flex-direction: column;
gap: 16px;
max-width: 800px;
margin: 0 auto;
}
.service-stack-item {
display: flex;
align-items: flex-start;
gap: 16px;
background: var(--paper);
border: 1.5px solid var(--ink);
padding: 24px;
transition: all 0.2s ease;
}
.service-stack-item:hover {
transform: translate(-2px, -2px);
box-shadow: 4px 4px 0 var(--ink);
}
.service-stack-num {
font: 900 24px/1 'JetBrains Mono', monospace;
color: var(--coral);
background: var(--brand-yellow);
border: 1.5px solid var(--ink);
padding: 6px 10px;
flex-shrink: 0;
}
.service-stack-icon {
font-size: 24px;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: var(--paper-2);
border: 1.5px solid var(--ink);
flex-shrink: 0;
}
.service-stack-body { flex: 1; }
.service-stack-title {
font: 800 18px/1.3 'Kanit', sans-serif;
color: var(--ink);
margin: 0 0 8px;
}
.service-stack-bullets {
list-style: none;
padding: 0;
margin: 0;
}
.service-stack-bullets li {
font: 400 14px/1.5 'Kanit', sans-serif;
color: var(--text-dim);
padding: 4px 0;
display: grid;
grid-template-columns: 16px 1fr;
gap: 8px;
}
.service-stack-bullets li::before {
content: '▸';
color: var(--coral);
font-weight: 700;
}
/* ----- Portfolio card (custom grid variant on portfolio page) ----- */
.portfolio-card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
@media (max-width: 1024px) { .portfolio-card-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .portfolio-card-grid { grid-template-columns: 1fr; } }
.portfolio-card-top {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: var(--paper-2);
border-bottom: 1px solid var(--ink);
font: 700 9px/1 'JetBrains Mono', monospace;
text-transform: uppercase;
}
.portfolio-card-badge {
background: var(--coral);
color: #FAFAFA;
padding: 4px 8px;
}
.portfolio-card-arrow { color: var(--text-dim); }
.portfolio-card-name {
font: 800 20px/1.2 'Kanit', sans-serif;
color: var(--ink);
margin: 16px 0 4px;
}
.portfolio-card-industry {
font: 400 12px/1.3 'Kanit', sans-serif;
color: var(--text-dim);
margin-bottom: 8px;
}
.portfolio-card-highlight {
font: 700 11px/1.4 'JetBrains Mono', monospace;
color: var(--coral);
}
/* ----- Contact form (legacy /contact page form) ----- */
.contact-form {
display: flex;
flex-direction: column;
gap: 16px;
max-width: 600px;
margin: 0 auto;
}
.contact-form .form-group { display: flex; flex-direction: column; gap: 4px; }
.contact-form .form-label {
font: 600 13px/1 'Kanit', sans-serif;
color: var(--ink);
}
.contact-form .form-input {
font: 400 14px/1.4 'Kanit', sans-serif;
padding: 10px 12px;
border: 1.5px solid var(--ink);
background: var(--paper);
color: var(--ink);
}
.contact-form .form-input:focus {
outline: none;
border-color: var(--coral);
box-shadow: 2px 2px 0 var(--brand-yellow);
}
.contact-form .form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
@media (max-width: 640px) { .contact-form .form-row { grid-template-columns: 1fr; } }
.contact-form .btn-submit {
background: var(--ink);
color: #FAFAFA;
border: 1.5px solid var(--ink);
padding: 12px 24px;
font: 800 12px/1 'JetBrains Mono', monospace;
text-transform: uppercase;
cursor: pointer;
transition: all 0.15s ease;
align-self: flex-start;
}
.contact-form .btn-submit:hover {
background: var(--coral);
border-color: var(--coral);
transform: translate(-2px, -2px);
box-shadow: 4px 4px 0 var(--brand-yellow);
}
.form-success {
background: var(--paper);
border: 2px solid var(--brand-yellow);
padding: 32px;
text-align: center;
max-width: 600px;
margin: 0 auto;
}
.form-success h3 {
font: 800 24px/1.2 'Kanit', sans-serif;
color: var(--ink);
margin: 16px 0 8px;
}
.form-success p {
font: 400 15px/1.5 'Kanit', sans-serif;
color: var(--text-dim);
}
/* ----- Utility (inner-page helpers) ----- */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 20px;
font: 800 12px/1 'JetBrains Mono', monospace;
text-transform: uppercase;
letter-spacing: 0.5px;
text-decoration: none;
border: 1.5px solid var(--ink);
cursor: pointer;
transition: all 0.15s ease;
}
.btn-primary {
background: var(--coral);
color: #FAFAFA;
}
.btn-primary:hover {
background: var(--ink);
color: var(--brand-yellow);
transform: translate(-2px, -2px);
box-shadow: 4px 4px 0 var(--coral);
}
.btn-outline-dark {
background: var(--paper);
color: var(--ink);
}
.btn-outline-dark:hover {
background: var(--brand-yellow);
transform: translate(-2px, -2px);
box-shadow: 4px 4px 0 var(--ink);
}
.info-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: var(--paper-2);
border: 1.5px solid var(--ink);
margin-bottom: 12px;
}
.checklist {
list-style: none;
padding: 0;
margin: 0;
}
.checklist li {
font: 400 14px/1.6 'Kanit', sans-serif;
color: var(--ink);
padding: 8px 0;
border-bottom: 1px solid var(--line);
}
/* ----- Filter bar (portfolio page) ----- */
.filter-section {
background: var(--paper-2);
padding: 20px 0;
border-top: 1px solid var(--line-2);
border-bottom: 1px solid var(--line-2);
position: sticky;
top: 70px;
z-index: 50;
}
.filter-bar {
display: flex;
gap: 8px;
overflow-x: auto;
padding: 4px 0;
}
.filter-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 20px;
background: var(--paper);
color: var(--ink);
border: 1px solid var(--line-2);
border-radius: 999px;
font: 600 14px/1 'Kanit', sans-serif;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.filter-btn:hover { border-color: var(--coral); }
.filter-btn.active {
background: var(--brand-yellow);
border-color: var(--brand-yellow);
color: var(--ink);
}
/* Dark mode overrides for legacy aliases ----- */
html.dark .section-soft { background: var(--paper-2); }
html.dark .section-badge { background: var(--brand-yellow); color: var(--ink); }
html.dark .section-title { color: var(--ink); }
html.dark .info-icon { background: var(--paper-2); }
html.dark .filter-section { background: var(--paper-2); }
html.dark .filter-btn { background: var(--paper); color: var(--ink); }
html.dark .filter-btn.active { background: var(--brand-yellow); color: var(--ink); }
html.dark .contact-form .form-input {
background: var(--paper-2);
color: var(--ink);
border-color: var(--ink);
}
html.dark .service-stack-item { background: var(--paper); }
html.dark .form-success { background: var(--paper); }
html.dark .reveal { /* keep hidden state, light/dark mode just affects colors */ }
/* ============================================
BENTO COMPONENT ALIASES (added 2026-06-13)
Pages still use <BentoGrid>, <BentoTile>, <DecoOrb>
components (in services/[slug], contact, faq, blog,
privacy, terms). These components emit divs with
classes like .bento-grid, .bento-tile, .surface-yellow,
.span-6 etc. We alias them to v6 design tokens so
the rendered output matches the rest of the site.
============================================ */
/* BentoGrid: 12-col grid */
.bento-grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-auto-rows: minmax(80px, auto);
gap: 16px;
position: relative;
max-width: 1200px;
margin: 0 auto;
}
@media (max-width: 1024px) { .bento-grid { grid-template-columns: repeat(6, 1fr); } }
@media (max-width: 640px) { .bento-grid { grid-template-columns: 1fr; gap: 12px; } }
/* BentoTile: card with v6 surface treatments */
.bento-tile {
padding: 24px;
background: var(--paper);
border: 1.5px solid var(--ink);
position: relative;
display: flex;
flex-direction: column;
gap: 12px;
transition: all 0.2s ease;
grid-column: span 6;
}
.bento-tile:hover { transform: translate(-2px, -2px); box-shadow: 4px 4px 0 var(--ink); }
@media (max-width: 640px) { .bento-tile { grid-column: span 1; } }
/* Span variants */
.bento-tile.span-3 { grid-column: span 3; }
.bento-tile.span-4 { grid-column: span 4; }
.bento-tile.span-5 { grid-column: span 5; }
.bento-tile.span-6 { grid-column: span 6; }
.bento-tile.span-7 { grid-column: span 7; }
.bento-tile.span-8 { grid-column: span 8; }
.bento-tile.span-12 { grid-column: span 12; }
/* Rows variants */
.bento-tile.rows-2 { grid-row: span 2; }
.bento-tile.rows-3 { grid-row: span 3; }
/* Surface treatments (v6 design tokens) */
.bento-tile.surface-white { background: var(--paper); color: var(--ink); }
.bento-tile.surface-soft { background: var(--paper-2); color: var(--ink); }
.bento-tile.surface-yellow { background: var(--brand-yellow); color: var(--ink); }
.bento-tile.surface-purple { background: #7c3aed; color: #FAFAFA; }
.bento-tile.surface-purple-soft { background: #ede9fe; color: var(--ink); }
.bento-tile.surface-teal { background: #0d9488; color: #FAFAFA; }
.bento-tile.surface-teal-soft { background: #bae6fd; color: var(--ink); }
.bento-tile.surface-mint { background: #10b981; color: #FAFAFA; }
.bento-tile.surface-dark { background: var(--ink); color: #FAFAFA; }
.bento-tile.surface-coral { background: var(--coral); color: #FAFAFA; }
/* BentoTile child elements */
.bento-tile .tile-eyebrow {
font: 700 10px/1 'JetBrains Mono', monospace;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--ink);
background: var(--brand-yellow);
border: 1.5px solid var(--ink);
padding: 6px 10px;
align-self: flex-start;
}
.bento-tile .tile-title {
font: 800 clamp(20px, 2.4vw, 28px)/1.2 'Kanit', sans-serif;
letter-spacing: -0.5px;
color: var(--ink);
margin: 0;
}
.bento-tile.surface-dark .tile-title,
.bento-tile.surface-coral .tile-title { color: #FAFAFA; }
.bento-tile .tile-body { flex: 1; }
.bento-tile .tile-body p {
font: 400 15px/1.6 'Kanit', sans-serif;
color: var(--ink);
margin: 0 0 12px;
}
.bento-tile.surface-dark .tile-body p,
.bento-tile.surface-coral .tile-body p { color: rgba(250,250,250,0.85); }
.bento-tile .tile-body ul {
list-style: none;
padding: 0;
margin: 0;
}
.bento-tile .tile-body li {
font: 400 14px/1.5 'Kanit', sans-serif;
color: var(--ink);
padding: 4px 0;
display: grid;
grid-template-columns: 16px 1fr;
gap: 8px;
}
.bento-tile.surface-dark .tile-body li,
.bento-tile.surface-coral .tile-body li { color: rgba(250,250,250,0.85); }
.bento-tile .tile-body li::before { content: '▸'; color: var(--coral); font-weight: 700; }
/* BentoTile links/buttons inside */
.bento-tile .tile-link {
font: 800 12px/1 'JetBrains Mono', monospace;
text-transform: uppercase;
color: var(--coral);
text-decoration: none;
align-self: flex-start;
margin-top: auto;
border-bottom: 2px solid var(--coral);
padding-bottom: 2px;
}
.bento-tile .tile-link:hover { color: var(--ink); border-color: var(--ink); }
/* Tile mega-cta (if used) */
.bento-tile .mega-cta {
background: var(--ink);
color: #FAFAFA;
padding: 10px 16px;
font: 800 11px/1 'JetBrains Mono', monospace;
text-transform: uppercase;
text-decoration: none;
align-self: flex-start;
margin-top: auto;
}
.bento-tile .mega-cta:hover { background: var(--coral); }
/* Reveal animation (when BentoTile used) */
.bento-tile.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.bento-tile.reveal.revealed { opacity: 1; transform: translateY(0); }
/* Container class (legacy bento variant) */
.bento-tile-container {
position: relative;
z-index: 1;
display: contents;
}
/* Dark mode bento overrides */
html.dark .bento-tile.surface-soft { background: var(--paper-2); color: var(--ink); }
html.dark .bento-tile.surface-purple-soft { background: rgba(124,58,237,0.18); color: var(--ink); }
html.dark .bento-tile.surface-yellow { color: var(--ink); }
/* 3 results the user positions the business around (เพิ่มยอดขาย / ลดต้นทุน / ประหยัดเวลา) */
.fx-hero-results {
display: grid;
@@ -708,6 +1256,7 @@ html.dark body { background: var(--body-bg); color: var(--body-fg); }
.fx-hero-results { grid-template-columns: 1fr; }
}
/* v7-5 uses rgba(10,10,10,0.3) for dim text (.ts in log + footer-bottom)
Invert to cream-equivalent for dark mode */
html.dark .ts { color: rgba(250,250,250,0.3); }