Fix website-creator: add missing functions and templates with IDs

This commit is contained in:
Kunthawat Greethong
2026-03-16 10:50:00 +07:00
parent 58ab725ef9
commit 12515acd5d
6 changed files with 1190 additions and 1 deletions

View File

@@ -28,8 +28,11 @@ import sys
import argparse import argparse
import shutil import shutil
import subprocess import subprocess
import json
import time
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from urllib.parse import urlparse
# ============================================================================ # ============================================================================
@@ -425,7 +428,269 @@ def save_analytics_config(output_path: str, config: dict):
print(f" ✓ Analytics config saved to context/data-services.json") print(f" ✓ Analytics config saved to context/data-services.json")
# ... (rest of functions remain the same - create_project, sync_to_gitea, etc.) # ============================================================================
# PROJECT CREATION FUNCTIONS
# ============================================================================
def create_project(args, languages, default_locale, features):
"""Create the Astro project structure with templates."""
output_path = Path(args.output)
project_name = args.name.lower().replace(' ', '-')
site_url = f"https://{project_name}.moreminimore.com"
# Get template directory
script_dir = Path(__file__).parent
template_dir = script_dir / 'templates'
print("\n📁 Creating project structure...")
# Create directories
dirs = [
output_path / 'public' / 'images',
output_path / 'src' / 'components' / 'common',
output_path / 'src' / 'components' / 'consent',
output_path / 'src' / 'components' / 'ui',
output_path / 'src' / 'layouts',
output_path / 'src' / 'pages',
output_path / 'src' / 'pages' / default_locale,
output_path / 'src' / 'styles',
output_path / 'src' / 'content' / 'blog',
output_path / 'src' / 'lib',
output_path / 'db',
]
for d in dirs:
d.mkdir(parents=True, exist_ok=True)
print(" ✓ Directory structure created")
# Copy templates if they exist
if template_dir.exists():
print(" 📦 Copying templates with IDs...")
# Copy layouts
layout_src = template_dir / 'layouts' / 'BaseLayout.astro'
if layout_src.exists():
content = layout_src.read_text(encoding='utf-8')
content = content.replace("const siteName = 'Website Name'", f"const siteName = '{args.name}'")
content = content.replace("const siteUrl = 'https://example.com'", f"const siteUrl = '{site_url}'")
(output_path / 'src' / 'layouts' / 'BaseLayout.astro').write_text(content, encoding='utf-8')
# Copy Header
header_src = template_dir / 'components' / 'common' / 'Header.astro'
if header_src.exists():
shutil.copy(header_src, output_path / 'src' / 'components' / 'common' / 'Header.astro')
# Copy Footer
footer_src = template_dir / 'components' / 'common' / 'Footer.astro'
if footer_src.exists():
shutil.copy(footer_src, output_path / 'src' / 'components' / 'common' / 'Footer.astro')
# Copy page templates
page_src = template_dir / 'pages' / 'index.astro'
if page_src.exists():
shutil.copy(page_src, output_path / 'src' / 'pages' / default_locale / 'index.astro')
# Copy styles
style_src = template_dir / 'styles' / 'global.css'
if style_src.exists():
shutil.copy(style_src, output_path / 'src' / 'styles' / 'global.css')
print(" ✓ Templates copied")
# Create astro.config.mjs
locales_str = ', '.join([f"'{lang}'" for lang in languages])
astro_config = ASTRO_CONFIG_TEMPLATE.format(
site_url=site_url,
locales=locales_str,
default_locale=default_locale
)
(output_path / 'astro.config.mjs').write_text(astro_config, encoding='utf-8')
print(" ✓ astro.config.mjs created")
# Create package.json
package_json = PACKAGE_JSON_TEMPLATE.format(name=project_name)
(output_path / 'package.json').write_text(package_json, encoding='utf-8')
print(" ✓ package.json created")
# Create tsconfig.json
tsconfig = """{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
"""
(output_path / 'tsconfig.json').write_text(tsconfig, encoding='utf-8')
# Create env file
env_content = f"""# Website Configuration
SITE_NAME={args.name}
SITE_URL={site_url}
# Umami Analytics (optional - get from Umami dashboard)
# UMAMI_WEBSITE_ID=
# UMAMI_URL=
"""
(output_path / '.env').write_text(env_content, encoding='utf-8')
print(" ✓ Configuration files created")
# Create basic index page if no template
if not (output_path / 'src' / 'pages' / default_locale / 'index.astro').exists():
index_content = f"""---
import BaseLayout from '../layouts/BaseLayout.astro';
import Header from '../components/common/Header.astro';
import Footer from '../components/common/Footer.astro';
---
<BaseLayout title="Home" description="Welcome to {args.name}">
<Header />
<main id="main-content">
<section id="hero-section" class="hero">
<h1 id="hero-title">Welcome to {args.name}</h1>
<p id="hero-subtitle">Your trusted partner</p>
</section>
</main>
<Footer />
</BaseLayout>
"""
(output_path / 'src' / 'pages' / default_locale / 'index.astro').write_text(index_content, encoding='utf-8')
print(" ✓ Basic pages created")
# Create Dockerfile
dockerfile = f"""FROM node:20-slim
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm install
# Copy source
COPY . .
# Build
RUN npm run build
# Serve
EXPOSE 80
CMD ["npm", "run", "preview"]
"""
(output_path / 'Dockerfile').write_text(dockerfile, encoding='utf-8')
print(" ✓ Dockerfile created")
# Create .gitignore
gitignore = """# Dependencies
node_modules/
# Build output
dist/
# Environment
.env
.env.*
!.env.example
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
"""
(output_path / '.gitignore').write_text(gitignore, encoding='utf-8')
print(" ✓ .gitignore created")
return output_path
def sync_to_gitea(output_path: str, repo_name: str) -> str:
"""Sync project to Gitea repository."""
try:
# Import gitea sync functionality
sys.path.insert(0, str(Path(__file__).parent.parent / 'gitea-sync' / 'scripts'))
from sync import sync_repo
# Use the gitea-sync script
result = sync_repo(
repo_name=repo_name,
repo_path=output_path,
description=f"Website: {repo_name}",
auto_push=True
)
if result.get('success'):
return result.get('url', f"https://git.moreminimore.com/user/{repo_name}.git")
else:
print(f" ⚠ Gitea sync failed: {result.get('error')}")
return f"https://git.moreminimore.com/user/{repo_name}.git"
except Exception as e:
print(f" ⚠ Gitea sync error: {e}")
print(" Continuing without Gitea sync...")
# Return a dummy URL so deployment can continue
return f"https://git.moreminimore.com/user/{repo_name}.git"
def deploy_to_easypanel(output_path: str, project_name: str, git_url: str) -> str:
"""Deploy project to Easypanel."""
try:
# Import easypanel deploy functionality
sys.path.insert(0, str(Path(__file__).parent.parent / 'easypanel-deploy' / 'scripts'))
from deploy import (
get_session_token,
create_service,
update_git_source,
update_build_type,
deploy_service,
load_env
)
# Load credentials
env = load_env()
username = env.get('EASYPANEL_USERNAME', '')
password = env.get('EASYPANEL_PASSWORD', '')
if not username or not password:
print(" ⚠ Easypanel credentials not found")
print(" Skipping deployment - you can deploy manually later")
return f"https://{project_name}.moreminimore.com"
# Get session token
token = get_session_token(username, password)
if not token:
print(" ⚠ Failed to get Easypanel session")
return f"https://{project_name}.moreminimore.com"
# Create service
create_service(project_name, 'web', token)
# Update git source
update_git_source(project_name, 'web', git_url, 'main', token)
# Set build type to dockerfile
update_build_type(project_name, 'web', token, 'dockerfile')
# Deploy
deploy_service(project_name, 'web', token)
return f"https://{project_name}.moreminimore.com"
except Exception as e:
print(f" ⚠ Easypanel deployment error: {e}")
print(" Continuing without deployment...")
return f"https://{project_name}.moreminimore.com"
def monitor_deployment(project_name: str):
"""Monitor deployment status."""
print(f" 📊 Monitoring deployment for {project_name}...")
print(" (Deployment is running in background)")
print(" Check status at: https://panelwebsite.moreminimore.com")
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -0,0 +1,131 @@
---
const currentYear = new Date().getFullYear();
const quickLinks = [
{ name: 'หน้าแรก', href: '/' },
{ name: 'เกี่ยวกับเรา', href: '/about' },
{ name: 'บริการ', href: '/services' },
{ name: 'สินค้า', href: '/products' },
{ name: 'ติดต่อเรา', href: '/contact' },
];
const services = [
{ name: 'บริการติดตั้ง', href: '/services/installation' },
{ name: 'บริการให้คำปรึกษา', href: '/services/consultation' },
{ name: 'บริการซ่อมบำรุง', href: '/services/maintenance' },
];
const legalLinks = [
{ name: 'นโยบายความเป็นส่วนตัว', href: '/privacy-policy' },
{ name: 'ข้อกำหนดและเงื่อนไข', href: '/terms-and-conditions' },
{ name: 'นโยบายคุกกี้', href: '/cookie-policy' },
];
const socialLinks = [
{ name: 'Facebook', href: 'https://facebook.com', icon: 'facebook' },
{ name: 'Line', href: 'https://line.me', icon: 'line' },
{ name: 'YouTube', href: 'https://youtube.com', icon: 'youtube' },
];
---
<footer id="footer-component" class="bg-secondary-900 text-white pt-16 pb-8">
<div id="footer-container" class="container-custom">
<!-- Main Footer Content -->
<div id="footer-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-12">
<!-- Company Info -->
<div id="footer-company">
<div id="footer-logo-container" class="mb-4">
<img id="footer-logo" src="/images/logo.png" alt="Logo" class="h-12" />
</div>
<div id="footer-description">
<p class="text-secondary-300 mb-4">
บริษัท ดีล พลัส เทค จำกัด ผู้เชี่ยวชาญด้านระบบท่อและอุปกรณ์ติดตั้งคุณภาพสูง
</p>
</div>
<!-- Social Links -->
<div id="footer-social">
<div id="social-links-container" class="flex space-x-4">
{socialLinks.map((social, index) => (
<a id={`social-${social.icon}`} href={social.href} target="_blank" rel="noopener noreferrer" class="w-10 h-10 bg-secondary-800 rounded-full flex items-center justify-center hover:bg-primary-600 transition-colors" aria-label={social.name}>
<span class="text-sm font-medium">{social.name[0]}</span>
</a>
))}
</div>
</div>
</div>
<!-- Quick Links -->
<div id="footer-quick-links">
<h3 id="quick-links-title" class="text-lg font-bold mb-4">ลิงก์ด่วน</h3>
<ul id="quick-links-list" class="space-y-2">
{quickLinks.map((link, index) => (
<li>
<a id={`quick-link-${index}`} href={link.href} class="text-secondary-300 hover:text-white transition-colors">
{link.name}
</a>
</li>
))}
</ul>
</div>
<!-- Services -->
<div id="footer-services">
<h3 id="services-title" class="text-lg font-bold mb-4">บริการ</h3>
<ul id="services-list" class="space-y-2">
{services.map((service, index) => (
<li>
<a id={`service-link-${index}`} href={service.href} class="text-secondary-300 hover:text-white transition-colors">
{service.name}
</a>
</li>
))}
</ul>
</div>
<!-- Contact Info -->
<div id="footer-contact">
<h3 id="contact-title" class="text-lg font-bold mb-4">ติดต่อเรา</h3>
<div id="contact-info" class="space-y-3">
<div id="contact-address" class="flex items-start">
<span class="text-secondary-400 mr-2">📍</span>
<span class="text-secondary-300">123 ถนนสุขุมวิท กรุงเทพมหานคร 10110</span>
</div>
<div id="contact-phone" class="flex items-center">
<span class="text-secondary-400 mr-2">📞</span>
<a href="tel:021234567" class="text-secondary-300 hover:text-white transition-colors">02-123-4567</a>
</div>
<div id="contact-email" class="flex items-center">
<span class="text-secondary-400 mr-2">✉️</span>
<a href="mailto:info@example.com" class="text-secondary-300 hover:text-white transition-colors">info@example.com</a>
</div>
<div id="contact-hours" class="flex items-center">
<span class="text-secondary-400 mr-2">🕐</span>
<span class="text-secondary-300">วันจันทร์-เสาร์ 08:00-18:00 น.</span>
</div>
</div>
</div>
</div>
<!-- Bottom Footer -->
<div id="footer-bottom" class="border-t border-secondary-800 pt-8">
<div id="footer-bottom-content" class="flex flex-col md:flex-row justify-between items-center gap-4">
<div id="copyright">
<p class="text-secondary-400 text-sm">
© {currentYear} บริษัท ดีล พลัส เทค จำกัด สงวนลิขสิทธิ์
</p>
</div>
<div id="footer-legal-links">
<ul id="legal-links-list" class="flex flex-wrap gap-4 text-sm">
{legalLinks.map((link, index) => (
<li>
<a id={`legal-link-${index}`} href={link.href} class="text-secondary-400 hover:text-white transition-colors">
{link.name}
</a>
</li>
))}
</ul>
</div>
</div>
</div>
</div>
</footer>

