feat: Fix product tables and responsive fonts

- Add product detail page ([slug].astro) with table rendering
- Display productTables from site-config.ts on product pages
- Add responsive font scaling for large screens (1280px+)
- Base font scales from 16px to 24px on 4K displays
- All text elements use responsive sizing (md/lg/xl breakpoints)
- Tables styled with green headers and alternating rows
- Add comprehensive documentation (FIXES_SUMMARY.md)

Fixes:
- Product specification tables now visible on product pages
- Font too small on large screens - now responsive
This commit is contained in:
Kunthawat Greethong
2026-03-02 12:22:13 +07:00
parent 6b453a8b86
commit ede8e32591
179 changed files with 35057 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
---
import type { CollectionEntry } from 'astro:content';
interface Props {
post: CollectionEntry<'blog'>;
}
const { post } = Astro.props;
const { title, excerpt, date, author, category, categories, image, featuredImage } = post.data;
// Support both 'category' and 'categories' field names
const postCategory = category || (Array.isArray(categories) ? categories[0] : 'ทั่วไป');
// Support both 'image' and 'featuredImage' field names
const postImage = image || featuredImage || '/images/2021/03/ppr-pipe_000C.jpg';
---
<a href={`/blog/${post.slug}`} class="card group">
<div class="relative aspect-video bg-secondary-100 overflow-hidden">
<img
src={postImage}
alt={title}
class="object-cover w-full h-48 group-hover:scale-105 transition-transform duration-300"
loading="lazy"
/>
<div class="absolute top-4 left-4">
<span class="industrial-badge">{postCategory}</span>
</div>
</div>
<div class="p-6">
<time class="text-sm text-secondary-500">
{new Date(date).toLocaleDateString('th-TH', { year: 'numeric', month: 'long', day: 'numeric' })}
</time>
<h3 class="mt-2 text-xl font-bold text-secondary-900 group-hover:text-primary-600 transition-colors line-clamp-2">
{title}
</h3>
{excerpt && (
<p class="mt-3 text-secondary-600 text-sm line-clamp-3">
{excerpt}
</p>
)}
<div class="mt-4 flex items-center text-primary-600 font-medium">
<span>อ่านต่อ</span>
<svg class="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M9 5l7 7-7 7" />
</svg>
</div>
</div>
</a>

View File

@@ -0,0 +1,29 @@
---
import { siteConfig } from '../data/site-config';
---
<div class="fixed bottom-6 right-6 z-40 flex flex-col gap-3">
<!-- LINE -->
<a
href={`https://line.me/ti/p/${siteConfig.lineId}`}
target="_blank"
rel="noopener noreferrer"
class="w-14 h-14 bg-[#00B900] rounded-full flex items-center justify-center shadow-lg hover:scale-110 transition-transform"
aria-label="ติดต่อผ่าน LINE"
>
<svg class="w-7 h-7 text-white" viewBox="0 0 24 24" fill="currentColor">
<path d="M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63H17.61v1.125h1.755c.349 0 .63.283.63.63 0 .344-.281.629-.63.629h-2.386c-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63h2.386c.346 0 .627.285.627.63 0 .349-.281.63-.63.63H17.61v1.125h1.755zm-3.855 3.016c0 .27-.174.51-.432.596-.064.021-.133.031-.199.031-.211 0-.391-.09-.51-.25l-2.443-3.317v2.94c0 .344-.279.629-.631.629-.346 0-.626-.285-.626-.629V8.108c0-.27.173-.51.43-.595.06-.023.136-.033.194-.033.195 0 .375.104.495.254l2.462 3.33V8.108c0-.345.282-.63.63-.63.345 0 .63.285.63.63v4.771zm-5.741 0c0 .344-.282.629-.631.629-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63.346 0 .628.285.628.63v4.771zm-2.466.629H4.917c-.345 0-.63-.285-.63-.629V8.108c0-.345.285-.63.63-.63.348 0 .63.285.63.63v4.141h1.756c.348 0 .629.283.629.63 0 .344-.282.629-.629.629M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608.391.082.923.258 1.058.59.12.301.079.766.038 1.08l-.164 1.02c-.045.301-.24 1.186 1.049.645 1.291-.539 6.916-4.078 9.436-6.975C23.176 14.393 24 12.458 24 10.314"/>
</svg>
</a>
<!-- Phone -->
<a
href={`tel:${siteConfig.phone}`}
class="w-14 h-14 bg-primary-600 rounded-full flex items-center justify-center shadow-lg hover:scale-110 transition-transform"
aria-label="โทรหาเรา"
>
<svg class="w-7 h-7 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
</a>
</div>

