9 Commits

Author SHA1 Message Date
hermes
d7aa7c2013 docs(ci): rewrite setup guide for Gitea Webhook deploy trigger
The previous doc assumed a Gitea Actions workflow (which requires a
self-hosted act_runner that we don't have, hence the 'No matching
online runner with label: ubuntu-latest' error).

The actual setup is much simpler: a Gitea Webhook pointing at the
deploy endpoint. No runner, no workflow file. Setup is one-time in
Settings -> Webhooks -> Add Webhook.

Documents:
- The push -> webhook -> deploy -> EasyPanel flow
- Exact payload URL, method, content type, events
- Test Delivery verification step
- Why Gitea Actions doesn't work without act_runner
- Troubleshooting: push not triggering, build failure modes
  (nixpacks 'No start command', node 20 vs 22), and a curl recipe
  for redeploying without a code change.
2026-06-09 14:54:14 +07:00
hermes
4820289252 ci: remove Gitea Actions workflows (rely on EasyPanel auto-deploy)
The workflows were never able to run — Gitea Actions has no managed
runners the way GitHub does, and 'ubuntu-latest' isn't a label this
self-hosted Gitea instance can match. Every push to main showed the
job as 'No matching online runner with label: ubuntu-latest' in
the Actions tab.

EasyPanel already watches the 'main' branch and rebuilds on push
(Dockerfile-based, git source). The CI step was duplicative and
produced noise in the Actions tab without running anything.

If a real CI step is needed later (lint, build artifact, test), a
Gitea act_runner has to be installed on a server, registered with
labels like 'self-hosted:host:linux', and the workflow has to use
those labels. Skipping that for now.

Files removed:
  .gitea/workflows/build-and-deploy.yml
  .gitea/workflows/lint.yml
  .gitea/  (empty after removal)
2026-06-09 14:36:47 +07:00
hermes
b7931731f9 fix(responsive): mobile footer restructure + categories 1-col + overflow fix
Some checks failed
Build & Deploy to EasyPanel / build-and-deploy (push) Has been cancelled
Lint & Test / build-check (push) Has been cancelled
Three mobile issues fixed:

1. Categories section: <640px (mobile) now 1 column. Previously it was
   already 2 columns at the smallest breakpoint which made the tiles feel
   cramped. Uses Tailwind's default sm: (640px) for the 1->2 transition,
   not a custom min-[500px].

2. Footer mobile layout (<1024px): three centered rows inside the same
   max-w-7xl container.
     - Row 1: Company Info (1 col, max-w-md, centered)
     - Row 2: Quick Links + สินค้ายอดนิยม (2 cols, max-w-2xl, centered)
     - Row 3: ติดต่อเรา (1 col, max-w-sm outer, max-w-xs button column)
   The narrow inner max-widths are deliberate: a full-width row of
   contact buttons looks stretched, especially the bottom one with the
   โทรเลย / แอดไลน์ buttons.

3. Horizontal overflow bug: data-animate='fade-left' and 'fade-right'
   children sit at translateX(30px) / translateX(-30px) in their
   initial state, before the IntersectionObserver fires. On small
   viewports this pushes the page 30px wider than the viewport and
   reveals a horizontal scrollbar as soon as the user loads the page.
   Adding overflow-x-clip on <footer> keeps the off-screen translation
   contained inside the footer.
2026-06-09 14:18:54 +07:00
Kunthawat Greethong
0bd480d103 merge: bring Dockerfile Node 22 fix from main 2026-06-09 13:33:30 +07:00
hermes
21a538cbb8 fix(deploy): bump Dockerfile Node from 20-alpine to 22-alpine
Some checks failed
Build & Deploy to EasyPanel / build-and-deploy (push) Has been cancelled
Lint & Test / build-check (push) Has been cancelled
Astro 6.x requires Node >=22.12.0. The previous Dockerfile used
node:20-alpine, which the Astro CLI rejected with:

  Node.js v20.20.2 is not supported by Astro!
  Please upgrade Node.js to a supported version: ">=22.12.0"

EasyPanel pulled the change, ran the build, and failed at
`RUN npm run build`. Bumping to node:22-alpine fixes it.

Also added two 'common failure' sections to docs/ci-setup.md
covering the nixpacks 'No start command' and Node version
mismatch errors we just hit.
2026-06-09 13:33:14 +07:00
hermes
d73e48351f fix(deploy): switch from nixpacks to Dockerfile + change branch to main
Some checks failed
Build & Deploy to EasyPanel / build-and-deploy (push) Has been cancelled
Lint & Test / build-check (push) Has been cancelled
Nixpacks auto-detect could not find a 'start' script in package.json
and bailed out. Astro builds to static files in dist/ — there is no
Node server to start. Switching to a Dockerfile + nginx fixes the
'No start command could be found' error from EasyPanel.

The workflow also pointed at the source-code branch, but the panel's
git source ref for the dealplustech-astro service is 'main', so the
trigger was firing for the wrong ref. Both workflows now run on push
to main.

- Dockerfile: multi-stage node:20-alpine build + nginx:1.27-alpine serve
- nginx.conf: gzip, security headers, 1-year cache for hashed assets,
  try_files fallback for UTF-8 slugs (Astro file-based routing)
- .dockerignore: keep build context small (skip CI, docs, .gitea, IDE)
- build-and-deploy.yml + lint.yml: branch source-code -> main
- docs/ci-setup.md: corrected project + service names (customerwebsite
  / dealplustech-astro), documented the Dockerfile rationale, added a
  note for the 'Failed to sync changes' server-side error
2026-06-09 10:28:46 +07:00
hermes
3efaf4d661 ci: switch deploy trigger from generic webhook to EasyPanel tRPC API
Some checks failed
Build & Deploy to EasyPanel / build-and-deploy (push) Has been cancelled
Lint & Test / build-check (push) Has been cancelled
Replaces the old EASYPANEL_WEBHOOK_URL flow with a direct tRPC call
to the panel, using three minimal secrets the operator fills in:

  EASYPANEL_TOKEN         - bearer token from EasyPanel profile
  EASYPANEL_PROJECT_NAME  - project name in the dashboard
  EASYPANEL_SERVICE_NAME  - service name inside that project

The endpoint (https://panelwebsite.moreminimore.com/api/trpc/services.
app.deployService) is hardcoded because the panel URL does not change.
Payload uses tRPC's wrapped-json shape: {"json":{"projectName":...,
"serviceName":...}}.