View File

@@ -0,0 +1,122 @@
---
const navItems = [
{ name: 'หน้าแรก', href: '/' },
{ name: 'เกี่ยวกับเรา', href: '/about' },
{ name: 'บริการ', href: '/services' },
{ name: 'ติดต่อเรา', href: '/contact' },
];
const categories = [
{ name: 'สินค้า', href: '/products', hasDropdown: true },
];
---
<header id="header-component" class="fixed w-full top-0 z-40 bg-white shadow-md">
<nav id="navbar" class="container-custom">
<div id="navbar-container" class="flex items-center justify-between h-16 md:h-20">
<!-- Logo -->
<div id="logo-container">
<a id="logo-link" href="/" class="flex items-center">
<img id="logo-image" src="/images/logo.png" alt="Logo" class="h-10 md:h-12" />
</a>
</div>
<!-- Desktop Navigation -->
<div id="desktop-nav" class="hidden md:flex items-center space-x-6">
<div id="nav-items-container">
{navItems.map((item) => (
<a id={`nav-${item.name.replace(' ', '-').toLowerCase()}`} href={item.href} class="nav-link text-secondary-700 hover:text-primary-600 font-medium transition-colors">
{item.name}
</a>
))}
</div>
<!-- Categories Dropdown -->
<div id="categories-dropdown" class="relative group">
<button id="categories-btn" class="nav-link flex items-center text-secondary-700 hover:text-primary-600 font-medium transition-colors">
สินค้า
<svg id="categories-chevron" class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div id="categories-menu" class="absolute left-0 mt-2 w-48 bg-white rounded-lg shadow-xl opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 transform origin-top-left">
<div id="categories-menu-container" class="py-2">
<a id="category-all-products" href="/products" class="block px-4 py-2 text-secondary-700 hover:bg-primary-50 hover:text-primary-600">
สินค้าทั้งหมด
</a>
<a id="category-pipes" href="/products/pipes" class="block px-4 py-2 text-secondary-700 hover:bg-primary-50 hover:text-primary-600">
ท่อ
</a>
<a id="category-valves" href="/products/valves" class="block px-4 py-2 text-secondary-700 hover:bg-primary-50 hover:text-primary-600">
วาล์ว
</a>
<a id="category-fittings" href="/products/fittings" class="block px-4 py-2 text-secondary-700 hover:bg-primary-50 hover:text-primary-600">
ข้อต่อ
</a>
</div>
</div>
</div>
<!-- CTA Button -->
<div id="cta-container">
<a id="cta-button" href="/contact" class="btn-primary px-4 py-2 rounded-lg font-medium">
ติดต่อเรา
</a>
</div>
</div>
<!-- Mobile Menu Button -->
<div id="mobile-menu-btn-container" class="md:hidden">
<button id="mobile-menu-btn" class="p-2 text-secondary-700 hover:text-primary-600" aria-label="เมนู">
<svg id="menu-icon" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
</div>
<!-- Mobile Menu -->
<div id="mobile-menu" class="hidden md:hidden bg-white border-t">
<div id="mobile-menu-container" class="px-4 py-4 space-y-2">
{navItems.map((item, index) => (
<a id={`mobile-nav-${index}`} href={item.href} class="block py-2 text-secondary-700 hover:text-primary-600 font-medium">
{item.name}
</a>
))}
<div id="mobile-categories-container">
<button id="mobile-categories-btn" class="flex items-center justify-between w-full py-2 text-secondary-700 font-medium">
สินค้า
<svg id="mobile-chevron" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div id="mobile-categories-menu" class="hidden pl-4 space-y-2">
<a id="mobile-all-products" href="/products" class="block py-2 text-secondary-600">- สินค้าทั้งหมด</a>
<a id="mobile-pipes" href="/products/pipes" class="block py-2 text-secondary-600">- ท่อ</a>
<a id="mobile-valves" href="/products/valves" class="block py-2 text-secondary-600">- วาล์ว</a>
<a id="mobile-fittings" href="/products/fittings" class="block py-2 text-secondary-600">- ข้อต่อ</a>
</div>
</div>
<a id="mobile-cta" href="/contact" class="block w-full text-center btn-primary px-4 py-3 rounded-lg font-medium mt-4">
ติดต่อเรา
</a>
</div>
</div>
</nav>
</header>
<script>
// Mobile Menu Toggle
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
const mobileCategoriesBtn = document.getElementById('mobile-categories-btn');
const mobileCategoriesMenu = document.getElementById('mobile-categories-menu');
mobileMenuBtn?.addEventListener('click', () => {
mobileMenu?.classList.toggle('hidden');
});
mobileCategoriesBtn?.addEventListener('click', () => {
mobileCategoriesMenu?.classList.toggle('hidden');
});
</script>

