diff --git a/skills/website-creator/scripts/create_astro_website.py b/skills/website-creator/scripts/create_astro_website.py
index 014b9c0..8c008fe 100644
--- a/skills/website-creator/scripts/create_astro_website.py
+++ b/skills/website-creator/scripts/create_astro_website.py
@@ -28,8 +28,11 @@ import sys
import argparse
import shutil
import subprocess
+import json
+import time
from pathlib import Path
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")
-# ... (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';
+---
+
+
+
+
+
+ Welcome to {args.name}
+ Your trusted partner
+
+
+
+
+"""
+ (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__':
diff --git a/skills/website-creator/scripts/templates/components/common/Footer.astro b/skills/website-creator/scripts/templates/components/common/Footer.astro
new file mode 100644
index 0000000..c228651
--- /dev/null
+++ b/skills/website-creator/scripts/templates/components/common/Footer.astro
@@ -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' },
+];
+---
+
+
diff --git a/skills/website-creator/scripts/templates/components/common/Header.astro b/skills/website-creator/scripts/templates/components/common/Header.astro
new file mode 100644
index 0000000..0073323
--- /dev/null
+++ b/skills/website-creator/scripts/templates/components/common/Header.astro
@@ -0,0 +1,122 @@
+---
+const navItems = [
+ { name: 'หน้าแรก', href: '/' },
+ { name: 'เกี่ยวกับเรา', href: '/about' },
+ { name: 'บริการ', href: '/services' },
+ { name: 'ติดต่อเรา', href: '/contact' },
+];
+
+const categories = [
+ { name: 'สินค้า', href: '/products', hasDropdown: true },
+];
+---
+
+
+
+
+
+
diff --git a/skills/website-creator/scripts/templates/layouts/BaseLayout.astro b/skills/website-creator/scripts/templates/layouts/BaseLayout.astro
new file mode 100644
index 0000000..59cc1db
--- /dev/null
+++ b/skills/website-creator/scripts/templates/layouts/BaseLayout.astro
@@ -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';
+---
+
+
+
+
+
+
+
+
+
+ {title} | {siteName}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
เราใช้คุกกี้เพื่อประสบการณ์ที่ดีที่สุด
+
+ เว็บไซต์ของเราใช้คุกกี้เพื่อเพิ่มประสิทธิภาพการใช้งาน คุณสามารถยอมรับหรือปฏิเสธได้
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
คุกกี้ที่จำเป็น
+
จำเป็นสำหรับการทำงานของเว็บไซต์
+
+
+
+
+
+
+
+
คุกกี้วิเคราะห์
+
ช่วยให้เราเข้าใจผู้ใช้งาน
+
+
+
+
+
+
+
+
คุกกี้การตลาด
+
ใช้สำหรับโฆษณา
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/skills/website-creator/scripts/templates/pages/index.astro b/skills/website-creator/scripts/templates/pages/index.astro
new file mode 100644
index 0000000..9705f4a
--- /dev/null
+++ b/skills/website-creator/scripts/templates/pages/index.astro
@@ -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 = 'ผู้เชี่ยวชาญด้านระบบท่อและอุปกรณ์ติดตั้งคุณภาพสูง ราคาโรงงาน';
+---
+
+
+
+
+
+
+
+
+
+
+ ผู้เชี่ยวชาญระบบน้ำ
+ คุณภาพสูง ราคาโรงงาน
+
+
+ เราเป็นผู้เชี่ยวชาญด้านระบบน้ำ ให้คำแนะนำและจำหน่ายท่อ PPR ท่อ HDPE ท่อ PVC และอุปกรณ์ติดตั้งคุณภาพสูง ราคาถูก
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {[
+ { 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) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ {[
+ { icon: '🏭', title: 'โรงงานผู้ผลิต', description: 'สินค้าจากโรงงานโดยตรง ราคาถูก' },
+ { icon: '✅', title: 'มาตรฐาน', description: 'ผ่านการรับรอง มอก.' },
+ { icon: '🚚', title: 'จัดส่งรวดเร็ว', description: 'ส่งทั่วประเทศไทย' },
+ ].map((feature, index) => (
+
+
{feature.icon}
+
{feature.title}
+
{feature.description}
+
+ ))}
+
+
+
+
+
+
+
+
+ ต้องการคำปรึกษาฟรี?
+
+
+ ทีมงานของเราพร้อมให้คำปรึกษาฟรี ไม่มีค่าใช้จ่าย
+
+
+
+
+
+
+
diff --git a/skills/website-creator/scripts/templates/styles/global.css b/skills/website-creator/scripts/templates/styles/global.css
new file mode 100644
index 0000000..b240af3
--- /dev/null
+++ b/skills/website-creator/scripts/templates/styles/global.css
@@ -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; }