The build still runs and the dist/ artifact still uploads when any
secret is empty — only the trigger step is skipped with a warning.

Also adds docs/ci-setup.md explaining the three secrets, the service
type requirement (must be 'app' / Dockerfile-based), and a curl recipe
for testing the payload shape locally before pushing.
2026-06-09 09:47:24 +07:00
hermes
02c7b7b29b ci: add Gitea Actions workflows for build + EasyPanel deploy
Some checks failed
Build & Deploy to EasyPanel / build-and-notify (push) Has been cancelled
Lint & Test / build-check (push) Has been cancelled
Two workflows under .gitea/workflows/:

- build-and-deploy.yml: on push to source-code, install deps, run
  astro build, then POST the dist/ SHA + ref to EASYPANEL_WEBHOOK_URL
  (read from repo secret) so EasyPanel redeploys. Uploads dist/ as a
  7-day artifact as a fallback. Gracefully warns if the secret is empty
  so contributors can run the build job without breaking.

- lint.yml: lightweight build check on every push + PR so syntax /
  missing-image errors surface before the deploy workflow runs.

To enable auto-deploy:
  1. Set EASYPANEL_WEBHOOK_URL secret in Gitea repo settings
  2. Configure EasyPanel service to source from this repo + branch
     source-code, build command 'npm run build', output 'dist'
2026-06-08 23:17:43 +07:00
hermes
fe442790ab feat(homepage): rework homepage + align about-us + footer popular products
## homepage (index.astro)

- Drop '500+ รายการสินค้า' from stat badges and rebuild the trust-badges
  section to show only 15+ ปีประสบการณ์, 400+ ลูกค้าทั่วประเทศ, 98%
  ลูกค้าพึงพอใจ — bigger numbers (text-5xl/6xl), no icons, counter animation
- Add 'ทำไมเลือกเรา' section (4 cards: ส่งฟรี กทม./ปริมณฑล, Lead time 1-3
  วัน, ราคาโรงงาน, ทีมช่างแนะนำ) — no icons, primary/accent border hover
- Add 'หมวดสินค้า' section (8 tiles: 7 categories + 'สินค้าทั้งหมด')
  placed after 'สินค้าแนะนำ' — each tile is a real product photo with
  dark gradient overlay and a CTA link to /all-products?filter=<id>
- Reorder: Hero → ทำไมเลือกเรา → สินค้าแนะนำ → หมวดสินค้า → Stats →
  บทความล่าสุด → CTA

## all-products (filter from URL)

- Script now reads ?filter=<id> on load and applies it without rewriting
  the URL, then on click updates both the filter and the URL via
  history.replaceState so the back/forward buttons work

## footer (BaseLayout.astro)

- Replace productLinks with the curated 6 popular products: ไทยพีพีอาร์,
  เทอร์โมเบรค, กริลแอร์, หัวจ่ายแอร์ Ball Jet, ท่อ HDPE, ท่อ Syler

## about-us

- Stats: 10+/1000+/500+ replaced with 15+ / 400+ + counter animation,
  bigger numbers, rounded-3xl + shadow (matches home)
- Why Choose Us: rebuilt with the same 4 cards + same style as home's
  'ทำไมเลือกเรา' (no icons, pill header, larger h2 + subtitle)
2026-06-08 22:56:52 +07:00
8 changed files with 596 additions and 107 deletions

34
.dockerignore Normal file
View File

@@ -0,0 +1,34 @@
# Build artifacts
node_modules/
dist/
.astro/
# CI / Gitea
.gitea/
.github/
# Local dev / IDE
.DS_Store
.vscode/
.idea/
*.log
*.tmp
.env
.env.*
!.env.example
# Git
.git/
.gitignore
# Docker (don't include Docker files in the build context of themselves)
Dockerfile
.dockerignore
docker-compose*.yml
# Docs
docs/
# Misc
coverage/
*.tsbuildinfo

29
Dockerfile Normal file
View File

@@ -0,0 +1,29 @@
# =====================================================================
# Stage 1: Build the Astro static site
# =====================================================================
FROM node:22-alpine AS builder
WORKDIR /app
# Install deps with cache layer
COPY package*.json ./
RUN npm ci --no-audit --no-fund
# Build
COPY . .
RUN npm run build
# =====================================================================
# Stage 2: Serve with nginx
# =====================================================================
FROM nginx:1.27-alpine
# Astro outputs to ./dist by default
COPY --from=builder /app/dist /usr/share/nginx/html
# nginx config: SPA-friendly, gzip, cache headers for static assets
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

109
docs/ci-setup.md Normal file
View File