View File

@@ -0,0 +1,190 @@
---
import '../styles/global.css';
interface Props {
title: string;
description?: string;
image?: string;
canonicalURL?: string;
}
const { title, description = '', image = '/images/logo.png', canonicalURL = Astro.url } = Astro.props;
const siteName = 'Website Name';
const siteUrl = 'https://example.com';
---
<!doctype html>
<html lang="th">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="generator" content={Astro.generator} />
<!-- SEO Meta Tags -->
<title>{title} | {siteName}</title>
<meta name="title" content={`${title} | ${siteName}`} />
<meta name="description" content={description} />
<link rel="canonical" href={canonicalURL} />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={canonicalURL} />
<meta property="og:title" content={`${title} | ${siteName}`} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image, siteUrl)} />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={canonicalURL} />
<meta property="twitter:title" content={`${title} | ${siteName}`} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={new URL(image, siteUrl)} />
<!-- Favicon -->
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<!-- Preconnect -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
</head>
<body id="body" class="flex flex-col min-h-screen">
<div id="page-wrapper">
<header id="header">
<slot name="header" />
</header>
<main id="main-content" class="flex-grow">
<slot />
</main>
<footer id="footer">
<slot name="footer" />
</footer>
<!-- Cookie Consent Banner -->
<div id="cookie-consent-banner" class="fixed bottom-0 left-0 right-0 z-50 bg-secondary-900 text-white p-6 shadow-2xl transform translate-y-full transition-transform duration-500">
<div id="cookie-consent-container" class="container-custom max-w-6xl">
<div id="cookie-consent-content" class="flex flex-col md:flex-row items-center justify-between gap-6">
<div id="cookie-consent-text" class="flex-1">
<h3 id="cookie-consent-title" class="text-xl font-bold mb-2">เราใช้คุกกี้เพื่อประสบการณ์ที่ดีที่สุด</h3>
<p id="cookie-consent-description" class="text-secondary-300 text-base">
เว็บไซต์ของเราใช้คุกกี้เพื่อเพิ่มประสิทธิภาพการใช้งาน คุณสามารถยอมรับหรือปฏิเสธได้
</p>
</div>
<div id="cookie-consent-buttons" class="flex flex-wrap gap-4">
<button id="cookie-reject-btn" class="btn-secondary px-6 py-3 text-sm">
ปฏิเสธทั้งหมด
</button>
<button id="cookie-accept-btn" class="btn-primary px-6 py-3 text-sm">
ยอมรับทั้งหมด
</button>
<button id="cookie-settings-btn" class="btn-outline px-6 py-3 text-sm">
ตั้งค่า
</button>
</div>
</div>
</div>
</div>
<!-- Cookie Preferences Modal -->
<div id="cookie-preferences-modal" class="fixed inset-0 z-50 hidden bg-black/50">
<div id="cookie-modal-content" class="flex items-center justify-center min-h-screen p-4">
<div id="cookie-modal-box" class="bg-white rounded-2xl shadow-2xl max-w-lg w-full p-6">
<div id="cookie-modal-header" class="flex justify-between items-center mb-4">
<h2 id="cookie-modal-title" class="text-xl font-bold">ตั้งค่าคุกกี้</h2>
<button id="cookie-modal-close" class="text-gray-500 hover:text-gray-700">
<span id="cookie-close-icon">✕</span>
</button>
</div>
<div id="cookie-modal-body">
<div id="cookie-necessary" class="mb-4">
<div class="flex items-center justify-between">
<div>
<h3 class="font-medium">คุกกี้ที่จำเป็น</h3>
<p class="text-sm text-gray-600">จำเป็นสำหรับการทำงานของเว็บไซต์</p>
</div>
<input type="checkbox" checked disabled class="w-5 h-5" />
</div>
</div>
<div id="cookie-analytics" class="mb-4">
<div class="flex items-center justify-between">
<div>
<h3 class="font-medium">คุกกี้วิเคราะห์</h3>
<p class="text-sm text-gray-600">ช่วยให้เราเข้าใจผู้ใช้งาน</p>
</div>
<input type="checkbox" id="cookie-analytics-checkbox" class="w-5 h-5" />
</div>
</div>
<div id="cookie-marketing" class="mb-4">
<div class="flex items-center justify-between">
<div>
<h3 class="font-medium">คุกกี้การตลาด</h3>
<p class="text-sm text-gray-600">ใช้สำหรับโฆษณา</p>
</div>
<input type="checkbox" id="cookie-marketing-checkbox" class="w-5 h-5" />
</div>
</div>
</div>
<div id="cookie-modal-footer" class="flex justify-end gap-4 mt-6">
<button id="cookie-save-btn" class="btn-primary px-6 py-3">บันทึก</button>
</div>
</div>
</div>
</div>
</div>
<script>
// Cookie Consent Logic
const banner = document.getElementById('cookie-consent-banner');
const acceptBtn = document.getElementById('cookie-accept-btn');
const rejectBtn = document.getElementById('cookie-reject-btn');
const settingsBtn = document.getElementById('cookie-settings-btn');
const modal = document.getElementById('cookie-preferences-modal');
const closeModal = document.getElementById('cookie-modal-close');
const saveBtn = document.getElementById('cookie-save-btn');
function showBanner() {
banner?.classList.remove('translate-y-full');
}
function hideBanner() {
banner?.classList.add('translate-y-full');
}
acceptBtn?.addEventListener('click', () => {
localStorage.setItem('cookie-consent', 'accepted');
hideBanner();
});
rejectBtn?.addEventListener('click', () => {
localStorage.setItem('cookie-consent', 'rejected');
hideBanner();
});
settingsBtn?.addEventListener('click', () => {
modal?.classList.remove('hidden');
});
closeModal?.addEventListener('click', () => {
modal?.classList.add('hidden');
});
saveBtn?.addEventListener('click', () => {
const analytics = (document.getElementById('cookie-analytics-checkbox') as HTMLInputElement)?.checked;
const marketing = (document.getElementById('cookie-marketing-checkbox') as HTMLInputElement)?.checked;
localStorage.setItem('cookie-consent', JSON.stringify({ analytics, marketing }));
modal?.classList.add('hidden');
hideBanner();
});
// Check consent on load
const consent = localStorage.getItem('cookie-consent');
if (!consent) {
showBanner();
}
</script>
</body>
</html>