View File

@@ -0,0 +1,114 @@
---
import { siteConfig, workHours, mainNavigation } from '../data/site-config';
---
<footer class="bg-secondary-50 text-secondary-900">
<!-- Main Footer -->
<div class="container mx-auto px-4 py-12">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
<!-- Company Info -->
<div>
<img
src="/images/2021/02/13523630950840.png"
alt="Deal Plus Tech"
class="h-10 w-auto mb-4"
/>
<p class="text-secondary-600 text-sm mb-4">
{siteConfig.description}
</p>
<div class="flex gap-3">
<a
href={siteConfig.facebookUrl}
target="_blank"
rel="noopener noreferrer"
class="w-10 h-10 bg-secondary-200 rounded-lg flex items-center justify-center text-secondary-700 hover:bg-primary-600 hover:text-white transition-colors"
aria-label="Facebook"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
</svg>
</a>
<a
href={`https://line.me/ti/p/${siteConfig.lineId}`}
target="_blank"
rel="noopener noreferrer"
class="w-10 h-10 bg-secondary-200 rounded-lg flex items-center justify-center text-secondary-700 hover:bg-primary-600 hover:text-white transition-colors"
aria-label="LINE"
>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63H17.61v1.125h1.755c.349 0 .63.283.63.63 0 .344-.281.629-.63.629h-2.386c-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63h2.386c.346 0 .627.285.627.63 0 .349-.281.63-.63.63H17.61v1.125h1.755zm-3.855 3.016c0 .27-.174.51-.432.596-.064.021-.133.031-.199.031-.211 0-.391-.09-.51-.25l-2.443-3.317v2.94c0 .344-.279.629-.631.629-.346 0-.626-.285-.626-.629V8.108c0-.27.173-.51.43-.595.06-.023.136-.033.194-.033.195 0 .375.104.495.254l2.462 3.33V8.108c0-.345.282-.63.63-.63.345 0 .63.285.63.63v4.771zm-5.741 0c0 .344-.282.629-.631.629-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63.346 0 .628.285.628.63v4.771zm-2.466.629H4.917c-.345 0-.63-.285-.63-.629V8.108c0-.345.285-.63.63-.63.348 0 .63.285.63.63v4.141h1.756c.348 0 .629.283.629.63 0 .344-.282.629-.629.629M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608.391.082.923.258 1.058.59.12.301.079.766.038 1.08l-.164 1.02c-.045.301-.24 1.186 1.049.645 1.291-.539 6.916-4.078 9.436-6.975C23.176 14.393 24 12.458 24 10.314"/>
</svg>
</a>
</div>
</div>
<!-- Quick Links -->
<div>
<h3 class="text-lg font-bold mb-4 text-primary-600">ลิงก์ด่วน</h3>
<ul class="space-y-2">
{mainNavigation.slice(0, 5).map((item) => (
<li>
<a href={item.href} class="text-secondary-600 hover:text-primary-600 transition-colors">
{item.label}
</a>
</li>
))}
</ul>
</div>
<!-- Contact Info -->
<div>
<h3 class="text-lg font-bold mb-4 text-primary-600">ติดต่อเรา</h3>
<ul class="space-y-3">
<li class="flex items-start gap-3">
<svg class="w-5 h-5 text-primary-500 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span class="text-secondary-600 text-sm">{siteConfig.address}</span>
</li>
<li class="flex items-center gap-3">
<svg class="w-5 h-5 text-primary-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
<a href={`tel:${siteConfig.phone}`} class="text-secondary-600 hover:text-primary-600 text-sm">
{siteConfig.phone}
</a>
</li>
<li class="flex items-center gap-3">
<svg class="w-5 h-5 text-primary-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
<a href={`mailto:${siteConfig.email}`} class="text-secondary-600 hover:text-primary-600 text-sm">
{siteConfig.email}
</a>
</li>
</ul>
</div>
<!-- Business Hours -->
<div>
<h3 class="text-lg font-bold mb-4 text-primary-600">เวลาทำการ</h3>
<ul class="space-y-2">
{workHours.map((item) => (
<li class="flex justify-between text-sm">
<span class="text-secondary-600">{item.day}</span>
<span class={item.isClosed ? 'text-red-500' : 'text-secondary-900 font-medium'}>
{item.hours}
</span>
</li>
))}
</ul>
</div>
</div>
</div>
<!-- Bottom Bar -->
<div class="border-t border-secondary-200">
<div class="container mx-auto px-4 py-4">
<p class="text-center text-secondary-500 text-sm">
© {new Date().getFullYear()} {siteConfig.name}. สงวนลิขสิทธิ์.
</p>
</div>
</div>
</footer>