@@ -0,0 +1,109 @@
# CI/CD Setup — Deploy via Gitea Webhook
The site auto-rebuilds on every push to `main` via a Gitea **webhook**
(no Actions runner, no `.gitea/workflows/`, no act_runner required).
## How it works
```
git push origin main
Gitea Webhook (Gitea built-in, not Gitea Actions)
│ POST Content-Type: application/json
http://110.164.146.47:3000/api/deploy/<token>
│ HTTP 200 "Deploying..."
EasyPanel pulls repo, builds with Dockerfile, redeploys
```
## One-time webhook setup
In `https://git.moreminimore.com/kunthawat/dealplustech-astroreal/settings/hooks`:
1. Click **Add Webhook → Gitea**
2. Fill in:
- **Payload URL**: `http://110.164.146.47:3000/api/deploy/772d2c3a4a7d8671657c947c059bc1cdc64bd816efb7fbe2`
- **HTTP Method**: `POST`
- **Content Type**: `application/json`
- **Events**: **Push events**
- **Active**: ✓
- **Branch filter**: leave empty (or `main` to restrict)
3. Click **Add Webhook**
4. Test: click the webhook row → **Test Delivery****Push events** → confirm
**Last Response** is HTTP 200.
Done. From now on every push to main redeploys automatically.
## EasyPanel service requirements
The service on the panel side (`project=customerwebsite`,
`service=dealplustech-astro`) must be:
- **Type: `app`**
- **Source: Git**, branch `main`
- **Build type: `dockerfile`**, file `Dockerfile` at repo root
- **Port: `80`**
The Dockerfile is two-stage:
1. `node:22-alpine` — runs `npm ci` and `npm run build` to produce
`dist/`
2. `nginx:1.27-alpine` — copies `dist/` to nginx's web root and serves
it (with gzip, security headers, 1-year cache for hashed assets,
and a `try_files` fallback for Astro's UTF-8 slugs)
## Why not Gitea Actions?
Gitea Actions does **not** ship with managed runners (unlike GitHub
Actions' `ubuntu-latest`). Without a self-hosted `act_runner` registered
with matching labels, any workflow run will fail with
**"No matching online runner with label: ubuntu-latest"**.
The Gitea Webhook mechanism is built into Gitea itself — no runner
required. It fires the same events but POSTs to a URL you control,
which is the simplest possible deploy trigger.
If a real CI step is ever needed (lint, test, build artifact), install
`act_runner` on a box, register it, and write a workflow using
`runs-on: self-hosted`. See `act_runner` docs for setup.
## Troubleshooting
### Push happened, but site didn't update
1. Open the webhook row in Settings → Webhooks
2. Open **Recent Deliveries**
3. Check the most recent one:
- Status 200 → deploy endpoint received it, check EasyPanel logs
- Status != 200 → check the response body
- **No recent delivery** → webhook is not bound to this branch/event
or the push target branch doesn't match the filter
### Build fails inside EasyPanel
1. EasyPanel UI → service `dealplustech-astro` → Logs tab
2. Look for the `nixpacks` or `docker build` step
3. Common failures:
- **"No start command could be found"**: you're on `nixpacks`. Switch
build type to `dockerfile` and point at `Dockerfile` at repo root.
- **"Node.js v20.x is not supported by Astro"**: the Dockerfile is
using `node:20-alpine`. It should be `node:22-alpine` (Astro 6
requires `>=22.12.0`).
- **"Failed to sync changes"** (HTTP 500 from the deploy endpoint):
usually a panel-side state issue. Retry, or open the service in
the panel UI and check container state.
### Want to redeploy without a code change
The webhook URL itself is idempotent. You can hit it directly with:
```bash
curl -X POST \
"http://110.164.146.47:3000/api/deploy/772d2c3a4a7d8671657c947c059bc1cdc64bd816efb7fbe2"
```
A `200` response with body `Deploying...` means the panel accepted the
trigger. The actual rebuild happens in the background — check EasyPanel
UI for the progress.

55
nginx.conf Normal file
View File

@@ -0,0 +1,55 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;
index index.html;
# gzip
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/javascript
text/xml
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml
font/ttf
font/otf;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Cache static assets aggressively (Astro hashes filenames)
location ~* \.(?:js|css|woff2?|ttf|otf|eot|svg|jpg|jpeg|png|webp|avif|ico)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# Don't cache HTML
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# Thai URL slugs (Astro file-based routing produces paths with UTF-8 chars)
location / {
try_files $uri $uri/ $uri.html /index.html;
}
# healthcheck
location /healthz {
access_log off;
return 200 "ok\n";
add_header Content-Type text/plain;
}
}

View File

@@ -314,12 +314,12 @@ if (jsonLd) {
}
const productLinks = [
{ title: "ท่อพีพีอาร์", href: "/ท่อ-ppr-thai-ppr" },
{ title: "ไทยพีพีอาร์", href: "/ท่อ-ppr-thai-ppr" },
{ title: "เทอร์โมเบรค", href: "/เทอร์โมเบรค-thermobreak" },
{ title: "กริลแอร์", href: "/grilles" },
{ title: "หัวจ่ายแอร์ Ball Jet", href: "/หัวจ่าย-ball-jet" },
{ title: "ท่อ HDPE", href: "/ท่อ-hdpe" },
{ title: "ท่อ UPVC", href: "/ท่อ-upvc" },
{ title: "เครื่องเชื่อมท่อ", href: "/เครื่องเชื่อม-hdpe" },
{ title: "วาล์ว", href: "/วาล์ว-valve" },
{ title: "ปั๊มน้ำ", href: "/water-pump" },
{ title: "ท่อ Syler", href: "/ท่อ-syler" },
];
---
@@ -430,9 +430,115 @@ const productLinks = [
<slot />
<!-- Footer -->
<footer class="text-neutral-800 bg-gradient-to-b from-white to-neutral-50" data-animate="fade-up">
<footer class="text-neutral-800 bg-gradient-to-b from-white to-neutral-50 overflow-x-clip" data-animate="fade-up">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 lg:py-16">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 lg:gap-12">
<!-- ============================================================ -->
<!-- Mobile layout (<1024px): 3 rows, all centered, content kept -->
<!-- narrower than the container so long rows (especially the -->
<!-- button row) do not stretch edge to edge. -->
<!-- `overflow-x-clip` on <footer> catches the initial state of -->
<!-- data-animate="fade-left"/"fade-right" children which are -->
<!-- translated off-screen until the IntersectionObserver fires. -->
<!-- ============================================================ -->
<div class="lg:hidden space-y-10 text-center">
<!-- Row 1: Company Info -->
<div class="max-w-md mx-auto" data-animate="fade-up">
<img src="/images/logo/dealplustech-logo.png" alt="ดีล พลัส เทค" class="h-10 w-auto mb-4 mx-auto transition-transform hover:scale-105 duration-300" />
<div class="space-y-3 text-sm text-neutral-600 inline-flex flex-col items-center">
<p class="flex items-start gap-2 hover:text-primary-600 transition-colors">
<svg class="w-5 h-5 shrink-0 mt-0.5 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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>{companyInfo.address}</span>
</p>
<p class="flex items-center gap-2 hover:text-primary-600 transition-colors">
<svg class="w-5 h-5 shrink-0 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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:${companyInfo.phone}`}>{companyInfo.phone}</a>
</p>
<p class="flex items-center gap-2 hover:text-primary-600 transition-colors">
<svg class="w-5 h-5 shrink-0 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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:${companyInfo.email}`}>{companyInfo.email}</a>
</p>
<p class="flex items-center gap-2 hover:text-primary-600 transition-colors">
<svg class="w-5 h-5 shrink-0 text-primary-600" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<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>
<span>LINE: {companyInfo.line}</span>
</p>
<p class="text-neutral-400 text-xs">{companyInfo.hours}</p>
</div>
</div>
<!-- Row 2: Quick Links + Products (2 columns) -->
<div class="grid grid-cols-2 gap-6 text-center max-w-2xl mx-auto">
<div data-animate="fade-up" data-animate-delay="100">
<h3 class="font-semibold text-lg mb-4 text-primary-700">ลิงก์ด่วน</h3>
<ul class="space-y-2 text-sm text-neutral-600">
<li><a href="/" class="hover:text-primary-600 hover:translate-x-1 transition-all inline-block">หน้าแรก</a></li>
<li><a href="/about-us" class="hover:text-primary-600 hover:translate-x-1 transition-all inline-block">เกี่ยวกับเรา</a></li>
<li><a href="/%E0%B8%9A%E0%B8%97%E0%B8%84%E0%B8%A7%E0%B8%B2%E0%B8%A1" class="hover:text-primary-600 hover:translate-x-1 transition-all inline-block">บทความ</a></li>
<li><a href="/all-products" class="hover:text-primary-600 hover:translate-x-1 transition-all inline-block">สินค้าทั้งหมด</a></li>
<li><a href="/portfolio" class="hover:text-primary-600 hover:translate-x-1 transition-all inline-block">ผลงาน</a></li>
<li><a href="/contact-us" class="hover:text-primary-600 hover:translate-x-1 transition-all inline-block">ติดต่อเรา</a></li>
</ul>
</div>
<div data-animate="fade-up" data-animate-delay="200">
<h3 class="font-semibold text-lg mb-4 text-primary-700">สินค้ายอดนิยม</h3>
<ul class="space-y-2 text-sm text-neutral-600">
{productLinks.map(link => (
<li>
<a href={link.href} class="hover:text-primary-600 hover:translate-x-1 transition-all inline-block">{link.title}</a>
</li>
))}
</ul>
</div>
</div>
<!-- Row 3: Contact CTA -->
<div class="max-w-sm mx-auto" data-animate="fade-up" data-animate-delay="300">
<h3 class="font-semibold text-lg mb-4 text-primary-700">ติดต่อเรา</h3>
<div class="space-y-3 max-w-xs mx-auto">
<a
href={`tel:${companyInfo.phone}`}
class="flex items-center justify-center gap-2 w-full bg-primary-600 hover:bg-primary-700 text-white py-3 px-4 rounded-xl font-medium transition-all hover:shadow-lg hover:shadow-primary-600/25 hover:-translate-y-0.5 magnetic-btn btn-shimmer"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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 class="flex flex-col items-center gap-2">
<img
src="/images/line-qr.svg"
alt="LINE QR Code"
class="w-28 h-28 rounded-xl bg-white p-2 shadow-lg hover:shadow-xl transition-shadow"
/>
<a
href="https://line.me/ti/p/~JPPSELECTION"
target="_blank"
rel="noopener"
class="flex items-center justify-center gap-2 w-full bg-accent-500 hover:bg-accent-600 text-white py-3 px-4 rounded-xl font-medium transition-all hover:shadow-lg hover:shadow-accent-500/25 hover:-translate-y-0.5 magnetic-btn btn-shimmer"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<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>
</div>
</div>
<!-- ============================================================ -->
<!-- Desktop layout (>=1024px): 4 columns, original layout -->
<!-- ============================================================ -->
<div class="hidden lg:grid lg:grid-cols-4 gap-8 lg:gap-12">
<!-- Company Info -->
<div class="lg:col-span-1" data-animate="fade-left" data-animate-delay="0">
<img src="/images/logo/dealplustech-logo.png" alt="ดีล พลัส เทค" class="h-10 w-auto mb-4 transition-transform hover:scale-105 duration-300" />

View File

@@ -122,20 +122,20 @@ import BaseLayout from '@/layouts/BaseLayout.astro';
</section>
<!-- Stats -->
<section class="py-16 bg-neutral-50">
<section class="py-16 bg-gradient-to-br from-neutral-50 to-primary-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="grid grid-cols-3 gap-6 lg:gap-8">
<div class="text-center p-8 bg-white rounded-2xl border border-neutral-200">
<div class="text-4xl lg:text-5xl font-bold text-primary-600 mb-2">10+</div>
<div class="text-neutral-600">ปีประสบการณ์</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8" data-animate-stagger>
<div class="text-center p-8 bg-white rounded-3xl shadow-sm hover:shadow-lg transition-shadow duration-300">
<h3 class="text-5xl lg:text-6xl font-bold text-primary-700 mb-2">
<span data-counter="15">0</span>+
</h3>
<p class="text-neutral-600 font-medium">ปีประสบการณ์</p>
</div>
<div class="text-center p-8 bg-white rounded-2xl border border-neutral-200">
<div class="text-4xl lg:text-5xl font-bold text-primary-600 mb-2">1000+</div>
<div class="text-neutral-600">โปรเจคสำเร็จ</div>
</div>
<div class="text-center p-8 bg-white rounded-2xl border border-neutral-200">
<div class="text-4xl lg:text-5xl font-bold text-primary-600 mb-2">500+</div>
<div class="text-neutral-600">สินค้าในคลัง</div>
<div class="text-center p-8 bg-white rounded-3xl shadow-sm hover:shadow-lg transition-shadow duration-300">
<h3 class="text-5xl lg:text-6xl font-bold text-primary-700 mb-2">
<span data-counter="400">0</span>+
</h3>
<p class="text-neutral-600 font-medium">ลูกค้าทั่วประเทศ</p>
</div>
</div>
</div>
@@ -145,44 +145,26 @@ import BaseLayout from '@/layouts/BaseLayout.astro';
<section class="py-16 lg:py-24 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="text-center mb-12">
<h2 class="text-3xl font-bold text-primary-700 mb-4">ทำไมต้องเลือกเรา</h2>
<span class="inline-block px-4 py-1 bg-primary-100 text-primary-700 rounded-full text-sm font-medium mb-4">จุดเด่นของเรา</span>
<h2 class="text-3xl lg:text-4xl xl:text-5xl font-bold text-neutral-900 mb-4">ทำไมช่าง/ผู้รับเหมาเลือกเรา</h2>
<p class="text-neutral-600 max-w-2xl mx-auto text-lg">ครบทั้งสินค้า บริการ และความเร็ว — เพื่อให้งานของคุณเดินหน้าไม่สะดุด</p>
</div>
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="text-center p-6">
<div class="w-16 h-16 mx-auto mb-4 bg-accent-100 rounded-2xl flex items-center justify-center">
<svg class="w-8 h-8 text-accent-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 class="text-lg font-semibold text-primary-700 mb-2">คุณภาพสูง</h3>
<p class="text-sm text-neutral-600">สินค้าผ่านมาตรฐาน มอก. รับประกันคุณภาพ</p>
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8" data-animate-stagger>
<div class="group bg-white rounded-3xl p-8 border border-neutral-200 hover:border-primary-300 hover:shadow-xl transition-all duration-300 hover:-translate-y-1">
<h3 class="text-xl font-bold text-neutral-900 mb-2">ส่งฟรี กทม./ปริมณฑล</h3>
<p class="text-neutral-600 text-sm leading-relaxed">ส่งถึงไซต์งานฟรี ไม่ต้องวิ่งรับเอง ลดเวลา ลดต้นทุนขนส่ง</p>
</div>
<div class="text-center p-6">
<div class="w-16 h-16 mx-auto mb-4 bg-accent-100 rounded-2xl flex items-center justify-center">
<svg class="w-8 h-8 text-accent-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 class="text-lg font-semibold text-primary-700 mb-2">ราคาโรงงาน</h3>
<p class="text-sm text-neutral-600">ราคาพิเศษ ราคาโรงงาน ไม่มีมาร์จิ้นกลาง</p>
<div class="group bg-white rounded-3xl p-8 border border-neutral-200 hover:border-primary-300 hover:shadow-xl transition-all duration-300 hover:-translate-y-1">
<h3 class="text-xl font-bold text-neutral-900 mb-2">Lead time 13 วัน</h3>
<p class="text-neutral-600 text-sm leading-relaxed">สั่งวันนี้ ได้ของไว — ไม่ต้องรอ 1-2 สัปดาห์เหมือนเจ้าอื่น</p>
</div>
<div class="text-center p-6">
<div class="w-16 h-16 mx-auto mb-4 bg-accent-100 rounded-2xl flex items-center justify-center">
<svg class="w-8 h-8 text-accent-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<h3 class="text-lg font-semibold text-primary-700 mb-2">จัดส่งรวดเร็ว</h3>
<p class="text-sm text-neutral-600">สินค้าพร้อมส่ง จัดส่งภายใน 1-3 วัน</p>
<div class="group bg-white rounded-3xl p-8 border border-neutral-200 hover:border-primary-300 hover:shadow-xl transition-all duration-300 hover:-translate-y-1">
<h3 class="text-xl font-bold text-neutral-900 mb-2">ราคาโรงงาน</h3>
<p class="text-neutral-600 text-sm leading-relaxed">ตรงจากผู้ผลิต ไม่ผ่านตัวกลาง คุณภาพเดียวกันในราคาที่ต่ำกว่า</p>
</div>
<div class="text-center p-6">
<div class="w-16 h-16 mx-auto mb-4 bg-accent-100 rounded-2xl flex items-center justify-center">
<svg class="w-8 h-8 text-accent-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</div>
<h3 class="text-lg font-semibold text-primary-700 mb-2">บริการดี</h3>
<p class="text-sm text-neutral-600">ให้คำปรึกษาฟรี ตอบคำถามทุกคำถาม</p>
<div class="group bg-white rounded-3xl p-8 border border-neutral-200 hover:border-primary-300 hover:shadow-xl transition-all duration-300 hover:-translate-y-1">
<h3 class="text-xl font-bold text-neutral-900 mb-2">ทีมช่างแนะนำ</h3>
<p class="text-neutral-600 text-sm leading-relaxed">ส่งสเปกมา ช่วยเทียบยี่ห้อ ขนาด PN ฟรี — ไม่ต้องเดาว่าท่อตัวไหนใช่</p>
</div>
</div>
</div>

View File

@@ -136,35 +136,58 @@ const categoryIdMap = Object.fromEntries(categories.map(c => [c.name, c.id]));
</section>
<script>
document.addEventListener('DOMContentLoaded', () => {
function applyFilter(filter, updateUrl = false) {
const filterBtns = document.querySelectorAll('.filter-btn');
const productCards = document.querySelectorAll('.product-card');
// Update active state on buttons
filterBtns.forEach(b => {
b.classList.remove('bg-primary-600', 'text-white', 'active');
b.classList.add('bg-neutral-100', 'text-neutral-700');
if (b.getAttribute('data-filter') === filter) {
b.classList.add('bg-primary-600', 'text-white', 'active');
b.classList.remove('bg-neutral-100', 'text-neutral-700');
}
});
// Filter products
productCards.forEach((card, index) => {
if (filter === 'all' || card.getAttribute('data-category') === filter) {
card.style.display = 'block';
card.style.animationDelay = `${index * 50}ms`;
card.classList.add('animate-fade-in');
} else {
card.style.display = 'none';
card.classList.remove('animate-fade-in');
}
});
if (updateUrl) {
const url = new URL(window.location.href);
if (filter === 'all') {
url.searchParams.delete('filter');
} else {
url.searchParams.set('filter', filter);
}
window.history.replaceState({}, '', url);
}
}
document.addEventListener('DOMContentLoaded', () => {
// Read ?filter=<id> from URL on page load
const params = new URLSearchParams(window.location.search);
const initialFilter = params.get('filter') || 'all';
const filterBtns = document.querySelectorAll('.filter-btn');
filterBtns.forEach(btn => {
btn.addEventListener('click', () => {
const filter = btn.getAttribute('data-filter');
// Update active state
filterBtns.forEach(b => {
b.classList.remove('bg-primary-600', 'text-white', 'active');
b.classList.add('bg-neutral-100', 'text-neutral-700');
});
btn.classList.add('bg-primary-600', 'text-white', 'active');
btn.classList.remove('bg-neutral-100', 'text-neutral-700');
// Filter products with animation
productCards.forEach((card, index) => {
if (filter === 'all' || card.getAttribute('data-category') === filter) {
card.style.display = 'block';
card.style.animationDelay = `${index * 50}ms`;
card.classList.add('animate-fade-in');
} else {
card.style.display = 'none';
card.classList.remove('animate-fade-in');
}
});
applyFilter(filter, true);
});
});
// Apply initial filter from URL (no URL update on load)
applyFilter(initialFilter, false);
});
</script>

View File

@@ -129,6 +129,64 @@ const articles = (await getCollection('blog')).sort(
</div>
</section>
<!-- ทำไมเลือกเรา -->
<section class="py-16 lg:py-24 bg-gradient-to-br from-neutral-50 to-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Section Header -->
<div class="text-center mb-12 lg:mb-16" data-animate="fade-up">
<span class="inline-block px-4 py-1 bg-primary-100 text-primary-700 rounded-full text-sm font-medium mb-4">จุดเด่นของเรา</span>
<h2 class="text-3xl lg:text-4xl xl:text-5xl font-bold text-neutral-900 mb-4">ทำไมช่าง/ผู้รับเหมาเลือกเรา</h2>
<p class="text-neutral-600 max-w-2xl mx-auto text-lg">ครบทั้งสินค้า บริการ และความเร็ว — เพื่อให้งานของคุณเดินหน้าไม่สะดุด</p>
</div>
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8" data-animate-stagger>
<!-- Why 1: ส่งฟรี -->
<div class="group bg-white rounded-3xl p-8 border border-neutral-200 hover:border-primary-300 hover:shadow-xl transition-all duration-300 hover:-translate-y-1">
<div class="inline-flex items-center justify-center w-14 h-14 bg-primary-100 rounded-2xl mb-5 group-hover:bg-primary-600 group-hover:text-white transition-colors">
<svg class="w-7 h-7 text-primary-600 group-hover:text-white transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16V6a1 1 0 00-1-1H4a1 1 0 00-1 1v10a1 1 0 001 1h1m8-1a1 1 0 01-1 1H9m4-1V8a1 1 0 011-1h2.586a1 1 0 01.707.293l3.414 3.414a1 1 0 01.293.707V16a1 1 0 01-1 1h-1m-6-1a1 1 0 001 1h1M5 17a2 2 0 104 0m-4 0a2 2 0 114 0m6 0a2 2 0 104 0m-4 0a2 2 0 114 0" />
</svg>
</div>
<h3 class="text-xl font-bold text-neutral-900 mb-2">ส่งฟรี กทม./ปริมณฑล</h3>
<p class="text-neutral-600 text-sm leading-relaxed">ส่งถึงไซต์งานฟรี ไม่ต้องวิ่งรับเอง ลดเวลา ลดต้นทุนขนส่ง</p>
</div>
<!-- Why 2: Lead time -->
<div class="group bg-white rounded-3xl p-8 border border-neutral-200 hover:border-primary-300 hover:shadow-xl transition-all duration-300 hover:-translate-y-1">
<div class="inline-flex items-center justify-center w-14 h-14 bg-accent-100 rounded-2xl mb-5 group-hover:bg-accent-500 group-hover:text-white transition-colors">
<svg class="w-7 h-7 text-accent-500 group-hover:text-white transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 class="text-xl font-bold text-neutral-900 mb-2">Lead time 13 วัน</h3>
<p class="text-neutral-600 text-sm leading-relaxed">สั่งวันนี้ ได้ของไว — ไม่ต้องรอ 1-2 สัปดาห์เหมือนเจ้าอื่น</p>
</div>
<!-- Why 3: ราคาโรงงาน -->
<div class="group bg-white rounded-3xl p-8 border border-neutral-200 hover:border-primary-300 hover:shadow-xl transition-all duration-300 hover:-translate-y-1">
<div class="inline-flex items-center justify-center w-14 h-14 bg-primary-100 rounded-2xl mb-5 group-hover:bg-primary-600 group-hover:text-white transition-colors">
<svg class="w-7 h-7 text-primary-600 group-hover:text-white transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 class="text-xl font-bold text-neutral-900 mb-2">ราคาโรงงาน</h3>
<p class="text-neutral-600 text-sm leading-relaxed">ตรงจากผู้ผลิต ไม่ผ่านตัวกลาง คุณภาพเดียวกันในราคาที่ต่ำกว่า</p>
</div>
<!-- Why 4: ทีมช่างแนะนำ -->
<div class="group bg-white rounded-3xl p-8 border border-neutral-200 hover:border-primary-300 hover:shadow-xl transition-all duration-300 hover:-translate-y-1">
<div class="inline-flex items-center justify-center w-14 h-14 bg-accent-100 rounded-2xl mb-5 group-hover:bg-accent-500 group-hover:text-white transition-colors">
<svg class="w-7 h-7 text-accent-500 group-hover:text-white transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
</div>
<h3 class="text-xl font-bold text-neutral-900 mb-2">ทีมช่างแนะนำ</h3>
<p class="text-neutral-600 text-sm leading-relaxed">ส่งสเปกมา ช่วยเทียบยี่ห้อ ขนาด PN ฟรี — ไม่ต้องเดาว่าท่อตัวไหนใช่</p>
</div>
</div>
</div>
</section>
<!-- Featured Products Section -->
<section class="py-16 lg:py-24 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
@@ -247,18 +305,134 @@ const articles = (await getCollection('blog')).sort(
</div>
</section>
<!-- หมวดสินค้า -->
<section class="py-16 lg:py-24 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Section Header -->
<div class="text-center mb-12 lg:mb-16" data-animate="fade-up">
<span class="inline-block px-4 py-1 bg-primary-100 text-primary-700 rounded-full text-sm font-medium mb-4">เลือกตามงาน</span>
<h2 class="text-3xl lg:text-4xl xl:text-5xl font-bold text-neutral-900 mb-4">หมวดสินค้าของเรา</h2>
<p class="text-neutral-600 max-w-2xl mx-auto text-lg">ทุกหมวดเน้นงานอาคาร/โรงงาน/โรงแรม — คลิกเพื่อดูสินค้าเฉพาะหมวด</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 lg:gap-6" data-animate-stagger>
<!-- Category 1: ท่อพีพีอาร์ (ไทย PPR) -->
<a href="/all-products?filter=ppr" class="group relative h-64 rounded-3xl overflow-hidden border border-neutral-200 hover:shadow-2xl transition-all duration-500 hover:-translate-y-1">
<img src="/images/products-cropped/ppr-pipe_000C.jpg" alt="ท่อพีพีอาร์" class="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" loading="lazy" />
<div class="absolute inset-0 bg-gradient-to-t from-neutral-900/90 via-neutral-900/40 to-transparent"></div>
<div class="relative h-full flex flex-col justify-end p-6 text-white">
<h3 class="text-xl font-bold mb-1">ท่อพีพีอาร์</h3>
<p class="text-sm text-white/80 mb-2">6 รายการ</p>
<span class="inline-flex items-center gap-1 text-sm font-medium group-hover:gap-2 transition-all">
ดูสินค้า
<svg 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="M9 5l7 7-7 7" /></svg>
</span>
</div>
</a>
<!-- Category 2: เครื่องเชื่อมท่อ -->
<a href="/all-products?filter=welding" class="group relative h-64 rounded-3xl overflow-hidden border border-neutral-200 hover:shadow-2xl transition-all duration-500 hover:-translate-y-1">
<img src="/images/products-misc/ppr-welding-machine-main.jpg" alt="เครื่องเชื่อมท่อ" class="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" loading="lazy" />
<div class="absolute inset-0 bg-gradient-to-t from-neutral-900/90 via-neutral-900/40 to-transparent"></div>
<div class="relative h-full flex flex-col justify-end p-6 text-white">
<h3 class="text-xl font-bold mb-1">เครื่องเชื่อมท่อ</h3>
<p class="text-sm text-white/80 mb-2">4 รายการ</p>
<span class="inline-flex items-center gap-1 text-sm font-medium group-hover:gap-2 transition-all">
ดูสินค้า
<svg 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="M9 5l7 7-7 7" /></svg>
</span>
</div>
</a>
<!-- Category 3: ระบบน้ำ -->
<a href="/all-products?filter=water" class="group relative h-64 rounded-3xl overflow-hidden border border-neutral-200 hover:shadow-2xl transition-all duration-500 hover:-translate-y-1">
<img src="/images/products-cropped/water-pump_000C.jpg" alt="ระบบน้ำ" class="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" loading="lazy" />
<div class="absolute inset-0 bg-gradient-to-t from-neutral-900/90 via-neutral-900/40 to-transparent"></div>
<div class="relative h-full flex flex-col justify-end p-6 text-white">
<h3 class="text-xl font-bold mb-1">ระบบน้ำ</h3>
<p class="text-sm text-white/80 mb-2">3 รายการ</p>
<span class="inline-flex items-center gap-1 text-sm font-medium group-hover:gap-2 transition-all">
ดูสินค้า
<svg 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="M9 5l7 7-7 7" /></svg>
</span>
</div>
</a>
<!-- Category 4: อุปกรณ์ปรับอากาศ (กริล) -->
<a href="/all-products?filter=ac" class="group relative h-64 rounded-3xl overflow-hidden border border-neutral-200 hover:shadow-2xl transition-all duration-500 hover:-translate-y-1">
<img src="/images/products-cropped/grilles_000C.jpg" alt="อุปกรณ์ปรับอากาศ" class="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" loading="lazy" />
<div class="absolute inset-0 bg-gradient-to-t from-neutral-900/90 via-neutral-900/40 to-transparent"></div>
<div class="relative h-full flex flex-col justify-end p-6 text-white">
<h3 class="text-xl font-bold mb-1">อุปกรณ์ปรับอากาศ</h3>
<p class="text-sm text-white/80 mb-2">3 รายการ</p>
<span class="inline-flex items-center gap-1 text-sm font-medium group-hover:gap-2 transition-all">
ดูสินค้า
<svg 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="M9 5l7 7-7 7" /></svg>
</span>
</div>
</a>
<!-- Category 5: อุปกรณ์ดับเพลิง -->
<a href="/all-products?filter=fire" class="group relative h-64 rounded-3xl overflow-hidden border border-neutral-200 hover:shadow-2xl transition-all duration-500 hover:-translate-y-1">
<img src="/images/products-cropped/extinguishers_000C.jpg" alt="อุปกรณ์ดับเพลิง" class="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" loading="lazy" />
<div class="absolute inset-0 bg-gradient-to-t from-neutral-900/90 via-neutral-900/40 to-transparent"></div>
<div class="relative h-full flex flex-col justify-end p-6 text-white">
<h3 class="text-xl font-bold mb-1">อุปกรณ์ดับเพลิง</h3>
<p class="text-sm text-white/80 mb-2">2 รายการ</p>
<span class="inline-flex items-center gap-1 text-sm font-medium group-hover:gap-2 transition-all">
ดูสินค้า
<svg 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="M9 5l7 7-7 7" /></svg>
</span>
</div>
</a>
<!-- Category 6: ฉนวนหุ้มท่อ (Thermobreak) -->
<a href="/all-products?filter=insulation" class="group relative h-64 rounded-3xl overflow-hidden border border-neutral-200 hover:shadow-2xl transition-all duration-500 hover:-translate-y-1">
<img src="/images/thermobreak/thermobreak-solarblock.png" alt="ฉนวนหุ้มท่อ" class="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" loading="lazy" />
<div class="absolute inset-0 bg-gradient-to-t from-neutral-900/90 via-neutral-900/40 to-transparent"></div>
<div class="relative h-full flex flex-col justify-end p-6 text-white">
<h3 class="text-xl font-bold mb-1">ฉนวนหุ้มท่อ</h3>
<p class="text-sm text-white/80 mb-2">4 รายการ</p>
<span class="inline-flex items-center gap-1 text-sm font-medium group-hover:gap-2 transition-all">
ดูสินค้า
<svg 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="M9 5l7 7-7 7" /></svg>
</span>
</div>
</a>
<!-- Category 7: ระบบรั้ว -->
<a href="/all-products?filter=fence" class="group relative h-64 rounded-3xl overflow-hidden border border-neutral-200 hover:shadow-2xl transition-all duration-500 hover:-translate-y-1">
<img src="/images/products-raw/vineman/ระบบรั้วไวน์แมน-Vineman-e1613286324569-1024x880.jpg" alt="ระบบรั้ว" class="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" loading="lazy" />
<div class="absolute inset-0 bg-gradient-to-t from-neutral-900/90 via-neutral-900/40 to-transparent"></div>
<div class="relative h-full flex flex-col justify-end p-6 text-white">
<h3 class="text-xl font-bold mb-1">ระบบรั้ว</h3>
<p class="text-sm text-white/80 mb-2">2 รายการ</p>
<span class="inline-flex items-center gap-1 text-sm font-medium group-hover:gap-2 transition-all">
ดูสินค้า
<svg 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="M9 5l7 7-7 7" /></svg>
</span>
</div>
</a>
<!-- All products tile (no image, just text CTA) -->
<a href="/all-products" class="group relative h-64 rounded-3xl overflow-hidden bg-gradient-to-br from-neutral-900 to-neutral-700 border border-neutral-700 hover:border-neutral-500 hover:shadow-2xl transition-all duration-500 hover:-translate-y-1 text-white flex flex-col justify-end p-6">
<h3 class="text-xl font-bold mb-1 group-hover:text-primary-300 transition-colors">สินค้าทั้งหมด</h3>
<p class="text-sm text-white/70 mb-2">ดูทุกหมวดพร้อมกัน</p>
<span class="inline-flex items-center gap-1 text-sm font-medium text-primary-300 group-hover:gap-2 transition-all">
เปิดหน้ารวม
<svg 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="M9 5l7 7-7 7" /></svg>
</span>
</a>
</div>
</div>
</section>
<!-- Trust Badges with Counter Animation -->
<section class="py-16 lg:py-20 bg-gradient-to-br from-neutral-50 to-primary-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8" data-animate-stagger>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8" data-animate-stagger>
<!-- Badge 1 -->
<div class="text-center p-6 bg-white rounded-3xl shadow-sm hover:shadow-lg transition-shadow duration-300">
<div class="inline-flex items-center justify-center w-16 h-16 bg-primary-100 rounded-2xl mb-4">
<svg class="w-8 h-8 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
</div>
<h3 class="text-3xl lg:text-4xl font-bold text-primary-700 mb-1">
<h3 class="text-5xl lg:text-6xl font-bold text-primary-700 mb-2">
<span data-counter="15">0</span>+
</h3>
<p class="text-neutral-600 font-medium">ปีประสบการณ์</p>
@@ -266,38 +440,15 @@ const articles = (await getCollection('blog')).sort(
<!-- Badge 2 -->
<div class="text-center p-6 bg-white rounded-3xl shadow-sm hover:shadow-lg transition-shadow duration-300">
<div class="inline-flex items-center justify-center w-16 h-16 bg-accent-100 rounded-2xl mb-4">
<svg class="w-8 h-8 text-accent-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
</div>
<h3 class="text-3xl lg:text-4xl font-bold text-primary-700 mb-1">
<span data-counter="2500">0</span>+
<h3 class="text-5xl lg:text-6xl font-bold text-primary-700 mb-2">
<span data-counter="400">0</span>+
</h3>
<p class="text-neutral-600 font-medium">ลูกค้าทั่วประเทศ</p>
</div>
<!-- Badge 3 -->
<div class="text-center p-6 bg-white rounded-3xl shadow-sm hover:shadow-lg transition-shadow duration-300">
<div class="inline-flex items-center justify-center w-16 h-16 bg-primary-100 rounded-2xl mb-4">
<svg class="w-8 h-8 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
</div>
<h3 class="text-3xl lg:text-4xl font-bold text-primary-700 mb-1">
<span data-counter="5000">0</span>+
</h3>
<p class="text-neutral-600 font-medium">รายการสินค้า</p>
</div>
<!-- Badge 4 -->
<div class="text-center p-6 bg-white rounded-3xl shadow-sm hover:shadow-lg transition-shadow duration-300">
<div class="inline-flex items-center justify-center w-16 h-16 bg-accent-100 rounded-2xl mb-4">
<svg class="w-8 h-8 text-accent-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<h3 class="text-3xl lg:text-4xl font-bold text-primary-700 mb-1">
<h3 class="text-5xl lg:text-6xl font-bold text-primary-700 mb-2">
<span data-counter="98">0</span>%
</h3>
<p class="text-neutral-600 font-medium">ลูกค้าพึงพอใจ</p>