View File

@@ -0,0 +1,183 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Header from '../components/common/Header.astro';
import Footer from '../components/common/Footer.astro';
const pageTitle = 'หน้าแรก';
const pageDescription = 'ผู้เชี่ยวชาญด้านระบบท่อและอุปกรณ์ติดตั้งคุณภาพสูง ราคาโรงงาน';
---
<BaseLayout title={pageTitle} description={pageDescription}>
<Header slot="header" />
<!-- Hero Section -->
<section id="hero-section" class="relative bg-white section overflow-hidden pt-24 md:pt-32">
<div id="hero-container" class="container-custom">
<div id="hero-grid" class="grid md:grid-cols-2 gap-8 md:gap-12 items-center">
<div id="hero-content" class="animate-fade-in">
<h1 id="hero-title" class="text-2xl sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl font-bold text-secondary-900 mb-4 md:mb-6 leading-tight">
ผู้เชี่ยวชาญระบบน้ำ<br/>
<span class="text-green-600">คุณภาพสูง ราคาโรงงาน</span>
</h1>
<p id="hero-description" class="text-base sm:text-lg md:text-xl text-secondary-600 mb-6 md:mb-8 leading-relaxed">
เราเป็นผู้เชี่ยวชาญด้านระบบน้ำ ให้คำแนะนำและจำหน่ายท่อ PPR ท่อ HDPE ท่อ PVC และอุปกรณ์ติดตั้งคุณภาพสูง ราคาถูก
</p>
<div id="hero-buttons" class="flex flex-wrap justify-center gap-3 md:gap-4">
<a id="hero-cta-products" href="/products" class="bg-green-600 hover:bg-green-700 text-white px-5 py-3 md:px-8 md:py-4 rounded-xl font-medium transition-all hover:shadow-lg active:scale-95 text-sm md:text-lg">
ดูสินค้าทั้งหมด
</a>
<a id="hero-cta-contact" href="/contact" class="bg-white text-green-600 px-5 py-3 md:px-8 md:py-4 rounded-xl border-2 border-green-500 font-medium transition-all hover:shadow-lg active:scale-95 text-sm md:text-lg">
ติดต่อเรา
</a>
</div>
<div id="hero-stats" class="flex items-center sm:space-x-8 space-x-4 sm:mt-12 mt-8 justify-center">
<div id="stat-experience">
<div id="stat-experience-value" class="text-xl sm:text-2xl md:text-3xl font-bold text-green-600">10+</div>
<div id="stat-experience-label" class="text-secondary-600 text-xs sm:text-base">ปีประสบการณ์</div>
</div>
<div id="stat-projects">
<div id="stat-projects-value" class="text-xl sm:text-2xl md:text-3xl font-bold text-green-600">1000+</div>
<div id="stat-projects-label" class="text-secondary-600 text-xs sm:text-base">โปรเจคต์</div>
</div>
<div id="stat-products">
<div id="stat-products-value" class="text-xl sm:text-2xl md:text-3xl font-bold text-green-600">500+</div>
<div id="stat-products-label" class="text-secondary-600 text-xs sm:text-base">สินค้า</div>
</div>
</div>
</div>
<div id="hero-image-container" class="relative animate-slide-up mt-6 md:mt-0">
<div id="hero-image-wrapper" class="absolute inset-0 bg-gradient-to-br from-green-500/20 to-accent-500/20 rounded-3xl blur-3xl"></div>
<div id="hero-image-grid" class="grid grid-cols-3 gap-2 md:gap-4 relative">
<div id="hero-image-main" class="col-span-2 row-span-2">
<img id="hero-img-1" src="/images/hero-1.jpg" alt="Products" class="w-full h-full object-cover rounded-2xl shadow-xl" />
</div>
<div id="hero-image-2">
<img id="hero-img-2" src="/images/hero-2.jpg" alt="Products" class="w-full h-full object-cover rounded-2xl shadow-xl" />
</div>
<div id="hero-image-3">
<img id="hero-img-3" src="/images/hero-3.jpg" alt="Products" class="w-full h-full object-cover rounded-2xl shadow-xl" />
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Categories Section -->
<section id="categories-section" class="py-16 md:py-24 bg-gray-50">
<div id="categories-container" class="container-custom">
<div id="categories-header" class="text-center mb-12">
<h2 id="categories-title" class="text-2xl md:text-3xl lg:text-4xl font-bold text-secondary-900 mb-4">
หมวดสินค้า
</h2>
<p id="categories-subtitle" class="text-secondary-600 text-lg max-w-2xl mx-auto">
สินค้าคุณภาพสูงสำหรับทุกการใช้งาน
</p>
</div>
<div id="categories-grid" class="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6">
{['ท่อ PPR', 'ท่อ HDPE', 'ท่อ UPVC', 'วาล์ว', 'ข้อต่อ', 'อุปกรณ์ติดตั้ง', 'ปั๊มน้ำ', 'อุปกรณ์ดับเพลิง'].map((category, index) => (
<a id={`category-card-${index}`} href={`/products/${category.toLowerCase().replace(' ', '-')}`} class="group bg-white rounded-xl shadow-md hover:shadow-xl transition-all duration-300 p-6 text-center">
<div id={`category-icon-${index}`} class="w-16 h-16 mx-auto mb-4 bg-primary-100 rounded-full flex items-center justify-center group-hover:bg-primary-600 transition-colors">
<span class="text-2xl">{category[0]}</span>
</div>
<h3 id={`category-name-${index}`} class="font-bold text-secondary-900 group-hover:text-primary-600 transition-colors">
{category}
</h3>
</a>
))}
</div>
</div>
</section>
<!-- Featured Products Section -->
<section id="featured-products-section" class="py-16 md:py-24 bg-white">
<div id="featured-products-container" class="container-custom">
<div id="featured-products-header" class="text-center mb-12">
<h2 id="featured-products-title" class="text-2xl md:text-3xl lg:text-4xl font-bold text-secondary-900 mb-4">
สินค้าแนะนำ
</h2>
<p id="featured-products-subtitle" class="text-secondary-600 text-lg max-w-2xl mx-auto">
สินค้ายอดนิยมจากลูกค้า
</p>
</div>
<div id="featured-products-grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
{[
{ name: 'ท่อ PPR ตราช้าง', description: 'ท่อ PPR คุณภาพสูง มาตรฐาน', image: '/images/products/ppr.jpg' },
{ name: 'วาล์วน้ำดับเพลิง', description: 'วาล์วคุณภาพสูง ทนทาน', image: '/images/products/valve.jpg' },
{ name: 'ข้อต่อ HDPE', description: 'ข้อต่อสำหรับท่อ HDPE', image: '/images/products/fitting.jpg' },
].map((product, index) => (
<div id={`featured-product-card-${index}`} class="bg-white rounded-xl shadow-md hover:shadow-xl transition-all duration-300 overflow-hidden">
<div id={`featured-product-image-${index}`} class="aspect-video overflow-hidden">
<img id={`featured-product-img-${index}`} src={product.image} alt={product.name} class="w-full h-full object-cover hover:scale-105 transition-transform duration-300" />
</div>
<div id={`featured-product-content-${index}`} class="p-6">
<h3 id={`featured-product-title-${index}`} class="font-bold text-lg text-secondary-900 mb-2">
{product.name}
</h3>
<p id={`featured-product-desc-${index}`} class="text-secondary-600 mb-4">
{product.description}
</p>
<a id={`featured-product-link-${index}`} href={`/products/${product.name.toLowerCase().replace(' ', '-')}`} class="text-primary-600 font-medium hover:text-primary-700 transition-colors">
ดูรายละเอียด →
</a>
</div>
</div>
))}
</div>
<div id="featured-products-cta" class="text-center mt-12">
<a id="featured-products-all" href="/products" class="btn-primary px-8 py-3 text-lg rounded-xl">
ดูสินค้าทั้งหมด
</a>
</div>
</div>
</section>
<!-- Why Choose Us Section -->
<section id="why-choose-us-section" class="py-16 md:py-24 bg-primary-50">
<div id="why-choose-us-container" class="container-custom">
<div id="why-choose-us-header" class="text-center mb-12">
<h2 id="why-choose-us-title" class="text-2xl md:text-3xl lg:text-4xl font-bold text-secondary-900 mb-4">
ทำไมต้องเลือกเรา
</h2>
<p id="why-choose-us-subtitle" class="text-secondary-600 text-lg max-w-2xl mx-auto">
เรามีความมุ่งมั่นในการให้บริการที่ดีที่สุด
</p>
</div>
<div id="why-choose-us-grid" class="grid grid-cols-1 md:grid-cols-3 gap-6 md:gap-8">
{[
{ icon: '🏭', title: 'โรงงานผู้ผลิต', description: 'สินค้าจากโรงงานโดยตรง ราคาถูก' },
{ icon: '✅', title: 'มาตรฐาน', description: 'ผ่านการรับรอง มอก.' },
{ icon: '🚚', title: 'จัดส่งรวดเร็ว', description: 'ส่งทั่วประเทศไทย' },
].map((feature, index) => (
<div id={`why-choose-us-card-${index}`} class="bg-white rounded-xl shadow-md p-8 text-center">
<div id={`why-choose-us-icon-${index}`} class="text-4xl mb-4">{feature.icon}</div>
<h3 id={`why-choose-us-feature-title-${index}`} class="font-bold text-xl text-secondary-900 mb-2">{feature.title}</h3>
<p id={`why-choose-us-feature-desc-${index}`} class="text-secondary-600">{feature.description}</p>
</div>
))}
</div>
</div>
</section>
<!-- CTA Section -->
<section id="cta-section" class="py-16 md:py-24 bg-green-600">
<div id="cta-container" class="container-custom text-center">
<h2 id="cta-title" class="text-2xl md:text-3xl lg:text-4xl font-bold text-white mb-4">
ต้องการคำปรึกษาฟรี?
</h2>
<p id="cta-description" class="text-white/80 text-lg mb-8 max-w-2xl mx-auto">
ทีมงานของเราพร้อมให้คำปรึกษาฟรี ไม่มีค่าใช้จ่าย
</p>
<div id="cta-buttons" class="flex flex-wrap justify-center gap-4">
<a id="cta-contact-btn" href="/contact" class="bg-white text-green-600 px-8 py-3 rounded-xl font-bold hover:bg-gray-100 transition-colors">
ติดต่อเราวันนี้
</a>
<a id="cta-line-btn" href="https://line.me" target="_blank" class="bg-green-500 text-white px-8 py-3 rounded-xl font-bold hover:bg-green-400 transition-colors">
ติดต่อผ่าน LINE
</a>
</div>
</div>
</section>
<Footer slot="footer" />
</BaseLayout>

