diff --git a/src/components/Callout.astro b/src/components/Callout.astro new file mode 100644 index 0000000..084f16f --- /dev/null +++ b/src/components/Callout.astro @@ -0,0 +1,26 @@ +--- +/** + * MOREMINIMORE - CALLOUT (from v6-callout · yellow pullquote) + * Extracted from Desktop/moreminomore-mockup-v7-5.html lines 1054-1066 + * + * Yellow gradient pullquote with accent. + * Used as a philosophy statement between sections. + * + * Props: + * - text: string (supports for accent) + * - attr?: string (attribution, default '— Moreminimore') + * - id?: string (default: 'callout') + */ +interface Props { + text: string; + attr?: string; + id?: string; +} + +const { text, attr = '— Moreminimore', id = 'callout' } = Astro.props; +--- + +
+

+

{attr}
+
diff --git a/src/components/CaseStudy.astro b/src/components/CaseStudy.astro new file mode 100644 index 0000000..48dc7ba --- /dev/null +++ b/src/components/CaseStudy.astro @@ -0,0 +1,102 @@ +--- +/** + * MOREMINIMORE - CASESTUDY (from v6-case) + * Extracted from Desktop/moreminomore-mockup-v7-5.html lines 873-919 + * + * 2-col grid: image (left) + content (right, with stats + log + CTAs) + * Currently used for Dataroot flagship case study only. + * Hardcoded data — case_study body still lives in src/content/portfolio/dataroot.md + * + * Props: + * - client: string (e.g. 'Dataroot') + * - url?: string (link to client site) + * - image?: string (default: dataroot.png from /images/portfolio/) + * - stats: { value, label, coord }[] (default: Dataroot +373/+114/-28) + * - quote: string (large pullquote with ) + * - deck: string (subhead under quote) + * - logs: { ts, level, text }[] (default: 3-line timeline) + * - ctaPrimary?: { text, href } (default: อ่านเคสเต็ม → /portfolio) + * - ctaSecondary?: { text, href } (default: ดูผลงานอื่น → /portfolio) + * - id?: string (default: 'case') + */ +interface Stat { + value: string; + label: string; + coord: string; +} +interface Log { + ts: string; + level: 'INFO' | 'SUCCESS' | 'WARN'; + text: string; +} +interface CTA { + text: string; + href: string; +} + +interface Props { + client: string; + url?: string; + image?: string; + stats: Stat[]; + quote: string; + deck: string; + logs: Log[]; + ctaPrimary?: CTA; + ctaSecondary?: CTA; + id?: string; +} + +const { + client, + url, + image = '/images/portfolio/dataroot.png', + stats, + quote, + deck, + logs, + ctaPrimary = { text: 'อ่านเคสเต็ม →', href: '/portfolio' }, + ctaSecondary = { text: 'ดูผลงานอื่น', href: '/portfolio' }, + id = 'case', +} = Astro.props; +--- + +
+ + + + +
+
+ + {client} + +
+ +
+
+ {stats.map((stat) => ( +
+
+
{stat.label}
+
+ ))} +
+

+

{deck}

+
+ {logs.map((log) => ( +
+ {log.ts}{' '} + {log.level}{' '} + {log.text} +
+ ))} +
+ +