View File

@@ -0,0 +1,222 @@
---
import { siteConfig, mainNavigation } from '../data/site-config';
import { cn } from '../lib/utils';
interface NavItemWithChildren {
label: string;
href: string;
children?: Array<{
label: string;
href: string;
children?: Array<{ label: string; href: string }>;
}>;
}
const navItems = mainNavigation as NavItemWithChildren[];
---
<header class="fixed top-0 left-0 right-0 z-50 bg-white shadow-md">
<!-- Top Bar -->
<div class="bg-primary-600 py-2">
<div class="container mx-auto px-4 flex justify-between items-center text-sm">
<div class="flex items-center gap-6 text-white">
<a href={`tel:${siteConfig.phone}`} class="flex items-center gap-2 hover:text-primary-100">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
{siteConfig.phone}
</a>
<a href={`mailto:${siteConfig.email}`} class="flex items-center gap-2 hover:text-primary-100">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
{siteConfig.email}
</a>
</div>
<div class="hidden md:flex items-center gap-4">
<a href={`https://line.me/ti/p/${siteConfig.lineId}`} class="flex items-center gap-1 text-white hover:text-primary-100">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63H17.61v1.125h1.755c.349 0 .63.283.63.63 0 .344-.281.629-.63.629h-2.386c-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63h2.386c.346 0 .627.285.627.63 0 .349-.281.63-.63.63H17.61v1.125h1.755zm-3.855 3.016c0 .27-.174.51-.432.596-.064.021-.133.031-.199.031-.211 0-.391-.09-.51-.25l-2.443-3.317v2.94c0 .344-.279.629-.631.629-.346 0-.626-.285-.626-.629V8.108c0-.27.173-.51.43-.595.06-.023.136-.033.194-.033.195 0 .375.104.495.254l2.462 3.33V8.108c0-.345.282-.63.63-.63.345 0 .63.285.63.63v4.771zm-5.741 0c0 .344-.282.629-.631.629-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63.346 0 .628.285.628.63v4.771zm-2.466.629H4.917c-.345 0-.63-.285-.63-.629V8.108c0-.345.285-.63.63-.63.348 0 .63.285.63.63v4.141h1.756c.348 0 .629.283.629.63 0 .344-.282.629-.629.629M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608.391.082.923.258 1.058.59.12.301.079.766.038 1.08l-.164 1.02c-.045.301-.24 1.186 1.049.645 1.291-.539 6.916-4.078 9.436-6.975C23.176 14.393 24 12.458 24 10.314"/>
</svg>
LINE
</a>
</div>
</div>
</div>
<!-- Main Navigation -->
<nav class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<!-- Logo -->
<a href="/" class="flex items-center gap-3">
<img
src="/images/2021/02/13523630950840.png"
alt="Deal Plus Tech"
class="h-12 w-auto"
loading="priority"
/>
</a>
<!-- Desktop Navigation -->
<div class="hidden lg:flex items-center gap-1">
{navItems.map((item) => (
<div class="relative group">
<a
href={item.href}
class:list={[
"px-4 py-2 text-secondary-700 font-medium hover:text-primary-600 transition-colors flex items-center gap-1",
item.children && "pr-2"
]}
>
{item.label}
{item.children && (
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M19 9l-7 7-7-7" />
</svg>
)}
</a>
<!-- Dropdown -->
{item.children && (
<div class="absolute top-full left-0 pt-2 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50">
<div class="absolute -top-2 left-0 right-0 h-2" />
<div class="min-w-[600px] bg-white shadow-xl rounded-lg py-4 border border-secondary-100">
<div class="grid grid-cols-2 gap-1 px-4">
{item.children.map((child) => (
<div class="relative group/sub">
<a
href={child.href}
class="block px-3 py-2 text-secondary-700 hover:bg-primary-50 hover:text-primary-700 transition-colors rounded font-medium"
>
{child.label}
</a>
{child.children && (
<div class="hidden group-hover/sub:block absolute left-full top-0 w-56 !bg-white shadow-xl rounded-lg py-2 border border-secondary-100 max-h-96 overflow-y-auto z-50">
<div class="absolute -top-2 -bottom-2 -left-2 w-2" />
{child.children.map((subChild) => (
<a
href={subChild.href}
class="block px-4 py-2 text-secondary-600 hover:bg-primary-50 hover:text-primary-700 text-sm"
>
{subChild.label}
</a>
))}
</div>
)}
</div>
))}
</div>
</div>
</div>
)}
</div>
))}
<a href="/contact-us" class="btn-primary ml-4">
ติดต่อเรา
</a>
</div>
<!-- Mobile Menu Button -->
<button
id="mobile-menu-button"
class="lg:hidden text-secondary-900 p-2"
aria-label="Toggle menu"
>
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
<!-- Mobile Menu -->
<div id="mobile-menu" class="lg:hidden py-4 border-t border-secondary-200 max-h-[80vh] overflow-y-auto hidden">
{navItems.map((item) => (
item.children ? (
<div class="border-b border-secondary-100">
<div class="px-4 py-3 font-semibold text-secondary-900 bg-secondary-50">
{item.label}
</div>
<div class="pl-4">
{item.children.map((child) => (
<div>
<a
href={child.href}
class="block px-4 py-2 text-secondary-700 hover:text-primary-600 hover:bg-primary-50 mobile-link"
>
{child.label}
</a>
{child.children && (
<div class="pl-4 bg-secondary-50">
{child.children.map((subChild) => (
<a
href={subChild.href}
class="block px-4 py-2 text-secondary-600 hover:text-primary-600 text-sm mobile-link"
>
{subChild.label}
</a>
))}
</div>
)}
</div>
))}
</div>
</div>
) : (
<a
href={item.href}
class="block px-4 py-3 text-secondary-700 hover:text-primary-600 font-medium mobile-link"
>
{item.label}
</a>
)
))}
<div class="p-4">
<a
href="/contact-us"
class="btn-primary block text-center mobile-link"
>
ติดต่อเรา
</a>
</div>
</div>
</nav>
</header>
<script>
// Mobile menu toggle
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
let mobileMenuOpen = false;
mobileMenuButton?.addEventListener('click', () => {
mobileMenuOpen = !mobileMenuOpen;
if (mobileMenuOpen) {
mobileMenu?.classList.remove('hidden');
mobileMenuButton.innerHTML = `
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M6 18L18 6M6 6l12 12" />
</svg>
`;
} else {
mobileMenu?.classList.add('hidden');
mobileMenuButton.innerHTML = `
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M4 6h16M4 12h16M4 18h16" />
</svg>
`;
}
});
// Close mobile menu when clicking links
document.querySelectorAll('.mobile-link').forEach(link => {
link.addEventListener('click', () => {
mobileMenuOpen = false;
mobileMenu?.classList.add('hidden');
mobileMenuButton.innerHTML = `
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M4 6h16M4 12h16M4 18h16" />
</svg>
`;
});
});
</script>

View File

@@ -0,0 +1,37 @@
---
import type { CollectionEntry } from 'astro:content';
interface Props {
product: CollectionEntry<'products'>;
}
const { product } = Astro.props;
const { name, shortDescription, image } = product.data;
---
<a href={`/products/${product.data.slug}`} class="card group">
<div class="aspect-w-16 aspect-h-9 overflow-hidden bg-secondary-100">
<img
src={image || '/placeholder.jpg'}
alt={name}
class="object-cover w-full h-48 group-hover:scale-105 transition-transform duration-300"
loading="lazy"
/>
</div>
<div class="p-6">
<h3 class="text-lg font-bold text-secondary-900 group-hover:text-primary-600 transition-colors">
{name}
</h3>
{shortDescription && (
<p class="mt-2 text-sm text-secondary-600 line-clamp-2">
{shortDescription}
</p>
)}
<div class="mt-4 flex items-center text-primary-600 font-medium">
<span>ดูรายละเอียด</span>
<svg class="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M9 5l7 7-7 7" />
</svg>
</div>
</div>
</a>