View File

@@ -0,0 +1,298 @@
/* Global Styles */
/* Base Typography */
html {
font-size: 18px;
}
@media (min-width: 1280px) {
html { font-size: 20px; }
}
@media (min-width: 1536px) {
html { font-size: 22px; }
}
@media (min-width: 1920px) {
html { font-size: 24px; }
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
}
/* Minimum font sizes */
.text-base { font-size: 1rem; }
.text-lg { font-size: 1.125rem; }
.text-xl { font-size: 1.25rem; }
/* Container */
.container-custom {
max-width: 1280px;
margin-left: auto;
margin-right: auto;
padding-left: 1rem;
padding-right: 1rem;
}
@media (min-width: 640px) {
.container-custom {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
}
/* Section */
.section {
padding-top: 3rem;
padding-bottom: 3rem;
}
@media (min-width: 768px) {
.section {
padding-top: 4rem;
padding-bottom: 4rem;
}
}
/* Buttons */
.btn-primary {
display: inline-block;
padding: 0.75rem 1.5rem;
background-color: #16a34a;
color: white;
border-radius: 0.5rem;
font-weight: 500;
transition: all 0.2s;
}
.btn-primary:hover {
background-color: #15803d;
}
.btn-secondary {
display: inline-block;
padding: 0.75rem 1.5rem;
background-color: #374151;
color: white;
border-radius: 0.5rem;
font-weight: 500;
transition: all 0.2s;
}
.btn-secondary:hover {
background-color: #4b5563;
}
.btn-outline {
display: inline-block;
padding: 0.75rem 1.5rem;
background-color: transparent;
color: white;
border: 2px solid white;
border-radius: 0.5rem;
font-weight: 500;
transition: all 0.2s;
}
.btn-outline:hover {
background-color: white;
color: #16a34a;
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.animate-fade-in {
animation: fadeIn 0.5s ease-out;
}
.animate-slide-up {
animation: slideUp 0.5s ease-out;
}
/* Utility Classes */
.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.gap-2 { gap: 0.5rem; }
.gap-4 { gap: 1rem; }
.gap-6 { gap: 1.5rem; }
.gap-8 { gap: 2rem; }
.grid { display: grid; }
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (min-width: 640px) {
.sm\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 768px) {
.md\:flex { display: flex; }
.md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.md\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
.lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.lg\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* Spacing */
.mt-2 { margin-top: 0.5rem; }
.mt-4 { margin-top: 1rem; }
.mt-6 { margin-top: 1.5rem; }
.mt-8 { margin-top: 2rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; }
.mb-8 { margin-bottom: 2rem; }
.p-2 { padding: 0.5rem; }
.p-4 { padding: 1rem; }
.p-6 { padding: 1.5rem; }
.p-8 { padding: 2rem; }
.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
.py-4 { padding-top: 1rem; padding-bottom: 1rem; }
/* Colors */
.text-white { color: white; }
.text-black { color: black; }
.text-gray-500 { color: #6b7280; }
.text-gray-600 { color: #4b5563; }
.text-gray-700 { color: #374151; }
.text-gray-900 { color: #111827; }
.text-green-500 { color: #22c55e; }
.text-green-600 { color: #16a34a; }
.bg-white { background-color: white; }
.bg-gray-50 { background-color: #f9fafb; }
.bg-gray-100 { background-color: #f3f4f6; }
.bg-black { background-color: black; }
.bg-green-500 { background-color: #22c55e; }
.bg-green-600 { background-color: #16a34a; }
/* Border Radius */
.rounded { border-radius: 0.25rem; }
.rounded-lg { border-radius: 0.5rem; }
.rounded-xl { border-radius: 0.75rem; }
.rounded-2xl { border-radius: 1rem; }
.rounded-full { border-radius: 9999px; }
/* Shadows */
.shadow-md { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); }
.shadow-xl { box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); }
.shadow-2xl { box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); }
/* Typography */
.font-bold { font-weight: 700; }
.font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; }
.text-xs { font-size: 0.75rem; }
.text-sm { font-size: 0.875rem; }
.text-lg { font-size: 1.125rem; }
.text-xl { font-size: 1.25rem; }
.text-2xl { font-size: 1.5rem; }
.text-3xl { font-size: 1.875rem; }
.text-4xl { font-size: 2.25rem; }
.leading-tight { line-height: 1.25; }
.leading-relaxed { line-height: 1.625; }
/* Width/Height */
.w-full { width: 100%; }
.h-full { height: 100%; }
.h-10 { height: 2.5rem; }
.h-12 { height: 3rem; }
.h-16 { height: 4rem; }
.min-h-screen { min-height: 100vh; }
/* Position */
.relative { position: relative; }
.absolute { position: absolute; }
.fixed { position: fixed; }
.inset-0 { top: 0; right: 0; bottom: 0; left: 0; }
.top-0 { top: 0; }
.right-0 { right: 0; }
.bottom-0 { bottom: 0; }
.left-0 { left: 0; }
.z-40 { z-index: 40; }
.z-50 { z-index: 50; }
/* Overflow */
.overflow-hidden { overflow: hidden; }
/* Transitions */
.transition-all { transition: all 0.2s; }
.transition-colors { transition: color 0.2s, background-color 0.2s; }
.transition-transform { transition: transform 0.2s; }
/* Transform */
.translate-y-full { transform: translateY(100%); }
/* Misc */
.cursor-pointer { cursor: pointer; }
.hover\:scale-105:hover { transform: scale(1.05); }
.active\:scale-95:active { transform: scale(0.95); }
/* Hidden by default */
.hidden { display: none; }
/* Space-x for flex items */
.space-x-4 > * + * { margin-left: 1rem; }
.space-x-6 > * + * { margin-left: 1.5rem; }
.space-x-8 > * + * { margin-left: 2rem; }
/* Space-y for flex/grid items */
.space-y-2 > * + * { margin-top: 0.5rem; }
.space-y-4 > * + * { margin-top: 1rem; }
.space-y-6 > * + * { margin-top: 1.5rem; }
/* Aspect ratio */
.aspect-video {
aspect-ratio: 16 / 9;
}
/* Object fit */
.object-cover {
object-fit: cover;
}
/* Border */
.border {
border-width: 1px;
border-style: solid;
}
.border-t {
border-top-width: 1px;
border-style: solid;
}
/* Text align */
.text-center { text-align: center; }
/* Max width */
.max-w-lg { max-width: 32rem; }
.max-w-2xl { max-width: 42rem; }
.max-w-6xl { max-width: 72rem; }
.mx-auto { margin-left: auto; margin-right: auto; }