Compare commits
84 Commits
494b50925e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c20883cb4f | ||
|
|
58162354c6 | ||
|
|
fd6b70dbf2 | ||
|
|
a7a2724c09 | ||
|
|
ab3ca64ec9 | ||
|
|
28d2be03f0 | ||
|
|
abafd4015f | ||
|
|
8f03e6af69 | ||
|
|
94a1325f1f | ||
|
|
0c31f800cf | ||
|
|
14aac31916 | ||
|
|
8aae25ef4f | ||
|
|
eb785a53aa | ||
|
|
33043260a9 | ||
|
|
b75ed98804 | ||
|
|
4c7531ddc5 | ||
|
|
4fbeb01003 | ||
|
|
236edb5f9f | ||
|
|
54c9b381b9 | ||
|
|
c004ee6504 | ||
|
|
479ed4722e | ||
|
|
bb1b1ba568 | ||
|
|
9ebbc91e5b | ||
|
|
f114a34a62 | ||
|
|
689a8924e6 | ||
|
|
2a3062357f | ||
|
|
1d893e1bcb | ||
|
|
61c2bd6924 | ||
|
|
fdb03f6117 | ||
|
|
0f244424c0 | ||
|
|
f827afb33f | ||
|
|
e279119f97 | ||
|
|
ceffb2a3f3 | ||
|
|
73d820412a | ||
|
|
40382bbf55 | ||
|
|
8b04c739e1 | ||
|
|
caab40d9a4 | ||
|
|
96caca4af6 | ||
|
|
5393cf611c | ||
|
|
b8ac07996e | ||
|
|
caa412dbe2 | ||
|
|
8c2bf3d303 | ||
|
|
154e3f2d91 | ||
|
|
b586464b5c | ||
|
|
1f859921cb | ||
|
|
582998a340 | ||
|
|
9eea9cbe6d | ||
|
|
ed03060ff7 | ||
|
|
45f548421d | ||
|
|
b54657f58a | ||
|
|
c334b8b650 | ||
|
|
dcd1d73f56 | ||
|
|
84e245a7bb | ||
|
|
5f05489316 | ||
|
|
d3421f4003 | ||
|
|
58c27d3bb3 | ||
|
|
f86c9b24c4 | ||
|
|
5ab00efd15 | ||
|
|
a7a0135727 | ||
|
|
9fca75044d | ||
|
|
57eaa9da8b | ||
|
|
746d51569b | ||
|
|
a1b1b16288 | ||
|
|
0ff6ae9020 | ||
|
|
f47949c4b3 | ||
|
|
c92d446ff7 | ||
|
|
bd1c979f1a | ||
|
|
9e7d27c03c | ||
|
|
b49931a87a | ||
|
|
525dc358a3 | ||
|
|
43f609a794 | ||
|
|
b5be45bcd6 | ||
|
|
789473271e | ||
|
|
ca7f99ed41 | ||
|
|
3f1c0061c7 | ||
|
|
892c75b7a6 | ||
|
|
0f79808a43 | ||
|
|
6701c462ee | ||
|
|
5437a34124 | ||
|
|
0855e3d77b | ||
|
|
00b0de2d9a | ||
|
|
9fba7f2fd6 | ||
|
|
0faf75a9a2 | ||
|
|
b7787cc403 |
@@ -1,578 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1",
|
|
||||||
"collections": [
|
|
||||||
{
|
|
||||||
"slug": "pages",
|
|
||||||
"label": "Pages",
|
|
||||||
"fields": [
|
|
||||||
{ "slug": "title", "type": "string", "label": "Title" },
|
|
||||||
{ "slug": "subtitle", "type": "string", "label": "Subtitle" },
|
|
||||||
{ "slug": "badge", "type": "string", "label": "Badge" },
|
|
||||||
{ "slug": "hero_image", "type": "image", "label": "Hero Image" },
|
|
||||||
{ "slug": "theme", "type": "select", "label": "Theme", "options": ["yellow", "accent"] },
|
|
||||||
{ "slug": "show_cta", "type": "boolean", "label": "Show CTA" },
|
|
||||||
{ "slug": "cta_text", "type": "string", "label": "CTA Text" },
|
|
||||||
{ "slug": "cta_link", "type": "string", "label": "CTA Link" },
|
|
||||||
{ "slug": "variant", "type": "select", "label": "Hero Variant", "options": ["split", "centered", "text_only", "floating_cards"] },
|
|
||||||
{ "slug": "size", "type": "select", "label": "Hero Size", "options": ["full", "compact"] }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "services",
|
|
||||||
"label": "Services",
|
|
||||||
"fields": [
|
|
||||||
{ "slug": "title", "type": "string", "label": "Title" },
|
|
||||||
{ "slug": "subtitle", "type": "string", "label": "Subtitle" },
|
|
||||||
{ "slug": "badge", "type": "string", "label": "Badge" },
|
|
||||||
{ "slug": "hero_image", "type": "image", "label": "Hero Image" },
|
|
||||||
{ "slug": "content", "type": "portableText", "label": "Content" },
|
|
||||||
{
|
|
||||||
"slug": "features",
|
|
||||||
"type": "repeater",
|
|
||||||
"label": "Features",
|
|
||||||
"fields": [
|
|
||||||
{ "slug": "icon", "type": "string", "label": "Icon" },
|
|
||||||
{ "slug": "feature_title", "type": "string", "label": "Title" },
|
|
||||||
{ "slug": "description", "type": "string", "label": "Description" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "portfolio",
|
|
||||||
"label": "Portfolio",
|
|
||||||
"fields": [
|
|
||||||
{ "slug": "name", "type": "string", "label": "Name" },
|
|
||||||
{ "slug": "url", "type": "url", "label": "URL" },
|
|
||||||
{ "slug": "category", "type": "select", "label": "Category", "options": ["webdev", "ecommerce", "marketing"] },
|
|
||||||
{ "slug": "category_label", "type": "string", "label": "Category Label" },
|
|
||||||
{ "slug": "thumbnail", "type": "image", "label": "Thumbnail" },
|
|
||||||
{ "slug": "description", "type": "text", "label": "Description" },
|
|
||||||
{
|
|
||||||
"slug": "services",
|
|
||||||
"type": "repeater",
|
|
||||||
"label": "Services",
|
|
||||||
"fields": [
|
|
||||||
{ "slug": "service_name", "type": "string", "label": "Name" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "blog",
|
|
||||||
"label": "Blog",
|
|
||||||
"fields": [
|
|
||||||
{ "slug": "title", "type": "string", "label": "Title" },
|
|
||||||
{ "slug": "excerpt", "type": "string", "label": "Excerpt" },
|
|
||||||
{ "slug": "image", "type": "image", "label": "Image" },
|
|
||||||
{ "slug": "date", "type": "datetime", "label": "Date" },
|
|
||||||
{ "slug": "category", "type": "string", "label": "Category" },
|
|
||||||
{ "slug": "content", "type": "portableText", "label": "Content" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "faq",
|
|
||||||
"label": "FAQ",
|
|
||||||
"fields": [
|
|
||||||
{ "slug": "category", "type": "string", "label": "Category" },
|
|
||||||
{ "slug": "question", "type": "string", "label": "Question" },
|
|
||||||
{ "slug": "answer", "type": "text", "label": "Answer" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "settings",
|
|
||||||
"label": "Site Settings",
|
|
||||||
"fields": [
|
|
||||||
{ "slug": "site_name", "type": "string", "label": "Site Name" },
|
|
||||||
{ "slug": "email", "type": "string", "label": "Contact Email" },
|
|
||||||
{ "slug": "phone", "type": "string", "label": "Contact Phone" },
|
|
||||||
{ "slug": "address", "type": "text", "label": "Address" },
|
|
||||||
{ "slug": "facebook", "type": "url", "label": "Facebook URL" },
|
|
||||||
{ "slug": "line", "type": "url", "label": "LINE URL" },
|
|
||||||
{ "slug": "linkedin", "type": "url", "label": "LinkedIn URL" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"content": {
|
|
||||||
"pages": [
|
|
||||||
{
|
|
||||||
"id": "page-home",
|
|
||||||
"slug": "home",
|
|
||||||
"data": {
|
|
||||||
"title": "เปลี่ยนธุรกิจของคุณ ด้วย AI และเทคโนโลยีสมัยใหม่",
|
|
||||||
"subtitle": "รับทำเว็บไซต์ SEO AI Chatbot สำหรับธุรกิจไทย<br>เพิ่มยอดขาย ลดต้นทุน ด้วยเทคโนโลยีล้ำสมัย",
|
|
||||||
"badge": "ดิจิทัลเอเจนซี่ในประเทศไทย",
|
|
||||||
"hero_image": "/images/hero/hero.jpg",
|
|
||||||
"theme": "yellow",
|
|
||||||
"variant": "split",
|
|
||||||
"size": "full",
|
|
||||||
"show_cta": true,
|
|
||||||
"cta_text": "เริ่มต้นวันนี้",
|
|
||||||
"cta_link": "/contact"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "page-about",
|
|
||||||
"slug": "about",
|
|
||||||
"data": {
|
|
||||||
"title": "เกี่ยวกับ มอร์มินิมอร์",
|
|
||||||
"subtitle": "บริษัท มอร์มินิมอร์ จำกัด<br>รับทำเว็บไซต์ SEO AI Chatbot สำหรับธุรกิจไทย",
|
|
||||||
"badge": "เกี่ยวกับเรา",
|
|
||||||
"hero_image": "/images/hero/about.jpg",
|
|
||||||
"theme": "yellow",
|
|
||||||
"variant": "centered",
|
|
||||||
"size": "compact",
|
|
||||||
"show_cta": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "page-portfolio",
|
|
||||||
"slug": "portfolio",
|
|
||||||
"data": {
|
|
||||||
"title": "ผลงานของเรา",
|
|
||||||
"subtitle": "รวมผลงานพัฒนาเว็บไซต์และโปรเจกต์ที่เราภาคภูมิใจ",
|
|
||||||
"badge": "พอร์ตโฟลิโอ",
|
|
||||||
"hero_image": "/images/hero/hero.jpg",
|
|
||||||
"theme": "yellow",
|
|
||||||
"variant": "text-only",
|
|
||||||
"size": "compact",
|
|
||||||
"show_cta": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "page-contact",
|
|
||||||
"slug": "contact",
|
|
||||||
"data": {
|
|
||||||
"title": "พูดคุยกับเรา วันนี้เลย!",
|
|
||||||
"subtitle": "พร้อมให้คำปรึกษาและช่วยเหลือคุณ<br>ปรึกษาฟรี ไม่มีค่าใช้จ่าย",
|
|
||||||
"badge": "ติดต่อเรา",
|
|
||||||
"hero_image": "/images/hero/contact.jpg",
|
|
||||||
"theme": "yellow",
|
|
||||||
"variant": "text-only",
|
|
||||||
"size": "compact",
|
|
||||||
"show_cta": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "page-faq",
|
|
||||||
"slug": "faq",
|
|
||||||
"data": {
|
|
||||||
"title": "คำถามที่พบบ่อย",
|
|
||||||
"subtitle": "หาคำตอบสำหรับคำถามที่พบบ่อยเกี่ยวกับบริการของเรา",
|
|
||||||
"badge": "FAQ",
|
|
||||||
"hero_image": "/images/hero/tech-consult.jpg",
|
|
||||||
"theme": "yellow",
|
|
||||||
"variant": "centered",
|
|
||||||
"size": "compact",
|
|
||||||
"show_cta": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "page-privacy",
|
|
||||||
"slug": "privacy",
|
|
||||||
"data": {
|
|
||||||
"title": "นโยบายความเป็นส่วนตัว",
|
|
||||||
"subtitle": "มีผลบังคับใช้วันที่ 5 พฤษภาคม 2569",
|
|
||||||
"badge": "กฎหมาย",
|
|
||||||
"hero_image": "/images/hero/ai-automation.jpg",
|
|
||||||
"theme": "yellow",
|
|
||||||
"variant": "text-only",
|
|
||||||
"size": "compact",
|
|
||||||
"show_cta": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "page-terms",
|
|
||||||
"slug": "terms",
|
|
||||||
"data": {
|
|
||||||
"title": "เงื่อนไขการให้บริการ",
|
|
||||||
"subtitle": "มีผลบังคับใช้วันที่ 5 พฤษภาคม 2569",
|
|
||||||
"badge": "กฎหมาย",
|
|
||||||
"hero_image": "/images/hero/marketing-automation.jpg",
|
|
||||||
"theme": "yellow",
|
|
||||||
"variant": "text-only",
|
|
||||||
"size": "compact",
|
|
||||||
"show_cta": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"services": [
|
|
||||||
{
|
|
||||||
"id": "service-webdev",
|
|
||||||
"slug": "webdev",
|
|
||||||
"data": {
|
|
||||||
"title": "พัฒนาเว็บไซต์",
|
|
||||||
"subtitle": "สร้างเว็บไซต์ที่ทันสมัย รวดเร็ว และตอบสนองความต้องการของลูกค้าของคุณ ด้วยเทคโนโลยีล่าสุดและ design ที่สวยงาม",
|
|
||||||
"badge": "บริการ",
|
|
||||||
"hero_image": "/images/hero/web-development-hero.jpg",
|
|
||||||
"feature1_icon": "⚡",
|
|
||||||
"feature1_title": "เร็วสุดใน class",
|
|
||||||
"feature1_desc": "โหลดเร็ว เพิ่ม conversion และ SEO ranking",
|
|
||||||
"feature2_icon": "📱",
|
|
||||||
"feature2_title": "Responsive ทุก device",
|
|
||||||
"feature2_desc": "แสดงผลได้ดีบน mobile tablet และ desktop",
|
|
||||||
"feature3_icon": "🎨",
|
|
||||||
"feature3_title": "Design สวยงาม",
|
|
||||||
"feature3_desc": "ออกแบบ UI/UX ที่ดึงดูดและใช้งานง่าย",
|
|
||||||
"feature4_icon": "🔒",
|
|
||||||
"feature4_title": "ปลอดภัย",
|
|
||||||
"feature4_desc": "Security ระดับสูง ป้องกันการโจมตี",
|
|
||||||
"feature5_icon": "🔍",
|
|
||||||
"feature5_title": "SEO Optimized",
|
|
||||||
"feature5_desc": "ออกแบบมาเพื่อให้ Google ชอบ",
|
|
||||||
"feature6_icon": "📊",
|
|
||||||
"feature6_title": "Analytics ในตัว",
|
|
||||||
"feature6_desc": "ติดตามผู้เข้าชมและวัดผลได้"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "service-marketing",
|
|
||||||
"slug": "marketing",
|
|
||||||
"data": {
|
|
||||||
"title": "Marketing Automation",
|
|
||||||
"subtitle": "เพิ่มประสิทธิภาพการตลาดและลดต้นทุนด้วยระบบอัตโนมัติ ประหยัดเวลา เพิ่ม conversion วัดผลได้",
|
|
||||||
"badge": "บริการ",
|
|
||||||
"hero_image": "/images/hero/marketing-automation-hero.jpg",
|
|
||||||
"feature1_icon": "📧",
|
|
||||||
"feature1_title": "Email Automation",
|
|
||||||
"feature1_desc": "ส่ง email อัตโนมัติเมื่อ trigger ตรงกับเงื่อนไขที่กำหนด",
|
|
||||||
"feature2_icon": "💬",
|
|
||||||
"feature2_title": "Chatbot Integration",
|
|
||||||
"feature2_desc": "เชื่อมต่อ chatbot กับระบบ CRM เพื่อเก็บข้อมูลลูกค้า",
|
|
||||||
"feature3_icon": "📱",
|
|
||||||
"feature3_title": "SMS/Line Automation",
|
|
||||||
"feature3_desc": "ส่ง SMS หรือ LINE message อัตโนมัติตาม timeline",
|
|
||||||
"feature4_icon": "📊",
|
|
||||||
"feature4_title": "Analytics Dashboard",
|
|
||||||
"feature4_desc": "ติดตามผลแคมเปญและวัด ROI ได้แบบ real-time",
|
|
||||||
"feature5_icon": "🎯",
|
|
||||||
"feature5_title": "Lead Scoring",
|
|
||||||
"feature5_desc": "ให้คะแนน leads ตามพฤติกรรมเพื่อจัดลำดับความสำคัญ",
|
|
||||||
"feature6_icon": "🔄",
|
|
||||||
"feature6_title": "Workflow Automation",
|
|
||||||
"feature6_desc": "สร้าง workflow อัตโนมัติสำหรับงานที่ทำซ้ำๆ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "service-ai",
|
|
||||||
"slug": "ai",
|
|
||||||
"data": {
|
|
||||||
"title": "AI Automation",
|
|
||||||
"subtitle": "นำ AI มาใช้เพื่อเพิ่มยอดขายและปรับปรุงการให้บริการลูกค้า เพิ่มประสิทธิภาพ ลดต้นทุน ด้วยเทคโนโลยีล้ำสมัย",
|
|
||||||
"badge": "บริการ",
|
|
||||||
"hero_image": "/images/hero/ai-automation-hero.jpg",
|
|
||||||
"feature1_icon": "🤖",
|
|
||||||
"feature1_title": "AI Chatbot",
|
|
||||||
"feature1_desc": "Chatbot ที่ใช้ AI ตอบคำถามลูกค้าได้ 24/7 รองรับภาษาไทย",
|
|
||||||
"feature2_icon": "💬",
|
|
||||||
"feature2_title": "AI Customer Service",
|
|
||||||
"feature2_desc": "ระบบตอบคำถามอัตโนมัติด้วย AI ที่เข้าใจบริบท",
|
|
||||||
"feature3_icon": "📝",
|
|
||||||
"feature3_title": "AI Content Generation",
|
|
||||||
"feature3_desc": "สร้าง content อัตโนมัติด้วย AI ที่ SEO friendly",
|
|
||||||
"feature4_icon": "📧",
|
|
||||||
"feature4_title": "AI Email Marketing",
|
|
||||||
"feature4_desc": "ส่ง email ที่ personalized ด้วย AI ที่วิเคราะห์พฤติกรรม",
|
|
||||||
"feature5_icon": "📊",
|
|
||||||
"feature5_title": "AI Sales Prediction",
|
|
||||||
"feature5_desc": "ทำนายยอดขายและวางแผนการตลาดด้วย AI",
|
|
||||||
"feature6_icon": "🎯",
|
|
||||||
"feature6_title": "AI Lead Scoring",
|
|
||||||
"feature6_desc": "ให้คะแนน leads อัตโนมัติด้วย AI ที่เรียนรู้จากข้อมูล"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "service-consult",
|
|
||||||
"slug": "consult",
|
|
||||||
"data": {
|
|
||||||
"title": "Tech Consult",
|
|
||||||
"subtitle": "ให้คำปรึกษาด้านเทคโนโลยีเพื่อธุรกิจของคุณ วางแผน ออกแบบ และ implement ระบบที่เหมาะสม",
|
|
||||||
"badge": "บริการ",
|
|
||||||
"hero_image": "/images/hero/tech-consult-hero.jpg",
|
|
||||||
"feature1_icon": "🔍",
|
|
||||||
"feature1_title": "Digital Audit",
|
|
||||||
"feature1_desc": "วิเคราะห์สถานะดิจิทัลปัจจุบันของธุรกิจคุณอย่างละเอียด",
|
|
||||||
"feature2_icon": "📋",
|
|
||||||
"feature2_title": "Digital Strategy",
|
|
||||||
"feature2_desc": "วางแผน digital transformation ที่เหมาะกับเป้าหมายธุรกิจ",
|
|
||||||
"feature3_icon": "🛠️",
|
|
||||||
"feature3_title": "Technology Selection",
|
|
||||||
"feature3_desc": "แนะนำเทคโนโลยีที่เหมาะสมกับ budget และความต้องการ",
|
|
||||||
"feature4_icon": "📐",
|
|
||||||
"feature4_title": "System Architecture",
|
|
||||||
"feature4_desc": "ออกแบบระบบที่ scalable และ maintainable",
|
|
||||||
"feature5_icon": "🔧",
|
|
||||||
"feature5_title": "Implementation Support",
|
|
||||||
"feature5_desc": "ดูแลและให้คำปรึกษาตลอดการ implement",
|
|
||||||
"feature6_icon": "📈",
|
|
||||||
"feature6_title": "Performance Optimization",
|
|
||||||
"feature6_desc": "ปรับปรุงประสิทธิภาพระบบให้ดีขึ้นอย่างต่อเนื่อง"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"portfolio": [
|
|
||||||
{
|
|
||||||
"id": "portfolio-001",
|
|
||||||
"slug": "lungfinler",
|
|
||||||
"data": {
|
|
||||||
"name": "Lungfinler",
|
|
||||||
"url": "https://lungfinler.com",
|
|
||||||
"category": "webdev",
|
|
||||||
"category_label": "พัฒนาเว็บไซต์",
|
|
||||||
"thumbnail": "/images/portfolio/lungfinler.png",
|
|
||||||
"description": "Digital Agency - บริการด้านการสร้างแบรนด์ กราฟิกดีไซน์ และถ่ายภาพสินค้าคุณภาพสูง"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "portfolio-002",
|
|
||||||
"slug": "jetindustries",
|
|
||||||
"data": {
|
|
||||||
"name": "Jet Industries",
|
|
||||||
"url": "https://jetindustries.co.th",
|
|
||||||
"category": "webdev",
|
|
||||||
"category_label": "พัฒนาเว็บไซต์",
|
|
||||||
"thumbnail": "/images/portfolio/jetindustries.png",
|
|
||||||
"description": "ผู้ผลิตพลาสติกฉีดขึ้นรูปอย่างแม่นยำ (Precision Plastic Injection Molding) มีประสบการณ์กว่า 40 ปี"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "portfolio-003",
|
|
||||||
"slug": "lawyernoom",
|
|
||||||
"data": {
|
|
||||||
"name": "สำนักงานกฎหมาย ตถาตา",
|
|
||||||
"url": "https://lawyernoom.com",
|
|
||||||
"category": "webdev",
|
|
||||||
"category_label": "พัฒนาเว็บไซต์",
|
|
||||||
"thumbnail": "/images/portfolio/lawyernoom.png",
|
|
||||||
"description": "สำนักงานกฎหมายโดย ทนายความ คมสัน ศรีวนิชย์ - บริการด้านคดีความ คดีแพ่ง คดีอาญา"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "portfolio-004",
|
|
||||||
"slug": "underdog",
|
|
||||||
"data": {
|
|
||||||
"name": "Underdog Marketing",
|
|
||||||
"url": "https://underdog.run",
|
|
||||||
"category": "webdev",
|
|
||||||
"category_label": "พัฒนาเว็บไซต์",
|
|
||||||
"thumbnail": "/images/portfolio/underdog.png",
|
|
||||||
"description": "บล็อกการตลาดและการขายสไตล์ ลุยไม่ยั้ง โดย บุ้ง ดีดติ่งหู"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "portfolio-005",
|
|
||||||
"slug": "baofuling",
|
|
||||||
"data": {
|
|
||||||
"name": "Baofuling Shop",
|
|
||||||
"url": "https://baofulingshop.com",
|
|
||||||
"category": "ecommerce",
|
|
||||||
"category_label": "อีคอมเมิร์ซ",
|
|
||||||
"thumbnail": "/images/portfolio/baofuling.png",
|
|
||||||
"description": "ร้านค้าออนไลน์ครีมบัวหิมะและผลิตภัณฑ์ความงามจีน"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "portfolio-006",
|
|
||||||
"slug": "trainersunny",
|
|
||||||
"data": {
|
|
||||||
"name": "เทรนเนอร์ซันนี่",
|
|
||||||
"url": "https://trainersunny.com",
|
|
||||||
"category": "webdev",
|
|
||||||
"category_label": "พัฒนาเว็บไซต์",
|
|
||||||
"thumbnail": "/images/portfolio/trainersunny.png",
|
|
||||||
"description": "ผู้เชี่ยวชาญด้านการพัฒนาบุคลากรและ Soft Skill"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "portfolio-007",
|
|
||||||
"slug": "luadjob",
|
|
||||||
"data": {
|
|
||||||
"name": "เลือดจระเข้วานิไทย",
|
|
||||||
"url": "https://เลือดจระเข้วานิไทย.com",
|
|
||||||
"category": "ecommerce",
|
|
||||||
"category_label": "อีคอมเมิร์ซ",
|
|
||||||
"thumbnail": "/images/portfolio/luadjob.png",
|
|
||||||
"description": "ตัวแทนจำหน่ายเลือดจระเข้วานิไทยอย่างเป็นทางการ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "portfolio-008",
|
|
||||||
"slug": "tuanthong",
|
|
||||||
"data": {
|
|
||||||
"name": "ทวนทอง 99",
|
|
||||||
"url": "https://tuanthong99.com",
|
|
||||||
"category": "ecommerce",
|
|
||||||
"category_label": "อีคอมเมิร์ซ",
|
|
||||||
"thumbnail": "/images/portfolio/tuanthong.png",
|
|
||||||
"description": "ร้านค้าออนไลน์สมุนไพรไทยคุณภาพสูง"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "portfolio-009",
|
|
||||||
"slug": "odooportal",
|
|
||||||
"data": {
|
|
||||||
"name": "Odoo Portal",
|
|
||||||
"url": "https://odooportal.com",
|
|
||||||
"category": "marketing",
|
|
||||||
"category_label": "ที่ปรึกษาการตลาด",
|
|
||||||
"thumbnail": "/images/portfolio/odooportal.png",
|
|
||||||
"description": "ตัวแทนจำหน่าย Odoo อย่างเป็นทางการในประเทศไทย"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"faq": [
|
|
||||||
{
|
|
||||||
"id": "faq-001",
|
|
||||||
"slug": "service-001",
|
|
||||||
"data": { "category": "บริการ", "category_icon": "🎯", "question": "MoreminiMore ให้บริการอะไรบ้าง?", "answer": "เราให้บริการ 4 ประเภทหลัก: พัฒนาเว็บไซต์, Marketing Automation, AI Automation และ Tech Consult สำหรับธุรกิจไทย" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "faq-002",
|
|
||||||
"slug": "service-002",
|
|
||||||
"data": { "category": "บริการ", "category_icon": "🎯", "question": "สามารถสั่งทำเว็บไซต์เฉพาะประเภทได้ไหม?", "answer": "ได้ เราสามารถพัฒนาเว็บไซต์ได้ทุกประเภท ไม่ว่าจะเป็นเว็บบริษัท เว็บขายของ เว็บ Landing Page หรือระบบ Web Application" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "faq-003",
|
|
||||||
"slug": "service-003",
|
|
||||||
"data": { "category": "บริการ", "category_icon": "🎯", "question": "AI Chatbot สามารถทำอะไรได้บ้าง?", "answer": "AI Chatbot ของเราสามารถตอบคำถามลูกค้า รับออร์เดอร์ นัดหมาย และเชื่อมต่อกับระบบ CRM หรือร้านค้าออนไลน์ได้" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "faq-004",
|
|
||||||
"slug": "price-001",
|
|
||||||
"data": { "category": "ราคา", "category_icon": "💰", "question": "ราคาเริ่มต้นของการทำเว็บไซต์เท่าไหร่?", "answer": "ราคาเริ่มต้นอยู่ที่ 15,000 บาท ขึ้นอยู่กับความซับซ้อนและฟีเจอร์ที่ต้องการ เราจะประเมินและเสนอราคาหลังจากการปรึกษาฟรี" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "faq-005",
|
|
||||||
"slug": "price-002",
|
|
||||||
"data": { "category": "ราคา", "category_icon": "💰", "question": "มีราคาแพ็คเกจหรือไม่?", "answer": "มี เรามีแพ็คเกจสำหรับธุรกิจที่ต้องการเริ่มต้นอย่างง่ายๆ และแพ็คเกจสำหรับธุรกิจที่ต้องการระบบครบวงจร สามารถเลือกได้ตามความต้องการ" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "faq-006",
|
|
||||||
"slug": "price-003",
|
|
||||||
"data": { "category": "ราคา", "category_icon": "💰", "question": "ชำระเงินอย่างไร?", "answer": "รองรับการชำระเงินผ่านโอนเงินธนาคาร หรือผ่อนชำระผ่านบัตรเครดิต 3-6 งวด (มีดอกเบี้ย)" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "faq-007",
|
|
||||||
"slug": "time-001",
|
|
||||||
"data": { "category": "ระยะเวลา", "category_icon": "⏱️", "question": "ใช้เวลาพัฒนานานเท่าไหร่?", "answer": "ขึ้นอยู่กับความซับซ้อน Landing Page ใช้เวลา 1-2 สัปดาห์ เว็บไซต์ขนาดกลาง 2-4 สัปดาห์ ระบบ Web Application 4-8 สัปดาห์" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "faq-008",
|
|
||||||
"slug": "time-002",
|
|
||||||
"data": { "category": "ระยะเวลา", "category_icon": "⏱️", "question": "ถ้าต้องการด่วนได้ไหม?", "answer": "ได้ เรามีบริการด่วนพิเศษ (เพิ่มค่าใช้จ่าย 30%) สามารถส่งมอบงานได้เร็วขึ้น 50%" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "faq-009",
|
|
||||||
"slug": "support-001",
|
|
||||||
"data": { "category": "หลังการขาย", "category_icon": "💬", "question": "มีการรับประกันหรือไม่?", "answer": "เรารับประกันงาน 30 วันหลังส่งมอบ หากมีปัญหาจากการพัฒนา เราจะแก้ไขให้ฟรี" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "faq-010",
|
|
||||||
"slug": "support-002",
|
|
||||||
"data": { "category": "หลังการขาย", "category_icon": "💬", "question": "มีบริการดูแลหลังการขายไหม?", "answer": "มี เรามีแพ็คเกจดูแลรายเดือนเริ่มต้นที่ 2,000 บาท/เดือน รวมการอัพเดทเนื้อหา ปรับปรุงความปลอดภัย และ Backup" }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"blog": [
|
|
||||||
{
|
|
||||||
"id": "post-001",
|
|
||||||
"slug": "5-ways-ai-increase-sales",
|
|
||||||
"data": {
|
|
||||||
"title": "5 วิธีใช้ AI เพิ่มยอดขายให้ธุรกิจของคุณ",
|
|
||||||
"excerpt": "ค้นพบ 5 วิธีที่ AI สามารถช่วยเพิ่มยอดขายให้ธุรกิจ SMEs ไทย พร้อมตัวอย่างและแนวทางการนำไปใช้จริง",
|
|
||||||
"image": "/images/blog/5-ways-ai-increase-sales.jpg",
|
|
||||||
"date": "2026-03-11",
|
|
||||||
"category": "AI Business",
|
|
||||||
"content": {
|
|
||||||
"type": "doc",
|
|
||||||
"content": [
|
|
||||||
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "วิธีที่ 1: ใช้ AI วิเคราะห์ลูกค้าและแนะนำสินค้าที่ตรงใจ" }] },
|
|
||||||
{ "type": "paragraph", "content": [{ "type": "text", "text": "หนึ่งในความสามารถที่ทรงพลังที่สุดของ AI คือการวิเคราะห์ข้อมูลลูกค้าและค้นหารูปแบบที่มนุษย์อาจมองไม่เห็น AI สามารถวิเคราะห์ว่าลูกค้าแต่ละคนชอบสินค้าประเภทไหน ซื้อในช่วงเวลาไหน และมีพฤติกรรมการซื้ออย่างไร" }] },
|
|
||||||
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "วิธีที่ 2: ใช้ Chatbot ดูแลลูกค้าตลอด 24 ชั่วโมง" }] },
|
|
||||||
{ "type": "paragraph", "content": [{ "type": "text", "text": "ลูกค้าจำนวนมากต้องการได้รับคำตอบทันที ไม่ว่าจะกี่โมง แต่การจ้างพนักงานทำงาน 24 ชั่วโมงนั้นมีค่าใช้จ่ายสูง Chatbot ที่ใช้ AI สามารถตอบคำถามลูกค้าได้ตลอดเวลา โดยไม่ต้องเสียค่าล่วงเวลา" }] },
|
|
||||||
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "วิธีที่ 3: ใช้ AI ส่งข้อความการตลาดในเวลาที่เหมาะสม" }] },
|
|
||||||
{ "type": "paragraph", "content": [{ "type": "text", "text": "การส่งข้อความการตลาดไม่ใช่แค่การส่งออกไปเท่านั้น แต่ต้องส่งในเวลาที่ลูกค้ามีโอกาสอ่านและตอบสนองมากที่สุด AI สามารถวิเคราะห์ว่าลูกค้าแต่ละคนมีช่วงเวลาไหนที่เปิดอ่านข้อความบ่อยที่สุด และส่งในเวลานั้น" }] },
|
|
||||||
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "วิธีที่ 4: ใช้ AI สร้างเนื้อหาการตลาด" }] },
|
|
||||||
{ "type": "paragraph", "content": [{ "type": "text", "text": "เนื้อหาการตลาดที่ดีเป็นหัวใจสำคัญในการดึงดูดลูกค้า แต่การสร้างเนื้อหาที่มีคุณภาพตลอดเวลาต้องใช้เวลาและทรัพยากรมาก AI สามารถช่วยสร้างเนื้อหาได้เร็วขึ้น ไม่ว่าจะเป็นโพสต์เฟซบุ๊ก คำบรรยายสินค้า อีเมลการตลาด หรือบทความบล็อก" }] },
|
|
||||||
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "วิธีที่ 5: ใช้ AI ทำนายแนวโน้มและวางแผนสินค้า" }] },
|
|
||||||
{ "type": "paragraph", "content": [{ "type": "text", "text": "การมีสินค้าคงคลงเป็นสิ่งจำเป็นสำหรับธุรกิจค้าปลีก แต่การมีสินค้ามากเกินไปก็เป็นปัญหา AI สามารถวิเคราะห์ข้อมูลยอดขายในอดีต ฤดูกาล และปัจจัยอื่นๆ เพื่อทำนายว่าควรสั่งสินค้าเท่าไหร่ในแต่ละช่วง" }] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "post-002",
|
|
||||||
"slug": "ai-content-google-love",
|
|
||||||
"data": {
|
|
||||||
"title": "วิธีสร้าง Content ด้วย AI ที่ Google รัก",
|
|
||||||
"excerpt": "เรียนรู้วิธีการใช้ AI ช่วยสร้างเนื้อหาการตลาดที่มีคุณภาพและได้รับการจัดอันดับดีจาก Google พร้อมเทคนิคและตัวอย่างจริง",
|
|
||||||
"image": "/images/blog/ai-content-google-love.jpg",
|
|
||||||
"date": "2026-03-10",
|
|
||||||
"category": "AI Content",
|
|
||||||
"content": {
|
|
||||||
"type": "doc",
|
|
||||||
"content": [
|
|
||||||
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "ทำไม AI Content ต้องมีคุณภาพ" }] },
|
|
||||||
{ "type": "paragraph", "content": [{ "type": "text", "text": "Google มีอัลกอริทึมที่ฉลาดมาก สามารถแยกแยะได้ว่าเนื้อหาที่สร้างโดย AI มีคุณภาพหรือไม่ เนื้อหาที่ไม่มีคุณค่า สร้างขึ้นแค่เพื่อใส่คีย์เวิร์ด จะถูกลงโทษและไม่ติดอันดับ" }] },
|
|
||||||
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "วิธีใช้ AI สร้างเนื้อหาที่ดี" }] },
|
|
||||||
{ "type": "paragraph", "content": [{ "type": "text", "text": "ใช้ AI เป็นผู้ช่วย ไม่ใช่ผู้เขียนทั้งหมด วิธีที่ดีที่สุดคือใช้ AI ช่วยในบางส่วน เช่น รวบรวมข้อมูล สร้างโครงสร้าง หรือเขียน draft แล้วนำมาปรับแก้ด้วยมนุษย์" }] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "post-003",
|
|
||||||
"slug": "ai-for-sme-thailand",
|
|
||||||
"data": {
|
|
||||||
"title": "AI สำหรับ SME ไทย: คู่มือฉบับสมบูรณ์",
|
|
||||||
"excerpt": "การใช้ AI สำหรับธุรกิจ SME ไทยเพื่อเพิ่มขีดความสามารถในการแข่งขัน พร้อมแนวทางปฏิบัติจริง",
|
|
||||||
"image": "/images/blog/ai-for-sme-thailand.jpg",
|
|
||||||
"date": "2026-03-08",
|
|
||||||
"category": "AI Business",
|
|
||||||
"content": {
|
|
||||||
"type": "doc",
|
|
||||||
"content": [
|
|
||||||
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "ทำไม SME ต้องใช้ AI" }] },
|
|
||||||
{ "type": "paragraph", "content": [{ "type": "text", "text": "AI ไม่ใช่เทคโนโลยีสำหรับบริษัทใหญ่เท่านั้น ธุรกิจ SME สามารถเริ่มใช้ประโยชน์จาก AI ได้ง่ายๆ ด้วยเครื่องมือที่เข้าถึงได้" }] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "post-004",
|
|
||||||
"slug": "digital-transformation-guide",
|
|
||||||
"data": {
|
|
||||||
"title": "คู่มือ Digital Transformation ฉบับสมบูรณ์",
|
|
||||||
"excerpt": "การ transform ธุรกิจของคุณสู่ดิจิทัลอย่างครบวงจร ตั้งแต่การวางแผนจนถึงการ Implement",
|
|
||||||
"image": "/images/blog/digital-transformation.jpg",
|
|
||||||
"date": "2026-03-05",
|
|
||||||
"category": "Business",
|
|
||||||
"content": {
|
|
||||||
"type": "doc",
|
|
||||||
"content": [
|
|
||||||
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "Digital Transformation คืออะไร" }] },
|
|
||||||
{ "type": "paragraph", "content": [{ "type": "text", "text": "Digital Transformation คือการนำเทคโนโลยีดิจิทัลมาใช้ในทุกด้านของธุรกิจ เพื่อเพิ่มประสิทธิภาพและสร้างคุณค่าใหม่ให้กับลูกค้า" }] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "post-005",
|
|
||||||
"slug": "marketing-automation-guide",
|
|
||||||
"data": {
|
|
||||||
"title": "Marketing Automation: คู่มือเริ่มต้น",
|
|
||||||
"excerpt": "เรียนรู้พื้นฐานของ Marketing Automation และวิธีเริ่มต้นใช้งานสำหรับธุรกิจของคุณ",
|
|
||||||
"image": "/images/blog/marketing-automation-guide.jpg",
|
|
||||||
"date": "2026-03-01",
|
|
||||||
"category": "Marketing",
|
|
||||||
"content": {
|
|
||||||
"type": "doc",
|
|
||||||
"content": [
|
|
||||||
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "Marketing Automation คืออะไร" }] },
|
|
||||||
{ "type": "paragraph", "content": [{ "type": "text", "text": "Marketing Automation คือการใช้ซอฟต์แวร์เพื่อ automate การตลาดซ้ำๆ เช่น การส่งอีเมล การโพสต์โซเชียลมีเดีย และการวิเคราะห์ข้อมูล" }] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
4
.gitignore
vendored
@@ -22,3 +22,7 @@ pnpm-debug.log*
|
|||||||
|
|
||||||
# jetbrains setting folder
|
# jetbrains setting folder
|
||||||
.idea/
|
.idea/
|
||||||
|
.hermes/
|
||||||
|
|
||||||
|
# archive (large local files)
|
||||||
|
_archive/
|
||||||
|
|||||||
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# ── MoreminiMore — Astro + Express + SES ─────────────────────────
|
||||||
|
# Build: npm run build → Runtime: node server.js (port 4321)
|
||||||
|
#
|
||||||
|
# Env vars:
|
||||||
|
# SES_ACCESS_KEY_ID (optional — SES email sending)
|
||||||
|
# SES_SECRET_ACCESS_KEY (optional)
|
||||||
|
# SES_REGION (default: ap-southeast-1)
|
||||||
|
# PORT (default: 4321)
|
||||||
|
|
||||||
|
FROM node:22-bookworm-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# ── Dependencies ──────────────────────────────────────────────────
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# ── Source + Build ───────────────────────────────────────────────
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ── Prune build-only deps (astro is ~200MB) ──────────────────────
|
||||||
|
# Keep: express, cors, nodemailer, @aws-sdk/client-ses
|
||||||
|
RUN npm prune --omit=dev 2>/dev/null; \
|
||||||
|
npm ls --depth=0 2>/dev/null || true
|
||||||
|
|
||||||
|
# ── Runtime ──────────────────────────────────────────────────────
|
||||||
|
EXPOSE 4321
|
||||||
|
CMD ["node", "server.js"]
|
||||||
22
PLAN-REVIEW-LOG.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Plan Review Log: Footer Component + Remove Yellow Lines from Process Section
|
||||||
|
Act 1 (grill) complete — plan locked with the user. MAX_ROUNDS=5.
|
||||||
|
|
||||||
|
## Round 1 — Codex
|
||||||
|
พบ 10 issues (3 critical, 3 significant, 4 minor):
|
||||||
|
1. 🔴 PageShell layout ignored — ควรแก้ผ่าน PageShell
|
||||||
|
2. 🔴 Privacy/Terms pages ไม่มี — dead links
|
||||||
|
3. 🔴 Liquid glass markup pattern ไม่ระบุ
|
||||||
|
4. 🟡 service-proof-grid yellow accent missed
|
||||||
|
5. 🟡 Mobile layout spec ambiguous
|
||||||
|
6. 🟡 Step-flow visual cue removed without alternative
|
||||||
|
7. 🟡 Missing semantic `<footer>` element
|
||||||
|
8. 🟠 LINE URL unspecified
|
||||||
|
9. 🟠 Logo dimensions omitted
|
||||||
|
10. 🟠 z-index risk unresolved
|
||||||
|
|
||||||
|
### Claude's response
|
||||||
|
แก้ทั้ง 10 ข้อใน PLAN.md revision
|
||||||
|
|
||||||
|
## Round 2 — Codex
|
||||||
|
All 10 findings addressed. Minor note: footer ควรวางระหว่าง `</main>` กับ floating-cta (ไม่ใช่ inside `<main>`).
|
||||||
|
VERDICT: APPROVED (2 rounds)
|
||||||
71
PLAN.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Plan: Footer Component + Remove Yellow Lines from Process Section
|
||||||
|
_Locked via grill — by Claude + Kunthawat — Revised Round 1_
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
สร้าง Footer component ใหม่ด้วย liquid glass style และลบเส้นสีเหลืองออกจาก How we work section
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
### 1. ลบเส้นสีเหลืองจาก Process Section (`src/styles/global.css`)
|
||||||
|
- ลบ `.process-grid::before` (เส้นแนวนอนสีเหลือง) → `display: none`
|
||||||
|
- ลบ `.process-grid article::after` (วงกลม ">" สีเหลืองระหว่าง step) → `display: none`
|
||||||
|
- `.process-grid .step-number`: เปลี่ยน `background: var(--yellow)` → `background: rgb(255 255 255 / .5)` + ลบ yellow box-shadow
|
||||||
|
- `.service-proof-grid .process-grid .step-number`: override สีเหลืองเหมือนกัน (scope เพิ่ม)
|
||||||
|
- **หมายเหตุ:** step numbers (01, 02, 03, 04) ยังคงแสดง sequential flow อยู่แล้ว ไม่ต้องเพิ่ม connector แทน
|
||||||
|
|
||||||
|
### 2. สร้าง Footer Component (`src/components/Footer.astro`)
|
||||||
|
- ใช้ `<footer>` semantic element
|
||||||
|
- ใช้ liquid glass style (ต้องมี 3 child divs):
|
||||||
|
```html
|
||||||
|
<footer class="site-footer liquid-glass liquidGlass-wrapper">
|
||||||
|
<div class="liquidGlass-effect" aria-hidden="true"></div>
|
||||||
|
<div class="liquidGlass-tint" aria-hidden="true"></div>
|
||||||
|
<div class="liquidGlass-shine" aria-hidden="true"></div>
|
||||||
|
<!-- content -->
|
||||||
|
</footer>
|
||||||
|
```
|
||||||
|
- `position: relative; z-index: auto` (ไม่ทับ lead-panel z-index: 110)
|
||||||
|
|
||||||
|
### 3. เนื้อหา Footer
|
||||||
|
**ซ้าย:**
|
||||||
|
- โลโก้: `/images/logos/logo-long-black.png` (width="205" height="36")
|
||||||
|
- คำอธิบาย: "ที่ปรึกษาเว็บไซต์ การตลาด และ AI สำหรับ SME"
|
||||||
|
|
||||||
|
**กลาง:**
|
||||||
|
- ลิงก์: หน้าแรก / บริการ / ผลงาน / บล็อก / ติดต่อ / นโยบายความเป็นส่วนตัว / เงื่อนไขการใช้งาน
|
||||||
|
|
||||||
|
**ขวา:**
|
||||||
|
- LINE: @moreminimore (link: https://line.me/ti/p/@moreminimore)
|
||||||
|
- Email: contact@moreminimore.com
|
||||||
|
|
||||||
|
**ล่าง:**
|
||||||
|
- Copyright: © {new Date().getFullYear()} MoreminiMore
|
||||||
|
|
||||||
|
### 4. Responsive
|
||||||
|
- Desktop (>768px): 3 columns (grid-template-columns: 1fr 1fr 1fr)
|
||||||
|
- Mobile (≤768px): vertical stack (flex-direction: column) — เรียง: โลโก้ → ลิงก์ → ติดต่อ → copyright
|
||||||
|
|
||||||
|
### 5. Integration Strategy
|
||||||
|
- **PageShell pages** (about, services, blog, blog/[slug], contact, faq, portfolio, services/[slug]):
|
||||||
|
เพิ่ม `<Footer />` import + render ใน `src/components/PageShell.astro` ก่อน `</main>` หรือก่อน `</body>`
|
||||||
|
- **index.astro** (standalone, ไม่ใช้ PageShell):
|
||||||
|
เพิ่ม `<Footer />` import + render ก่อน `</body>` โดยตรง
|
||||||
|
|
||||||
|
### 6. สร้าง Placeholder Pages
|
||||||
|
- สร้าง `src/pages/privacy.astro` (placeholder — "นโยบายความเป็นส่วนตัว กำลังอัพเดท")
|
||||||
|
- สร้าง `src/pages/terms.astro` (placeholder — "เงื่อนไขการใช้งาน กำลังอัพเดท")
|
||||||
|
- ใช้ PageShell layout เหมือนหน้าอื่น
|
||||||
|
|
||||||
|
## Key decisions & tradeoffs
|
||||||
|
- ใช้ liquid glass style เดียวกับ navbar เพื่อความ cohesive
|
||||||
|
- ลบเส้นเหลืองทั้งหมด — step numbers ยังบ่งบอกลำดับอยู่
|
||||||
|
- ใช้ dynamic year (`new Date().getFullYear()`) ใน copyright (Astro SSG → build-time inlined)
|
||||||
|
- Footer ใน PageShell = แก้จุดเดียว ครอบคลุม 8 หน้า
|
||||||
|
- z-index: auto (ไม่ทับ lead-panel)
|
||||||
|
|
||||||
|
## Risks / open questions
|
||||||
|
- Privacy/Terms pages เป็น placeholder — ต้องเพิ่ม content ทีหลัง
|
||||||
|
- Logo อาจต้องเปลี่ยนเป็นสีขาวเมื่ออยู่บนพื้นเข้ม (invert)
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
- ไม่แก้ไขหน้า Privacy/Terms content จริง
|
||||||
|
- ไม่เพิ่ม social media icons (Facebook/X/LinkedIn)
|
||||||
43
README.md
@@ -1,43 +0,0 @@
|
|||||||
# Astro Starter Kit: Minimal
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm create astro@latest -- --template minimal
|
|
||||||
```
|
|
||||||
|
|
||||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
|
||||||
|
|
||||||
## 🚀 Project Structure
|
|
||||||
|
|
||||||
Inside of your Astro project, you'll see the following folders and files:
|
|
||||||
|
|
||||||
```text
|
|
||||||
/
|
|
||||||
├── public/
|
|
||||||
├── src/
|
|
||||||
│ └── pages/
|
|
||||||
│ └── index.astro
|
|
||||||
└── package.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
|
||||||
|
|
||||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
|
||||||
|
|
||||||
Any static assets, like images, can be placed in the `public/` directory.
|
|
||||||
|
|
||||||
## 🧞 Commands
|
|
||||||
|
|
||||||
All commands are run from the root of the project, from a terminal:
|
|
||||||
|
|
||||||
| Command | Action |
|
|
||||||
| :------------------------ | :----------------------------------------------- |
|
|
||||||
| `npm install` | Installs dependencies |
|
|
||||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
|
||||||
| `npm run build` | Build your production site to `./dist/` |
|
|
||||||
| `npm run preview` | Preview your build locally, before deploying |
|
|
||||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
|
||||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
|
||||||
|
|
||||||
## 👀 Want to learn more?
|
|
||||||
|
|
||||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
|
||||||
@@ -1,27 +1,9 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
import react from "@astrojs/react";
|
|
||||||
import mdx from "@astrojs/mdx";
|
|
||||||
import node from "@astrojs/node";
|
|
||||||
import emdash, { local } from "emdash/astro";
|
|
||||||
import { sqlite } from "emdash/db";
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
output: "server",
|
site: 'https://moreminimore.com',
|
||||||
adapter: node({
|
output: 'static',
|
||||||
mode: 'standalone'
|
image: { layout: 'constrained', responsiveStyles: true },
|
||||||
}),
|
devToolbar: { enabled: false },
|
||||||
integrations: [
|
});
|
||||||
react(),
|
|
||||||
mdx(),
|
|
||||||
emdash({
|
|
||||||
database: sqlite({ url: "file:./data.db" }),
|
|
||||||
storage: local({
|
|
||||||
directory: "./uploads",
|
|
||||||
baseUrl: "/_emdash/api/media/file",
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
image: { layout: "constrained", responsiveStyles: true },
|
|
||||||
devToolbar: { enabled: true },
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
const seed = require('./seed/seed.json');
|
|
||||||
const Database = require('better-sqlite3');
|
|
||||||
const db = new Database('./data.db');
|
|
||||||
|
|
||||||
const cols = ['title','subtitle','badge','hero_image',
|
|
||||||
'feature1_icon','feature1_title','feature1_desc',
|
|
||||||
'feature2_icon','feature2_title','feature2_desc',
|
|
||||||
'feature3_icon','feature3_title','feature3_desc',
|
|
||||||
'feature4_icon','feature4_title','feature4_desc',
|
|
||||||
'feature5_icon','feature5_title','feature5_desc',
|
|
||||||
'feature6_icon','feature6_title','feature6_desc'];
|
|
||||||
|
|
||||||
const placeholders = cols.map(() => '?').join(', ');
|
|
||||||
const sql = `INSERT OR REPLACE INTO ec_services (id, slug, status, ${cols.join(', ')}, created_at, updated_at, published_at) VALUES (?, ?, 'published', ${placeholders}, datetime('now'), datetime('now'), datetime('now'))`;
|
|
||||||
|
|
||||||
console.log('Columns:', 3 + cols.length); // id, slug, status + cols
|
|
||||||
console.log('SQL placeholders:', (sql.match(/\?/g) || []).length);
|
|
||||||
|
|
||||||
db.close();
|
|
||||||
BIN
data.db-shm
116
emdash-env.d.ts
vendored
@@ -1,116 +0,0 @@
|
|||||||
// Generated by EmDash on dev server start
|
|
||||||
// Do not edit manually
|
|
||||||
|
|
||||||
/// <reference types="emdash/locals" />
|
|
||||||
|
|
||||||
import type { ContentBylineCredit, PortableTextBlock } from "emdash";
|
|
||||||
|
|
||||||
export interface Blog {
|
|
||||||
id: string;
|
|
||||||
slug: string | null;
|
|
||||||
status: string;
|
|
||||||
title?: string;
|
|
||||||
excerpt?: string;
|
|
||||||
image?: { id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> };
|
|
||||||
date?: string;
|
|
||||||
category?: string;
|
|
||||||
content?: PortableTextBlock[];
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
publishedAt: Date | null;
|
|
||||||
bylines?: ContentBylineCredit[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Faq {
|
|
||||||
id: string;
|
|
||||||
slug: string | null;
|
|
||||||
status: string;
|
|
||||||
category?: string;
|
|
||||||
question?: string;
|
|
||||||
answer?: string;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
publishedAt: Date | null;
|
|
||||||
bylines?: ContentBylineCredit[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Page {
|
|
||||||
id: string;
|
|
||||||
slug: string | null;
|
|
||||||
status: string;
|
|
||||||
title?: string;
|
|
||||||
subtitle?: string;
|
|
||||||
badge?: string;
|
|
||||||
hero_image?: { id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> };
|
|
||||||
theme?: string;
|
|
||||||
show_cta?: boolean;
|
|
||||||
cta_text?: string;
|
|
||||||
cta_link?: string;
|
|
||||||
variant?: string;
|
|
||||||
size?: string;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
publishedAt: Date | null;
|
|
||||||
bylines?: ContentBylineCredit[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Portfolio {
|
|
||||||
id: string;
|
|
||||||
slug: string | null;
|
|
||||||
status: string;
|
|
||||||
name?: string;
|
|
||||||
url?: string;
|
|
||||||
category?: string;
|
|
||||||
category_label?: string;
|
|
||||||
thumbnail?: { id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> };
|
|
||||||
description?: string;
|
|
||||||
services?: unknown;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
publishedAt: Date | null;
|
|
||||||
bylines?: ContentBylineCredit[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Service {
|
|
||||||
id: string;
|
|
||||||
slug: string | null;
|
|
||||||
status: string;
|
|
||||||
title?: string;
|
|
||||||
subtitle?: string;
|
|
||||||
badge?: string;
|
|
||||||
hero_image?: { id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> };
|
|
||||||
content?: PortableTextBlock[];
|
|
||||||
features?: unknown;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
publishedAt: Date | null;
|
|
||||||
bylines?: ContentBylineCredit[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Setting {
|
|
||||||
id: string;
|
|
||||||
slug: string | null;
|
|
||||||
status: string;
|
|
||||||
site_name?: string;
|
|
||||||
email?: string;
|
|
||||||
phone?: string;
|
|
||||||
address?: string;
|
|
||||||
facebook?: string;
|
|
||||||
line?: string;
|
|
||||||
linkedin?: string;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
publishedAt: Date | null;
|
|
||||||
bylines?: ContentBylineCredit[];
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "emdash" {
|
|
||||||
interface EmDashCollections {
|
|
||||||
blog: Blog;
|
|
||||||
faq: Faq;
|
|
||||||
pages: Page;
|
|
||||||
portfolio: Portfolio;
|
|
||||||
services: Service;
|
|
||||||
settings: Setting;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
153
google-apps-script/SETUP.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# Google Apps Script Lead Form Setup
|
||||||
|
|
||||||
|
ใช้ไฟล์นี้เพื่อตั้งระบบรับ lead จากฟอร์ม MoreminiMore แบบง่ายก่อน โดยให้ Google Apps Script บันทึกข้อมูลลง Google Sheet และส่งอีเมลแจ้งเตือนเข้า Gmail/Google Workspace
|
||||||
|
|
||||||
|
อ้างอิง official docs:
|
||||||
|
|
||||||
|
- Apps Script Web App deployment: https://developers.google.com/apps-script/guides/web
|
||||||
|
- Apps Script MailApp: https://developers.google.com/apps-script/reference/mail/mail-app
|
||||||
|
|
||||||
|
## สิ่งที่ต้องมี
|
||||||
|
|
||||||
|
- Google account หรือ Google Workspace account ที่จะใช้รับ lead
|
||||||
|
- แนะนำให้ใช้บัญชีของโดเมนบริษัท เช่น `contact@moreminimore.com`
|
||||||
|
- Google Sheet ใหม่ 1 ไฟล์ สำหรับเก็บ lead
|
||||||
|
|
||||||
|
## ขั้นตอนติดตั้ง
|
||||||
|
|
||||||
|
### 1. สร้าง Google Sheet
|
||||||
|
|
||||||
|
1. เข้า Google Drive
|
||||||
|
2. สร้าง Google Sheet ใหม่
|
||||||
|
3. ตั้งชื่อเช่น `MoreminiMore Website Leads`
|
||||||
|
4. ไม่ต้องสร้าง column เอง script จะสร้าง header ให้ตอนมี lead แรก
|
||||||
|
|
||||||
|
### 2. เปิด Apps Script จาก Sheet
|
||||||
|
|
||||||
|
1. ใน Google Sheet ไปที่ `Extensions`
|
||||||
|
2. เลือก `Apps Script`
|
||||||
|
3. จะเปิดหน้า Apps Script editor
|
||||||
|
4. ลบโค้ดเดิมใน `Code.gs`
|
||||||
|
5. Copy โค้ดทั้งหมดจาก `google-apps-script/lead-form.gs`
|
||||||
|
6. Paste ลงใน `Code.gs`
|
||||||
|
|
||||||
|
### 3. แก้อีเมลผู้รับ
|
||||||
|
|
||||||
|
ในไฟล์ `Code.gs` หา:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const CONFIG = {
|
||||||
|
RECIPIENT_EMAIL: 'contact@moreminimore.com',
|
||||||
|
```
|
||||||
|
|
||||||
|
เปลี่ยน `RECIPIENT_EMAIL` เป็นอีเมลที่จะรับแจ้งเตือน lead
|
||||||
|
|
||||||
|
ถ้าใช้ `contact@moreminimore.com` อยู่แล้ว ไม่ต้องแก้
|
||||||
|
|
||||||
|
### 4. Save project
|
||||||
|
|
||||||
|
1. กด Save
|
||||||
|
2. ตั้งชื่อ project เช่น `MoreminiMore Lead Form`
|
||||||
|
|
||||||
|
### 5. Deploy เป็น Web App
|
||||||
|
|
||||||
|
ตาม official docs ของ Google ให้ deploy web app โดย:
|
||||||
|
|
||||||
|
1. มุมขวาบน กด `Deploy`
|
||||||
|
2. เลือก `New deployment`
|
||||||
|
3. ตรง `Select type` กด icon ตั้งค่า แล้วเลือก `Web app`
|
||||||
|
4. ตั้งค่า:
|
||||||
|
- Description: `MoreminiMore lead form endpoint`
|
||||||
|
- Execute as: `Me`
|
||||||
|
- Who has access: `Anyone`
|
||||||
|
5. กด `Deploy`
|
||||||
|
6. Google จะขอ authorize permissions
|
||||||
|
7. เลือก account ของคุณ
|
||||||
|
8. อนุญาตสิทธิ์ที่เกี่ยวกับ Google Sheets และส่งอีเมล
|
||||||
|
9. Copy `Web app URL` เก็บไว้
|
||||||
|
|
||||||
|
URL จะหน้าตาประมาณ:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://script.google.com/macros/s/xxxxxxxxxxxxxxxx/exec
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. ทดสอบ endpoint
|
||||||
|
|
||||||
|
เปิด URL ที่ copy มาใน browser ถ้าระบบทำงาน จะเห็น JSON ประมาณ:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"ok":true,"service":"MoreminiMore lead form"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. เอา URL ไปใส่ในเว็บ
|
||||||
|
|
||||||
|
ตอน implement หน้าเว็บ ให้ตั้งค่า URL นี้เป็น endpoint ของฟอร์ม
|
||||||
|
|
||||||
|
ข้อควรระวัง: Apps Script web app มักไม่เหมาะกับ fetch ที่ต้องอ่าน JSON response ข้ามโดเมนแบบเต็ม ๆ เพราะอาจติด CORS ได้ วิธีที่เหมาะกับ static site คือส่งข้อมูลแบบ simple POST หรือ `fetch(..., { mode: "no-cors" })` แล้วให้หน้าเว็บแสดง success state หลัง request ถูกส่งออกไป
|
||||||
|
|
||||||
|
ตัวอย่าง payload ที่เว็บควรส่ง:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "คุณเอ",
|
||||||
|
"phone": "0800000000",
|
||||||
|
"email": "owner@example.com",
|
||||||
|
"problems": ["ads_not_worth_it", "wrong_leads"],
|
||||||
|
"message": "ยิงแอดอยู่ แต่ยอดขายไม่คุ้ม อยากรู้ว่าควรแก้อะไรก่อน",
|
||||||
|
"pageUrl": "https://moreminimore.com/",
|
||||||
|
"userAgent": "browser user agent"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Problem Keys ที่ script รองรับ
|
||||||
|
|
||||||
|
| Key | ข้อความ |
|
||||||
|
| --- | --- |
|
||||||
|
| `website_no_leads` | เว็บมีอยู่แล้ว แต่ไม่ค่อยมีลูกค้าทัก |
|
||||||
|
| `ads_not_worth_it` | ยิงแอดอยู่ แต่ยอดขายไม่คุ้ม |
|
||||||
|
| `wrong_leads` | มีคนทักมา แต่ไม่ใช่ลูกค้าที่ใช่ |
|
||||||
|
| `slow_or_error_work` | ทีมงานทำงานเดิม ๆ แต่ทำงานช้า หรือผิดพลาดบ่อย |
|
||||||
|
| `ai_not_sure` | อยากใช้ AI แต่ไม่รู้เริ่มตรงไหน |
|
||||||
|
| `not_sure` | ยังไม่แน่ใจว่าควรแก้อะไรก่อน |
|
||||||
|
|
||||||
|
## วิธีลดโอกาสเมลเข้าขยะ
|
||||||
|
|
||||||
|
Apps Script จะส่งเมลจากบัญชี Google ที่ deploy script ดังนั้นควร:
|
||||||
|
|
||||||
|
- ใช้บัญชี Google Workspace ของบริษัท ถ้ามี
|
||||||
|
- ตั้งค่า SPF/DKIM/DMARC ของโดเมนให้ถูกต้อง
|
||||||
|
- ใช้ subject ปกติ ไม่ spammy เช่น `มีโจทย์ธุรกิจใหม่จากเว็บไซต์ MoreminiMore`
|
||||||
|
- อย่าใช้ email ลูกค้าเป็น `From`
|
||||||
|
- ให้ script ใช้ email ลูกค้าเป็น `Reply-To` แทน
|
||||||
|
- เนื้อหาอีเมลควรเป็นข้อความสะอาด ไม่ใส่คำขายหรือ link เยอะ
|
||||||
|
|
||||||
|
## เวลาแก้ script หลัง deploy
|
||||||
|
|
||||||
|
ถ้าแก้โค้ดหลังจาก deploy แล้ว:
|
||||||
|
|
||||||
|
1. กด `Deploy`
|
||||||
|
2. เลือก `Manage deployments`
|
||||||
|
3. เลือก deployment เดิม
|
||||||
|
4. กด edit
|
||||||
|
5. เลือก version ใหม่ หรือ new version
|
||||||
|
6. กด deploy/update
|
||||||
|
|
||||||
|
ถ้าสร้าง deployment ใหม่ URL อาจเปลี่ยน ต้องเอา URL ใหม่ไปใส่ในเว็บอีกครั้ง
|
||||||
|
|
||||||
|
## Debug เบื้องต้น
|
||||||
|
|
||||||
|
ถ้า submit แล้วไม่เข้า Sheet:
|
||||||
|
|
||||||
|
1. เปิด Apps Script
|
||||||
|
2. ดูเมนู `Executions`
|
||||||
|
3. เปิด execution ล่าสุดเพื่อดู error
|
||||||
|
4. ตรวจว่า deploy เป็น `Who has access: Anyone`
|
||||||
|
5. ตรวจว่าใช้ URL ที่ลงท้าย `/exec` ไม่ใช่ `/dev`
|
||||||
|
|
||||||
|
ถ้าเข้า Sheet แต่ไม่ส่งเมล:
|
||||||
|
|
||||||
|
1. ตรวจสิทธิ์ MailApp ตอน authorize
|
||||||
|
2. ตรวจ `RECIPIENT_EMAIL`
|
||||||
|
3. ตรวจ quota ของ Google account
|
||||||
|
4. ดู error ใน `Executions`
|
||||||
290
google-apps-script/lead-form.gs
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
/**
|
||||||
|
* MoreminiMore lead form endpoint.
|
||||||
|
*
|
||||||
|
* Recommended setup:
|
||||||
|
* 1. Create a Google Sheet for leads.
|
||||||
|
* 2. Open Extensions > Apps Script.
|
||||||
|
* 3. Paste this entire file into Code.gs.
|
||||||
|
* 4. Update CONFIG.RECIPIENT_EMAIL.
|
||||||
|
* 5. Deploy as Web app.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const CONFIG = {
|
||||||
|
RECIPIENT_EMAIL: 'contact@moreminimore.com',
|
||||||
|
SHEET_NAME: 'Leads',
|
||||||
|
TIMEZONE: 'Asia/Bangkok',
|
||||||
|
EMAIL_SUBJECT: 'มีโจทย์ธุรกิจใหม่จากเว็บไซต์ MoreminiMore',
|
||||||
|
};
|
||||||
|
|
||||||
|
const PROBLEM_LABELS = {
|
||||||
|
website_no_leads: 'เว็บมีอยู่แล้ว แต่ไม่ค่อยมีลูกค้าทัก',
|
||||||
|
ads_not_worth_it: 'ยิงแอดอยู่ แต่ยอดขายไม่คุ้ม',
|
||||||
|
wrong_leads: 'มีคนทักมา แต่ไม่ใช่ลูกค้าที่ใช่',
|
||||||
|
slow_or_error_work: 'ทีมงานทำงานเดิม ๆ แต่ทำงานช้า หรือผิดพลาดบ่อย',
|
||||||
|
ai_not_sure: 'อยากใช้ AI แต่ไม่รู้เริ่มตรงไหน',
|
||||||
|
not_sure: 'ยังไม่แน่ใจว่าควรแก้อะไรก่อน',
|
||||||
|
};
|
||||||
|
|
||||||
|
function doGet() {
|
||||||
|
return jsonResponse({
|
||||||
|
ok: true,
|
||||||
|
service: 'MoreminiMore lead form',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function doPost(e) {
|
||||||
|
try {
|
||||||
|
const data = parseRequest(e);
|
||||||
|
|
||||||
|
if (isSpam(data)) {
|
||||||
|
return jsonResponse({ ok: true, skipped: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const lead = normalizeLead(data);
|
||||||
|
const validation = validateLead(lead);
|
||||||
|
|
||||||
|
if (!validation.ok) {
|
||||||
|
return jsonResponse({
|
||||||
|
ok: false,
|
||||||
|
error: validation.error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const lock = LockService.getScriptLock();
|
||||||
|
lock.waitLock(10000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
appendLead(lead);
|
||||||
|
} finally {
|
||||||
|
lock.releaseLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendLeadEmail(lead);
|
||||||
|
|
||||||
|
return jsonResponse({
|
||||||
|
ok: true,
|
||||||
|
message: 'Lead received',
|
||||||
|
diagnosis: buildLightDiagnosis(lead.problems),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return jsonResponse({
|
||||||
|
ok: false,
|
||||||
|
error: 'ระบบรับข้อมูลมีปัญหา กรุณาลองใหม่อีกครั้ง',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRequest(e) {
|
||||||
|
if (!e) return {};
|
||||||
|
|
||||||
|
const contentType = String(e.postData && e.postData.type || '').toLowerCase();
|
||||||
|
const raw = e.postData && e.postData.contents;
|
||||||
|
|
||||||
|
if (raw && contentType.indexOf('application/json') !== -1) {
|
||||||
|
return JSON.parse(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw && contentType.indexOf('text/plain') !== -1) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(raw);
|
||||||
|
} catch (error) {
|
||||||
|
return e.parameter || {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.parameter || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLead(data) {
|
||||||
|
const problems = normalizeProblems(data.problems || data.problem || data.problemKeys);
|
||||||
|
|
||||||
|
return {
|
||||||
|
createdAt: Utilities.formatDate(new Date(), CONFIG.TIMEZONE, 'yyyy-MM-dd HH:mm:ss'),
|
||||||
|
name: cleanText(data.name),
|
||||||
|
phone: cleanText(data.phone),
|
||||||
|
email: cleanText(data.email).toLowerCase(),
|
||||||
|
message: cleanText(data.message || data.details || data.note),
|
||||||
|
problems,
|
||||||
|
pageUrl: cleanText(data.pageUrl || data.url),
|
||||||
|
userAgent: cleanText(data.userAgent),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeProblems(value) {
|
||||||
|
if (!value) return [];
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map(String).map(cleanText).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value)
|
||||||
|
.split(',')
|
||||||
|
.map(cleanText)
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateLead(lead) {
|
||||||
|
if (!lead.name) {
|
||||||
|
return { ok: false, error: 'กรุณาใส่ชื่อ' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lead.phone && !lead.email) {
|
||||||
|
return { ok: false, error: 'ใส่เบอร์โทรหรืออีเมลอย่างใดอย่างหนึ่งก็ได้' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lead.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(lead.email)) {
|
||||||
|
return { ok: false, error: 'รูปแบบอีเมลไม่ถูกต้อง' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lead.message.length > 2000) {
|
||||||
|
return { ok: false, error: 'รายละเอียดโจทย์ยาวเกินไป' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendLead(lead) {
|
||||||
|
const sheet = getLeadSheet();
|
||||||
|
sheet.appendRow([
|
||||||
|
lead.createdAt,
|
||||||
|
lead.name,
|
||||||
|
lead.phone,
|
||||||
|
lead.email,
|
||||||
|
problemLabels(lead.problems).join(', '),
|
||||||
|
lead.message,
|
||||||
|
buildLightDiagnosis(lead.problems),
|
||||||
|
lead.pageUrl,
|
||||||
|
lead.userAgent,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLeadSheet() {
|
||||||
|
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
|
||||||
|
let sheet = spreadsheet.getSheetByName(CONFIG.SHEET_NAME);
|
||||||
|
|
||||||
|
if (!sheet) {
|
||||||
|
sheet = spreadsheet.insertSheet(CONFIG.SHEET_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sheet.getLastRow() === 0) {
|
||||||
|
sheet.appendRow([
|
||||||
|
'วันที่ส่ง',
|
||||||
|
'ชื่อ',
|
||||||
|
'เบอร์โทร',
|
||||||
|
'อีเมล',
|
||||||
|
'ปัญหาที่เลือก',
|
||||||
|
'รายละเอียด',
|
||||||
|
'แนวทางเริ่มต้น',
|
||||||
|
'Page URL',
|
||||||
|
'User Agent',
|
||||||
|
]);
|
||||||
|
sheet.setFrozenRows(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendLeadEmail(lead) {
|
||||||
|
const labels = problemLabels(lead.problems);
|
||||||
|
const diagnosis = buildLightDiagnosis(lead.problems);
|
||||||
|
|
||||||
|
const plainBody = [
|
||||||
|
'มีโจทย์ธุรกิจใหม่จากเว็บไซต์ MoreminiMore',
|
||||||
|
'',
|
||||||
|
`ชื่อ: ${lead.name}`,
|
||||||
|
`เบอร์โทร: ${lead.phone || '-'}`,
|
||||||
|
`อีเมล: ${lead.email || '-'}`,
|
||||||
|
`ปัญหาที่เลือก: ${labels.length ? labels.join(', ') : '-'}`,
|
||||||
|
'',
|
||||||
|
'รายละเอียด:',
|
||||||
|
lead.message || '-',
|
||||||
|
'',
|
||||||
|
`แนวทางเริ่มต้น: ${diagnosis}`,
|
||||||
|
'',
|
||||||
|
`Page URL: ${lead.pageUrl || '-'}`,
|
||||||
|
`เวลาที่ส่ง: ${lead.createdAt}`,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const htmlBody = `
|
||||||
|
<div style="font-family:Arial,sans-serif;line-height:1.6;color:#17120a">
|
||||||
|
<h2 style="margin:0 0 12px">มีโจทย์ธุรกิจใหม่จากเว็บไซต์ MoreminiMore</h2>
|
||||||
|
<p><strong>ชื่อ:</strong> ${escapeHtml(lead.name)}</p>
|
||||||
|
<p><strong>เบอร์โทร:</strong> ${escapeHtml(lead.phone || '-')}</p>
|
||||||
|
<p><strong>อีเมล:</strong> ${escapeHtml(lead.email || '-')}</p>
|
||||||
|
<p><strong>ปัญหาที่เลือก:</strong> ${escapeHtml(labels.length ? labels.join(', ') : '-')}</p>
|
||||||
|
<p><strong>รายละเอียด:</strong><br>${escapeHtml(lead.message || '-').replace(/\n/g, '<br>')}</p>
|
||||||
|
<p><strong>แนวทางเริ่มต้น:</strong> ${escapeHtml(diagnosis)}</p>
|
||||||
|
<hr>
|
||||||
|
<p style="color:#666;font-size:13px">
|
||||||
|
Page URL: ${escapeHtml(lead.pageUrl || '-')}<br>
|
||||||
|
เวลาที่ส่ง: ${escapeHtml(lead.createdAt)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
name: 'MoreminiMore Website',
|
||||||
|
htmlBody,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (lead.email) {
|
||||||
|
options.replyTo = lead.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
MailApp.sendEmail(CONFIG.RECIPIENT_EMAIL, CONFIG.EMAIL_SUBJECT, plainBody, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLightDiagnosis(problemKeys) {
|
||||||
|
const keys = problemKeys || [];
|
||||||
|
|
||||||
|
if (keys.indexOf('ads_not_worth_it') !== -1 || keys.indexOf('wrong_leads') !== -1) {
|
||||||
|
return 'น่าจะเริ่มจากการดูข้อมูลแอด กลุ่มเป้าหมาย และคุณภาพลูกค้าที่ทักเข้ามาก่อน';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.indexOf('website_no_leads') !== -1) {
|
||||||
|
return 'น่าจะเริ่มจากการดูเว็บ เส้นทางลูกค้า และจุดที่ควรชวนให้ติดต่อก่อน';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.indexOf('slow_or_error_work') !== -1) {
|
||||||
|
return 'น่าจะเริ่มจากการดูขั้นตอนทำงานซ้ำ จุดที่ช้า และจุดที่ผิดพลาดบ่อยก่อน';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.indexOf('ai_not_sure') !== -1) {
|
||||||
|
return 'น่าจะเริ่มจากการดูงานจริงของทีมก่อน แล้วค่อยเลือกจุดที่ AI ช่วยได้อย่างเหมาะสม';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'เราจะเริ่มจากการทำความเข้าใจธุรกิจและข้อมูลที่มีอยู่ก่อน แล้วค่อยแนะนำทางที่คุ้มที่สุด';
|
||||||
|
}
|
||||||
|
|
||||||
|
function problemLabels(problemKeys) {
|
||||||
|
return (problemKeys || []).map(function (key) {
|
||||||
|
return PROBLEM_LABELS[key] || key;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSpam(data) {
|
||||||
|
return Boolean(data.website || data.company_url || data.url2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanText(value) {
|
||||||
|
return String(value || '')
|
||||||
|
.replace(/\r/g, '')
|
||||||
|
.trim()
|
||||||
|
.slice(0, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(value) {
|
||||||
|
return String(value || '')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsonResponse(data) {
|
||||||
|
return ContentService
|
||||||
|
.createTextOutput(JSON.stringify(data))
|
||||||
|
.setMimeType(ContentService.MimeType.JSON);
|
||||||
|
}
|
||||||
6548
package-lock.json
generated
17
package.json
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "moreminimore-site",
|
"name": "moreminimore-site",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.12.0"
|
"node": ">=22.12.0"
|
||||||
},
|
},
|
||||||
@@ -9,15 +10,17 @@
|
|||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
|
"start": "node server.js",
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/mdx": "^5.0.6",
|
"@aws-sdk/client-sesv2": "^3.1077.0",
|
||||||
"@astrojs/node": "^10.1.1",
|
|
||||||
"@astrojs/react": "^5.0.5",
|
|
||||||
"astro": "^6.2.2",
|
"astro": "^6.2.2",
|
||||||
"emdash": "^0.12.0",
|
"cors": "^2.8.6",
|
||||||
"react": "^19.2.5",
|
"express": "^5.2.1",
|
||||||
"react-dom": "^19.2.5"
|
"nodemailer": "^9.0.1"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@rollup/rollup-linux-x64-gnu": "^4.62.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
182
public/demos/a-orbital.html
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="th">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>A: Orbital — ระบบดาวเคราะห์</title>
|
||||||
|
<style>
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: 'Kanit', system-ui, sans-serif;
|
||||||
|
background: #0a0f1a;
|
||||||
|
color: #fff;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
min-height: 100vh; overflow: hidden;
|
||||||
|
}
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Kanit:wght@400;600;800;900&display=swap');
|
||||||
|
|
||||||
|
.demo { position: relative; width: 650px; height: 580px; }
|
||||||
|
|
||||||
|
/* Canvas for orbital rings */
|
||||||
|
canvas#orbitalCanvas {
|
||||||
|
position: absolute; inset: 0; pointer-events: none; z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scene {
|
||||||
|
position: relative; width: 100%; height: 100%;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transition: transform 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node {
|
||||||
|
position: absolute; border-radius: 50%;
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
align-items: center; justify-content: center;
|
||||||
|
text-align: center; backface-visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center Sun */
|
||||||
|
.sun {
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 170px; height: 170px;
|
||||||
|
background: radial-gradient(circle at 40% 35%, #fff7cc, #fed400 40%, #d4a000 100%);
|
||||||
|
box-shadow: 0 0 60px rgba(254,212,0,0.6), 0 0 120px rgba(254,212,0,0.3), 0 0 200px rgba(254,180,0,0.15);
|
||||||
|
z-index: 10; animation: sunPulse 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes sunPulse {
|
||||||
|
0%, 100% { box-shadow: 0 0 60px rgba(254,212,0,0.6), 0 0 120px rgba(254,212,0,0.3), 0 0 200px rgba(254,180,0,0.15); }
|
||||||
|
50% { box-shadow: 0 0 80px rgba(254,212,0,0.8), 0 0 150px rgba(254,212,0,0.4), 0 0 230px rgba(254,180,0,0.2); }
|
||||||
|
}
|
||||||
|
.sun .label { font-size: 2.8rem; font-weight: 900; color: #3a2e00; }
|
||||||
|
.sun .sub { font-size: 0.75rem; color: #6b5500; margin-top: 4px; font-weight: 600; }
|
||||||
|
|
||||||
|
/* Orbiting planets */
|
||||||
|
.planet {
|
||||||
|
width: 110px; height: 110px;
|
||||||
|
background: rgba(255,255,255,0.06);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1.5px solid rgba(255,255,255,0.2);
|
||||||
|
box-shadow: 0 8px 32px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.15);
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
animation: orbit var(--orbit-dur) linear infinite;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
.planet:nth-child(2) { --orbit-dur: 12s; --radius: 230px; --angle: 0deg; --z: -40px; }
|
||||||
|
.planet:nth-child(3) { --orbit-dur: 15s; --radius: 280px; --angle: 120deg; --z: -70px; }
|
||||||
|
.planet:nth-child(4) { --orbit-dur: 18s; --radius: 330px; --angle: 240deg; --z: -100px; }
|
||||||
|
|
||||||
|
@keyframes orbit {
|
||||||
|
0% { transform: translate(-50%, -50%) rotate(0deg) translateX(var(--radius)) rotate(0deg); }
|
||||||
|
100% { transform: translate(-50%, -50%) rotate(360deg) translateX(var(--radius)) rotate(-360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.planet .tag { font-size: 1rem; font-weight: 800; color: #fff; text-transform: uppercase; }
|
||||||
|
.planet .desc { font-size: 0.7rem; color: rgba(255,255,255,0.6); margin-top: 4px; }
|
||||||
|
|
||||||
|
/* Connecting lines (drawn on canvas) */
|
||||||
|
|
||||||
|
.title {
|
||||||
|
position: fixed; top: 24px; left: 50%; transform: translateX(-50%);
|
||||||
|
font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.2em;
|
||||||
|
color: rgba(255,255,255,0.4); z-index: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span class="title">A. Orbital — ระบบดาวเคราะห์โคจร</span>
|
||||||
|
<div class="demo">
|
||||||
|
<canvas id="orbitalCanvas"></canvas>
|
||||||
|
<div class="scene" id="scene">
|
||||||
|
<div class="node sun" data-node="center">
|
||||||
|
<span class="label">กำไร</span>
|
||||||
|
<span class="sub">เป้าหมายของทุกธุรกิจ</span>
|
||||||
|
</div>
|
||||||
|
<div class="node planet" data-node="mkt">
|
||||||
|
<span class="tag">Marketing</span>
|
||||||
|
<span class="desc">เพิ่มรายได้</span>
|
||||||
|
</div>
|
||||||
|
<div class="node planet" data-node="ai">
|
||||||
|
<span class="tag">AI</span>
|
||||||
|
<span class="desc">ลดต้นทุนและเวลา</span>
|
||||||
|
</div>
|
||||||
|
<div class="node planet" data-node="biz">
|
||||||
|
<span class="tag">Business<br>Knowledge</span>
|
||||||
|
<span class="desc">ลดความเสี่ยง</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const canvas = document.getElementById('orbitalCanvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const scene = document.getElementById('scene');
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
const rect = canvas.parentElement.getBoundingClientRect();
|
||||||
|
canvas.width = rect.width;
|
||||||
|
canvas.height = rect.height;
|
||||||
|
}
|
||||||
|
resize();
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
const rect = canvas.parentElement.getBoundingClientRect();
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const sun = document.querySelector('[data-node="center"]');
|
||||||
|
const planets = document.querySelectorAll('.planet');
|
||||||
|
if (!sun) return;
|
||||||
|
|
||||||
|
const sunRect = sun.getBoundingClientRect();
|
||||||
|
const sx = sunRect.left + sunRect.width/2 - rect.left;
|
||||||
|
const sy = sunRect.top + sunRect.height/2 - rect.top;
|
||||||
|
|
||||||
|
// Draw orbital rings
|
||||||
|
const sr = 85; // sun radius
|
||||||
|
const radii = [230, 280, 330];
|
||||||
|
radii.forEach((r, i) => {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(sx, sy, r, r * 0.45, 0, 0, Math.PI * 2);
|
||||||
|
ctx.strokeStyle = `rgba(254,212,0,${0.15 - i * 0.03})`;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.setLineDash([8, 14]);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw connection lines
|
||||||
|
planets.forEach(p => {
|
||||||
|
const pRect = p.getBoundingClientRect();
|
||||||
|
const px = pRect.left + pRect.width/2 - rect.left;
|
||||||
|
const py = pRect.top + pRect.height/2 - rect.top;
|
||||||
|
|
||||||
|
const dx = px - sx, dy = py - sy;
|
||||||
|
const dist = Math.sqrt(dx*dx + dy*dy);
|
||||||
|
const r = 85;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(sx + (dx/dist)*r, sy + (dy/dist)*r);
|
||||||
|
ctx.lineTo(px - (dx/dist)*55, py - (dy/dist)*55);
|
||||||
|
const grad = ctx.createLinearGradient(sx, sy, px, py);
|
||||||
|
grad.addColorStop(0, 'rgba(254,212,0,0.7)');
|
||||||
|
grad.addColorStop(1, 'rgba(254,212,0,0.15)');
|
||||||
|
ctx.strokeStyle = grad;
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
|
||||||
|
requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse parallax
|
||||||
|
document.addEventListener('mousemove', e => {
|
||||||
|
const x = (e.clientX / window.innerWidth - 0.5) * 8;
|
||||||
|
const y = (e.clientY / window.innerHeight - 0.5) * -8;
|
||||||
|
scene.style.transform = `rotateX(${y}deg) rotateY(${x}deg)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
draw();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
180
public/demos/b-energyflow.html
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="th">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>B: Energy Flow — กระแสพลังงาน</title>
|
||||||
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Kanit:wght@400;600;800;900&display=swap');
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: 'Kanit', system-ui, sans-serif;
|
||||||
|
background: #080d14;
|
||||||
|
color: #fff; overflow: hidden;
|
||||||
|
display: flex; align-items: center; justify-content: center; min-height: 100vh;
|
||||||
|
}
|
||||||
|
.demo { position: relative; width: 650px; height: 580px; }
|
||||||
|
canvas#flowCanvas { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
|
||||||
|
.scene {
|
||||||
|
position: relative; width: 100%; height: 100%;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transition: transform 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node {
|
||||||
|
position: absolute;
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
align-items: center; justify-content: center; text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Core */
|
||||||
|
.core {
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 160px; height: 160px; border-radius: 50%;
|
||||||
|
background: radial-gradient(circle at 35% 30%, #2a1a00, #100800);
|
||||||
|
border: 2px solid rgba(254,212,0,0.8);
|
||||||
|
box-shadow: 0 0 40px rgba(254,212,0,0.3), 0 0 80px rgba(254,180,0,0.1), inset 0 0 40px rgba(254,212,0,0.08);
|
||||||
|
z-index: 10;
|
||||||
|
animation: coreBeat 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes coreBeat {
|
||||||
|
0%, 100% { box-shadow: 0 0 40px rgba(254,212,0,0.3), 0 0 80px rgba(254,180,0,0.1), inset 0 0 40px rgba(254,212,0,0.08); }
|
||||||
|
50% { box-shadow: 0 0 60px rgba(254,212,0,0.5), 0 0 100px rgba(254,180,0,0.2), inset 0 0 50px rgba(254,212,0,0.12); }
|
||||||
|
}
|
||||||
|
.core .label { font-size: 2.6rem; font-weight: 900; color: #fed400; }
|
||||||
|
.core .sub { font-size: 0.7rem; color: rgba(254,212,0,0.5); margin-top: 4px; font-weight: 600; }
|
||||||
|
|
||||||
|
/* Satellites */
|
||||||
|
.satellite {
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255,255,255,0.04);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border: 1px solid rgba(254,212,0,0.25);
|
||||||
|
width: 120px; height: 120px;
|
||||||
|
z-index: 5;
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
}
|
||||||
|
.satellite:nth-child(2) { transform: translate(-50%, -50%) translateY(-230px); animation: float1 4s ease-in-out infinite; }
|
||||||
|
.satellite:nth-child(3) { transform: translate(-50%, -50%) translateX(220px) translateY(10px); animation: float2 4.5s ease-in-out infinite; }
|
||||||
|
.satellite:nth-child(4) { transform: translate(-50%, -50%) translateX(-160px) translateY(190px); animation: float3 5s ease-in-out infinite; }
|
||||||
|
@keyframes float1 { 0%,100%{ transform: translate(-50%,-50%) translateY(-230px); } 50%{ transform: translate(-50%,-50%) translateY(-245px); } }
|
||||||
|
@keyframes float2 { 0%,100%{ transform: translate(-50%,-50%) translateX(220px) translateY(10px); } 50%{ transform: translate(-50%,-50%) translateX(235px) translateY(0px); } }
|
||||||
|
@keyframes float3 { 0%,100%{ transform: translate(-50%,-50%) translateX(-160px) translateY(190px); } 50%{ transform: translate(-50%,-50%) translateX(-175px) translateY(205px); } }
|
||||||
|
|
||||||
|
.satellite .tag { font-size: 0.9rem; font-weight: 800; color: #fff; }
|
||||||
|
.satellite .desc { font-size: 0.65rem; color: rgba(255,255,255,0.5); margin-top: 3px; }
|
||||||
|
|
||||||
|
.title {
|
||||||
|
position: fixed; top: 24px; left: 50%; transform: translateX(-50%);
|
||||||
|
font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.2em;
|
||||||
|
color: rgba(255,255,255,0.4); z-index: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span class="title">B. Energy Flow — กระแสพลังงาน พร้อม particles</span>
|
||||||
|
<div class="demo">
|
||||||
|
<canvas id="flowCanvas"></canvas>
|
||||||
|
<div class="scene" id="scene">
|
||||||
|
<div class="node core" data-node="center">
|
||||||
|
<span class="label">กำไร</span>
|
||||||
|
<span class="sub">เป้าหมายของทุกธุรกิจ</span>
|
||||||
|
</div>
|
||||||
|
<div class="node satellite" data-node="mkt">
|
||||||
|
<span class="tag">Marketing</span>
|
||||||
|
<span class="desc">เพิ่มรายได้</span>
|
||||||
|
</div>
|
||||||
|
<div class="node satellite" data-node="ai">
|
||||||
|
<span class="tag">AI</span>
|
||||||
|
<span class="desc">ลดต้นทุนและเวลา</span>
|
||||||
|
</div>
|
||||||
|
<div class="node satellite" data-node="biz">
|
||||||
|
<span class="tag">Business<br>Knowledge</span>
|
||||||
|
<span class="desc">ลดความเสี่ยง</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const canvas = document.getElementById('flowCanvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const scene = document.getElementById('scene');
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
const rect = canvas.parentElement.getBoundingClientRect();
|
||||||
|
canvas.width = rect.width;
|
||||||
|
canvas.height = rect.height;
|
||||||
|
}
|
||||||
|
resize();
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
// Particle system per connection
|
||||||
|
const connections = [];
|
||||||
|
let t = 0;
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
t++;
|
||||||
|
const rect = canvas.parentElement.getBoundingClientRect();
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const sun = document.querySelector('[data-node="center"]');
|
||||||
|
const sats = document.querySelectorAll('.satellite');
|
||||||
|
if (!sun) { requestAnimationFrame(draw); return; }
|
||||||
|
|
||||||
|
const sr = sun.getBoundingClientRect();
|
||||||
|
const sx = sr.left + sr.width/2 - rect.left;
|
||||||
|
const sy = sr.top + sr.height/2 - rect.top;
|
||||||
|
|
||||||
|
sats.forEach((sat, i) => {
|
||||||
|
const pr = sat.getBoundingClientRect();
|
||||||
|
const px = pr.left + pr.width/2 - rect.left;
|
||||||
|
const py = pr.top + pr.height/2 - rect.top;
|
||||||
|
|
||||||
|
const dx = px - sx, dy = py - sy;
|
||||||
|
const dist = Math.sqrt(dx*dx + dy*dy);
|
||||||
|
|
||||||
|
// Draw stream line
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(sx + (dx/dist)*80, sy + (dy/dist)*80);
|
||||||
|
ctx.lineTo(px - (dx/dist)*60, py - (dy/dist)*60);
|
||||||
|
const grad = ctx.createLinearGradient(sx, sy, px, py);
|
||||||
|
grad.addColorStop(0, 'rgba(254,212,0,0.6)');
|
||||||
|
grad.addColorStop(0.5, 'rgba(254,212,0,0.3)');
|
||||||
|
grad.addColorStop(1, 'rgba(254,212,0,0.1)');
|
||||||
|
ctx.strokeStyle = grad;
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Animated particles along the line
|
||||||
|
const count = 12;
|
||||||
|
for (let j = 0; j < count; j++) {
|
||||||
|
const phase = (t * 0.03 + j / count + i * 0.33) % 1;
|
||||||
|
const pp = phase < 0.5 ? phase * 2 : 2 - phase * 2;
|
||||||
|
const x = sx + (dx/dist)*80 + dx * (1 - 80/dist - 60/dist) * phase;
|
||||||
|
const y = sy + (dy/dist)*80 + dy * (1 - 80/dist - 60/dist) * phase;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, 2.5, 0, Math.PI*2);
|
||||||
|
ctx.fillStyle = `rgba(254,230,100,${0.8 * pp})`;
|
||||||
|
ctx.fill();
|
||||||
|
// Glow
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, 5, 0, Math.PI*2);
|
||||||
|
ctx.fillStyle = `rgba(254,212,0,${0.25 * pp})`;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', e => {
|
||||||
|
const x = (e.clientX / window.innerWidth - 0.5) * 6;
|
||||||
|
const y = (e.clientY / window.innerHeight - 0.5) * -6;
|
||||||
|
scene.style.transform = `rotateX(${y}deg) rotateY(${x}deg)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
draw();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
179
public/demos/c-holographic.html
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="th">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>C: Holographic 3D — Hologram Sci-Fi</title>
|
||||||
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Kanit:wght@400;600;800;900&display=swap');
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: 'Kanit', system-ui, sans-serif;
|
||||||
|
background: #020810;
|
||||||
|
color: #fff; overflow: hidden;
|
||||||
|
display: flex; align-items: center; justify-content: center; min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo { position: relative; width: 700px; height: 600px; }
|
||||||
|
canvas#holoCanvas { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
|
||||||
|
.scene {
|
||||||
|
position: relative; width: 100%; height: 100%;
|
||||||
|
transform-style: preserve-3d; perspective: 1200px;
|
||||||
|
transition: transform 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node {
|
||||||
|
position: absolute;
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
align-items: center; justify-content: center; text-align: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center hologram */
|
||||||
|
.holo-core {
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 170px; height: 170px;
|
||||||
|
background: radial-gradient(circle at 40% 35%, rgba(0,255,255,0.15), rgba(0,200,255,0.05));
|
||||||
|
border: 2px solid rgba(0,255,255,0.5);
|
||||||
|
box-shadow: 0 0 50px rgba(0,255,255,0.2), 0 0 100px rgba(0,200,255,0.08), inset 0 0 30px rgba(0,255,255,0.05);
|
||||||
|
z-index: 10;
|
||||||
|
animation: holoPulse 2.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes holoPulse {
|
||||||
|
0%, 100% { border-color: rgba(0,255,255,0.5); box-shadow: 0 0 50px rgba(0,255,255,0.2), 0 0 100px rgba(0,200,255,0.08); }
|
||||||
|
50% { border-color: rgba(0,255,255,0.8); box-shadow: 0 0 70px rgba(0,255,255,0.35), 0 0 120px rgba(0,200,255,0.15); }
|
||||||
|
}
|
||||||
|
.holo-core .label { font-size: 2.8rem; font-weight: 900; color: #0ff; text-shadow: 0 0 20px rgba(0,255,255,0.5); }
|
||||||
|
.holo-core .sub { font-size: 0.7rem; color: rgba(0,255,255,0.5); margin-top: 4px; font-weight: 600; }
|
||||||
|
|
||||||
|
/* Scanline effect */
|
||||||
|
.holo-core::after {
|
||||||
|
content: ''; position: absolute; inset: 0; border-radius: 50%;
|
||||||
|
background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,255,255,0.03) 2px, rgba(0,255,255,0.03) 4px);
|
||||||
|
pointer-events: none; z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Satellites - holographic */
|
||||||
|
.holo-sat {
|
||||||
|
width: 115px; height: 115px;
|
||||||
|
background: rgba(0,255,255,0.04);
|
||||||
|
border: 1.5px solid rgba(0,255,255,0.35);
|
||||||
|
box-shadow: 0 0 25px rgba(0,255,255,0.1), inset 0 0 20px rgba(0,255,255,0.04);
|
||||||
|
z-index: 5;
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
}
|
||||||
|
.holo-sat::after {
|
||||||
|
content: ''; position: absolute; inset: 0; border-radius: 50%;
|
||||||
|
background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,255,255,0.02) 2px, rgba(0,255,255,0.02) 4px);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.holo-sat:nth-child(2) { transform: translate(-50%,-50%) translate3d(-220px,-140px,-80px); animation: hfloat1 5s ease-in-out infinite; }
|
||||||
|
.holo-sat:nth-child(3) { transform: translate(-50%,-50%) translate3d(180px,-20px,-30px); animation: hfloat2 6s ease-in-out infinite; }
|
||||||
|
.holo-sat:nth-child(4) { transform: translate(-50%,-50%) translate3d(-100px,210px,-100px); animation: hfloat3 5.5s ease-in-out infinite; }
|
||||||
|
@keyframes hfloat1 { 0%,100%{transform:translate(-50%,-50%) translate3d(-220px,-140px,-80px)} 50%{transform:translate(-50%,-50%) translate3d(-230px,-155px,-100px)} }
|
||||||
|
@keyframes hfloat2 { 0%,100%{transform:translate(-50%,-50%) translate3d(180px,-20px,-30px)} 50%{transform:translate(-50%,-50%) translate3d(195px,-35px,-55px)} }
|
||||||
|
@keyframes hfloat3 { 0%,100%{transform:translate(-50%,-50%) translate3d(-100px,210px,-100px)} 50%{transform:translate(-50%,-50%) translate3d(-115px,225px,-130px)} }
|
||||||
|
|
||||||
|
.holo-sat .tag { font-size: 0.9rem; font-weight: 800; color: #0ff; text-shadow: 0 0 10px rgba(0,255,255,0.4); }
|
||||||
|
.holo-sat .desc { font-size: 0.65rem; color: rgba(0,255,255,0.5); margin-top: 3px; }
|
||||||
|
|
||||||
|
.title {
|
||||||
|
position: fixed; top: 24px; left: 50%; transform: translateX(-50%);
|
||||||
|
font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.2em;
|
||||||
|
color: rgba(0,255,255,0.4); z-index: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span class="title">C. Holographic 3D — Hologram Sci-Fi</span>
|
||||||
|
<div class="demo">
|
||||||
|
<canvas id="holoCanvas"></canvas>
|
||||||
|
<div class="scene" id="scene">
|
||||||
|
<div class="node holo-core" data-node="center">
|
||||||
|
<span class="label">กำไร</span>
|
||||||
|
<span class="sub">เป้าหมายของทุกธุรกิจ</span>
|
||||||
|
</div>
|
||||||
|
<div class="node holo-sat" data-node="mkt">
|
||||||
|
<span class="tag">Marketing</span>
|
||||||
|
<span class="desc">เพิ่มรายได้</span>
|
||||||
|
</div>
|
||||||
|
<div class="node holo-sat" data-node="ai">
|
||||||
|
<span class="tag">AI</span>
|
||||||
|
<span class="desc">ลดต้นทุนและเวลา</span>
|
||||||
|
</div>
|
||||||
|
<div class="node holo-sat" data-node="biz">
|
||||||
|
<span class="tag">Business<br>Knowledge</span>
|
||||||
|
<span class="desc">ลดความเสี่ยง</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const canvas = document.getElementById('holoCanvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const scene = document.getElementById('scene');
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
const rect = canvas.parentElement.getBoundingClientRect();
|
||||||
|
canvas.width = rect.width;
|
||||||
|
canvas.height = rect.height;
|
||||||
|
}
|
||||||
|
resize(); window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
let t = 0;
|
||||||
|
function draw() {
|
||||||
|
t++;
|
||||||
|
const rect = canvas.parentElement.getBoundingClientRect();
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const center = document.querySelector('[data-node="center"]');
|
||||||
|
const sats = document.querySelectorAll('.holo-sat');
|
||||||
|
if (!center) { requestAnimationFrame(draw); return; }
|
||||||
|
|
||||||
|
const cr = center.getBoundingClientRect();
|
||||||
|
const cx = cr.left + cr.width/2 - rect.left;
|
||||||
|
const cy = cr.top + cr.height/2 - rect.top;
|
||||||
|
|
||||||
|
sats.forEach((sat, i) => {
|
||||||
|
const pr = sat.getBoundingClientRect();
|
||||||
|
const px = pr.left + pr.width/2 - rect.left;
|
||||||
|
const py = pr.top + pr.height/2 - rect.top;
|
||||||
|
const dx = px - cx, dy = py - cy;
|
||||||
|
const dist = Math.sqrt(dx*dx + dy*dy);
|
||||||
|
|
||||||
|
// Hologram beam line (dotted)
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.setLineDash([4, 8]);
|
||||||
|
ctx.lineDashOffset = -t * 2;
|
||||||
|
ctx.moveTo(cx + (dx/dist)*85, cy + (dy/dist)*85);
|
||||||
|
ctx.lineTo(px - (dx/dist)*58, py - (dy/dist)*58);
|
||||||
|
ctx.strokeStyle = 'rgba(0,255,255,0.3)';
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
|
||||||
|
// Glow nodes at intervals
|
||||||
|
for (let j = 0; j < 5; j++) {
|
||||||
|
const phase = ((t * 0.02 + j/5 + i*0.33) % 1);
|
||||||
|
const x = cx + (dx/dist)*85 + dx * (1 - 85/dist - 58/dist) * phase;
|
||||||
|
const y = cy + (dy/dist)*85 + dy * (1 - 85/dist - 58/dist) * phase;
|
||||||
|
const alpha = Math.sin(phase * Math.PI);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, 2, 0, Math.PI*2);
|
||||||
|
ctx.fillStyle = `rgba(0,255,255,${0.6 * alpha})`;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', e => {
|
||||||
|
scene.style.transform = `rotateX(${(e.clientY/window.innerHeight-0.5)*-8}deg) rotateY(${(e.clientX/window.innerWidth-0.5)*8}deg)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
draw();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
208
public/demos/d-constellation.html
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="th">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>D: Constellation — แผนที่ดาว</title>
|
||||||
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Kanit:wght@400;600;800;900&display=swap');
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: 'Kanit', system-ui, sans-serif;
|
||||||
|
background: radial-gradient(ellipse at center, #0d1520 0%, #040810 100%);
|
||||||
|
color: #fff; overflow: hidden;
|
||||||
|
display: flex; align-items: center; justify-content: center; min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Starfield background */
|
||||||
|
.stars {
|
||||||
|
position: fixed; inset: 0; z-index: 0; pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo { position: relative; width: 700px; height: 600px; z-index: 1; }
|
||||||
|
canvas#constCanvas { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
|
||||||
|
.scene {
|
||||||
|
position: relative; width: 100%; height: 100%;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transition: transform 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node {
|
||||||
|
position: absolute;
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
align-items: center; justify-content: center; text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Polaris - main star */
|
||||||
|
.star-main {
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 150px; height: 150px; border-radius: 50%;
|
||||||
|
background: radial-gradient(circle at 38% 35%, rgba(255,255,255,0.25), rgba(254,212,0,0.15) 50%, transparent 100%);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.star-main .glow {
|
||||||
|
position: absolute; inset: -30px; border-radius: 50%;
|
||||||
|
background: radial-gradient(circle, rgba(254,212,0,0.08), transparent 70%);
|
||||||
|
animation: starGlow 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes starGlow {
|
||||||
|
0%, 100% { transform: scale(1); opacity: 0.5; }
|
||||||
|
50% { transform: scale(1.15); opacity: 0.8; }
|
||||||
|
}
|
||||||
|
.star-main .label { font-size: 2.6rem; font-weight: 900; color: #fed400; text-shadow: 0 0 30px rgba(254,212,0,0.6); position: relative; z-index: 1; }
|
||||||
|
.star-main .sub { font-size: 0.7rem; color: rgba(254,212,0,0.5); margin-top: 4px; font-weight: 600; position: relative; z-index: 1; }
|
||||||
|
|
||||||
|
/* Constellation stars */
|
||||||
|
.c-star {
|
||||||
|
width: 70px; height: 70px; border-radius: 50%;
|
||||||
|
background: radial-gradient(circle at 40% 35%, rgba(255,255,255,0.15), transparent);
|
||||||
|
z-index: 5;
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
}
|
||||||
|
/* Star twinkle */
|
||||||
|
.c-star::before {
|
||||||
|
content: ''; position: absolute; inset: -15px; border-radius: 50%;
|
||||||
|
background: radial-gradient(circle, rgba(254,240,200,0.1), transparent 70%);
|
||||||
|
animation: twinkle 4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes twinkle {
|
||||||
|
0%, 100% { opacity: 0.3; transform: scale(0.8); }
|
||||||
|
50% { opacity: 0.7; transform: scale(1.15); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-star:nth-child(2)::before { animation-delay: 0s; }
|
||||||
|
.c-star:nth-child(3)::before { animation-delay: 1.2s; }
|
||||||
|
.c-star:nth-child(4)::before { animation-delay: 2.5s; }
|
||||||
|
|
||||||
|
/* Star points (4-point cross) */
|
||||||
|
.c-star::after {
|
||||||
|
content: ''; position: absolute; inset: -8px; border-radius: 50%;
|
||||||
|
box-shadow:
|
||||||
|
0 -25px 0 -8px rgba(254,240,200,0.3),
|
||||||
|
0 25px 0 -8px rgba(254,240,200,0.3),
|
||||||
|
-25px 0 0 -8px rgba(254,240,200,0.3),
|
||||||
|
25px 0 0 -8px rgba(254,240,200,0.3);
|
||||||
|
animation: starRotate 10s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes starRotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
.c-star:nth-child(2) { transform: translate(-50%,-50%) translate3d(-220px,-160px,-60px); }
|
||||||
|
.c-star:nth-child(3) { transform: translate(-50%,-50%) translate3d(190px,-30px,-30px); }
|
||||||
|
.c-star:nth-child(4) { transform: translate(-50%,-50%) translate3d(-120px,200px,-70px); }
|
||||||
|
|
||||||
|
.c-star .tag { font-size: 0.8rem; font-weight: 800; color: #fff; position: relative; z-index: 1; text-shadow: 0 0 15px rgba(255,255,255,0.4); }
|
||||||
|
.c-star .desc { font-size: 0.6rem; color: rgba(255,255,255,0.5); margin-top: 2px; position: relative; z-index: 1; }
|
||||||
|
|
||||||
|
.title {
|
||||||
|
position: fixed; top: 24px; left: 50%; transform: translateX(-50%);
|
||||||
|
font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.2em;
|
||||||
|
color: rgba(255,255,255,0.4); z-index: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span class="title">D. Constellation — แผนที่ดาว</span>
|
||||||
|
|
||||||
|
<!-- Starfield -->
|
||||||
|
<svg class="stars" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="s"><stop offset="0%" stop-color="#fff" stop-opacity="0.8"/><stop offset="100%" stop-color="#fff" stop-opacity="0"/></radialGradient>
|
||||||
|
</defs>
|
||||||
|
<circle cx="8%" cy="12%" r="1.5" fill="#fff" opacity="0.6"/><circle cx="15%" cy="8%" r="1" fill="#fff" opacity="0.3"/>
|
||||||
|
<circle cx="22%" cy="18%" r="0.8" fill="#fff" opacity="0.5"/><circle cx="78%" cy="10%" r="1.2" fill="#fff" opacity="0.4"/>
|
||||||
|
<circle cx="85%" cy="22%" r="1" fill="#fff" opacity="0.3"/><circle cx="92%" cy="8%" r="0.6" fill="#fff" opacity="0.7"/>
|
||||||
|
<circle cx="6%" cy="85%" r="1" fill="#fff" opacity="0.4"/><circle cx="14%" cy="92%" r="0.8" fill="#fff" opacity="0.5"/>
|
||||||
|
<circle cx="88%" cy="88%" r="1.3" fill="#fff" opacity="0.35"/><circle cx="95%" cy="78%" r="0.7" fill="#fff" opacity="0.6"/>
|
||||||
|
<circle cx="42%" cy="5%" r="0.9" fill="#fff" opacity="0.45"/><circle cx="55%" cy="3%" r="0.5" fill="#fff" opacity="0.55"/>
|
||||||
|
<circle cx="65%" cy="92%" r="1.1" fill="#fff" opacity="0.3"/><circle cx="35%" cy="95%" r="0.7" fill="#fff" opacity="0.5"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div class="demo">
|
||||||
|
<canvas id="constCanvas"></canvas>
|
||||||
|
<div class="scene" id="scene">
|
||||||
|
<div class="node star-main" data-node="center">
|
||||||
|
<div class="glow"></div>
|
||||||
|
<span class="label">กำไร</span>
|
||||||
|
<span class="sub">เป้าหมายของทุกธุรกิจ</span>
|
||||||
|
</div>
|
||||||
|
<div class="node c-star" data-node="mkt">
|
||||||
|
<span class="tag">Marketing</span>
|
||||||
|
<span class="desc">เพิ่มรายได้</span>
|
||||||
|
</div>
|
||||||
|
<div class="node c-star" data-node="ai">
|
||||||
|
<span class="tag">AI</span>
|
||||||
|
<span class="desc">ลดต้นทุน</span>
|
||||||
|
</div>
|
||||||
|
<div class="node c-star" data-node="biz">
|
||||||
|
<span class="tag">Business Knowledge</span>
|
||||||
|
<span class="desc">ลดความเสี่ยง</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const canvas = document.getElementById('constCanvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const scene = document.getElementById('scene');
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
const rect = canvas.parentElement.getBoundingClientRect();
|
||||||
|
canvas.width = rect.width;
|
||||||
|
canvas.height = rect.height;
|
||||||
|
}
|
||||||
|
resize(); window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
const rect = canvas.parentElement.getBoundingClientRect();
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const center = document.querySelector('[data-node="center"]');
|
||||||
|
const stars = document.querySelectorAll('.c-star');
|
||||||
|
if (!center) { requestAnimationFrame(draw); return; }
|
||||||
|
|
||||||
|
const cr = center.getBoundingClientRect();
|
||||||
|
const cx = cr.left + cr.width/2 - rect.left;
|
||||||
|
const cy = cr.top + cr.height/2 - rect.top;
|
||||||
|
|
||||||
|
// Draw constellation lines (thin, elegant)
|
||||||
|
stars.forEach((star, i) => {
|
||||||
|
const pr = star.getBoundingClientRect();
|
||||||
|
const px = pr.left + pr.width/2 - rect.left;
|
||||||
|
const py = pr.top + pr.height/2 - rect.top;
|
||||||
|
const dx = px - cx, dy = py - cy;
|
||||||
|
const dist = Math.sqrt(dx*dx + dy*dy);
|
||||||
|
|
||||||
|
// Constellation line
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(cx + (dx/dist)*75, cy + (dy/dist)*75);
|
||||||
|
ctx.lineTo(px - (dx/dist)*35, py - (dy/dist)*35);
|
||||||
|
ctx.strokeStyle = 'rgba(254,240,200,0.25)';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.setLineDash([2, 6]);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
|
||||||
|
// Tiny connecting stars along line
|
||||||
|
for (let j = 0; j < 3; j++) {
|
||||||
|
const p = 0.25 + j * 0.25;
|
||||||
|
const sx = cx + (dx/dist)*75 + dx * (1 - 75/dist - 35/dist) * p;
|
||||||
|
const sy = cy + (dy/dist)*75 + dy * (1 - 75/dist - 35/dist) * p;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(sx, sy, 1.2, 0, Math.PI*2);
|
||||||
|
ctx.fillStyle = 'rgba(255,255,255,0.3)';
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', e => {
|
||||||
|
scene.style.transform = `rotateX(${(e.clientY/window.innerHeight-0.5)*-5}deg) rotateY(${(e.clientX/window.innerWidth-0.5)*5}deg)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
draw();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
197
public/demos/e-magnetic.html
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="th">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>E: Magnetic Field — สนามแม่เหล็ก</title>
|
||||||
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Kanit:wght@400;600;800;900&display=swap');
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: 'Kanit', system-ui, sans-serif;
|
||||||
|
background: #060a12;
|
||||||
|
color: #fff; overflow: hidden;
|
||||||
|
display: flex; align-items: center; justify-content: center; min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo { position: relative; width: 700px; height: 600px; }
|
||||||
|
canvas#magCanvas { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
|
||||||
|
.scene {
|
||||||
|
position: relative; width: 100%; height: 100%;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transition: transform 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node {
|
||||||
|
position: absolute;
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
align-items: center; justify-content: center; text-align: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Magnet core */
|
||||||
|
.magnet {
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 170px; height: 170px;
|
||||||
|
background: radial-gradient(circle at 40% 35%, #3a2000, #0d0500);
|
||||||
|
border: 3px solid #fed400;
|
||||||
|
box-shadow: 0 0 0 12px rgba(254,212,0,0.1), 0 0 60px rgba(254,212,0,0.3), 0 0 140px rgba(254,180,0,0.1);
|
||||||
|
z-index: 10;
|
||||||
|
animation: magnetPulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes magnetPulse {
|
||||||
|
0%, 100% { box-shadow: 0 0 0 12px rgba(254,212,0,0.1), 0 0 60px rgba(254,212,0,0.3), 0 0 140px rgba(254,180,0,0.1); }
|
||||||
|
50% { box-shadow: 0 0 0 18px rgba(254,212,0,0.15), 0 0 80px rgba(254,212,0,0.4), 0 0 160px rgba(254,180,0,0.15); }
|
||||||
|
}
|
||||||
|
.magnet .label { font-size: 2.6rem; font-weight: 900; color: #fed400; }
|
||||||
|
.magnet .sub { font-size: 0.7rem; color: rgba(254,212,0,0.5); margin-top: 4px; font-weight: 600; }
|
||||||
|
|
||||||
|
/* Attracted nodes */
|
||||||
|
.attract {
|
||||||
|
width: 105px; height: 105px;
|
||||||
|
background: rgba(254,212,0,0.04);
|
||||||
|
border: 1.5px solid rgba(254,212,0,0.3);
|
||||||
|
box-shadow: 0 0 20px rgba(254,212,0,0.08);
|
||||||
|
z-index: 5;
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
animation: attract 3s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
.attract:nth-child(2) {
|
||||||
|
transform: translate(-50%,-50%) translate3d(-200px,-150px,-60px);
|
||||||
|
animation-name: attract1;
|
||||||
|
}
|
||||||
|
.attract:nth-child(3) {
|
||||||
|
transform: translate(-50%,-50%) translate3d(170px,-20px,-25px);
|
||||||
|
animation-name: attract2;
|
||||||
|
}
|
||||||
|
.attract:nth-child(4) {
|
||||||
|
transform: translate(-50%,-50%) translate3d(-100px,190px,-70px);
|
||||||
|
animation-name: attract3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes attract1 {
|
||||||
|
0% { transform: translate(-50%,-50%) translate3d(-220px,-160px,-80px); }
|
||||||
|
100% { transform: translate(-50%,-50%) translate3d(-185px,-140px,-50px); }
|
||||||
|
}
|
||||||
|
@keyframes attract2 {
|
||||||
|
0% { transform: translate(-50%,-50%) translate3d(185px,-25px,-35px); }
|
||||||
|
100% { transform: translate(-50%,-50%) translate3d(155px,-15px,-20px); }
|
||||||
|
}
|
||||||
|
@keyframes attract3 {
|
||||||
|
0% { transform: translate(-50%,-50%) translate3d(-120px,210px,-90px); }
|
||||||
|
100% { transform: translate(-50%,-50%) translate3d(-90px,180px,-60px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.attract .tag { font-size: 0.9rem; font-weight: 800; color: #fff; }
|
||||||
|
.attract .desc { font-size: 0.65rem; color: rgba(255,255,255,0.5); margin-top: 3px; }
|
||||||
|
|
||||||
|
.title {
|
||||||
|
position: fixed; top: 24px; left: 50%; transform: translateX(-50%);
|
||||||
|
font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.2em;
|
||||||
|
color: rgba(255,255,255,0.4); z-index: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span class="title">E. Magnetic Field — สนามแม่เหล็กดึงดูด</span>
|
||||||
|
<div class="demo">
|
||||||
|
<canvas id="magCanvas"></canvas>
|
||||||
|
<div class="scene" id="scene">
|
||||||
|
<div class="node magnet" data-node="center">
|
||||||
|
<span class="label">กำไร</span>
|
||||||
|
<span class="sub">เป้าหมายของทุกธุรกิจ</span>
|
||||||
|
</div>
|
||||||
|
<div class="node attract" data-node="mkt">
|
||||||
|
<span class="tag">Marketing</span>
|
||||||
|
<span class="desc">เพิ่มรายได้</span>
|
||||||
|
</div>
|
||||||
|
<div class="node attract" data-node="ai">
|
||||||
|
<span class="tag">AI</span>
|
||||||
|
<span class="desc">ลดต้นทุนและเวลา</span>
|
||||||
|
</div>
|
||||||
|
<div class="node attract" data-node="biz">
|
||||||
|
<span class="tag">Business<br>Knowledge</span>
|
||||||
|
<span class="desc">ลดความเสี่ยง</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const canvas = document.getElementById('magCanvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const scene = document.getElementById('scene');
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
const rect = canvas.parentElement.getBoundingClientRect();
|
||||||
|
canvas.width = rect.width;
|
||||||
|
canvas.height = rect.height;
|
||||||
|
}
|
||||||
|
resize(); window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
let t = 0;
|
||||||
|
function draw() {
|
||||||
|
t++;
|
||||||
|
const rect = canvas.parentElement.getBoundingClientRect();
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const center = document.querySelector('[data-node="center"]');
|
||||||
|
const nodes = document.querySelectorAll('.attract');
|
||||||
|
if (!center) { requestAnimationFrame(draw); return; }
|
||||||
|
|
||||||
|
const cr = center.getBoundingClientRect();
|
||||||
|
const cx = cr.left + cr.width/2 - rect.left;
|
||||||
|
const cy = cr.top + cr.height/2 - rect.top;
|
||||||
|
|
||||||
|
// Magnetic field lines (ripple waves)
|
||||||
|
for (let r = 0; r < 4; r++) {
|
||||||
|
const radius = 95 + r * 30 + (t * 0.02) % 30;
|
||||||
|
const alpha = Math.max(0, 0.25 - r * 0.06 - ((t * 0.02) % 30) / 120);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cx, cy, radius, 0, Math.PI*2);
|
||||||
|
ctx.strokeStyle = `rgba(254,212,0,${alpha})`;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection lines with magnetic field arcs
|
||||||
|
nodes.forEach((node, i) => {
|
||||||
|
const pr = node.getBoundingClientRect();
|
||||||
|
const px = pr.left + pr.width/2 - rect.left;
|
||||||
|
const py = pr.top + pr.height/2 - rect.top;
|
||||||
|
const dx = px - cx, dy = py - cy;
|
||||||
|
const dist = Math.sqrt(dx*dx + dy*dy);
|
||||||
|
|
||||||
|
// Main connection
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(cx + (dx/dist)*85, cy + (dy/dist)*85);
|
||||||
|
ctx.lineTo(px - (dx/dist)*53, py - (dy/dist)*53);
|
||||||
|
ctx.strokeStyle = `rgba(254,212,0,0.4)`;
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Field curve arcs on both sides
|
||||||
|
for (let side = -1; side <= 1; side += 2) {
|
||||||
|
const offset = side * 15;
|
||||||
|
const midX = cx + dx * 0.5 + (-dy/dist) * offset;
|
||||||
|
const midY = cy + dy * 0.5 + (dx/dist) * offset;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(cx + (dx/dist)*85, cy + (dy/dist)*85);
|
||||||
|
ctx.quadraticCurveTo(midX, midY, px - (dx/dist)*53, py - (dy/dist)*53);
|
||||||
|
ctx.strokeStyle = `rgba(254,212,0,0.1)`;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', e => {
|
||||||
|
scene.style.transform = `rotateX(${(e.clientY/window.innerHeight-0.5)*-6}deg) rotateY(${(e.clientX/window.innerWidth-0.5)*6}deg)`;
|
||||||
|
});
|
||||||
|
|
||||||
|
draw();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
75
public/demos/index.html
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="th">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Hero Design Demos</title>
|
||||||
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Kanit:wght@400;600;800;900&display=swap');
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: 'Kanit', system-ui, sans-serif;
|
||||||
|
background: #0d1117;
|
||||||
|
color: #fff;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 48px 24px;
|
||||||
|
}
|
||||||
|
h1 { font-size: 2rem; text-align: center; margin-bottom: 8px; }
|
||||||
|
h1 span { color: #fed400; }
|
||||||
|
.sub { text-align: center; color: rgba(255,255,255,0.5); margin-bottom: 40px; font-size: 1rem; }
|
||||||
|
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 20px; max-width: 1000px; margin: 0 auto; }
|
||||||
|
.card {
|
||||||
|
background: rgba(255,255,255,0.04);
|
||||||
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 32px 24px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #fff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex; flex-direction: column; gap: 12px;
|
||||||
|
}
|
||||||
|
.card:hover { background: rgba(254,212,0,0.06); border-color: rgba(254,212,0,0.3); transform: translateY(-2px); }
|
||||||
|
.card .emoji { font-size: 3rem; }
|
||||||
|
.card .name { font-size: 1.3rem; font-weight: 800; }
|
||||||
|
.card .name span { color: #fed400; }
|
||||||
|
.card .desc { font-size: 0.82rem; color: rgba(255,255,255,0.5); line-height: 1.5; }
|
||||||
|
.card .tag { display: inline-block; font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.15em; color: rgba(254,212,0,0.6); border: 1px solid rgba(254,212,0,0.2); border-radius: 20px; padding: 4px 10px; width: fit-content; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hero Section — <span>5 Design Demos</span></h1>
|
||||||
|
<p class="sub">เลือกดูแต่ละแนวคิดเพื่อเปรียบเทียบ</p>
|
||||||
|
<div class="grid">
|
||||||
|
<a href="a-orbital.html" class="card">
|
||||||
|
<div class="emoji">🌌</div>
|
||||||
|
<div class="name">A. <span>Orbital</span></div>
|
||||||
|
<div class="tag">ระบบดาวเคราะห์โคจร</div>
|
||||||
|
<div class="desc">กำไรเป็นดวงอาทิตย์เรืองแสง 3 ป้ายโคจรรอบด้วยวงแหวน orbital พร้อมหมุนอัตโนมัติ</div>
|
||||||
|
</a>
|
||||||
|
<a href="b-energyflow.html" class="card">
|
||||||
|
<div class="emoji">⚡</div>
|
||||||
|
<div class="name">B. <span>Energy Flow</span></div>
|
||||||
|
<div class="tag">กระแสพลังงาน</div>
|
||||||
|
<div class="desc">Particle วิ่งตามเส้นเชื่อมสีทอง เหมือนพลังงาน/ข้อมูลไหลเข้าสู่กำไร ดูมีชีวิตชีวา</div>
|
||||||
|
</a>
|
||||||
|
<a href="c-holographic.html" class="card">
|
||||||
|
<div class="emoji">🌀</div>
|
||||||
|
<div class="name">C. <span>Holographic 3D</span></div>
|
||||||
|
<div class="tag">Hologram Sci-Fi</div>
|
||||||
|
<div class="desc">โทน Cyan เรืองแสง พร้อม scanline และ beam เชื่อมต่อ ดูล้ำสมัย ดูแพง</div>
|
||||||
|
</a>
|
||||||
|
<a href="d-constellation.html" class="card">
|
||||||
|
<div class="emoji">✨</div>
|
||||||
|
<div class="name">D. <span>Constellation</span></div>
|
||||||
|
<div class="tag">แผนที่ดาว</div>
|
||||||
|
<div class="desc">โหนดเป็นดาวระยิบระยับ เส้นเชื่อมแบบกลุ่มดาว พื้นหลังมีดาวกระจาย ดูลึกลับสง่า</div>
|
||||||
|
</a>
|
||||||
|
<a href="e-magnetic.html" class="card">
|
||||||
|
<div class="emoji">🧲</div>
|
||||||
|
<div class="name">E. <span>Magnetic Field</span></div>
|
||||||
|
<div class="tag">สนามแม่เหล็ก</div>
|
||||||
|
<div class="desc">กำไรเป็นแม่เหล็กทรงพลัง มี ripple wave + field curve สื่อถึงการดึงดูดทุกอย่างสู่กำไร</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/favicon-180x180.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/favicon-48x48.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 655 B After Width: | Height: | Size: 1.9 KiB |
BIN
public/favicon.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
@@ -1,9 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
|
||||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
|
||||||
<style>
|
|
||||||
path { fill: #000; }
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
path { fill: #FFF; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 749 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="#0866FF" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Facebook</title><path d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 558 B |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
|
||||||
<g transform="matrix(1,0,0,1,-6,-6)">
|
|
||||||
<path d="M12.5,42L35.5,42C39.09,42 42,39.09 42,35.5L42,12.5C42,8.91 39.09,6 35.5,6L12.5,6C8.91,6 6,8.91 6,12.5L6,35.5C6,39.09 8.91,42 12.5,42Z" style="fill:rgb(0,195,0);fill-rule:nonzero;"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(1,0,0,1,-6,-6)">
|
|
||||||
<path d="M37.113,22.417C37.113,16.552 31.233,11.78 24.006,11.78C16.779,11.78 10.898,16.552 10.898,22.417C10.898,27.675 15.561,32.079 21.86,32.912C22.287,33.004 22.868,33.194 23.015,33.558C23.147,33.889 23.101,34.408 23.057,34.743C23.057,34.743 22.904,35.668 22.87,35.865C22.813,36.196 22.607,37.161 24.005,36.572C25.404,35.983 31.553,32.127 34.303,28.961L34.302,28.961C36.203,26.879 37.113,24.764 37.113,22.417ZM18.875,25.907L16.271,25.907C15.892,25.907 15.584,25.599 15.584,25.219L15.584,20.01C15.584,19.631 15.892,19.323 16.271,19.323C16.65,19.323 16.958,19.631 16.958,20.01L16.958,24.531L18.875,24.531C19.254,24.531 19.562,24.839 19.562,25.218C19.562,25.598 19.254,25.907 18.875,25.907ZM21.568,25.219C21.568,25.598 21.26,25.907 20.881,25.907C20.502,25.907 20.194,25.599 20.194,25.219L20.194,20.01C20.194,19.631 20.502,19.323 20.881,19.323C21.26,19.323 21.568,19.631 21.568,20.01L21.568,25.219ZM27.838,25.219C27.838,25.516 27.65,25.778 27.368,25.871C27.297,25.895 27.223,25.907 27.15,25.907C26.935,25.907 26.73,25.804 26.601,25.632L23.932,21.997L23.932,25.219C23.932,25.598 23.624,25.907 23.244,25.907C22.865,25.907 22.556,25.599 22.556,25.219L22.556,20.01C22.556,19.714 22.745,19.452 23.026,19.358C23.097,19.334 23.17,19.323 23.244,19.323C23.458,19.323 23.664,19.426 23.793,19.598L26.463,23.233L26.463,20.01C26.463,19.631 26.772,19.323 27.151,19.323C27.53,19.323 27.838,19.631 27.838,20.01L27.838,25.219ZM32.052,21.927C32.431,21.927 32.74,22.235 32.74,22.615C32.74,22.994 32.432,23.302 32.052,23.302L30.135,23.302L30.135,24.532L32.052,24.532C32.431,24.532 32.74,24.84 32.74,25.219C32.74,25.598 32.431,25.907 32.052,25.907L29.448,25.907C29.07,25.907 28.761,25.599 28.761,25.219L28.761,20.011C28.761,19.632 29.069,19.324 29.448,19.324L32.052,19.324C32.431,19.324 32.74,19.632 32.74,20.011C32.74,20.39 32.432,20.698 32.052,20.698L30.135,20.698L30.135,21.928L32.052,21.928L32.052,21.927Z" style="fill:white;fill-rule:nonzero;"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path fill="#00c300" d="M12.5,42h23c3.59,0,6.5-2.91,6.5-6.5v-23C42,8.91,39.09,6,35.5,6h-23C8.91,6,6,8.91,6,12.5v23C6,39.09,8.91,42,12.5,42z"/><path fill="#fff" d="M37.113,22.417c0-5.865-5.88-10.637-13.107-10.637s-13.108,4.772-13.108,10.637c0,5.258,4.663,9.662,10.962,10.495c0.427,0.092,1.008,0.282,1.155,0.646c0.132,0.331,0.086,0.85,0.042,1.185c0,0-0.153,0.925-0.187,1.122c-0.057,0.331-0.263,1.296,1.135,0.707c1.399-0.589,7.548-4.445,10.298-7.611h-0.001C36.203,26.879,37.113,24.764,37.113,22.417z M18.875,25.907h-2.604c-0.379,0-0.687-0.308-0.687-0.688V20.01c0-0.379,0.308-0.687,0.687-0.687c0.379,0,0.687,0.308,0.687,0.687v4.521h1.917c0.379,0,0.687,0.308,0.687,0.687C19.562,25.598,19.254,25.907,18.875,25.907z M21.568,25.219c0,0.379-0.308,0.688-0.687,0.688s-0.687-0.308-0.687-0.688V20.01c0-0.379,0.308-0.687,0.687-0.687s0.687,0.308,0.687,0.687V25.219z M27.838,25.219c0,0.297-0.188,0.559-0.47,0.652c-0.071,0.024-0.145,0.036-0.218,0.036c-0.215,0-0.42-0.103-0.549-0.275l-2.669-3.635v3.222c0,0.379-0.308,0.688-0.688,0.688c-0.379,0-0.688-0.308-0.688-0.688V20.01c0-0.296,0.189-0.558,0.47-0.652c0.071-0.024,0.144-0.035,0.218-0.035c0.214,0,0.42,0.103,0.549,0.275l2.67,3.635V20.01c0-0.379,0.309-0.687,0.688-0.687c0.379,0,0.687,0.308,0.687,0.687V25.219z M32.052,21.927c0.379,0,0.688,0.308,0.688,0.688c0,0.379-0.308,0.687-0.688,0.687h-1.917v1.23h1.917c0.379,0,0.688,0.308,0.688,0.687c0,0.379-0.309,0.688-0.688,0.688h-2.604c-0.378,0-0.687-0.308-0.687-0.688v-2.603c0-0.001,0-0.001,0-0.001c0,0,0-0.001,0-0.001v-2.601c0-0.001,0-0.001,0-0.002c0-0.379,0.308-0.687,0.687-0.687h2.604c0.379,0,0.688,0.308,0.688,0.687s-0.308,0.687-0.688,0.687h-1.917v1.23H32.052z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#0A66C2"><title>LinkedIn</title><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 626 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="#000000" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>X</title><path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 315 B |
1
public/images/astro-logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Astro</title><path d="M8.358 20.162c-1.186-1.07-1.532-3.316-1.038-4.944.856 1.026 2.043 1.352 3.272 1.535 1.897.283 3.76.177 5.522-.678.202-.098.388-.229.608-.36.166.473.209.95.151 1.437-.14 1.185-.738 2.1-1.688 2.794-.38.277-.782.525-1.175.787-1.205.804-1.531 1.747-1.078 3.119l.044.148a3.158 3.158 0 0 1-1.407-1.188 3.31 3.31 0 0 1-.544-1.815c-.004-.32-.004-.642-.048-.958-.106-.769-.472-1.113-1.161-1.133-.707-.02-1.267.411-1.415 1.09-.012.053-.028.104-.045.165h.002zm-5.961-4.445s3.24-1.575 6.49-1.575l2.451-7.565c.092-.366.36-.614.662-.614.302 0 .57.248.662.614l2.45 7.565c3.85 0 6.491 1.575 6.491 1.575L16.088.727C15.93.285 15.663 0 15.303 0H8.697c-.36 0-.615.285-.784.727l-5.516 14.99z"/></svg>
|
||||||
|
After Width: | Height: | Size: 779 B |
BIN
public/images/backgrounds/liquid-glass-bg.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
28
public/images/blog-ai-unsplash.jpg
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Application Error</title>
|
||||||
|
<style media="screen">
|
||||||
|
html,body,iframe {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,body {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<iframe src="https://www.herokucdn.com/error-pages/application-error.html"></iframe>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
public/images/blog-ai.jpg
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
28
public/images/blog-automation-unsplash.jpg
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Application Error</title>
|
||||||
|
<style media="screen">
|
||||||
|
html,body,iframe {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,body {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<iframe src="https://www.herokucdn.com/error-pages/application-error.html"></iframe>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
public/images/blog-automation.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
28
public/images/blog-marketing-unsplash.jpg
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Application Error</title>
|
||||||
|
<style media="screen">
|
||||||
|
html,body,iframe {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,body {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<iframe src="https://www.herokucdn.com/error-pages/application-error.html"></iframe>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
public/images/blog-marketing.jpg
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
1
public/images/blog-placeholder.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns='http://www.w3.org/2000/svg' width='800' height='420'><rect width='800' height='420' fill='#fed400' opacity='0.12'/><rect x='40' y='40' width='720' height='340' rx='20' fill='none' stroke='#fed400' stroke-width='2' opacity='0.4'/><text x='400' y='160' text-anchor='middle' font-family='sans-serif' font-size='64' font-weight='900' fill='#13120d' opacity='0.3'>More</text><text x='400' y='240' text-anchor='middle' font-family='sans-serif' font-size='64' font-weight='900' fill='#13120d' opacity='0.3'>mini</text><text x='400' y='320' text-anchor='middle' font-family='sans-serif' font-size='64' font-weight='900' fill='#13120d' opacity='0.3'>More</text></svg>
|
||||||
|
After Width: | Height: | Size: 671 B |
28
public/images/blog-website-tmp.jpg
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Application Error</title>
|
||||||
|
<style media="screen">
|
||||||
|
html,body,iframe {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,body {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<iframe src="https://www.herokucdn.com/error-pages/application-error.html"></iframe>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
public/images/blog-website.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 965 KiB |
|
After Width: | Height: | Size: 832 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 992 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 911 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 745 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 996 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 693 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 670 KiB |
|
After Width: | Height: | Size: 768 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 815 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1016 KiB |
|
After Width: | Height: | Size: 887 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 899 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 731 KiB |
|
After Width: | Height: | Size: 1011 KiB |
|
After Width: | Height: | Size: 980 KiB |
|
After Width: | Height: | Size: 600 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 796 KiB |
|
After Width: | Height: | Size: 1.2 MiB |