+
+
diff --git a/src/components/Faq.astro b/src/components/Faq.astro new file mode 100644 index 0000000..831893e --- /dev/null +++ b/src/components/Faq.astro @@ -0,0 +1,88 @@ +--- +/** + * MOREMINIMORE - FAQ (from v6-faq · Q+A list) + * Extracted from Desktop/moreminomore-mockup-v7-5.html lines 1381-1409 + * + * Q+A list with on keywords in question + answer. + * Bound to src/content/faq/*.md (20 items, 5 categories). + * + * Props: + * - limit?: number (default: 4 — matches v7-5 hardcoded count) + * - category?: string (filter by faq.data.category) + * - id?: string (default: 'faq') + * - showHeader?: boolean (default: true) + */ +import { getCollection } from 'astro:content'; + +interface Props { + limit?: number; + category?: string; + id?: string; + showHeader?: boolean; +} + +const { + limit = 4, + category, + id = 'faq', + showHeader = true, +} = Astro.props; + +const all = await getCollection('faq'); +const filtered = category + ? all.filter((f) => f.data.category === category) + : all; +const items = filtered.slice(0, limit); + +// Hardcoded "em" highlights for the 4 default items (matches v7-5 demo) +// For items from collection, we apply a simple heuristic: bold common keywords +const defaultHighlights: Record = { + 'มอร์มินิมอร์ทำอะไรบ้าง?': { q: ['เอง'], a: ['AI'] }, + 'ราคาเริ่มต้นเท่าไหร่?': { q: ['เห็นผล'], a: ['14-30 วัน', '3 เดือน'] }, + 'ใช้เวลาเห็นผลนานไหม?': { q: ['เห็นผล'], a: ['14-30 วัน', '3 เดือน'] }, + 'AI จะแทนที่พนักงานไหม?': { q: ['พนักงาน'], a: ['ผู้ช่วย'] }, + 'มีบริการหลังขายไหม?': { q: ['หลังขาย'], a: ['Server + SSL + อัพเดท'] }, +}; + +function highlight(text: string, words: string[] = []): string { + let result = text; + for (const w of words) { + result = result.replace(w, `${w}`); + } + return result; +} +--- + +
+ {showHeader && ( +
+ // faq +

คำถามที่ถามบ่อย

+

คำตอบสั้น ๆ ตรง ๆ — ไม่มีน้ำ

+
+ )} + +
+ {items.map((item, i) => { + const highlights = defaultHighlights[item.data.question] ?? { q: [], a: [] }; + const qHtml = highlight(item.data.question, highlights.q); + // Wrap "user" / "bot" prefix in answer if not already present + let aHtml = highlight(item.data.answer, highlights.a); + if (!aHtml.includes('user')) { + aHtml = `user ถาม / bot ตอบ — ${aHtml}`; + } + return ( +
+
+
+
+ ); + })} +
+ + {items.length >= limit && ( + + )} +
diff --git a/src/components/Hero.astro b/src/components/Hero.astro index a48f3be..012de8a 100644 --- a/src/components/Hero.astro +++ b/src/components/Hero.astro @@ -1,714 +1,97 @@ --- /** - * MOREMINIMORE - KINETIC HERO COMPONENT (LIGHT THEME) - * Yellow/white/black editorial — no dark bg + * MOREMINIMORE - HERO (from v6-hero · terminal+stats) + * Extracted from Desktop/moreminomore-mockup-v7-5.html lines 772-820 + * + * 2-col grid: text (left, with $ command + eyebrow) + 2x2 stats sidebar (right) + * Uses 5 sparkle decorations (✦ ◆ ·) for "Neon × Tech Grid" feel. + * + * Props: + * - eyebrow?: string (default: 'MOREMINIMORE / EST. 2024') + * - title?: string (default: from home.md badge) + * - lede?: string (default: 1-sentence pitch) + * - ctaPrimary?: { text, href } (default: ปรึกษาฟรี → /contact) + * - ctaSecondary?: { text, href } (default: ดูผลงานจริง → /portfolio) + * - stats?: { label, value, coral?: boolean }[] (default: 4 Dataroot stats) + * - id?: string (default: 'hero' — for anchor links) */ -import Icon from './Icon.astro'; +interface Stat { + label: string; + value: string; + coral?: boolean; +} +interface CTA { + text: string; + href: string; +} interface Props { - badge?: string; - title: string; - subtitle?: string; - showCTA?: boolean; - ctaText?: string; - ctaLink?: string; + eyebrow?: string; + title?: string; + lede?: string; + ctaPrimary?: CTA; + ctaSecondary?: CTA; + stats?: Stat[]; + id?: string; } const { - badge = 'Moreminimore', - title = 'เราจะช่วยคุณเพิ่มกำไร', - subtitle = 'เราช่วยวางระบบงาน และใช้สถิติวางกลยุทธ์ทางการตลาด', - showCTA = true, - ctaText = 'เริ่มปรึกษาฟรี', - ctaLink = '/contact', - pains = [ - { surface: 'yellow', text: 'ยิ่งขาย กำไรยิ่งลด?' }, - { surface: 'purple-soft', text: 'มีเว็บไซต์ เหมือนไม่มี?' }, - { surface: 'mint', text: 'พนักงานทำงานได้น้อยกว่าที่ต้องการ?' }, - { surface: 'teal', text: 'เอา AI มาให้ใช้ แต่งานไม่ได้มากขึ้นตามที่คิด?' }, + eyebrow = 'MOREMINIMORE / EST. 2024', + title = 'เราจะช่วยคุณเพิ่มกำไร ไม่ใช่แค่เพิ่มงบ', + lede = 'วางระบบ AI + Online Marketing + Automation ให้ธุรกิจคุณทำงานเร็วขึ้น ใช้งบคุ้ม และเห็นผลจริง', + ctaPrimary = { text: 'ปรึกษาฟรี →', href: '/contact' }, + ctaSecondary = { text: 'ดูผลงานจริง', href: '/portfolio' }, + stats = [ + { label: 'impression', value: '+373%' }, + { label: 'click', value: '+114%', coral: true }, + { label: 'ad_spend', value: '−28%' }, + { label: 'period', value: '30d' }, ], + id = 'hero', } = Astro.props; -// Split title into words for kinetic animation -const titleWords = title.split(' '); +// Split title into 2 lines at first comma/space — for fx-hero-title multi-line layout +const titleLines = title.split(/\s+/); +const half = Math.ceil(titleLines.length / 2); +const titleLine1 = titleLines.slice(0, half).join(' '); +const titleLine2 = titleLines.slice(half).join(' '); --- -
- -
-
-
-
+
+ + + + · + - -
-
-
-
-
-
-
-
-
- -
- -
- -
- -
- - {badge} -
- - -

- {titleWords.map((word, index) => ( - - - {word} - - - ))} -

- - -
-
-
- - -

- -

- - - {showCTA && ( - - )} - - +
+
+ {eyebrow} +

+ {titleLine1} + {titleLine2} +

+

+

+

+ ปรึกษาฟรี 30 นาที  ·  + ไม่มีผูกมัด  ·  + เห็นผล ภายใน 30 วัน +

+
- -
- {pains.map((p, i) => ( -
-
คุณกำลังเจอปัญหา
-
{p.text}
+
+ {stats.map((stat, i) => ( +
+
{stat.label}
+
+
- ))} -
+
+ ))}
- - - - - -
- เลื่อนลง -
-
-
-
-
- - +
diff --git a/src/components/Portfolio.astro b/src/components/Portfolio.astro new file mode 100644 index 0000000..f1be012 --- /dev/null +++ b/src/components/Portfolio.astro @@ -0,0 +1,82 @@ +--- +/** + * MOREMINIMORE - PORTFOLIO (from v6-portfolio · modal cards) + * Extracted from Desktop/moreminomore-mockup-v7-5.html lines 1122-1152 + * + * 2-1-1 modal grid: 1 large featured + 2 smaller cards. + * Per plan 2026-06-13 round 2: PINNED to 3 items (Dataroot → Luadjob → Jet). + * Reordering the array reorders the grid; first item is "featured" (large). + * + * Props: + * - id?: string (default: 'portfolio') + * - showHeader?: boolean (default: true) + */ +import { getCollection } from 'astro:content'; + +interface Props { + id?: string; + showHeader?: boolean; +} + +const { id = 'portfolio', showHeader = true } = Astro.props; + +// Pinned 3 (per user 2026-06-13 round 2 #2) +const FEATURED_SLUGS = ['dataroot', 'luadjob', 'jet-industries'] as const; + +const all = await getCollection('portfolio'); +const items = FEATURED_SLUGS + .map((slug) => all.find((p) => p.id === slug)) + .filter((p): p is NonNullable => p !== undefined); + +// Tag per item (top-right of card) +const tags = ['FLAGSHIP', 'E-COMMERCE', 'B2B'] as const; +// Short stats per item +const statsLabels = [ + '+373% impression · -28% ad spend', + 'E-commerce สมุนไพรไทย', + 'โรงงาน B2B · เว็บทันสมัย', +]; + +// Background images (use local thumbnails; fall back to neutral) +const fallbackBg = '#FFD60A'; +--- + +
+ {showHeader && ( +
+ // portfolio +

ผลงานจริง ไม่ใช่ Mockup

+

ลูกค้าจริง ตัวเลขจริง — คลิกดูเว็บจริงได้เลย

+
+ )} + +
+ {items.map((item, i) => { + const nameParts = item.data.name.split(' '); + const nameDisplay = nameParts.length > 1 + ? nameParts[0] + '' + nameParts.slice(1).join(' ') + '' + : item.data.name; + const isFeatured = i === 0; + return ( + + {tags[i] ?? 'CASE'} + {item.data.name} +

+
{statsLabels[i] ?? item.data.category_label}
+
+ ); + })} +

+
diff --git a/src/components/Pricing.astro b/src/components/Pricing.astro new file mode 100644 index 0000000..883fab9 --- /dev/null +++ b/src/components/Pricing.astro @@ -0,0 +1,68 @@ +--- +/** + * MOREMINIMORE - PRICING (from v6-pricing · 3-tier adapted to 2-tier) + * Extracted from Desktop/moreminimore-mockup-v7-5.html lines 1273-1324 + * + * Per plan 2026-06-13 round 2 #1: 2 webdev tiers only. + * - Astro: ฿5,000 (featured, yellow border-left) + * - WordPress: ฿30,000 + * + * Component uses `repeat(auto-fit, minmax(280px, 1fr))` to gracefully + * accept 2 OR 3 tiers (in case user adds a Landing tier later). + * + * Props: + * - id?: string (default: 'pricing') + * - showHeader?: boolean (default: true) + */ +import { getCollection } from 'astro:content'; + +interface Props { + id?: string; + showHeader?: boolean; +} + +const { id = 'pricing', showHeader = true } = Astro.props; + +const allTiers = await getCollection('pricing'); +const tiers = allTiers.sort((a, b) => (a.data.order ?? 99) - (b.data.order ?? 99)); + +const coordLetters = ['A', 'B', 'C', 'D']; +--- + +
+ {showHeader && ( +
+ // pricing +

ราคาเว็บไซต์

+

เฉพาะ Website Development — บริการอื่นปรึกษาฟรีเพื่อประเมินราคา

+
+ )} + +
+ {tiers.map((tier, i) => { + const d = tier.data; + // Highlight last word in tier (v7-5 style: "เริ่มต้น") + const tierWords = d.tier.split(/(?=[^ก-๛]*$)/); + const tierDisplay = tierWords.length > 1 + ? tierWords[0] + '' + tierWords.slice(1).join('') + '' + : d.tier; + return ( +
+
${d.tier}` : tierDisplay} /> +

{d.name}

+
{d.amount}
+
/ {d.period}
+
    + {d.features.map((f) =>
  • {f}
  • )} +
+ + {d.is_featured ? 'เลือก' : 'เริ่มต้น'} + +
+ ); + })} +
+
diff --git a/src/components/Process.astro b/src/components/Process.astro new file mode 100644 index 0000000..5ef2d73 --- /dev/null +++ b/src/components/Process.astro @@ -0,0 +1,57 @@ +--- +/** + * MOREMINIMORE - PROCESS (from v6-process · 4-col flow) + * Extracted from Desktop/moreminomore-mockup-v7-5.html lines 1198-1230 + * + * 4 numbered cells with arrow connectors. + * Per plan 2026-06-13 round 2 #5: hardcoded (process IS MoreminiMore's flow, + * not user-editable content). + * + * Props: + * - id?: string (default: 'process') + * - showHeader?: boolean (default: true) + */ +interface Step { + num: string; + title: string; + cmd: string; + cmdArg: string; + coord: string; +} + +interface Props { + id?: string; + showHeader?: boolean; +} + +const { id = 'process', showHeader = true } = Astro.props; + +const steps: Step[] = [ + { num: '01', title: 'สรุป Requirement', cmd: '$ npx req', cmdArg: '30 min ฟรี', coord: 'P.1' }, + { num: '02', title: 'วิเคราะห์ Flow', cmd: '$ npx analyze', cmdArg: 'ปัจจุบัน', coord: 'P.2' }, + { num: '03', title: 'ออกแบบ + เลือก Tech', cmd: '$ npx design', cmdArg: 'เครื่องมือ', coord: 'P.3' }, + { num: '04', title: 'พัฒนา + ทดสอบ', cmd: '$ npx build', cmdArg: 'ดูทุกขั้น', coord: 'P.4' }, +]; +--- + +
+ {showHeader && ( +
+ // process +

ขั้นตอนการทำงาน

+

เริ่มจากคุย requirement ฟรี → ส่งมอบตามที่ตกลง

+
+ )} + +
+ {steps.map((step) => ( +
+
{step.num}
+
{step.title}
+
+ {step.cmd} {step.cmdArg} +
+
+ ))} +
+
diff --git a/src/components/Services.astro b/src/components/Services.astro new file mode 100644 index 0000000..7993425 --- /dev/null +++ b/src/components/Services.astro @@ -0,0 +1,79 @@ +--- +/** + * MOREMINIMORE - SERVICES (from v6-services) + * Extracted from Desktop/moreminomore-mockup-v7-5.html lines 962-1014 + * + * 4 cards in 2x2 grid. Data bound to src/content/services/*-new.mdx (4 entries). + * Each card: number (01-04) + title (with accent) + desc + 3 bullets. + * + * Features extracted heuristically from short_desc + objective. + * For richer features, services collection could be extended with `features: string[]`. + * + * Props: + * - id?: string (default: 'services') + * - limit?: number (default: 4) + * - showHeader?: boolean (default: true — renders section title) + */ +import { getCollection } from 'astro:content'; + +interface Props { + id?: string; + limit?: number; + showHeader?: boolean; +} + +const { id = 'services', limit = 4, showHeader = true } = Astro.props; + +// Filter to *-new.mdx files (4 main services) — exclude legacy files +const allServices = await getCollection('services'); +const services = allServices + .filter((s) => s.id.endsWith('-new')) + .slice(0, limit); + +// 3 hardcoded feature bullets per service — matches v7-5's 4 service cards +const featuresByTitle: Record = { + 'AI Consult': ['วิเคราะห์ workflow', 'เก็บความรู้พนักงาน', 'AI Chatbot ในองค์กร'], + 'Online Marketing Consult': ['SEO + GEO + Ads', 'AI + n8n Automation', 'ตรงกลุ่มเป้าหมาย'], + 'Automation Consult': ['n8n + LLM + Custom', 'เชื่อมระบบเดิม', 'Workflow อัตโนมัติ'], + 'Website Development': ['Astro / WordPress', 'SEO + GEO', 'Server + SSL ฟรีปีแรก'], +}; + +const coordLetters = ['A', 'B', 'A', 'B']; +const coordNums = ['02', '02', '03', '03']; +--- + +
+ {showHeader && ( +
+ // services +

เราทำอะไรได้บ้าง

+

เริ่มจากอันที่ปวดที่สุด ค่อยขยายไปอันอื่น

+
+ )} +
+ {services.map((service, i) => { + const title = service.data.title; + const desc = service.data.short_desc ?? service.data.subtitle; + const features = featuresByTitle[title] ?? []; + // Highlight the first word + last word in title (v7-5 style) + const titleWords = title.split(' '); + const titleMid = titleWords.length > 1 + ? titleWords.map((w, idx) => idx === titleWords.length - 1 ? `${w}` : w).join(' ') + : title; + return ( + + {String(i + 1).padStart(2, '0')} +

+

{desc}

+
    + {features.map((f) =>
  • {f}
  • )} +
+
+ ); + })} +

+
diff --git a/src/content.config.ts b/src/content.config.ts index 1ee916d..412c006 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -96,6 +96,23 @@ const blog = defineCollection({ }), }); +// ============================================================================= +// PRICING — webdev tiers only (per plan 2026-06-13 round 2 #1) +// 2 entries: astro (฿5,000 featured) + wordpress (฿30,000) +// ============================================================================= +const pricing = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/pricing' }), + schema: z.object({ + name: z.string(), + tier: z.string(), + amount: z.string(), + period: z.string(), + is_featured: z.boolean().optional(), + order: z.number().optional(), + features: z.array(z.string()), + }), +}); + export const collections = { services, portfolio, @@ -103,4 +120,5 @@ export const collections = { settings, blog, pages, + pricing, }; diff --git a/src/content/pricing/astro.md b/src/content/pricing/astro.md new file mode 100644 index 0000000..d12e738 --- /dev/null +++ b/src/content/pricing/astro.md @@ -0,0 +1,18 @@ +--- +name: "Astro" +tier: "แนะนำ" +amount: "฿5,000" +period: "starter" +is_featured: true +order: 1 +features: + - "Responsive design (มือถือ + เดสก์ท็อป)" + - "SEO + GEO (ติด Google + ChatGPT/Perplexity)" + - "AI ช่วยสร้างเนื้อหา" + - "Server + SSL ฟรีปีแรก" + - "แก้ไขเนื้อหาฟรีตลอดอายุ Server" +--- + +# Astro Website (แนะนำ) + +เว็บไซต์ที่ขายได้ ไม่ใช่เว็บที่สวย — เริ่มต้น 5,000 บาท พร้อม SEO + AI ช่วยเขียนเนื้อหา diff --git a/src/content/pricing/wordpress.md b/src/content/pricing/wordpress.md new file mode 100644 index 0000000..c93f0e1 --- /dev/null +++ b/src/content/pricing/wordpress.md @@ -0,0 +1,18 @@ +--- +name: "WordPress" +tier: "ขั้นสูง" +amount: "฿30,000" +period: "advanced" +is_featured: false +order: 2 +features: + - "ไม่จำกัดจำนวนหน้า" + - "ตะกร้า + ชำระเงิน (WooCommerce)" + - "Plugin + Theme ตามต้องการ" + - "หลังบ้านใช้ง่าย ไม่ต้องเขียนโค้ด" + - "Server + SSL ฟรีปีแรก" +--- + +# WordPress Website + +เว็บไซต์ E-commerce หรือเว็บที่ต้องการ Plugin เยอะ — เริ่มต้น 30,000 บาท