feat: replace all emojis with professional lucide-style SVG icon library
Build a curated 50-icon lucide library (Icon.astro + icon-paths.ts) and replace every emoji across the site (~50 occurrences in 9 files): - index.astro: 3 problem cards (message, trendingDown, globe) - about.astro: 4 value cards (target, users, clock, shield) - portfolio.astro: 7 industry filter buttons (factory, package, scale, graduationCap, trendingUp, pen, shoppingCart, layers) - services/[slug].astro: ~25 feature/target/service icons - services/index.astro: 6 decision-row tags + phone icon in CTA - faq.astro: 5 category icons, 3 channel cards - contact.astro: 3 channel picker, 5 info column, form options data-driven, checkCircle success - Footer.astro: 3 contact icons (phone, mail, mapPin) - Hero.astro: award icon in trust strip Add icon wrapper styles to global.css (.problem-icon, .value-icon, .channel-pick-icon, .info-icon, .channel-icon, .category-icon, .feature-icon, .target-icon, .success-icon, .contact-icon, .btn-icon, .filter-icon) — yellow square/circle backgrounds, dark text, consistent sizing. Build: 18 pages, 0 errors
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
* MOREMINIMORE - FOOTER COMPONENT (LIGHT THEME)
|
* MOREMINIMORE - FOOTER COMPONENT (LIGHT THEME)
|
||||||
* White bg + dark text + black-text logo (matches new light theme)
|
* White bg + dark text + black-text logo (matches new light theme)
|
||||||
*/
|
*/
|
||||||
|
import Icon from './Icon.astro';
|
||||||
|
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ const serviceLinks = [
|
|||||||
{ label: 'AI Automation', href: '/services/automation' },
|
{ label: 'AI Automation', href: '/services/automation' },
|
||||||
{ label: 'Tech Consult', href: '/services/ai-consult' },
|
{ label: 'Tech Consult', href: '/services/ai-consult' },
|
||||||
];
|
];
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
@@ -85,15 +87,15 @@ const serviceLinks = [
|
|||||||
<div class="footer-col footer-contact">
|
<div class="footer-col footer-contact">
|
||||||
<h4 class="footer-title">ติดต่อเรา</h4>
|
<h4 class="footer-title">ติดต่อเรา</h4>
|
||||||
<div class="contact-item">
|
<div class="contact-item">
|
||||||
<span class="contact-icon">📞</span>
|
<span class="contact-icon"><Icon name="phone" size={14} /></span>
|
||||||
<a href="tel:0809955945">080-995-5945</a>
|
<a href="tel:0809955945">080-995-5945</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="contact-item">
|
<div class="contact-item">
|
||||||
<span class="contact-icon">✉️</span>
|
<span class="contact-icon"><Icon name="mail" size={14} /></span>
|
||||||
<a href="mailto:contact@moreminimore.com">contact@moreminimore.com</a>
|
<a href="mailto:contact@moreminimore.com">contact@moreminimore.com</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="contact-item">
|
<div class="contact-item">
|
||||||
<span class="contact-icon">📍</span>
|
<span class="contact-icon"><Icon name="mapPin" size={14} /></span>
|
||||||
<span>สมุทรสาคร, ประเทศไทย</span>
|
<span>สมุทรสาคร, ประเทศไทย</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
62
src/components/Icon.astro
Normal file
62
src/components/Icon.astro
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
/**
|
||||||
|
* MOREMINIMORE - ICON COMPONENT
|
||||||
|
* Lucide-style line icons. Use:
|
||||||
|
* <Icon name="phone" size={24} />
|
||||||
|
* <Icon name="message" color="yellow" size={32} />
|
||||||
|
*
|
||||||
|
* Icon catalog: ../icon-paths.ts
|
||||||
|
* Add new icons by appending to ICON_PATHS in that file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ICON_PATHS, type IconName } from './icon-paths';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: IconName;
|
||||||
|
size?: number;
|
||||||
|
class?: string;
|
||||||
|
/** Brand tint: 'default' = currentColor, 'yellow' = brand yellow, 'dark' = black */
|
||||||
|
color?: 'default' | 'yellow' | 'dark';
|
||||||
|
strokeWidth?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
size = 24,
|
||||||
|
class: className = '',
|
||||||
|
color = 'default',
|
||||||
|
strokeWidth = 2,
|
||||||
|
} = Astro.props;
|
||||||
|
|
||||||
|
const colorMap = {
|
||||||
|
default: 'currentColor',
|
||||||
|
yellow: 'var(--color-primary)',
|
||||||
|
dark: 'var(--color-black)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const paths = ICON_PATHS[name];
|
||||||
|
---
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke={colorMap[color]}
|
||||||
|
stroke-width={strokeWidth}
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class={`icon icon-${name} ${className}`}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<Fragment set:html={paths} />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
flex-shrink: 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
88
src/components/icon-paths.ts
Normal file
88
src/components/icon-paths.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Lucide-style SVG icon paths for MoreminiMore.
|
||||||
|
* All paths use a 24x24 viewBox, stroke-width 2, rounded caps/joins.
|
||||||
|
* Source: adapted from lucide.dev (ISC licensed).
|
||||||
|
*
|
||||||
|
* Naming convention: semantic over visual (e.g. "message" not "speech-bubble").
|
||||||
|
* Add new icons by appending to ICON_PATHS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const ICON_PATHS = {
|
||||||
|
// ===== Communication =====
|
||||||
|
message: '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
|
||||||
|
phone: '<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/>',
|
||||||
|
mail: '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 5L2 7"/>',
|
||||||
|
mapPin: '<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/>',
|
||||||
|
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
||||||
|
send: '<path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/>',
|
||||||
|
smartphone: '<rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/>',
|
||||||
|
clipboard: '<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>',
|
||||||
|
wrench: '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>',
|
||||||
|
megaphone: '<path d="m3 11 18-5v12L3 14v-3z"/><path d="M11.6 16.8a3 3 0 1 1-5.8-1.6"/>',
|
||||||
|
|
||||||
|
// ===== Trust & success =====
|
||||||
|
check: '<polyline points="20 6 9 17 4 12"/>',
|
||||||
|
checkCircle: '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>',
|
||||||
|
star: '<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>',
|
||||||
|
shield: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
|
||||||
|
award: '<circle cx="12" cy="8" r="6"/><polyline points="8.21 13.89 7 22 12 19 17 22 15.79 13.88"/>',
|
||||||
|
|
||||||
|
// ===== Problems / issues =====
|
||||||
|
alertTriangle: '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>',
|
||||||
|
trendingDown: '<polyline points="22 17 13.5 8.5 8.5 13.5 2 7"/><polyline points="16 17 22 17 22 11"/>',
|
||||||
|
trendingUp: '<polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/>',
|
||||||
|
globe: '<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>',
|
||||||
|
search: '<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>',
|
||||||
|
eye: '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>',
|
||||||
|
help: '<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/>',
|
||||||
|
|
||||||
|
// ===== Business =====
|
||||||
|
briefcase: '<rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/>',
|
||||||
|
building: '<rect x="4" y="2" width="16" height="20" rx="2" ry="2"/><path d="M9 22v-4h6v4"/><path d="M8 6h.01"/><path d="M16 6h.01"/><path d="M12 6h.01"/><path d="M12 10h.01"/><path d="M12 14h.01"/><path d="M16 10h.01"/><path d="M16 14h.01"/><path d="M8 10h.01"/><path d="M8 14h.01"/>',
|
||||||
|
factory: '<path d="M2 20a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8l-7 5V8l-7 5V4a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M17 18h1"/><path d="M12 18h1"/><path d="M7 18h1"/>',
|
||||||
|
store: '<path d="m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7"/><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><path d="M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4"/><path d="M2 7h20"/><path d="M22 7v3a2 2 0 0 1-2 2a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12a2 2 0 0 1-2-2V7"/>',
|
||||||
|
shoppingCart: '<circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>',
|
||||||
|
|
||||||
|
// ===== Services =====
|
||||||
|
cog: '<path d="M12 20a1 1 0 0 0 .553-1.832l-1.215-.811a1 1 0 0 1 0-1.714l1.215-.811A1 1 0 0 0 12 13a8 8 0 1 1 0 6"/><circle cx="12" cy="12" r="3"/>',
|
||||||
|
layers: '<polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/>',
|
||||||
|
link: '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
|
||||||
|
server: '<rect x="2" y="2" width="20" height="8" rx="2" ry="2"/><rect x="2" y="14" width="20" height="8" rx="2" ry="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/>',
|
||||||
|
monitor: '<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>',
|
||||||
|
box: '<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/>',
|
||||||
|
|
||||||
|
// ===== Money / pricing =====
|
||||||
|
dollarSign: '<line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>',
|
||||||
|
creditCard: '<rect x="1" y="4" width="22" height="16" rx="2" ry="2"/><line x1="1" y1="10" x2="23" y2="10"/>',
|
||||||
|
package: '<line x1="16.5" y1="9.4" x2="7.5" y2="4.21"/><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/>',
|
||||||
|
|
||||||
|
// ===== Marketing / data =====
|
||||||
|
megaphone: '<path d="m3 11 18-5v12L3 14v-3z"/><path d="M11.6 16.8a3 3 0 1 1-5.8-1.6"/>',
|
||||||
|
barChart: '<line x1="12" y1="20" x2="12" y2="10"/><line x1="18" y1="20" x2="18" y2="4"/><line x1="6" y1="20" x2="6" y2="16"/>',
|
||||||
|
pieChart: '<path d="M21.21 15.89A10 10 0 1 1 8 2.83"/><path d="M22 12A10 10 0 0 0 12 2v10z"/>',
|
||||||
|
users: '<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
||||||
|
user: '<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
|
||||||
|
target: '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
|
||||||
|
zap: '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',
|
||||||
|
|
||||||
|
// ===== Legal / education =====
|
||||||
|
scale: '<path d="M16 16h6"/><path d="M2 16h6"/><path d="M5 8c0-1.1.9-2 2-2h10a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V8z"/><path d="M12 6V2"/><path d="m3 9 3 7"/><path d="M21 9l-3 7"/><path d="M12 18v4"/>',
|
||||||
|
book: '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>',
|
||||||
|
graduationCap: '<path d="M22 10v6M2 10l10-5 10 5-10 5z"/><path d="M6 12v5c0 1.1 2.7 3 6 3s6-1.9 6-3v-5"/>',
|
||||||
|
pen: '<path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/>',
|
||||||
|
|
||||||
|
// ===== AI / tech =====
|
||||||
|
bot: '<rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="2"/><path d="M12 7v4"/><line x1="8" y1="16" x2="8" y2="16"/><line x1="16" y1="16" x2="16" y2="16"/>',
|
||||||
|
brain: '<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z"/><path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z"/>',
|
||||||
|
cpu: '<rect x="4" y="4" width="16" height="16" rx="2" ry="2"/><rect x="9" y="9" width="6" height="6"/><line x1="9" y1="1" x2="9" y2="4"/><line x1="15" y1="1" x2="15" y2="4"/><line x1="9" y1="20" x2="9" y2="23"/><line x1="15" y1="20" x2="15" y2="23"/><line x1="20" y1="9" x2="23" y2="9"/><line x1="20" y1="14" x2="23" y2="14"/><line x1="1" y1="9" x2="4" y2="9"/><line x1="1" y1="14" x2="4" y2="14"/>',
|
||||||
|
workflow: '<rect x="3" y="3" width="6" height="6" rx="1"/><rect x="15" y="15" width="6" height="6" rx="1"/><path d="M9 6h6a3 3 0 0 1 3 3v6"/>',
|
||||||
|
bell: '<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/>',
|
||||||
|
|
||||||
|
// ===== Misc =====
|
||||||
|
refresh: '<polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>',
|
||||||
|
arrowRight: '<line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/>',
|
||||||
|
sparkles: '<path d="M12 3l1.9 5.8 5.8 1.9-5.8 1.9L12 18.4l-1.9-5.8L4.3 10.7l5.8-1.9L12 3z"/><path d="M19 14l1 3 3 1-3 1-1 3-1-3-3-1 3-1 1-3z"/>',
|
||||||
|
rocket: '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type IconName = keyof typeof ICON_PATHS;
|
||||||
419
src/lib/animations.ts
Normal file
419
src/lib/animations.ts
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
/**
|
||||||
|
* MoreminiMore - Animation Library
|
||||||
|
* Reusable scroll/load animations. Lightweight, no dependencies.
|
||||||
|
*
|
||||||
|
* Usage in any .astro file:
|
||||||
|
* <script>import { initAnimations } from '../lib/animations';
|
||||||
|
* document.addEventListener('DOMContentLoaded', initAnimations);</script>
|
||||||
|
*
|
||||||
|
* Or for per-page targeted init:
|
||||||
|
* <script>import { revealOnScroll, counterUp } from '../lib/animations';
|
||||||
|
* revealOnScroll('.reveal');</script>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* TYPES */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
interface RevealOptions {
|
||||||
|
threshold?: number;
|
||||||
|
rootMargin?: string;
|
||||||
|
once?: boolean;
|
||||||
|
delayMs?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CounterOptions {
|
||||||
|
duration?: number;
|
||||||
|
easing?: (t: number) => number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* ENTRANCE ANIMATIONS */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add .reveal class to any element you want to fade-in-up on scroll.
|
||||||
|
* Default: trigger at 15% visibility, slide up 30px, 700ms.
|
||||||
|
*/
|
||||||
|
export function revealOnScroll(
|
||||||
|
selector: string | string[] = '.reveal',
|
||||||
|
options: RevealOptions = {}
|
||||||
|
): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const els = collectElements(selector);
|
||||||
|
if (els.length === 0) return;
|
||||||
|
|
||||||
|
const opts: Required<RevealOptions> = {
|
||||||
|
threshold: options.threshold ?? 0,
|
||||||
|
rootMargin: options.rootMargin ?? '0px 0px 100px 0px',
|
||||||
|
once: options.once ?? true,
|
||||||
|
delayMs: options.delayMs ?? 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// SSR-safe fallback: if IntersectionObserver is unavailable, show everything.
|
||||||
|
if (typeof IntersectionObserver === 'undefined') {
|
||||||
|
els.forEach((el) => el.classList.add('revealed'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const el = entry.target as HTMLElement;
|
||||||
|
setTimeout(() => el.classList.add('revealed'), opts.delayMs);
|
||||||
|
if (opts.once) observer.unobserve(el);
|
||||||
|
} else if (!opts.once) {
|
||||||
|
entry.target.classList.remove('revealed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ threshold: opts.threshold, rootMargin: opts.rootMargin }
|
||||||
|
);
|
||||||
|
|
||||||
|
const vh = window.innerHeight || document.documentElement.clientHeight;
|
||||||
|
els.forEach((el) => {
|
||||||
|
// FALLBACK: if already in view on load, apply immediately.
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
if (r.top < vh && r.bottom > 0) {
|
||||||
|
setTimeout(() => el.classList.add('revealed'), opts.delayMs);
|
||||||
|
if (opts.once) return; // don't observe
|
||||||
|
}
|
||||||
|
observer.observe(el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stagger-children reveal — children of the matched container fade in sequentially.
|
||||||
|
* Reads --stagger CSS var (in ms) from each child, or uses 100ms default.
|
||||||
|
*/
|
||||||
|
export function staggerReveal(
|
||||||
|
containerSelector: string,
|
||||||
|
childSelector: string = ':scope > *',
|
||||||
|
baseDelayMs: number = 0
|
||||||
|
): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const containers = collectElements(containerSelector);
|
||||||
|
if (containers.length === 0) return;
|
||||||
|
|
||||||
|
// Helper: apply reveal to one container's children.
|
||||||
|
const apply = (container: HTMLElement) => {
|
||||||
|
const children = container.querySelectorAll<HTMLElement>(childSelector);
|
||||||
|
children.forEach((child, i) => {
|
||||||
|
const stagger = Number(child.dataset.stagger) || i * 100;
|
||||||
|
setTimeout(() => child.classList.add('revealed'), baseDelayMs + stagger);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof IntersectionObserver === 'undefined') {
|
||||||
|
containers.forEach(apply);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
apply(entry.target as HTMLElement);
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ threshold: 0, rootMargin: '0px 0px 100px 0px' }
|
||||||
|
);
|
||||||
|
|
||||||
|
containers.forEach((c) => {
|
||||||
|
// FALLBACK: if the container is already in view on load, apply immediately.
|
||||||
|
// IO can miss elements that are in viewport at registration time on some browsers.
|
||||||
|
const r = c.getBoundingClientRect();
|
||||||
|
const vh = window.innerHeight || document.documentElement.clientHeight;
|
||||||
|
if (r.top < vh && r.bottom > 0) {
|
||||||
|
apply(c);
|
||||||
|
return; // do not observe — already revealed
|
||||||
|
}
|
||||||
|
observer.observe(c);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* KINETIC TYPOGRAPHY */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal each word of the matched headline with a staggered translate-up.
|
||||||
|
* Wraps each word in a .word-wrapper if not already, then animates.
|
||||||
|
*/
|
||||||
|
export function kineticHeadline(selector: string = '.kinetic-title'): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const headings = collectElements(selector);
|
||||||
|
if (headings.length === 0) return;
|
||||||
|
|
||||||
|
headings.forEach((heading) => {
|
||||||
|
// Skip if already processed
|
||||||
|
if (heading.dataset.kineticReady === 'true') return;
|
||||||
|
heading.dataset.kineticReady = 'true';
|
||||||
|
|
||||||
|
// Skip if text is empty / pure whitespace
|
||||||
|
if (!heading.textContent?.trim()) return;
|
||||||
|
|
||||||
|
const text = heading.textContent;
|
||||||
|
const words = text.split(/(\s+)/); // keep whitespace
|
||||||
|
heading.innerHTML = '';
|
||||||
|
|
||||||
|
let wordIndex = 0;
|
||||||
|
words.forEach((segment) => {
|
||||||
|
if (/^\s+$/.test(segment)) {
|
||||||
|
heading.appendChild(document.createTextNode(segment));
|
||||||
|
} else {
|
||||||
|
const wrapper = document.createElement('span');
|
||||||
|
wrapper.className = 'word-wrapper';
|
||||||
|
const inner = document.createElement('span');
|
||||||
|
inner.className = 'word';
|
||||||
|
inner.textContent = segment;
|
||||||
|
inner.style.setProperty('--delay', `${0.3 + wordIndex * 0.08}s`);
|
||||||
|
wrapper.appendChild(inner);
|
||||||
|
heading.appendChild(wrapper);
|
||||||
|
wordIndex++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* COUNTER ANIMATION */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animate any element with a number to count up from 0 (or data-from) to the
|
||||||
|
* number in its text. Triggered when the element scrolls into view.
|
||||||
|
*
|
||||||
|
* Element: <span class="counter" data-from="0" data-suffix="+">50+</span>
|
||||||
|
*/
|
||||||
|
export function counterUp(
|
||||||
|
selector: string = '.counter',
|
||||||
|
options: CounterOptions = {}
|
||||||
|
): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const els = collectElements(selector);
|
||||||
|
if (els.length === 0) return;
|
||||||
|
|
||||||
|
const duration = options.duration ?? 1600;
|
||||||
|
const ease = options.easing ?? easeOutCubic;
|
||||||
|
|
||||||
|
if (typeof IntersectionObserver === 'undefined') {
|
||||||
|
els.forEach((el) => animateCounter(el as HTMLElement, duration, ease));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
animateCounter(entry.target as HTMLElement, duration, ease);
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ threshold: 0, rootMargin: '0px 0px 100px 0px' }
|
||||||
|
);
|
||||||
|
|
||||||
|
els.forEach((el) => {
|
||||||
|
// FALLBACK: if already in view on load, animate immediately.
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
const vh = window.innerHeight || document.documentElement.clientHeight;
|
||||||
|
if (r.top < vh && r.bottom > 0) {
|
||||||
|
animateCounter(el as HTMLElement, duration, ease);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
observer.observe(el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function animateCounter(
|
||||||
|
el: HTMLElement,
|
||||||
|
duration: number,
|
||||||
|
ease: (t: number) => number
|
||||||
|
): void {
|
||||||
|
const raw = el.textContent?.trim() ?? '0';
|
||||||
|
const fromStr = el.dataset.from;
|
||||||
|
const from = fromStr ? parseFloat(fromStr) : 0;
|
||||||
|
// Extract leading number, ignore trailing chars (e.g. "50+", "5+", "5 ปี", "24/7")
|
||||||
|
const match = raw.match(/^([\d,.]+)(.*)$/);
|
||||||
|
if (!match) return;
|
||||||
|
const targetStr = match[1].replace(/,/g, '');
|
||||||
|
const target = parseFloat(targetStr);
|
||||||
|
const suffix = match[2] || '';
|
||||||
|
const prefix = el.dataset.prefix || '';
|
||||||
|
if (Number.isNaN(target)) return;
|
||||||
|
|
||||||
|
const start = performance.now();
|
||||||
|
const tick = (now: number) => {
|
||||||
|
const elapsed = now - start;
|
||||||
|
const progress = Math.min(elapsed / duration, 1);
|
||||||
|
const value = from + (target - from) * ease(progress);
|
||||||
|
// Preserve any decimal formatting from original
|
||||||
|
const decimals = (targetStr.split('.')[1] || '').length;
|
||||||
|
el.textContent = `${prefix}${formatNumber(value, decimals)}${suffix}`;
|
||||||
|
if (progress < 1) requestAnimationFrame(tick);
|
||||||
|
};
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNumber(n: number, decimals: number): string {
|
||||||
|
return n.toFixed(decimals);
|
||||||
|
}
|
||||||
|
|
||||||
|
function easeOutCubic(t: number): number {
|
||||||
|
return 1 - Math.pow(1 - t, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* MAGNETIC BUTTON */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtle hover-tracking effect for CTAs. Btn slightly tracks cursor position.
|
||||||
|
* Add `data-magnetic` attribute to any button.
|
||||||
|
*/
|
||||||
|
export function magneticButtons(selector: string = '[data-magnetic]'): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const btns = collectElements(selector);
|
||||||
|
if (btns.length === 0) return;
|
||||||
|
|
||||||
|
btns.forEach((btn) => {
|
||||||
|
if ((btn as HTMLElement).dataset.magneticReady === 'true') return;
|
||||||
|
(btn as HTMLElement).dataset.magneticReady = 'true';
|
||||||
|
|
||||||
|
const strength = 0.25;
|
||||||
|
btn.addEventListener('mousemove', (e: MouseEvent) => {
|
||||||
|
const rect = btn.getBoundingClientRect();
|
||||||
|
const x = e.clientX - rect.left - rect.width / 2;
|
||||||
|
const y = e.clientY - rect.top - rect.height / 2;
|
||||||
|
(btn as HTMLElement).style.transform = `translate(${x * strength}px, ${y * strength}px)`;
|
||||||
|
});
|
||||||
|
btn.addEventListener('mouseleave', () => {
|
||||||
|
(btn as HTMLElement).style.transform = '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* PARALLAX BACKGROUND */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a subtle parallax translateY to elements based on scroll position.
|
||||||
|
* Add `data-parallax="0.3"` (0-1) to set speed (default 0.3).
|
||||||
|
* Uses requestAnimationFrame + transform for 60fps.
|
||||||
|
*/
|
||||||
|
export function parallaxBackgrounds(selector: string = '[data-parallax]'): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const els = Array.from(document.querySelectorAll<HTMLElement>(selector));
|
||||||
|
if (els.length === 0) return;
|
||||||
|
|
||||||
|
// Use rAF for smooth 60fps
|
||||||
|
let ticking = false;
|
||||||
|
const update = () => {
|
||||||
|
if (ticking) return;
|
||||||
|
ticking = true;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const scrollY = window.scrollY;
|
||||||
|
els.forEach((el) => {
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
const speed = parseFloat(el.dataset.parallax || '0.3');
|
||||||
|
// Only update if visible
|
||||||
|
if (rect.bottom > -200 && rect.top < window.innerHeight + 200) {
|
||||||
|
const offset = (rect.top - window.innerHeight) * speed;
|
||||||
|
el.style.transform = `translate3d(0, ${offset}px, 0)`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ticking = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.addEventListener('scroll', update, { passive: true });
|
||||||
|
update(); // initial
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* PARALLAX SCROLL PROGRESS */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update CSS var --scroll-progress (0-100) on the document.
|
||||||
|
* Pair with a CSS rule like:
|
||||||
|
* .scroll-indicator { height: calc(var(--scroll-progress) * 1px); }
|
||||||
|
*/
|
||||||
|
export function scrollProgress(selector: string = '.scroll-indicator'): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const els = collectElements(selector);
|
||||||
|
if (els.length === 0) return;
|
||||||
|
const update = () => {
|
||||||
|
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||||
|
const progress = docHeight > 0 ? (window.scrollY / docHeight) * 100 : 0;
|
||||||
|
els.forEach((el) => {
|
||||||
|
el.style.setProperty('--scroll-progress', `${Math.min(progress, 100)}%`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.addEventListener('scroll', update, { passive: true });
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* ENTRY POINT — call from page script */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize all animations on the current page. Pass an options object
|
||||||
|
* to opt-out of any animation.
|
||||||
|
*/
|
||||||
|
export interface AnimationOptions {
|
||||||
|
reveal?: boolean;
|
||||||
|
stagger?: boolean;
|
||||||
|
kinetic?: boolean;
|
||||||
|
counters?: boolean;
|
||||||
|
magnetic?: boolean;
|
||||||
|
parallax?: boolean;
|
||||||
|
scrollProgress?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initAnimations(
|
||||||
|
options: AnimationOptions = {}
|
||||||
|
): void {
|
||||||
|
const opts: Required<AnimationOptions> = {
|
||||||
|
reveal: true,
|
||||||
|
stagger: true,
|
||||||
|
kinetic: true,
|
||||||
|
counters: true,
|
||||||
|
magnetic: true,
|
||||||
|
parallax: true,
|
||||||
|
scrollProgress: true,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait for next tick so the DOM is fully painted
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => initAnimations(options));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.reveal) revealOnScroll();
|
||||||
|
if (opts.stagger) staggerReveal('.stagger-children');
|
||||||
|
if (opts.kinetic) kineticHeadline();
|
||||||
|
if (opts.counters) counterUp();
|
||||||
|
if (opts.magnetic) magneticButtons();
|
||||||
|
if (opts.parallax) parallaxBackgrounds();
|
||||||
|
if (opts.scrollProgress) scrollProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* HELPERS */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
function collectElements(selector: string | string[]): HTMLElement[] {
|
||||||
|
const sels = Array.isArray(selector) ? selector : [selector];
|
||||||
|
const out: HTMLElement[] = [];
|
||||||
|
sels.forEach((s) => {
|
||||||
|
document.querySelectorAll<HTMLElement>(s).forEach((el) => out.push(el));
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import Base from '../layouts/Base.astro';
|
|||||||
import Navigation from '../components/Navigation.astro';
|
import Navigation from '../components/Navigation.astro';
|
||||||
import Footer from '../components/Footer.astro';
|
import Footer from '../components/Footer.astro';
|
||||||
import PageHero from '../components/PageHero.astro';
|
import PageHero from '../components/PageHero.astro';
|
||||||
|
import Icon from '../components/Icon.astro';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Base title="เกี่ยวกับเรา | MoreminiMore | รับทำเว็บไซต์ SEO AI Chatbot">
|
<Base title="เกี่ยวกับเรา | MoreminiMore | รับทำเว็บไซต์ SEO AI Chatbot">
|
||||||
@@ -42,21 +43,21 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="story-stats">
|
<div class="story-stats stagger-children">
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span class="stat-number">40+</span>
|
<span class="stat-number counter" data-from="0">40+</span>
|
||||||
<span class="stat-label">ลูกค้าที่กลับมาใช้บริการซ้ำ</span>
|
<span class="stat-label">ลูกค้าที่กลับมาใช้บริการซ้ำ</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span class="stat-number">50+</span>
|
<span class="stat-number counter" data-from="0">50+</span>
|
||||||
<span class="stat-label">โปรเจกต์ที่ส่งมอบ</span>
|
<span class="stat-label">โปรเจกต์ที่ส่งมอบ</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span class="stat-number">5+</span>
|
<span class="stat-number counter" data-from="0">5+</span>
|
||||||
<span class="stat-label">ปีของการทำงานหนัก</span>
|
<span class="stat-label">ปีของการทำงานหนัก</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span class="stat-number">100%</span>
|
<span class="stat-number counter" data-from="0">100%</span>
|
||||||
<span class="stat-label">โค้ดโดยทีมเรา ไม่ Outsource</span>
|
<span class="stat-label">โค้ดโดยทีมเรา ไม่ Outsource</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,31 +68,39 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
<!-- VALUES SECTION (yellow accent cards on soft) -->
|
<!-- VALUES SECTION (yellow accent cards on soft) -->
|
||||||
<section class="section section-soft values-section">
|
<section class="section section-soft values-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header reveal">
|
||||||
<span class="section-badge">ค่านิยมของเรา</span>
|
<span class="section-badge">ค่านิยมของเรา</span>
|
||||||
<h2 class="section-title">
|
<h2 class="section-title">
|
||||||
4 สิ่งที่เรา <span class="highlight">ไม่เคยเปลี่ยน</span>
|
4 สิ่งที่เรา <span class="highlight">ไม่เคยเปลี่ยน</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="values-grid">
|
<div class="values-grid stagger-children">
|
||||||
<div class="value-card">
|
<div class="value-card">
|
||||||
<div class="value-icon">🎯</div>
|
<div class="value-icon">
|
||||||
|
<Icon name="target" />
|
||||||
|
</div>
|
||||||
<h3 class="value-title">เข้าใจธุรกิจก่อนเขียนโค้ด</h3>
|
<h3 class="value-title">เข้าใจธุรกิจก่อนเขียนโค้ด</h3>
|
||||||
<p class="value-desc">30 นาทีแรกของทุกโปรเจกต์คือการถาม ไม่ใช่การ present เราถามจนเข้าใจว่าคุณขายให้ใคร กำไรเท่าไหร่ ปวดหัวตรงไหน แล้วค่อยแนะนำ solution</p>
|
<p class="value-desc">30 นาทีแรกของทุกโปรเจกต์คือการถาม ไม่ใช่การ present เราถามจนเข้าใจว่าคุณขายให้ใคร กำไรเท่าไหร่ ปวดหัวตรงไหน แล้วค่อยแนะนำ solution</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="value-card">
|
<div class="value-card">
|
||||||
<div class="value-icon">🤝</div>
|
<div class="value-icon">
|
||||||
|
<Icon name="users" />
|
||||||
|
</div>
|
||||||
<h3 class="value-title">เป็น Partner ไม่ใช่ Vendor</h3>
|
<h3 class="value-title">เป็น Partner ไม่ใช่ Vendor</h3>
|
||||||
<p class="value-desc">เราแชร์ progress ทุกสัปดาห์ผ่าน LINE Group เดียวกับที่ลูกค้าใช้ คุณเห็นทุก decision ไม่มี hidden cost ไม่มี "อันนี้เพิ่มเงินนะ" ตอนใกล้ deliver</p>
|
<p class="value-desc">เราแชร์ progress ทุกสัปดาห์ผ่าน LINE Group เดียวกับที่ลูกค้าใช้ คุณเห็นทุก decision ไม่มี hidden cost ไม่มี "อันนี้เพิ่มเงินนะ" ตอนใกล้ deliver</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="value-card">
|
<div class="value-card">
|
||||||
<div class="value-icon">⏱️</div>
|
<div class="value-icon">
|
||||||
|
<Icon name="clock" />
|
||||||
|
</div>
|
||||||
<h3 class="value-title">Deliver ตรงเวลา หรือบอกล่วงหน้า</h3>
|
<h3 class="value-title">Deliver ตรงเวลา หรือบอกล่วงหน้า</h3>
|
||||||
<p class="value-desc">เราไม่สัญญา deadline แบบเลื่อนได้ ถ้าจะติด เราจะบอกก่อน 7 วัน ไม่ใช่บอกตอนส่งงาน เคสไหนที่เคยส่งช้า เราคืนเงิน Pro-rata</p>
|
<p class="value-desc">เราไม่สัญญา deadline แบบเลื่อนได้ ถ้าจะติด เราจะบอกก่อน 7 วัน ไม่ใช่บอกตอนส่งงาน เคสไหนที่เคยส่งช้า เราคืนเงิน Pro-rata</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="value-card">
|
<div class="value-card">
|
||||||
<div class="value-icon">💚</div>
|
<div class="value-icon">
|
||||||
|
<Icon name="shield" />
|
||||||
|
</div>
|
||||||
<h3 class="value-title">ดูแลหลังส่งมอบ</h3>
|
<h3 class="value-title">ดูแลหลังส่งมอบ</h3>
|
||||||
<p class="value-desc">เว็บไซต์ที่ส่งแล้วทิ้งเป็นเว็บตาย เราเลยมีแพ็คเกจดูแลรายเดือนเริ่ม 2,000 บาท รวมอัปเดตเนื้อหา ปรับ SEO แก้บั๊ก ตอบคำถามผ่าน LINE</p>
|
<p class="value-desc">เว็บไซต์ที่ส่งแล้วทิ้งเป็นเว็บตาย เราเลยมีแพ็คเกจดูแลรายเดือนเริ่ม 2,000 บาท รวมอัปเดตเนื้อหา ปรับ SEO แก้บั๊ก ตอบคำถามผ่าน LINE</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,7 +111,7 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
<!-- PROCESS SECTION (4 steps, white) -->
|
<!-- PROCESS SECTION (4 steps, white) -->
|
||||||
<section class="section process-section">
|
<section class="section process-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header reveal">
|
||||||
<span class="section-badge">กระบวนการทำงาน</span>
|
<span class="section-badge">กระบวนการทำงาน</span>
|
||||||
<h2 class="section-title">
|
<h2 class="section-title">
|
||||||
วิธีที่เราทำงาน <span class="highlight">กับคุณ</span>
|
วิธีที่เราทำงาน <span class="highlight">กับคุณ</span>
|
||||||
@@ -110,7 +119,7 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
<p class="section-desc">4 ขั้น โปร่งใสทุกข้อ</p>
|
<p class="section-desc">4 ขั้น โปร่งใสทุกข้อ</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="process-grid">
|
<div class="process-grid stagger-children">
|
||||||
<div class="process-step">
|
<div class="process-step">
|
||||||
<span class="step-num">01</span>
|
<span class="step-num">01</span>
|
||||||
<h3 class="step-title">ปรึกษาฟรี</h3>
|
<h3 class="step-title">ปรึกษาฟรี</h3>
|
||||||
@@ -138,7 +147,7 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
<!-- FINAL CTA (yellow) -->
|
<!-- FINAL CTA (yellow) -->
|
||||||
<section class="section section-yellow cta-section">
|
<section class="section section-yellow cta-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content">
|
<div class="cta-content reveal">
|
||||||
<h2 class="cta-title">อยากรู้ว่าธุรกิจคุณเหมาะกับอะไร?</h2>
|
<h2 class="cta-title">อยากรู้ว่าธุรกิจคุณเหมาะกับอะไร?</h2>
|
||||||
<p class="cta-desc">ปรึกษาฟรี 30 นาที ไม่มี script sales ไม่มี upsell จะบอกตรง ๆ ว่าทำได้หรือไม่ควรทำ</p>
|
<p class="cta-desc">ปรึกษาฟรี 30 นาที ไม่มี script sales ไม่มี upsell จะบอกตรง ๆ ว่าทำได้หรือไม่ควรทำ</p>
|
||||||
<div class="cta-actions">
|
<div class="cta-actions">
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const formattedDate = post.data.date.toLocaleDateString('th-TH', {
|
|||||||
|
|
||||||
<section class="section article-section">
|
<section class="section article-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="article-grid">
|
<div class="article-grid reveal">
|
||||||
<article class="article-content">
|
<article class="article-content">
|
||||||
<div class="article-body">
|
<div class="article-body">
|
||||||
<Content />
|
<Content />
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const sortedPosts = blogPosts.sort((a, b) => b.data.date.valueOf() - a.data.date
|
|||||||
{sortedPosts.length > 0 && (
|
{sortedPosts.length > 0 && (
|
||||||
<section class="featured-section section-soft">
|
<section class="featured-section section-soft">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a href={`/blog/${sortedPosts[0].slug}`} class="featured-card">
|
<a href={`/blog/${sortedPosts[0].id}`} class="featured-card">
|
||||||
<div class="featured-image">
|
<div class="featured-image">
|
||||||
{sortedPosts[0].data.image && (
|
{sortedPosts[0].data.image && (
|
||||||
<img src={sortedPosts[0].data.image} alt={sortedPosts[0].data.title} loading="eager" />
|
<img src={sortedPosts[0].data.image} alt={sortedPosts[0].data.title} loading="eager" />
|
||||||
@@ -53,7 +53,7 @@ const sortedPosts = blogPosts.sort((a, b) => b.data.date.valueOf() - a.data.date
|
|||||||
|
|
||||||
<div class="blog-grid">
|
<div class="blog-grid">
|
||||||
{sortedPosts.slice(1).map((post, i) => (
|
{sortedPosts.slice(1).map((post, i) => (
|
||||||
<a href={`/blog/${post.slug}`} class="blog-card" style={`--delay: ${i * 0.1}s`}>
|
<a href={`/blog/${post.id}`} class="blog-card" style={`--delay: ${i * 0.1}s`}>
|
||||||
<div class="blog-image">
|
<div class="blog-image">
|
||||||
{post.data.image && (
|
{post.data.image && (
|
||||||
<img src={post.data.image} alt={post.data.title} loading="lazy" />
|
<img src={post.data.image} alt={post.data.title} loading="lazy" />
|
||||||
@@ -75,7 +75,7 @@ const sortedPosts = blogPosts.sort((a, b) => b.data.date.valueOf() - a.data.date
|
|||||||
|
|
||||||
<section class="section section-yellow cta-section">
|
<section class="section section-yellow cta-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content">
|
<div class="cta-content reveal">
|
||||||
<h2 class="cta-title">ต้องการความช่วยเหลือ?</h2>
|
<h2 class="cta-title">ต้องการความช่วยเหลือ?</h2>
|
||||||
<p class="cta-desc">ปรึกษาฟรี! เราพร้อมช่วยวิเคราะห์และให้คำแนะนำ</p>
|
<p class="cta-desc">ปรึกษาฟรี! เราพร้อมช่วยวิเคราะห์และให้คำแนะนำ</p>
|
||||||
<div class="cta-actions">
|
<div class="cta-actions">
|
||||||
|
|||||||
@@ -3,6 +3,18 @@ import Base from '../layouts/Base.astro';
|
|||||||
import Navigation from '../components/Navigation.astro';
|
import Navigation from '../components/Navigation.astro';
|
||||||
import Footer from '../components/Footer.astro';
|
import Footer from '../components/Footer.astro';
|
||||||
import PageHero from '../components/PageHero.astro';
|
import PageHero from '../components/PageHero.astro';
|
||||||
|
import Icon from '../components/Icon.astro';
|
||||||
|
|
||||||
|
// Service options for the form — with lucide icon names
|
||||||
|
const serviceOptions = [
|
||||||
|
{ value: 'webdev', label: 'AI-Enhanced Website (เว็บ + Chatbot + SEO)', icon: 'globe' },
|
||||||
|
{ value: 'automation', label: 'AI Automation (Workflow + Chatbot)', icon: 'cog' },
|
||||||
|
{ value: 'marketing', label: 'Online Marketing Automation (Email + LINE + Facebook)', icon: 'megaphone' },
|
||||||
|
{ value: 'seo', label: 'SEO + AI Content System', icon: 'search' },
|
||||||
|
{ value: 'consult', label: 'Tech Consult (Server / Data Pipeline)', icon: 'server' },
|
||||||
|
{ value: 'audit', label: 'Audit เว็บไซต์เดิมฟรี 30 นาที', icon: 'briefcase' },
|
||||||
|
{ value: 'unsure', label: 'ยังไม่แน่ใจ — ช่วยแนะนำ', icon: 'help' },
|
||||||
|
];
|
||||||
---
|
---
|
||||||
|
|
||||||
<Base title="ติดต่อเรา | MoreminiMore | รับทำเว็บไซต์ SEO AI Chatbot">
|
<Base title="ติดต่อเรา | MoreminiMore | รับทำเว็บไซต์ SEO AI Chatbot">
|
||||||
@@ -17,23 +29,29 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
<!-- Quick Channel Picker -->
|
<!-- Quick Channel Picker -->
|
||||||
<section class="section channels-pick-section">
|
<section class="section channels-pick-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="channels-pick-grid">
|
<div class="channels-pick-grid stagger-children">
|
||||||
<a href="https://line.me/ti/p/~@539hdlul" target="_blank" rel="noopener" class="channel-pick-card">
|
<a href="https://line.me/ti/p/~@539hdlul" target="_blank" rel="noopener" class="channel-pick-card">
|
||||||
<div class="channel-pick-icon">💬</div>
|
<div class="channel-pick-icon">
|
||||||
|
<Icon name="message" size={32} />
|
||||||
|
</div>
|
||||||
<h3 class="channel-pick-name">LINE Official</h3>
|
<h3 class="channel-pick-name">LINE Official</h3>
|
||||||
<p class="channel-pick-best">คนที่อยากคุยเร็ว ๆ แบบเป็นกันเอง</p>
|
<p class="channel-pick-best">คนที่อยากคุยเร็ว ๆ แบบเป็นกันเอง</p>
|
||||||
<p class="channel-pick-time">ตอบใน 30 นาที (เวลาทำการ)</p>
|
<p class="channel-pick-time">ตอบใน 30 นาที (เวลาทำการ)</p>
|
||||||
<span class="channel-pick-cta">ทักเลย →</span>
|
<span class="channel-pick-cta">ทักเลย →</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="tel:0809955945" class="channel-pick-card">
|
<a href="tel:0809955945" class="channel-pick-card">
|
||||||
<div class="channel-pick-icon">📞</div>
|
<div class="channel-pick-icon">
|
||||||
|
<Icon name="phone" size={32} />
|
||||||
|
</div>
|
||||||
<h3 class="channel-pick-name">โทรศัพท์</h3>
|
<h3 class="channel-pick-name">โทรศัพท์</h3>
|
||||||
<p class="channel-pick-best">คนที่อยากคุยยาว ๆ 5–10 นาที ถามตอบสด</p>
|
<p class="channel-pick-best">คนที่อยากคุยยาว ๆ 5–10 นาที ถามตอบสด</p>
|
||||||
<p class="channel-pick-time">รับสายทันที หรือโทรกลับภายใน 2 ชม.</p>
|
<p class="channel-pick-time">รับสายทันที หรือโทรกลับภายใน 2 ชม.</p>
|
||||||
<span class="channel-pick-cta">080-995-5945</span>
|
<span class="channel-pick-cta">080-995-5945</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="mailto:contact@moreminimore.com" class="channel-pick-card">
|
<a href="mailto:contact@moreminimore.com" class="channel-pick-card">
|
||||||
<div class="channel-pick-icon">📧</div>
|
<div class="channel-pick-icon">
|
||||||
|
<Icon name="mail" size={32} />
|
||||||
|
</div>
|
||||||
<h3 class="channel-pick-name">Email</h3>
|
<h3 class="channel-pick-name">Email</h3>
|
||||||
<p class="channel-pick-best">คนที่อยากส่งรายละเอียดโปรเจกต์ + ไฟล์แนบ</p>
|
<p class="channel-pick-best">คนที่อยากส่งรายละเอียดโปรเจกต์ + ไฟล์แนบ</p>
|
||||||
<p class="channel-pick-time">ตอบภายใน 1 วันทำการ</p>
|
<p class="channel-pick-time">ตอบภายใน 1 วันทำการ</p>
|
||||||
@@ -46,13 +64,15 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
<!-- Contact Form Section -->
|
<!-- Contact Form Section -->
|
||||||
<section class="section form-section">
|
<section class="section form-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="form-grid">
|
<div class="form-grid reveal">
|
||||||
<!-- Info column -->
|
<!-- Info column -->
|
||||||
<div class="info-column">
|
<div class="info-column">
|
||||||
<h2 class="info-title">ข้อมูลติดต่อ</h2>
|
<h2 class="info-title">ข้อมูลติดต่อ</h2>
|
||||||
|
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<div class="info-icon">📞</div>
|
<div class="info-icon">
|
||||||
|
<Icon name="phone" size={22} />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3>โทรศัพท์</h3>
|
<h3>โทรศัพท์</h3>
|
||||||
<a href="tel:0809955945">080-995-5945</a>
|
<a href="tel:0809955945">080-995-5945</a>
|
||||||
@@ -60,7 +80,9 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<div class="info-icon">✉️</div>
|
<div class="info-icon">
|
||||||
|
<Icon name="mail" size={22} />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3>อีเมล</h3>
|
<h3>อีเมล</h3>
|
||||||
<a href="mailto:contact@moreminimore.com">contact@moreminimore.com</a>
|
<a href="mailto:contact@moreminimore.com">contact@moreminimore.com</a>
|
||||||
@@ -68,7 +90,9 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<div class="info-icon">💬</div>
|
<div class="info-icon">
|
||||||
|
<Icon name="message" size={22} />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3>LINE</h3>
|
<h3>LINE</h3>
|
||||||
<a href="https://line.me/ti/p/~@539hdlul" target="_blank" rel="noopener">@moreminimore</a>
|
<a href="https://line.me/ti/p/~@539hdlul" target="_blank" rel="noopener">@moreminimore</a>
|
||||||
@@ -76,7 +100,9 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<div class="info-icon">📍</div>
|
<div class="info-icon">
|
||||||
|
<Icon name="mapPin" size={22} />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3>ออฟฟิศ</h3>
|
<h3>ออฟฟิศ</h3>
|
||||||
<p>53 หมู่ 1 ต.บ้านแพ้ว อ.บ้านแพ้ว สมุทรสาคร 74120</p>
|
<p>53 หมู่ 1 ต.บ้านแพ้ว อ.บ้านแพ้ว สมุทรสาคร 74120</p>
|
||||||
@@ -84,7 +110,9 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<div class="info-icon">🕐</div>
|
<div class="info-icon">
|
||||||
|
<Icon name="clock" size={22} />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3>เวลาทำการ</h3>
|
<h3>เวลาทำการ</h3>
|
||||||
<p>จันทร์ - ศุกร์ 09:00 - 18:00 น.</p>
|
<p>จันทร์ - ศุกร์ 09:00 - 18:00 น.</p>
|
||||||
@@ -133,13 +161,9 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
<label for="service" class="form-label">บริการที่สนใจ</label>
|
<label for="service" class="form-label">บริการที่สนใจ</label>
|
||||||
<select id="service" name="service" class="form-input">
|
<select id="service" name="service" class="form-input">
|
||||||
<option value="">เลือกบริการ (ไม่บังคับ)</option>
|
<option value="">เลือกบริการ (ไม่บังคับ)</option>
|
||||||
<option value="webdev">🌐 AI-Enhanced Website (เว็บ + Chatbot + SEO)</option>
|
{serviceOptions.map(opt => (
|
||||||
<option value="automation">⚙️ AI Automation (Workflow + Chatbot)</option>
|
<option value={opt.value}>{opt.label}</option>
|
||||||
<option value="marketing">📈 Online Marketing Automation (Email + LINE + Facebook)</option>
|
))}
|
||||||
<option value="seo">🔍 SEO + AI Content System</option>
|
|
||||||
<option value="consult">🖥️ Tech Consult (Server / Data Pipeline)</option>
|
|
||||||
<option value="audit">💼 Audit เว็บไซต์เดิมฟรี 30 นาที</option>
|
|
||||||
<option value="unsure">❓ ยังไม่แน่ใจ — ช่วยแนะนำ</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -159,7 +183,9 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
|
|
||||||
<!-- Success message (hidden by default) -->
|
<!-- Success message (hidden by default) -->
|
||||||
<div class="form-success" id="form-success" hidden>
|
<div class="form-success" id="form-success" hidden>
|
||||||
<div class="success-icon">✅</div>
|
<div class="success-icon">
|
||||||
|
<Icon name="checkCircle" size={44} />
|
||||||
|
</div>
|
||||||
<h3>ส่งแล้ว!</h3>
|
<h3>ส่งแล้ว!</h3>
|
||||||
<p>เราจะตอบกลับภายใน 2 ชั่วโมง (ในเวลาทำการ) ถ้าเร่งด่วน ทัก LINE @moreminimore ครับ</p>
|
<p>เราจะตอบกลับภายใน 2 ชั่วโมง (ในเวลาทำการ) ถ้าเร่งด่วน ทัก LINE @moreminimore ครับ</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,12 +197,12 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
<!-- What Happens Next -->
|
<!-- What Happens Next -->
|
||||||
<section class="section section-soft next-section">
|
<section class="section section-soft next-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header reveal">
|
||||||
<span class="section-badge">หลังส่งฟอร์ม</span>
|
<span class="section-badge">หลังส่งฟอร์ม</span>
|
||||||
<h2 class="section-title">3 ขั้นตอนถัดไป — <span class="highlight">ไม่มีอะไรซับซ้อน</span></h2>
|
<h2 class="section-title">3 ขั้นตอนถัดไป — <span class="highlight">ไม่มีอะไรซับซ้อน</span></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="next-grid">
|
<div class="next-grid stagger-children">
|
||||||
<div class="next-step">
|
<div class="next-step">
|
||||||
<div class="next-num">1</div>
|
<div class="next-num">1</div>
|
||||||
<h3>ตอบกลับภายใน 2 ชั่วโมง</h3>
|
<h3>ตอบกลับภายใน 2 ชั่วโมง</h3>
|
||||||
@@ -200,12 +226,12 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
<!-- Pre-submit FAQ -->
|
<!-- Pre-submit FAQ -->
|
||||||
<section class="section pre-faq-section">
|
<section class="section pre-faq-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header reveal">
|
||||||
<span class="section-badge">ก่อนกดส่ง</span>
|
<span class="section-badge">ก่อนกดส่ง</span>
|
||||||
<h2 class="section-title">4 คำถามที่คนถาม <span class="highlight">ก่อน</span> กดส่งฟอร์ม</h2>
|
<h2 class="section-title">4 คำถามที่คนถาม <span class="highlight">ก่อน</span> กดส่งฟอร์ม</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pre-faq-list">
|
<div class="pre-faq-list stagger-children">
|
||||||
<div class="pre-faq-item">
|
<div class="pre-faq-item">
|
||||||
<h3>คุยกัน 30 นาทีแล้วจะถูกบังคับซื้อไหม?</h3>
|
<h3>คุยกัน 30 นาทีแล้วจะถูกบังคับซื้อไหม?</h3>
|
||||||
<p>ไม่ คุยแล้วคุณไม่ชอบก็ไม่เป็นไร ไม่มี follow-up ไม่มีขายของเพิ่ม</p>
|
<p>ไม่ คุยแล้วคุณไม่ชอบก็ไม่เป็นไร ไม่มี follow-up ไม่มีขายของเพิ่ม</p>
|
||||||
@@ -228,7 +254,7 @@ import PageHero from '../components/PageHero.astro';
|
|||||||
|
|
||||||
<section class="section section-yellow cta-section">
|
<section class="section section-yellow cta-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content">
|
<div class="cta-content reveal">
|
||||||
<h2 class="cta-title">หรือจะ <span class="highlight">อ่านก่อน</span> ก็ได้</h2>
|
<h2 class="cta-title">หรือจะ <span class="highlight">อ่านก่อน</span> ก็ได้</h2>
|
||||||
<p class="cta-desc">ไม่มี commitment ไม่มี pressure ไม่มีใครตาม</p>
|
<p class="cta-desc">ไม่มี commitment ไม่มี pressure ไม่มีใครตาม</p>
|
||||||
<div class="cta-actions">
|
<div class="cta-actions">
|
||||||
|
|||||||
@@ -3,16 +3,19 @@ import Base from '../layouts/Base.astro';
|
|||||||
import Navigation from '../components/Navigation.astro';
|
import Navigation from '../components/Navigation.astro';
|
||||||
import Footer from '../components/Footer.astro';
|
import Footer from '../components/Footer.astro';
|
||||||
import PageHero from '../components/PageHero.astro';
|
import PageHero from '../components/PageHero.astro';
|
||||||
|
import Icon from '../components/Icon.astro';
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
const faqItems = await getCollection('faq');
|
const faqItems = await getCollection('faq');
|
||||||
const categories = ['บริการ', 'ราคา', 'ระยะเวลา', 'AI & เทคนิค', 'หลังการขาย'];
|
const categories = ['บริการ', 'ราคา', 'ระยะเวลา', 'AI & เทคนิค', 'หลังการขาย'];
|
||||||
|
|
||||||
|
// Map categories to lucide icon names (semantic match, not visual emoji)
|
||||||
const categoryIcons: Record<string, string> = {
|
const categoryIcons: Record<string, string> = {
|
||||||
'บริการ': '💼',
|
'บริการ': 'briefcase',
|
||||||
'ราคา': '💰',
|
'ราคา': 'dollarSign',
|
||||||
'ระยะเวลา': '⏱️',
|
'ระยะเวลา': 'clock',
|
||||||
'AI & เทคนิค': '🤖',
|
'AI & เทคนิค': 'bot',
|
||||||
'หลังการขาย': '🛠️',
|
'หลังการขาย': 'wrench',
|
||||||
};
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -31,9 +34,11 @@ const categoryIcons: Record<string, string> = {
|
|||||||
const items = faqItems.filter(f => f.data.category === cat);
|
const items = faqItems.filter(f => f.data.category === cat);
|
||||||
if (items.length === 0) return null;
|
if (items.length === 0) return null;
|
||||||
return (
|
return (
|
||||||
<div class="faq-category">
|
<div class="faq-category reveal">
|
||||||
<h2 class="category-title">
|
<h2 class="category-title">
|
||||||
<span class="category-icon">{categoryIcons[cat]}</span>
|
<span class="category-icon">
|
||||||
|
<Icon name={categoryIcons[cat] as any} size={18} />
|
||||||
|
</span>
|
||||||
{cat}
|
{cat}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="faq-list">
|
<div class="faq-list">
|
||||||
@@ -56,7 +61,9 @@ const categoryIcons: Record<string, string> = {
|
|||||||
<!-- Other Topics -->
|
<!-- Other Topics -->
|
||||||
<div class="faq-category">
|
<div class="faq-category">
|
||||||
<h2 class="category-title">
|
<h2 class="category-title">
|
||||||
<span class="category-icon">📚</span>
|
<span class="category-icon">
|
||||||
|
<Icon name="book" size={18} />
|
||||||
|
</span>
|
||||||
เรื่องอื่น ๆ ที่ลูกค้าถามบ่อย
|
เรื่องอื่น ๆ ที่ลูกค้าถามบ่อย
|
||||||
</h2>
|
</h2>
|
||||||
<div class="tag-cloud">
|
<div class="tag-cloud">
|
||||||
@@ -78,23 +85,29 @@ const categoryIcons: Record<string, string> = {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Quick Channels -->
|
<!-- Quick Channels -->
|
||||||
<div class="channels-section">
|
<div class="channels-section reveal">
|
||||||
<h2 class="channels-title">ไม่เจอคำตอบ? ถามตรง ๆ เลย</h2>
|
<h2 class="channels-title">ไม่เจอคำตอบ? ถามตรง ๆ เลย</h2>
|
||||||
<div class="channels-grid">
|
<div class="channels-grid stagger-children">
|
||||||
<a href="https://line.me/ti/p/~@539hdlul" target="_blank" rel="noopener" class="channel-card">
|
<a href="https://line.me/ti/p/~@539hdlul" target="_blank" rel="noopener" class="channel-card">
|
||||||
<div class="channel-icon">💬</div>
|
<div class="channel-icon">
|
||||||
|
<Icon name="message" size={28} />
|
||||||
|
</div>
|
||||||
<h3 class="channel-name">LINE</h3>
|
<h3 class="channel-name">LINE</h3>
|
||||||
<p class="channel-handle">@moreminimore</p>
|
<p class="channel-handle">@moreminimore</p>
|
||||||
<p class="channel-time">ตอบใน 30 นาที (เวลาทำการ)</p>
|
<p class="channel-time">ตอบใน 30 นาที (เวลาทำการ)</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="tel:0809955945" class="channel-card">
|
<a href="tel:0809955945" class="channel-card">
|
||||||
<div class="channel-icon">📞</div>
|
<div class="channel-icon">
|
||||||
|
<Icon name="phone" size={28} />
|
||||||
|
</div>
|
||||||
<h3 class="channel-name">โทร</h3>
|
<h3 class="channel-name">โทร</h3>
|
||||||
<p class="channel-handle">080-995-5945</p>
|
<p class="channel-handle">080-995-5945</p>
|
||||||
<p class="channel-time">จ-ศ 09:00-18:00</p>
|
<p class="channel-time">จ-ศ 09:00-18:00</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="mailto:contact@moreminimore.com" class="channel-card">
|
<a href="mailto:contact@moreminimore.com" class="channel-card">
|
||||||
<div class="channel-icon">📧</div>
|
<div class="channel-icon">
|
||||||
|
<Icon name="mail" size={28} />
|
||||||
|
</div>
|
||||||
<h3 class="channel-name">Email</h3>
|
<h3 class="channel-name">Email</h3>
|
||||||
<p class="channel-handle">contact@moreminimore.com</p>
|
<p class="channel-handle">contact@moreminimore.com</p>
|
||||||
<p class="channel-time">ตอบภายใน 1 วัน</p>
|
<p class="channel-time">ตอบภายใน 1 วัน</p>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import Base from '../layouts/Base.astro';
|
import Base from '../layouts/Base.astro';
|
||||||
import Navigation from '../components/Navigation.astro';
|
import Navigation from '../components/Navigation.astro';
|
||||||
import Footer from '../components/Footer.astro';
|
import Footer from '../components/Footer.astro';
|
||||||
|
import Icon from '../components/Icon.astro';
|
||||||
import Hero from '../components/Hero.astro';
|
import Hero from '../components/Hero.astro';
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import { render } from 'astro:content';
|
import { render } from 'astro:content';
|
||||||
@@ -34,71 +35,77 @@ const serviceImages: Record<string, string> = {
|
|||||||
subtitle={home?.data.subtitle}
|
subtitle={home?.data.subtitle}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- STATS SECTION — yellow band -->
|
<!-- STATS SECTION — yellow band (counters animate on view) -->
|
||||||
<section class="section section-stats">
|
<section class="section section-stats reveal">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-number">50+</span>
|
<span class="stat-number counter" data-from="0">50+</span>
|
||||||
<span class="stat-label">โปรเจกต์สำเร็จ</span>
|
<span class="stat-label">โปรเจกต์สำเร็จ</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-number">40+</span>
|
<span class="stat-number counter" data-from="0">40+</span>
|
||||||
<span class="stat-label">ลูกค้าที่ไว้วางใจ</span>
|
<span class="stat-label">ลูกค้าที่ไว้วางใจ</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-number">5+</span>
|
<span class="stat-number counter" data-from="0">5+</span>
|
||||||
<span class="stat-label">ปีประสบการณ์</span>
|
<span class="stat-label">ปีประสบการณ์</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-number">24/7</span>
|
<span class="stat-number counter" data-from="0">24/7</span>
|
||||||
<span class="stat-label">ให้บริการ</span>
|
<span class="stat-label">ให้บริการ</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- PROBLEM SECTION — light, 3 cards -->
|
<!-- PROBLEM SECTION — light, 3 cards (stagger-children) -->
|
||||||
<section class="section section-soft">
|
<section class="section section-soft">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header reveal">
|
||||||
<span class="section-badge">ปัญหาที่ SME ไทยเจอซ้ำทุกวัน</span>
|
<span class="section-badge">ปัญหาที่ SME ไทยเจอซ้ำทุกวัน</span>
|
||||||
<h2 class="section-title">
|
<h2 class="section-title">
|
||||||
คุณกำลังเจอ <span class="highlight">แบบนี้อยู่</span> ใช่ไหม?
|
คุณกำลังเจอ <span class="highlight">แบบนี้อยู่</span> ใช่ไหม?
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="problem-grid">
|
<div class="problem-grid stagger-children">
|
||||||
<div class="problem-card">
|
<div class="problem-card">
|
||||||
<div class="problem-icon">😩</div>
|
<div class="problem-icon">
|
||||||
|
<Icon name="message" />
|
||||||
|
</div>
|
||||||
<h3 class="problem-title">ลูกค้าทัก LINE เข้ามา แต่ตอบไม่ทัน</h3>
|
<h3 class="problem-title">ลูกค้าทัก LINE เข้ามา แต่ตอบไม่ทัน</h3>
|
||||||
<p class="problem-desc">ทีมเซลล์ 1–2 คน ตอบแชตไม่ไหว ลูกค้ารอ 5 นาทีแล้วไปซื้อที่อื่น</p>
|
<p class="problem-desc">ทีมเซลล์ 1–2 คน ตอบแชตไม่ไหว ลูกค้ารอ 5 นาทีแล้วไปซื้อที่อื่น</p>
|
||||||
<span class="problem-result">→ ยอดหายก่อนที่คุณจะเห็น</span>
|
<span class="problem-result">→ ยอดหายก่อนที่คุณจะเห็น</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="problem-card">
|
<div class="problem-card">
|
||||||
<div class="problem-icon">📉</div>
|
<div class="problem-icon">
|
||||||
|
<Icon name="trendingDown" />
|
||||||
|
</div>
|
||||||
<h3 class="problem-title">ลงโฆษณา แต่ยอดขายไม่ขยับ</h3>
|
<h3 class="problem-title">ลงโฆษณา แต่ยอดขายไม่ขยับ</h3>
|
||||||
<p class="problem-desc">ไม่มีระบบ Lead ไม่มี Funnel ไม่รู้ว่าใครซื้อ ใครแค่ทักมาถามเล่น</p>
|
<p class="problem-desc">ไม่มีระบบ Lead ไม่มี Funnel ไม่รู้ว่าใครซื้อ ใครแค่ทักมาถามเล่น</p>
|
||||||
<span class="problem-result">→ เงินหายไปกับคลิกที่ไม่มีคุณภาพ</span>
|
<span class="problem-result">→ เงินหายไปกับคลิกที่ไม่มีคุณภาพ</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="problem-card">
|
<div class="problem-card">
|
||||||
<div class="problem-icon">🌐</div>
|
<div class="problem-icon">
|
||||||
|
<Icon name="globe" />
|
||||||
|
</div>
|
||||||
<h3 class="problem-title">เว็บไซต์มีอยู่ แต่ไม่มีใครเจอใน Google</h3>
|
<h3 class="problem-title">เว็บไซต์มีอยู่ แต่ไม่มีใครเจอใน Google</h3>
|
||||||
<p class="problem-desc">อันดับหน้า 5 ไม่มีใครเปิด ขณะที่คู่แข่งติดหน้าแรกจาก SEO อย่างเดียว</p>
|
<p class="problem-desc">อันดับหน้า 5 ไม่มีใครเปิด ขณะที่คู่แข่งติดหน้าแรกจาก SEO อย่างเดียว</p>
|
||||||
<span class="problem-result">→ เสียเงินฟรีทุกเดือน</span>
|
<span class="problem-result">→ เสียเงินฟรีทุกเดือน</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="problem-closing">
|
<p class="problem-closing reveal">
|
||||||
<span class="highlight">ทุกปัญหาข้างต้นแก้ได้ด้วยระบบเดียว</span> — ดูว่าเราทำยังไง
|
<span class="highlight">ทุกปัญหาข้างต้นแก้ได้ด้วยระบบเดียว</span> — ดูว่าเราทำยังไง
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- SERVICES SECTION — 4 mega cards on white -->
|
<!-- SERVICES SECTION — 4 mega cards on white (stagger-children) -->
|
||||||
<section class="section services-section">
|
<section class="section services-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header reveal">
|
||||||
<span class="section-badge">บริการของเรา</span>
|
<span class="section-badge">บริการของเรา</span>
|
||||||
<h2 class="section-title">
|
<h2 class="section-title">
|
||||||
ครบจบในที่เดียว — <span class="highlight">โซลูชัน AI สำหรับธุรกิจไทย</span>
|
ครบจบในที่เดียว — <span class="highlight">โซลูชัน AI สำหรับธุรกิจไทย</span>
|
||||||
@@ -106,7 +113,7 @@ const serviceImages: Record<string, string> = {
|
|||||||
<p class="section-desc">เลือกแค่ที่คุณต้องการ หรือให้เราวางแผนทั้งระบบให้ก็ได้</p>
|
<p class="section-desc">เลือกแค่ที่คุณต้องการ หรือให้เราวางแผนทั้งระบบให้ก็ได้</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="services-mega-grid">
|
<div class="services-mega-grid stagger-children">
|
||||||
{allServices.map(s => (
|
{allServices.map(s => (
|
||||||
<a href={`/services/${s.id}`} class="mega-card">
|
<a href={`/services/${s.id}`} class="mega-card">
|
||||||
<span class="mega-tag">{s.data.badge}</span>
|
<span class="mega-tag">{s.data.badge}</span>
|
||||||
@@ -128,7 +135,7 @@ const serviceImages: Record<string, string> = {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- PULL QUOTE — black section, oversized white text -->
|
<!-- PULL QUOTE — black section, oversized white text -->
|
||||||
<section class="section section-dark-quote">
|
<section class="section section-dark-quote reveal">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<blockquote class="pull-quote">
|
<blockquote class="pull-quote">
|
||||||
<p class="quote-text">
|
<p class="quote-text">
|
||||||
@@ -142,7 +149,7 @@ const serviceImages: Record<string, string> = {
|
|||||||
<!-- PORTFOLIO PREVIEW — 3 latest on soft -->
|
<!-- PORTFOLIO PREVIEW — 3 latest on soft -->
|
||||||
<section class="section section-soft">
|
<section class="section section-soft">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header reveal">
|
||||||
<span class="section-badge">ผลงานจริง · ไม่ใช่ Mockup</span>
|
<span class="section-badge">ผลงานจริง · ไม่ใช่ Mockup</span>
|
||||||
<h2 class="section-title">
|
<h2 class="section-title">
|
||||||
ลูกค้าจริง <span class="highlight">ผลลัพธ์จริง</span>
|
ลูกค้าจริง <span class="highlight">ผลลัพธ์จริง</span>
|
||||||
@@ -150,9 +157,9 @@ const serviceImages: Record<string, string> = {
|
|||||||
<p class="section-desc">9 โปรเจกต์ที่เราภาคภูมิใจ — ตั้งแต่ร้านค้าออนไลน์ ไปจนถึงโรงงานฉีดพลาสติก</p>
|
<p class="section-desc">9 โปรเจกต์ที่เราภาคภูมิใจ — ตั้งแต่ร้านค้าออนไลน์ ไปจนถึงโรงงานฉีดพลาสติก</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="blog-editorial">
|
<div class="blog-editorial reveal">
|
||||||
{sortedPosts[0] && (
|
{sortedPosts[0] && (
|
||||||
<a href={`/blog/${sortedPosts[0].slug}`} class="blog-featured">
|
<a href={`/blog/${sortedPosts[0].id}`} class="blog-featured">
|
||||||
<div class="blog-image">
|
<div class="blog-image">
|
||||||
<img src={sortedPosts[0].data.image} alt={sortedPosts[0].data.title} loading="lazy" />
|
<img src={sortedPosts[0].data.image} alt={sortedPosts[0].data.title} loading="lazy" />
|
||||||
<span class="blog-badge">บทความล่าสุด</span>
|
<span class="blog-badge">บทความล่าสุด</span>
|
||||||
@@ -168,7 +175,7 @@ const serviceImages: Record<string, string> = {
|
|||||||
|
|
||||||
<div class="blog-sidebar">
|
<div class="blog-sidebar">
|
||||||
{sortedPosts.slice(1, 4).map((post) => (
|
{sortedPosts.slice(1, 4).map((post) => (
|
||||||
<a href={`/blog/${post.slug}`} class="blog-card-mini">
|
<a href={`/blog/${post.id}`} class="blog-card-mini">
|
||||||
<div class="blog-card-image">
|
<div class="blog-card-image">
|
||||||
<img src={post.data.image} alt={post.data.title} loading="lazy" />
|
<img src={post.data.image} alt={post.data.title} loading="lazy" />
|
||||||
</div>
|
</div>
|
||||||
@@ -193,7 +200,7 @@ const serviceImages: Record<string, string> = {
|
|||||||
<!-- FINAL CTA — yellow section -->
|
<!-- FINAL CTA — yellow section -->
|
||||||
<section class="section section-yellow cta-section">
|
<section class="section section-yellow cta-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content">
|
<div class="cta-content reveal">
|
||||||
<h2 class="cta-title">พร้อมเปลี่ยนธุรกิจของคุณ?</h2>
|
<h2 class="cta-title">พร้อมเปลี่ยนธุรกิจของคุณ?</h2>
|
||||||
<p class="cta-desc">ปรึกษาฟรี 30 นาที — เราจะถามคำถาม 5 ข้อ แล้วบอกคุณได้เลยว่าควรเริ่มจากตรงไหน</p>
|
<p class="cta-desc">ปรึกษาฟรี 30 นาที — เราจะถามคำถาม 5 ข้อ แล้วบอกคุณได้เลยว่าควรเริ่มจากตรงไหน</p>
|
||||||
<div class="cta-actions">
|
<div class="cta-actions">
|
||||||
@@ -318,7 +325,7 @@ const serviceImages: Record<string, string> = {
|
|||||||
box-shadow: var(--shadow-md);
|
box-shadow: var(--shadow-md);
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
.problem-icon { font-size: 48px; margin-bottom: 16px; }
|
.problem-icon { margin-bottom: 16px; }
|
||||||
.problem-title {
|
.problem-title {
|
||||||
font-family: var(--font-display);
|
font-family: var(--font-display);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
|||||||
@@ -4,9 +4,23 @@ import Navigation from '../components/Navigation.astro';
|
|||||||
import Footer from '../components/Footer.astro';
|
import Footer from '../components/Footer.astro';
|
||||||
import PageHero from '../components/PageHero.astro';
|
import PageHero from '../components/PageHero.astro';
|
||||||
import PortfolioCard from '../components/PortfolioCard.astro';
|
import PortfolioCard from '../components/PortfolioCard.astro';
|
||||||
|
import Icon from '../components/Icon.astro';
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
const portfolio = await getCollection('portfolio');
|
const portfolio = await getCollection('portfolio');
|
||||||
|
|
||||||
|
// Industry filter metadata: id -> { label, icon }
|
||||||
|
// Icons are lucide-style SVGs; emoji-free.
|
||||||
|
const industryFilters = [
|
||||||
|
{ id: 'all', label: 'ทั้งหมด', icon: 'layers' },
|
||||||
|
{ id: '🏭 โรงงาน', label: 'โรงงาน', icon: 'factory' },
|
||||||
|
{ id: '💊 สินค้าอุปโภค', label: 'สินค้าอุปโภค', icon: 'package' },
|
||||||
|
{ id: '⚖️ สำนักงานกฎหมาย', label: 'สำนักงานกฎหมาย', icon: 'scale' },
|
||||||
|
{ id: '📚 สถาบัน / การศึกษา', label: 'สถาบัน / การศึกษา', icon: 'graduationCap' },
|
||||||
|
{ id: '📈 ที่ปรึกษาธุรกิจ', label: 'ที่ปรึกษาธุรกิจ', icon: 'trendingUp' },
|
||||||
|
{ id: '🎨 Digital Agency', label: 'Digital Agency', icon: 'pen' },
|
||||||
|
{ id: '🛒 E-commerce', label: 'E-commerce', icon: 'shoppingCart' },
|
||||||
|
];
|
||||||
---
|
---
|
||||||
|
|
||||||
<Base title="ผลงาน | MoreminiMore | รับทำเว็บไซต์ SEO AI Chatbot">
|
<Base title="ผลงาน | MoreminiMore | รับทำเว็บไซต์ SEO AI Chatbot">
|
||||||
@@ -22,21 +36,24 @@ const portfolio = await getCollection('portfolio');
|
|||||||
<section class="filter-section">
|
<section class="filter-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="filter-bar">
|
<div class="filter-bar">
|
||||||
<button class="filter-btn active" data-filter="all">ทั้งหมด ({portfolio.length})</button>
|
{industryFilters.map(f => (
|
||||||
<button class="filter-btn" data-filter="🏭 โรงงาน">🏭 โรงงาน</button>
|
<button
|
||||||
<button class="filter-btn" data-filter="💊 สินค้าอุปโภค">💊 สินค้าอุปโภค</button>
|
class="filter-btn"
|
||||||
<button class="filter-btn" data-filter="⚖️ สำนักงานกฎหมาย">⚖️ สำนักงานกฎหมาย</button>
|
class:list={[{ active: f.id === 'all' }]}
|
||||||
<button class="filter-btn" data-filter="📚 สถาบัน / การศึกษา">📚 สถาบัน / การศึกษา</button>
|
data-filter={f.id}
|
||||||
<button class="filter-btn" data-filter="📈 ที่ปรึกษาธุรกิจ">📈 ที่ปรึกษาธุรกิจ</button>
|
>
|
||||||
<button class="filter-btn" data-filter="🎨 Digital Agency">🎨 Digital Agency</button>
|
{f.id !== 'all' && <Icon name={f.icon as any} size={16} class="filter-icon" />}
|
||||||
<button class="filter-btn" data-filter="🛒 E-commerce">🛒 E-commerce</button>
|
{f.id === 'all' && <Icon name={f.icon as any} size={16} class="filter-icon" />}
|
||||||
|
<span>{f.id === 'all' ? `ทั้งหมด (${portfolio.length})` : f.label}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section portfolio-section">
|
<section class="section portfolio-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="portfolio-grid">
|
<div class="portfolio-grid stagger-children">
|
||||||
{portfolio.map(item => (
|
{portfolio.map(item => (
|
||||||
<PortfolioCard
|
<PortfolioCard
|
||||||
name={item.data.name}
|
name={item.data.name}
|
||||||
@@ -57,14 +74,14 @@ const portfolio = await getCollection('portfolio');
|
|||||||
<!-- "ดีลที่เราเลือก" Section -->
|
<!-- "ดีลที่เราเลือก" Section -->
|
||||||
<section class="section section-soft">
|
<section class="section section-soft">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header reveal">
|
||||||
<span class="section-badge">ดีลที่เราเลือก</span>
|
<span class="section-badge">ดีลที่เราเลือก</span>
|
||||||
<h2 class="section-title">
|
<h2 class="section-title">
|
||||||
เรา <span class="highlight">เลือก</span> โปรเจกต์ที่ทำ — ไม่ใช่ทุกงานที่มา เรารับ
|
เรา <span class="highlight">เลือก</span> โปรเจกต์ที่ทำ — ไม่ใช่ทุกงานที่มา เรารับ
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="reasons-grid">
|
<div class="reasons-grid stagger-children">
|
||||||
<div class="reason-card">
|
<div class="reason-card">
|
||||||
<div class="reason-num">1</div>
|
<div class="reason-num">1</div>
|
||||||
<h3 class="reason-title">ธุรกิจที่พร้อมจริง ๆ</h3>
|
<h3 class="reason-title">ธุรกิจที่พร้อมจริง ๆ</h3>
|
||||||
@@ -86,7 +103,7 @@ const portfolio = await getCollection('portfolio');
|
|||||||
|
|
||||||
<section class="section section-yellow cta-section">
|
<section class="section section-yellow cta-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content">
|
<div class="cta-content reveal">
|
||||||
<h2 class="cta-title">อยากเป็น <span class="highlight">ผลงานชิ้นต่อไป</span> ของเรา?</h2>
|
<h2 class="cta-title">อยากเป็น <span class="highlight">ผลงานชิ้นต่อไป</span> ของเรา?</h2>
|
||||||
<p class="cta-desc">ถ้าธุรกิจคุณพร้อม เราพร้อม — คุยกันก่อน 30 นาที แล้วตัดสินใจเอง</p>
|
<p class="cta-desc">ถ้าธุรกิจคุณพร้อม เราพร้อม — คุยกันก่อน 30 นาที แล้วตัดสินใจเอง</p>
|
||||||
<div class="cta-actions">
|
<div class="cta-actions">
|
||||||
@@ -139,16 +156,19 @@ const portfolio = await getCollection('portfolio');
|
|||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
}
|
}
|
||||||
.filter-btn {
|
.filter-btn {
|
||||||
flex-shrink: 0;
|
display: inline-flex;
|
||||||
padding: 8px 16px;
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 20px;
|
||||||
background: var(--color-white);
|
background: var(--color-white);
|
||||||
color: var(--color-gray-700);
|
color: var(--color-black);
|
||||||
border: 1px solid var(--color-gray-200);
|
border: 1px solid var(--color-gray-200);
|
||||||
border-radius: var(--radius-full);
|
border-radius: var(--radius-full);
|
||||||
font-size: 13px;
|
font-family: var(--font-display);
|
||||||
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s var(--ease-out-expo);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.filter-btn:hover {
|
.filter-btn:hover {
|
||||||
@@ -160,6 +180,7 @@ const portfolio = await getCollection('portfolio');
|
|||||||
color: var(--color-black);
|
color: var(--color-black);
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
.filter-icon { color: currentColor; }
|
||||||
|
|
||||||
.portfolio-section { background: var(--color-white); }
|
.portfolio-section { background: var(--color-white); }
|
||||||
.section-soft { background: var(--color-bg-alt); }
|
.section-soft { background: var(--color-bg-alt); }
|
||||||
|
|||||||
@@ -3,10 +3,21 @@ import Base from '../../layouts/Base.astro';
|
|||||||
import Navigation from '../../components/Navigation.astro';
|
import Navigation from '../../components/Navigation.astro';
|
||||||
import Footer from '../../components/Footer.astro';
|
import Footer from '../../components/Footer.astro';
|
||||||
import PageHero from '../../components/PageHero.astro';
|
import PageHero from '../../components/PageHero.astro';
|
||||||
|
import Icon from '../../components/Icon.astro';
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
const services = await getCollection('services');
|
const services = await getCollection('services');
|
||||||
|
|
||||||
|
// Decision-table icon lookup
|
||||||
|
const serviceIcons: Record<string, string> = {
|
||||||
|
'webdev': 'globe',
|
||||||
|
'automation': 'cog',
|
||||||
|
'marketing': 'megaphone',
|
||||||
|
'seo': 'search',
|
||||||
|
'consult': 'server',
|
||||||
|
'audit': 'refresh',
|
||||||
|
};
|
||||||
|
|
||||||
// Map service slugs to images
|
// Map service slugs to images
|
||||||
const serviceImages: Record<string, string> = {
|
const serviceImages: Record<string, string> = {
|
||||||
'automation': '/images/services/automation.jpg',
|
'automation': '/images/services/automation.jpg',
|
||||||
@@ -41,32 +52,32 @@ const serviceImages: Record<string, string> = {
|
|||||||
</div>
|
</div>
|
||||||
<div class="decision-row">
|
<div class="decision-row">
|
||||||
<div>ยังไม่มีเว็บไซต์ หรือเว็บเก่าโหลดช้า</div>
|
<div>ยังไม่มีเว็บไซต์ หรือเว็บเก่าโหลดช้า</div>
|
||||||
<div><span class="dec-tag">🌐 AI-Enhanced Website</span></div>
|
<div><span class="dec-tag"><Icon name="globe" size={14} class="dec-icon" />AI-Enhanced Website</span></div>
|
||||||
<div>2–4 สัปดาห์</div>
|
<div>2–4 สัปดาห์</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="decision-row">
|
<div class="decision-row">
|
||||||
<div>ทีมเซลล์ตอบแชตไม่ทัน ลูกค้าหายตอนกลางคืน</div>
|
<div>ทีมเซลล์ตอบแชตไม่ทัน ลูกค้าหายตอนกลางคืน</div>
|
||||||
<div><span class="dec-tag">⚙️ AI Automation</span></div>
|
<div><span class="dec-tag"><Icon name="cog" size={14} class="dec-icon" />AI Automation</span></div>
|
||||||
<div>1–3 เดือน</div>
|
<div>1–3 เดือน</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="decision-row">
|
<div class="decision-row">
|
||||||
<div>ลงโฆษณาเยอะ แต่ยอดขายไม่โต</div>
|
<div>ลงโฆษณาเยอะ แต่ยอดขายไม่โต</div>
|
||||||
<div><span class="dec-tag">📈 Online Marketing Automation</span></div>
|
<div><span class="dec-tag"><Icon name="megaphone" size={14} class="dec-icon" />Online Marketing Automation</span></div>
|
||||||
<div>1–3 เดือน</div>
|
<div>1–3 เดือน</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="decision-row">
|
<div class="decision-row">
|
||||||
<div>อยากติดหน้าแรก Google แต่ไม่รู้จะเริ่มยังไง</div>
|
<div>อยากติดหน้าแรก Google แต่ไม่รู้จะเริ่มยังไง</div>
|
||||||
<div><span class="dec-tag">🔍 SEO + AI Content</span></div>
|
<div><span class="dec-tag"><Icon name="search" size={14} class="dec-icon" />SEO + AI Content</span></div>
|
||||||
<div>3–6 เดือน</div>
|
<div>3–6 เดือน</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="decision-row">
|
<div class="decision-row">
|
||||||
<div>ไม่อยากจ้างทีม IT ประจำ แต่อยากมี Server/ระบบหลังบ้าน</div>
|
<div>ไม่อยากจ้างทีม IT ประจำ แต่อยากมี Server/ระบบหลังบ้าน</div>
|
||||||
<div><span class="dec-tag">🖥️ Tech Consult</span></div>
|
<div><span class="dec-tag"><Icon name="server" size={14} class="dec-icon" />Tech Consult</span></div>
|
||||||
<div>2–6 สัปดาห์</div>
|
<div>2–6 สัปดาห์</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="decision-row">
|
<div class="decision-row">
|
||||||
<div>มีเว็บอยู่แล้ว แต่ขายไม่ได้</div>
|
<div>มีเว็บอยู่แล้ว แต่ขายไม่ได้</div>
|
||||||
<div><span class="dec-tag">🔄 เริ่มจาก Audit ฟรี 30 นาที</span></div>
|
<div><span class="dec-tag"><Icon name="refresh" size={14} class="dec-icon" />เริ่มจาก Audit ฟรี 30 นาที</span></div>
|
||||||
<div>1 สัปดาห์</div>
|
<div>1 สัปดาห์</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,12 +91,12 @@ const serviceImages: Record<string, string> = {
|
|||||||
<!-- Service Grid -->
|
<!-- Service Grid -->
|
||||||
<section class="section services-grid-section">
|
<section class="section services-grid-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header reveal">
|
||||||
<span class="section-badge">บริการทั้งหมด</span>
|
<span class="section-badge">บริการทั้งหมด</span>
|
||||||
<h2 class="section-title">เลือกตาม <span class="highlight">เป้าหมาย</span> ของคุณ</h2>
|
<h2 class="section-title">เลือกตาม <span class="highlight">เป้าหมาย</span> ของคุณ</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="services-cards">
|
<div class="services-cards stagger-children">
|
||||||
{services.map(s => (
|
{services.map(s => (
|
||||||
<a href={`/services/${s.id}`} class="service-block">
|
<a href={`/services/${s.id}`} class="service-block">
|
||||||
<span class="service-tag">{s.data.badge}</span>
|
<span class="service-tag">{s.data.badge}</span>
|
||||||
@@ -105,12 +116,12 @@ const serviceImages: Record<string, string> = {
|
|||||||
<!-- Pricing tiers -->
|
<!-- Pricing tiers -->
|
||||||
<section class="section pricing-section">
|
<section class="section pricing-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-header">
|
<div class="section-header reveal">
|
||||||
<span class="section-badge">งบประมาณ</span>
|
<span class="section-badge">งบประมาณ</span>
|
||||||
<h2 class="section-title">ไม่ใช่ทุกงบที่จะ<span class="highlight">เหมือนกัน</span></h2>
|
<h2 class="section-title">ไม่ใช่ทุกงบที่จะ<span class="highlight">เหมือนกัน</span></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pricing-grid">
|
<div class="pricing-grid stagger-children">
|
||||||
<div class="pricing-card">
|
<div class="pricing-card">
|
||||||
<h3 class="pricing-tier">เริ่มต้น</h3>
|
<h3 class="pricing-tier">เริ่มต้น</h3>
|
||||||
<div class="pricing-range">15,000–35,000 บาท</div>
|
<div class="pricing-range">15,000–35,000 บาท</div>
|
||||||
@@ -133,7 +144,7 @@ const serviceImages: Record<string, string> = {
|
|||||||
|
|
||||||
<section class="section section-yellow cta-section">
|
<section class="section section-yellow cta-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="cta-content">
|
<div class="cta-content reveal">
|
||||||
<h2 class="cta-title">ยังไม่รู้ว่าจะเริ่มจากตรงไหน?</h2>
|
<h2 class="cta-title">ยังไม่รู้ว่าจะเริ่มจากตรงไหน?</h2>
|
||||||
<p class="cta-desc">Audit ฟรี 30 นาที — เราจะถาม 5 คำถาม แล้วบอกว่าคุณควรลงทุนกับอะไรก่อน ไม่มี upsell ไม่มี commitment</p>
|
<p class="cta-desc">Audit ฟรี 30 นาที — เราจะถาม 5 คำถาม แล้วบอกว่าคุณควรลงทุนกับอะไรก่อน ไม่มี upsell ไม่มี commitment</p>
|
||||||
<div class="cta-actions">
|
<div class="cta-actions">
|
||||||
@@ -204,7 +215,9 @@ const serviceImages: Record<string, string> = {
|
|||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
.dec-tag {
|
.dec-tag {
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
background: var(--color-primary);
|
background: var(--color-primary);
|
||||||
color: var(--color-black);
|
color: var(--color-black);
|
||||||
@@ -212,6 +225,7 @@ const serviceImages: Record<string, string> = {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
.dec-icon { flex-shrink: 0; }
|
||||||
.decision-closing {
|
.decision-closing {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
|
|||||||
Reference in New Issue
Block a user