feat: add contact SMTP API, pricing section, and nodemailer
Some checks failed
PR Sweep / Sweep Open PRs (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Tests (push) Has been cancelled
CI / Validate Plugins (push) Has been cancelled
CI / Smoke Tests (push) Has been cancelled
CI / Integration Tests (push) Has been cancelled
CI / Browser Tests (push) Has been cancelled
CI / E2E tests (1/8) (push) Has been cancelled
CI / E2E tests (2/8) (push) Has been cancelled
CI / E2E tests (3/8) (push) Has been cancelled
CI / E2E tests (4/8) (push) Has been cancelled
CI / E2E tests (5/8) (push) Has been cancelled
CI / E2E tests (6/8) (push) Has been cancelled
CI / E2E tests (7/8) (push) Has been cancelled
CI / E2E tests (8/8) (push) Has been cancelled
Seed Marketplace Plugins / Seed Plugins (push) Has been cancelled
Format / Format (push) Has been cancelled
Preview Releases / Publish Preview (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

- Replace contact form with AJAX fetch + /api/contact endpoint
- Add nodemailer SMTP integration for email sending
- Add pricing cards section to web-development.astro
- Fix package.json nodemailer catalog entry
This commit is contained in:
MoreminiMore
2026-04-08 08:30:55 +07:00
parent 12d73ff456
commit 69c4ba6bbe
4 changed files with 642 additions and 279 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@emdash-cms/template-marketing",
"version": "0.0.3",
"name": "moreminimore",
"version": "1.0.0",
"private": true,
"type": "module",
"emdash": {
@@ -18,9 +18,11 @@
"dependencies": {
"@astrojs/node": "catalog:",
"@astrojs/react": "catalog:",
"@moreminimore/consent": "file:./plugins/consent",
"astro": "catalog:",
"better-sqlite3": "catalog:",
"emdash": "workspace:*",
"nodemailer": "^6.9.16",
"react": "catalog:",
"react-dom": "catalog:"
},

View File

@@ -0,0 +1,216 @@
import type { APIRoute } from "astro";
import nodemailer from "nodemailer";
export const POST: APIRoute = async ({ request }) => {
try {
const data = await request.formData();
const name = data.get("name")?.toString().trim() || "";
const email = data.get("email")?.toString().trim() || "";
const phone = data.get("phone")?.toString().trim() || "";
const service = data.get("service")?.toString().trim() || "";
const message = data.get("message")?.toString().trim() || "";
// Validation
if (!name || !email || !message) {
return new Response(
JSON.stringify({ success: false, error: "กรุณากรอกข้อมูลให้ครบ" }),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
return new Response(
JSON.stringify({ success: false, error: "รูปแบบอีเมลไม่ถูกต้อง" }),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
}
// Check SMTP config from environment
const smtpHost = import.meta.env.SMTP_HOST;
const smtpPort = import.meta.env.SMTP_PORT || "587";
const smtpUser = import.meta.env.SMTP_USER;
const smtpPass = import.meta.env.SMTP_PASS;
const smtpFrom = import.meta.env.SMTP_FROM || "noreply@moreminimore.com";
const toEmail = import.meta.env.CONTACT_EMAIL || "contact@moreminimore.com";
const serviceLabel: Record<string, string> = {
"web-development": "Web Development",
"ai-automation": "AI Automation",
"marketing-automation": "Marketing Automation",
"tech-consult": "Tech Consult",
other: "อื่นๆ",
};
const htmlBody = `
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<style>
body { font-family: 'Noto Sans Thai', sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.container { max-width: 600px; margin: 0 auto; background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.header { background: #0a0a0a; color: #fed400; padding: 24px; text-align: center; }
.header h1 { margin: 0; font-size: 20px; }
.body { padding: 24px; }
.field { margin-bottom: 16px; }
.label { font-size: 12px; color: #737373; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px; }
.value { font-size: 16px; color: #0a0a0a; }
.message-box { background: #f5f5f5; border-left: 4px solid #fed400; padding: 12px 16px; border-radius: 0 8px 8px 0; }
.message-box .value { white-space: pre-wrap; }
.footer { padding: 16px 24px; border-top: 1px solid #e5e5e5; font-size: 12px; color: #737373; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📬 มีข้อความใหม่จากเว็บไซต์</h1>
</div>
<div class="body">
<div class="field">
<div class="label">ชื่อ</div>
<div class="value">${name}</div>
</div>
<div class="field">
<div class="label">อีเมล</div>
<div class="value"><a href="mailto:${email}">${email}</a></div>
</div>
${phone ? `
<div class="field">
<div class="label">เบอร์โทรศัพท์</div>
<div class="value"><a href="tel:${phone}">${phone}</a></div>
</div>` : ""}
${service ? `
<div class="field">
<div class="label">บริการที่สนใจ</div>
<div class="value">${serviceLabel[service] || service}</div>
</div>` : ""}
<div class="field">
<div class="label">ข้อความ</div>
<div class="message-box">
<div class="value">${message.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>")}</div>
</div>
</div>
</div>
<div class="footer">
ส่งเมื่อ: ${new Date().toLocaleString("th-TH", { timeZone: "Asia/Bangkok" })}<br>
MoreminiMore — contact.moreminimore.com
</div>
</div>
</body>
</html>`;
const textBody = `
มีข้อความใหม่จากเว็บไซต์
ชื่อ: ${name}
อีเมล: ${email}
${phone ? `เบอร์โทร: ${phone}` : ""}
${service ? `บริการ: ${serviceLabel[service] || service}` : ""}
ข้อความ:
${message}
---
ส่งเมื่อ: ${new Date().toLocaleString("th-TH", { timeZone: "Asia/Bangkok" })}
MoreminiMore
`;
// Send email if SMTP is configured
if (smtpHost && smtpUser && smtpPass) {
const transporter = nodemailer.createTransport({
host: smtpHost,
port: Number(smtpPort),
secure: Number(smtpPort) === 465,
auth: {
user: smtpUser,
pass: smtpPass,
},
});
await transporter.sendMail({
from: `"MoreminiMore Website" <${smtpFrom}>`,
to: toEmail,
replyTo: email,
subject: `📬 ข้อความใหม่จาก ${name}${service ? ` - ${serviceLabel[service] || service}` : ""}`,
text: textBody,
html: htmlBody,
});
}
// Auto-reply to sender
if (smtpHost && smtpUser && smtpPass) {
const transporter = nodemailer.createTransport({
host: smtpHost,
port: Number(smtpPort),
secure: Number(smtpPort) === 465,
auth: {
user: smtpUser,
pass: smtpPass,
},
});
await transporter.sendMail({
from: `"MoreminiMore" <${smtpFrom}>`,
to: email,
subject: "📧 เราได้รับข้อความของคุณแล้ว",
html: `
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<style>
body { font-family: 'Noto Sans Thai', sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.container { max-width: 600px; margin: 0 auto; background: #fff; border-radius: 12px; overflow: hidden; }
.header { background: #0a0a0a; color: #fed400; padding: 24px; text-align: center; }
.header h1 { margin: 0; font-size: 20px; }
.body { padding: 24px; color: #0a0a0a; }
.body p { line-height: 1.7; margin-bottom: 16px; }
.highlight { background: #fff4b3; padding: 12px 16px; border-radius: 8px; margin: 16px 0; }
.footer { padding: 16px 24px; border-top: 1px solid #e5e5e5; font-size: 12px; color: #737373; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>✨ ขอบคุณที่ติดต่อ MoreminiMore</h1>
</div>
<div class="body">
<p>สวัสดีคุณ ${name},</p>
<p>เราได้รับข้อความของคุณแล้ว และจะตอบกลับภายใน 24 ชั่วโมงทำการ</p>
<div class="highlight">
<strong>ข้อความของคุณ:</strong><br>
${message.substring(0, 200)}${message.length > 200 ? "..." : ""}
</div>
<p>หากมีเรื่องด่วน สามารถติดต่อเราได้โดยตรงที่:</p>
<p>
📞 080-995-5945<br>
📧 contact@moreminimore.com
</p>
<p>ขอบคุณครับ/ค่ะ<br>MoreminiMore Team</p>
</div>
<div class="footer">
บริษัท มอร์มินิมอร์ จำกัด<br>
53 หมู่ 1 ต.บ้านแพ้ว อ.บ้านแพ้ว สมุทรสาคร 74120
</div>
</div>
</body>
</html>`,
});
}
return new Response(
JSON.stringify({
success: true,
message: smtpHost ? "ส่งข้อความเรียบร้อยแล้ว เราจะติดต่อกลับภายใน 24 ชั่วโมง" : "บันทึกข้อความเรียบร้อยแล้ว"
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
} catch (error) {
console.error("[Contact API] Error:", error);
return new Response(
JSON.stringify({ success: false, error: "เกิดข้อผิดพลาด กรุณาลองใหม่อีกครั้ง" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
}
};

View File

@@ -1,250 +1,161 @@
---
import { getEmDashEntry } from "emdash";
import Base from "../layouts/Base.astro";
import MarketingBlocks from "../components/MarketingBlocks.astro";
const { entry: page, cacheHint } = await getEmDashEntry("pages", "contact");
try {
Astro.cache.set(cacheHint);
} catch {}
// Handle form submission
// NOTE: This is demo code. For production, add:
// - CSRF token validation
// - Rate limiting (e.g., via Cloudflare or middleware)
// - Actual email sending or webhook integration
let formStatus: "idle" | "success" | "error" = "idle";
let formMessage = "";
if (Astro.request.method === "POST") {
try {
const formData = await Astro.request.formData();
const name = formData.get("name")?.toString() || "";
const email = formData.get("email")?.toString() || "";
const company = formData.get("company")?.toString() || "";
const message = formData.get("message")?.toString() || "";
if (!name || !email || !message) {
formStatus = "error";
formMessage = "Please fill in all required fields.";
} else if (!email.includes("@")) {
formStatus = "error";
formMessage = "Please enter a valid email address.";
} else {
// TODO: Replace with actual email/webhook integration
console.log("Contact form submission:", {
name,
email,
company,
message,
});
formStatus = "success";
formMessage =
"Thanks for reaching out! We'll get back to you within 24 hours.";
}
} catch {
formStatus = "error";
formMessage = "Something went wrong. Please try again.";
}
}
const pageContent = page?.data.content;
---
<Base
title="Contact"
description="Have questions? Want a demo? We'd love to hear from you."
title="ติดต่อเรา"
description="ติดต่อ MoreminiMore สำหรับปรึกษาฟรีเกี่ยวกับเว็บไซต์ AI Chatbot และ Marketing Automation"
>
{pageContent && <MarketingBlocks value={pageContent} />}
<section class="contact-form-section section">
<section class="contact-hero">
<div class="container">
<div class="contact-grid">
<div class="contact-info">
<h2>Talk to our team</h2>
<p>Fill out the form and we'll be in touch within 24 hours.</p>
<h1>ติดต่อเรา</h1>
<p class="lead">พร้อมให้คำปรึกษาฟรี! ติดต่อมาได้เลย</p>
</div>
</section>
<div class="contact-methods">
<div class="contact-method">
<div class="contact-icon">
<i class="ph ph-envelope" aria-hidden="true"></i>
</div>
<div class="contact-method-content">
<h4>Email</h4>
<a href="mailto:hello@acme.example">hello@acme.example</a>
</div>
<section class="section">
<div class="container contact-grid">
<div class="contact-info">
<h2>ช่องทางการติดต่อ</h2>
<div class="contact-cards">
<div class="contact-card">
<i class="ph ph-phone"></i>
<div>
<h3>โทรศัพท์</h3>
<p>080-995-5945</p>
</div>
<div class="contact-method">
<div class="contact-icon">
<i class="ph ph-lifebuoy" aria-hidden="true"></i>
</div>
<div class="contact-method-content">
<h4>Support</h4>
<a href="mailto:support@acme.example">support@acme.example</a>
</div>
</div>
<div class="contact-card">
<i class="ph ph-envelope"></i>
<div>
<h3>อีเมล</h3>
<p>contact@moreminimore.com</p>
</div>
<div class="contact-method">
<div class="contact-icon">
<i class="ph ph-currency-dollar" aria-hidden="true"></i>
</div>
<div class="contact-method-content">
<h4>Sales</h4>
<a href="mailto:sales@acme.example">sales@acme.example</a>
</div>
</div>
<div class="contact-card">
<i class="ph ph-map-pin"></i>
<div>
<h3>ที่อยู่</h3>
<p>53 หมู่ 1 ต.บ้านแพ้ว<br />อ.บ้านแพ้ว สมุทรสาคร 74120</p>
</div>
</div>
<div class="contact-card">
<i class="ph ph-clock"></i>
<div>
<h3>เวลาทำการ</h3>
<p>จ-ศ: 9:00-18:00<br />ส: 10:00-16:00</p>
</div>
</div>
</div>
<div class="contact-form-wrapper">
{
formStatus === "success" ? (
<div class="form-success">
<div class="success-icon">✓</div>
<h3>Message Sent!</h3>
<p>{formMessage}</p>
<a href="/contact" class="btn btn-secondary">
Send another message
</a>
</div>
) : (
<form method="POST" class="contact-form">
{formStatus === "error" && (
<div class="form-error">
<p>{formMessage}</p>
</div>
)}
<div class="form-row">
<div class="form-field">
<label for="name">Name *</label>
<input
type="text"
id="name"
name="name"
required
placeholder="Your name"
autocomplete="name"
/>
</div>
<div class="form-field">
<label for="email">Email *</label>
<input
type="email"
id="email"
name="email"
required
placeholder="you@company.com"
autocomplete="email"
/>
</div>
</div>
<div class="form-field">
<label for="company">Company</label>
<input
type="text"
id="company"
name="company"
placeholder="Your company"
autocomplete="organization"
/>
</div>
<div class="form-field">
<label for="message">Message *</label>
<textarea
id="message"
name="message"
required
rows="5"
placeholder="Tell us about your project or question..."
/>
</div>
<button type="submit" class="btn btn-primary btn-lg">
Send Message
</button>
</form>
)
}
</div>
</div>
<div class="contact-form-wrapper">
<h2>ส่งข้อความถึงเรา</h2>
<form class="contact-form" id="contactForm">
<div class="form-group">
<label for="name">ชื่อของคุณ</label>
<input type="text" id="name" name="name" required placeholder="กรอกชื่อของคุณ" />
</div>
<div class="form-group">
<label for="email">อีเมล</label>
<input type="email" id="email" name="email" required placeholder="your@email.com" />
</div>
<div class="form-group">
<label for="phone">เบอร์โทรศัพท์ (ไม่บังคับ)</label>
<input type="tel" id="phone" name="phone" placeholder="080-xxx-xxxx" />
</div>
<div class="form-group">
<label for="service">บริการที่สนใจ</label>
<select id="service" name="service">
<option value="">เลือกบริการ</option>
<option value="web-development">Web Development</option>
<option value="ai-automation">AI Automation</option>
<option value="marketing-automation">Marketing Automation</option>
<option value="tech-consult">Tech Consult</option>
<option value="other">อื่นๆ</option>
</select>
</div>
<div class="form-group">
<label for="message">ข้อความ</label>
<textarea id="message" name="message" rows="5" required placeholder="บอกเราเกี่ยวกับโปรเจกต์ของคุณ..."></textarea>
</div>
<div id="formStatus" class="form-status" hidden></div>
<button type="submit" class="btn btn-primary btn-lg" id="submitBtn">
ส่งข้อความ
<i class="ph ph-paper-plane-right"></i>
</button>
</form>
</div>
</div>
</section>
</Base>
<style>
.contact-form-section {
padding-bottom: var(--spacing-5xl);
.contact-hero {
background: var(--color-dark);
color: #f5f5f5;
padding: var(--spacing-5xl) 0;
text-align: center;
}
.contact-hero h1 {
color: #f5f5f5;
margin-bottom: var(--spacing-lg);
}
.contact-hero .lead {
font-size: var(--font-size-xl);
color: #a3a3a3;
}
.contact-grid {
display: grid;
grid-template-columns: 1fr 1.5fr;
gap: var(--spacing-4xl);
align-items: start;
grid-template-columns: 1fr;
gap: var(--spacing-3xl);
}
.contact-info h2 {
@media (min-width: 768px) {
.contact-grid {
grid-template-columns: 1fr 1fr;
}
}
.contact-info h2,
.contact-form-wrapper h2 {
font-size: var(--font-size-2xl);
margin-bottom: var(--spacing-md);
margin-bottom: var(--spacing-xl);
}
.contact-info > p {
color: var(--color-muted);
margin-bottom: var(--spacing-2xl);
}
.contact-methods {
.contact-cards {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
}
.contact-method {
.contact-card {
display: flex;
align-items: center;
gap: var(--spacing-md);
gap: var(--spacing-lg);
padding: var(--spacing-lg);
background: var(--color-bg-subtle);
border-radius: var(--radius-lg);
}
.contact-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
color: white;
background: linear-gradient(
135deg,
var(--color-primary),
var(--color-accent)
);
border-radius: var(--radius);
.contact-card i {
font-size: var(--font-size-3xl);
color: var(--color-primary);
flex-shrink: 0;
}
.contact-method-content h4 {
font-size: var(--font-size-sm);
.contact-card h3 {
font-size: var(--font-size-base);
font-weight: 600;
margin-bottom: var(--spacing-xs);
}
.contact-method-content a {
color: var(--color-primary);
font-size: var(--font-size-lg);
}
.contact-method-content a:hover {
text-decoration: underline;
}
.contact-form-wrapper {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: var(--spacing-2xl);
.contact-card p {
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
margin: 0;
}
.contact-form {
@@ -253,105 +164,116 @@ const pageContent = page?.data.content;
gap: var(--spacing-lg);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-lg);
}
.form-field {
.form-group {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
gap: var(--spacing-xs);
}
.form-field label {
.form-group label {
font-size: var(--font-size-sm);
font-weight: 500;
}
.form-field input,
.form-field textarea {
.form-group input,
.form-group select,
.form-group textarea {
padding: var(--spacing-md);
font-family: inherit;
font-size: var(--font-size-base);
color: var(--color-text);
background: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
transition:
border-color var(--transition-fast),
box-shadow var(--transition-fast);
border-radius: var(--radius);
font-size: var(--font-size-base);
background: var(--color-bg);
color: var(--color-text);
transition: border-color var(--transition-fast);
}
.form-field input:focus,
.form-field textarea:focus {
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.form-field input::placeholder,
.form-field textarea::placeholder {
color: var(--color-muted);
}
.form-field textarea {
.form-group textarea {
resize: vertical;
min-height: 120px;
}
.form-error {
.contact-form .btn {
align-self: flex-start;
}
.form-status {
padding: var(--spacing-md);
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: var(--radius-sm);
color: #dc2626;
border-radius: var(--radius);
font-size: var(--font-size-sm);
font-weight: 500;
}
@media (prefers-color-scheme: dark) {
.form-error {
color: #f87171;
}
.form-status[hidden] {
display: none;
}
.form-success {
text-align: center;
padding: var(--spacing-2xl);
.form-status-success {
background: #dcfce7;
color: #166534;
border: 1px solid #86efac;
}
.success-icon {
width: 64px;
height: 64px;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
color: white;
background: var(--color-success);
border-radius: var(--radius-full);
margin: 0 auto var(--spacing-lg);
}
.form-success h3 {
font-size: var(--font-size-xl);
margin-bottom: var(--spacing-sm);
}
.form-success p {
color: var(--color-muted);
margin-bottom: var(--spacing-xl);
}
@media (max-width: 768px) {
.contact-grid {
grid-template-columns: 1fr;
gap: var(--spacing-2xl);
}
.form-row {
grid-template-columns: 1fr;
}
.form-status-error {
background: #fee2e2;
color: #991b1b;
border: 1px solid #fca5a5;
}
</style>
<script>
const form = document.getElementById("contactForm") as HTMLFormElement | null;
const statusEl = document.getElementById("formStatus");
const submitBtn = document.getElementById("submitBtn") as HTMLButtonElement | null;
function showStatus(message: string, type: "success" | "error") {
if (!statusEl) return;
statusEl.textContent = message;
statusEl.className = `form-status form-status-${type}`;
statusEl.removeAttribute("hidden");
}
function setLoading(loading: boolean) {
if (!submitBtn) return;
submitBtn.disabled = loading;
submitBtn.textContent = loading ? "กำลังส่ง..." : "ส่งข้อความ";
const icon = loading ? "" : '<i class="ph ph-paper-plane-right"></i>';
if (!loading) {
submitBtn.innerHTML = `ส่งข้อความ ${icon}`;
}
}
form?.addEventListener("submit", async (e) => {
e.preventDefault();
if (!statusEl) return;
statusEl.setAttribute("hidden", "");
setLoading(true);
try {
const formData = new FormData(form);
const res = await fetch("/api/contact", {
method: "POST",
body: formData,
});
const data = await res.json();
if (data.success) {
showStatus(data.message || "ส่งข้อความเรียบร้อยแล้ว!", "success");
form.reset();
} else {
showStatus(data.error || "เกิดข้อผิดพลาด กรุณาลองใหม่", "error");
}
} catch {
showStatus("เกิดข้อผิดพลาดในการเชื่อมต่อ กรุณาลองใหม่", "error");
} finally {
setLoading(false);
}
});
</script>

View File

@@ -0,0 +1,223 @@
---
import { getEmDashEntry } from "emdash";
import Base from "../../layouts/Base.astro";
import MarketingBlocks from "../../components/MarketingBlocks.astro";
const { entry, cacheHint } = await getEmDashEntry("pages", "services/web-development");
try {
Astro.cache.set(cacheHint);
} catch {}
const pageContent = entry?.data.content;
---
<Base
title="Web Development"
description="รับทำเว็บไซต์ด้วย Astro หรือ WordPress เริ่มต้น 5,000-10,000 บาท รวม AI Editor ที่ลูกค้าปรับได้เอง"
>
{pageContent ? (
<MarketingBlocks value={pageContent} />
) : (
<section class="service-hero" style="--accent: #f59e0b">
<div class="container">
<span class="service-badge">Web Development</span>
<h1>Web Development</h1>
<p class="lead">เว็บไซต์ที่ลูกค้าปรับเองได้ด้วย AI Editor พร้อม SEO และ Dark Mode</p>
<a href="/contact" class="btn btn-primary btn-lg">ปรึกษาฟรี</a>
</div>
</section>
)}
<section class="section">
<div class="container">
<h2>เทคโนโลยีที่เราใช้</h2>
<div class="tech-grid">
<div class="tech-card">
<h3>Astro</h3>
<p>เว็บไซต์เร็ว ปลอดภัย SEO ดี เริ่มต้น 5,000 บาท</p>
</div>
<div class="tech-card">
<h3>WordPress</h3>
<p>CMS ยอดนิยม ปรับแต่งง่าย เริ่มต้น 10,000 บาท</p>
</div>
<div class="tech-card">
<h3>AI Editor</h3>
<p>ลูกค้าปรับเนื้อหาได้เองง่ายๆ ด้วย AI</p>
</div>
</div>
</div>
</section>
<section class="section" style="background: var(--color-bg-subtle);">
<div class="container">
<div class="pricing-header">
<h2>ราคา Web Development</h2>
<p class="pricing-sub">เลือกแพ็กเกจที่เหมาะกับธุรกิจคุณ</p>
</div>
<div class="pricing-grid">
<div class="pricing-card">
<h3 class="plan-name">Astro Starter</h3>
<div class="plan-price">
<span class="price-amount">5,000</span>
<span class="price-period">บาท</span>
</div>
<p class="plan-description">เหมาะสำหรับเว็บไซต์พื้นฐาน 5 หน้า</p>
<ul class="plan-features">
<li><i class="ph ph-check-circle"></i>5 sections</li>
<li><i class="ph ph-check-circle"></i>Mobile responsive</li>
<li><i class="ph ph-check-circle"></i>AI content editor</li>
<li><i class="ph ph-check-circle"></i>SEO พื้นฐาน</li>
<li><i class="ph ph-check-circle"></i>1 เดือน support</li>
<li><i class="ph ph-check-circle"></i>Hosting หรือ deploy ให้</li>
</ul>
<a href="/contact" class="btn btn-secondary btn-lg">ติดต่อสอบถาม</a>
</div>
<div class="pricing-card pricing-card-highlighted">
<div class="pricing-badge">ยอดนิยม</div>
<h3 class="plan-name">WordPress Starter</h3>
<div class="plan-price">
<span class="price-amount">10,000</span>
<span class="price-period">บาท</span>
</div>
<p class="plan-description">เหมาะสำหรับเว็บไซต์ที่ต้องการ CMS ยืดหยุ่น</p>
<ul class="plan-features">
<li><i class="ph ph-check-circle"></i>5 sections</li>
<li><i class="ph ph-check-circle"></i>Mobile responsive</li>
<li><i class="ph ph-check-circle"></i>AI content editor</li>
<li><i class="ph ph-check-circle"></i>SEO ขั้นสูง</li>
<li><i class="ph ph-check-circle"></i>1 เดือน support</li>
<li><i class="ph ph-check-circle"></i>Hosting หรือ deploy ให้</li>
</ul>
<a href="/contact" class="btn btn-primary btn-lg">ติดต่อสอบถาม</a>
</div>
</div>
<p class="pricing-note">* ราคาไม่รวมโดเมนและโฮสติ้ง ราคาขึ้นอยู่กับความซับซ้อนของงาน</p>
</div>
</section>
<section class="section cta-dark">
<div class="container cta-center">
<h2>พร้อมสร้างเว็บไซต์แล้วหรือยัง?</h2>
<p>ติดต่อเราวันนี้ พร้อมให้คำปรึกษาฟรี</p>
<a href="/contact" class="btn btn-primary btn-lg">ปรึกษาฟรี</a>
</div>
</section>
</Base>
<style>
.service-hero {
background: var(--color-dark);
color: #f5f5f5;
padding: var(--spacing-5xl) 0;
}
.service-hero .container { max-width: 720px; }
.service-badge {
display: inline-block;
font-size: var(--font-size-xs);
font-weight: 700;
text-transform: uppercase;
letter-spacing: var(--tracking-wider);
color: var(--accent, var(--color-primary));
background: rgba(255, 255, 255, 0.1);
padding: var(--spacing-xs) var(--spacing-md);
border-radius: var(--radius-full);
margin-bottom: var(--spacing-lg);
}
.service-hero h1 { color: #f5f5f5; margin-bottom: var(--spacing-lg); }
.service-hero .lead { font-size: var(--font-size-xl); color: #a3a3a3; margin-bottom: var(--spacing-xl); }
.tech-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: var(--spacing-xl);
margin-top: var(--spacing-2xl);
}
.tech-card {
background: var(--color-bg-subtle);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
}
.tech-card h3 { font-size: var(--font-size-xl); margin-bottom: var(--spacing-sm); }
.tech-card p { font-size: var(--font-size-sm); color: var(--color-text-secondary); margin: 0; }
.cta-dark { background: var(--color-dark); color: #f5f5f5; }
.cta-center { text-align: center; max-width: 600px; margin: 0 auto; }
.cta-dark h2 { color: #f5f5f5; margin-bottom: var(--spacing-md); }
.cta-dark p { color: #a3a3a3; margin-bottom: var(--spacing-xl); }
/* Pricing section */
.pricing-header { text-align: center; margin-bottom: var(--spacing-3xl); }
.pricing-header h2 { font-size: var(--font-size-4xl); margin-bottom: var(--spacing-sm); }
.pricing-sub { font-size: var(--font-size-lg); color: var(--color-text-secondary); margin: 0; }
.pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--spacing-xl);
max-width: 800px;
margin: 0 auto;
}
.pricing-card {
position: relative;
background: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--radius-xl);
padding: var(--spacing-2xl);
display: flex;
flex-direction: column;
}
.pricing-card-highlighted {
border-color: var(--color-primary);
border-width: 2px;
background: var(--color-dark);
color: #f5f5f5;
}
.pricing-badge {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
background: var(--color-primary);
color: var(--color-dark);
font-size: var(--font-size-xs);
font-weight: 700;
padding: var(--spacing-xs) var(--spacing-md);
border-radius: var(--radius-full);
text-transform: uppercase;
}
.plan-name { font-size: var(--font-size-xl); font-weight: 600; margin-bottom: var(--spacing-md); }
.pricing-card-highlighted .plan-name { color: var(--color-primary); }
.plan-price { display: flex; align-items: baseline; gap: var(--spacing-xs); margin-bottom: var(--spacing-md); }
.price-amount { font-family: var(--font-display); font-size: var(--font-size-5xl); font-weight: 700; line-height: 1; }
.price-period { font-size: var(--font-size-sm); color: var(--color-muted); }
.pricing-card-highlighted .price-period { color: #a3a3a3; }
.plan-description { font-size: var(--font-size-sm); color: var(--color-text-secondary); margin-bottom: var(--spacing-lg); }
.pricing-card-highlighted .plan-description { color: #a3a3a3; }
.plan-features { list-style: none; padding: 0; margin: 0 0 var(--spacing-xl); flex-grow: 1; }
.plan-features li {
display: flex; align-items: flex-start; gap: var(--spacing-sm);
font-size: var(--font-size-sm); padding: var(--spacing-sm) 0;
border-bottom: 1px solid var(--color-border-subtle);
}
.pricing-card-highlighted .plan-features li { border-bottom-color: #262626; }
.plan-features li:last-child { border-bottom: none; }
.plan-features i { color: var(--color-success); font-size: var(--font-size-lg); flex-shrink: 0; margin-top: 2px; }
.pricing-card .btn { width: 100%; justify-content: center; }
.pricing-card-highlighted .btn-primary { background: var(--color-primary); color: var(--color-dark); }
.pricing-note { text-align: center; font-size: var(--font-size-sm); color: var(--color-muted); margin-top: var(--spacing-xl); }
</style>