From 0d13390f345beed9a3d9ebd6ff8ad2a5c6ea38a3 Mon Sep 17 00:00:00 2001
From: Thanakarn Klangkasame <77600906+Simulationable@users.noreply.github.com>
Date: Fri, 31 Oct 2025 16:57:00 +0700
Subject: [PATCH] Add Jenkinsfile
---
Jenkinsfile | 109 +++++++++++
src/app/(public)/about/page.tsx | 61 +++---
src/app/(public)/companies/page.tsx | 77 ++++++--
src/app/(public)/contact/page.tsx | 119 +++++++++---
src/app/(public)/distribution/page.tsx | 58 ++++--
src/app/(public)/founders/page.tsx | 89 +++++++--
src/app/(public)/future/page.tsx | 93 +++++++--
src/app/(public)/milestones/page.tsx | 70 +++++--
src/app/(public)/performance/page.tsx | 91 +++++----
src/app/(public)/products/page.tsx | 60 +++++-
src/app/components/Footer.tsx | 114 +++++++++--
src/app/components/Nav.tsx | 171 ++++++++++++----
src/app/components/ProductGrid.tsx | 80 ++++++--
src/app/components/Section.tsx | 42 ++--
src/app/components/StatCard.tsx | 31 ++-
src/app/components/Timeline.tsx | 78 ++++++--
src/app/fonts.ts | 10 +
src/app/layout.tsx | 45 ++---
src/app/page.tsx | 259 +++++++++++++++++++------
19 files changed, 1294 insertions(+), 363 deletions(-)
create mode 100644 Jenkinsfile
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..3e1d2a4
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,109 @@
+pipeline {
+ agent any
+ options {
+ ansiColor('xterm'); timestamps(); disableConcurrentBuilds()
+ timeout(time: 25, unit: 'MINUTES')
+ buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '10'))
+ }
+
+ environment {
+ REGISTRY = 'registry.aetherframe.tech'
+ IMAGE = 'simulationable/amrez-nova-web-services-app' // 👈 ชื่ออิมเมจให้ตรง repo นี้
+ APP_PORT = '3000' // Next.js in container
+ HOST_PORT = '5007' // 👈 พอร์ต host (กันชนตัวอื่น)
+ NODE_ENV = 'production'
+ NEXT_TELEMETRY_DISABLED = '1'
+ DOCKER_BUILDKIT = '0'
+ DOCKER_CLI_HINTS = 'false'
+ }
+
+ stages {
+ stage('Checkout'){ steps { checkout scm } }
+
+ stage('Preflight') {
+ steps {
+ sh '''
+ set -eux
+ echo "== Sys =="; whoami || true; uname -a || true
+ echo "== Git =="; git rev-parse --abbrev-ref HEAD; git rev-parse --short=12 HEAD
+ echo "== Docker =="; docker version; docker info || true
+ echo "== Registry =="; getent hosts ${REGISTRY} || true; curl -sS -I https://${REGISTRY}/v2/ || true
+ '''
+ }
+ }
+
+ stage('Docker Build'){
+ steps {
+ script {
+ def branch = (env.BRANCH_NAME ?: env.GIT_BRANCH ?: sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim())
+ .replaceFirst(/^origin\\//,'').toLowerCase()
+ def commit = sh(script: 'git rev-parse --short=12 HEAD', returnStdout: true).trim()
+ def tag = "${branch}-${env.BUILD_NUMBER}"
+ env.IMAGE_TAG = tag
+
+ sh """
+ set -eux
+ docker build --pull \
+ --label org.opencontainers.image.revision="${commit}" \
+ -t ${REGISTRY}/${IMAGE}:${tag} \
+ -t ${REGISTRY}/${IMAGE}:latest \
+ .
+ docker images ${REGISTRY}/${IMAGE} --digests || true
+ """
+ }
+ }
+ }
+
+ stage('Registry Check') {
+ steps {
+ sh '''
+ set -eux
+ curl -sS -I https://${REGISTRY}/v2/ || true
+ docker images ${REGISTRY}/${IMAGE} --digests || true
+ '''
+ }
+ }
+
+ stage('Docker Push'){
+ steps {
+ withCredentials([usernamePassword(credentialsId:'reg-creds', usernameVariable:'REG_USER', passwordVariable:'REG_PASS')]){
+ sh '''
+ set -eux
+ docker logout ${REGISTRY} || true
+ echo "$REG_PASS" | docker login ${REGISTRY} -u "$REG_USER" --password-stdin
+ docker push ${REGISTRY}/${IMAGE}:${IMAGE_TAG}
+ docker push ${REGISTRY}/${IMAGE}:latest
+ curl -sS -u "$REG_USER:$REG_PASS" https://${REGISTRY}/v2/${IMAGE}/tags/list || true
+ docker logout ${REGISTRY} || true
+ '''
+ }
+ }
+ }
+
+ stage('Deploy (same host)'){
+ when { branch 'main' }
+ steps {
+ sh """
+ set -eux
+ docker rm -f amrez-nova-web || true
+ docker run -d --name amrez-nova-web \\
+ -p 127.0.0.1:${HOST_PORT}:${APP_PORT} \\
+ -e NODE_ENV=${NODE_ENV} \\
+ -e PORT=${APP_PORT} \\
+ --restart=always \\
+ ${REGISTRY}/${IMAGE}:latest
+ sleep 2
+ docker ps --no-trunc | sed -n '1,5p' || true
+ docker logs --tail=200 amrez-nova-web || true
+ (curl -fsS http://127.0.0.1:${HOST_PORT}/ || true)
+ """
+ }
+ }
+ }
+
+ post {
+ success { echo "✅ Deployed at http://127.0.0.1:${HOST_PORT}" }
+ failure { echo "❌ Failed — ดูสเตจ Preflight/Push/Deploy" }
+ always { cleanWs() }
+ }
+}
diff --git a/src/app/(public)/about/page.tsx b/src/app/(public)/about/page.tsx
index 163b1d1..6d626b3 100644
--- a/src/app/(public)/about/page.tsx
+++ b/src/app/(public)/about/page.tsx
@@ -1,24 +1,41 @@
-import Section from "../../components/Section";
-import { company } from "../../lib/data";
+// src/app/about/page.tsx
+import Section from "@/app/components/Section";
+import {company} from "@/app/lib/data";
export default function AboutPage() {
- return (
-
-
-
-
Vision
-
{company.vision}
-
-
-
Mission
-
{company.mission}
-
-
-
-
Company
-
{company.about}
-
Source: Company profile
-
-
- );
-}
\ No newline at end of file
+ return (
+
+
+ {/* Vision */}
+
+
+ Vision
+
+
+ {company.vision}
+
+
+
+ {/* Mission */}
+
+
+ Mission
+
+
+ {company.mission}
+
+
+
+
+ {/* Company */}
+
+
+ Company
+
+
+ {company.about}
+
+
+
+ );
+}
diff --git a/src/app/(public)/companies/page.tsx b/src/app/(public)/companies/page.tsx
index 6ed953b..d54c83b 100644
--- a/src/app/(public)/companies/page.tsx
+++ b/src/app/(public)/companies/page.tsx
@@ -1,17 +1,64 @@
-import Section from "../../components/Section";
-import { groupCompanies } from "../../lib/data";
+// src/app/companies/page.tsx
+import Section from "@/app/components/Section";
+import Link from "next/link";
+import {groupCompanies} from "@/app/lib/data";
export default function CompaniesPage() {
- return (
-
-
- {groupCompanies.map((c, i) => (
-
- ))}
-
-
- );
-}
\ No newline at end of file
+ return (
+
+
+ {groupCompanies.map((c: any, i: number) => (
+
+
+ {c.logo ? (
+

+ ) : (
+
+ {(c.name?.[0] ?? "•").toUpperCase()}
+
+ )}
+
+
+ {c.name}
+
+ {c.focus && (
+
{c.focus}
+ )}
+
+
+
+ {(c.url || c.href) && (
+
+ )}
+
+ ))}
+
+
+ );
+}
diff --git a/src/app/(public)/contact/page.tsx b/src/app/(public)/contact/page.tsx
index 5b8e107..d619ace 100644
--- a/src/app/(public)/contact/page.tsx
+++ b/src/app/(public)/contact/page.tsx
@@ -1,26 +1,101 @@
+// src/app/contact/page.tsx
"use client";
-import Section from "../../components/Section";
-import { useState } from "react";
+import Section from "@/app/components/Section";
+import {useState} from "react";
export default function ContactPage() {
- const [sent, setSent] = useState(false);
- return (
-
- {!sent ? (
-
- ) : (
-
-
Thanks!
-
We‘ll be in touch shortly.
-
- )}
-
- );
-}
\ No newline at end of file
+ const [sent, setSent] = useState(false);
+
+ const field =
+ "w-full rounded-xl border border-black/10 bg-white px-4 py-3 text-neutral-900 placeholder:text-neutral-400 " +
+ "focus:outline-none focus:ring-2 focus:ring-neutral-900/10 focus:border-neutral-300";
+
+ return (
+
+ );
+}
diff --git a/src/app/(public)/distribution/page.tsx b/src/app/(public)/distribution/page.tsx
index 9cfda35..28e15f4 100644
--- a/src/app/(public)/distribution/page.tsx
+++ b/src/app/(public)/distribution/page.tsx
@@ -1,19 +1,43 @@
-import Section from "../../components/Section";
-import { channels } from "../../lib/data";
+// src/app/distribution/page.tsx
+import Section from "@/app/components/Section";
+import {channels} from "@/app/lib/data";
export default function DistributionPage() {
- return (
-
-
- {channels.map((c, i) => (
-
-
{c.name}
-
- {c.items.map((it, idx) => - {it}
)}
-
-
- ))}
-
-
- );
-}
\ No newline at end of file
+ return (
+
+
+ {channels.map((c, i) => (
+
+
+ {c.name}
+
+
+
+ {c.items.map((it: string, idx: number) => (
+ -
+
+ {it}
+
+
+ ))}
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/app/(public)/founders/page.tsx b/src/app/(public)/founders/page.tsx
index e7e348c..ae3849d 100644
--- a/src/app/(public)/founders/page.tsx
+++ b/src/app/(public)/founders/page.tsx
@@ -1,18 +1,75 @@
-import Section from "../../components/Section";
-import { founders } from "../../lib/data";
+// src/app/founders/page.tsx
+import Section from "@/app/components/Section";
+import { founders } from "@/app/lib/data";
export default function FoundersPage() {
- return (
-
-
- {founders.map((f, i) => (
-
-
{f.name}
-
{f.role}
-
{f.bio}
-
- ))}
-
-
- );
-}
\ No newline at end of file
+ return (
+
+
+ {founders.map((f: any, i: number) => (
+
+
+ {/* avatar */}
+ {f.photo ? (
+

+ ) : (
+
+ {(f.name?.[0] ?? "•").toUpperCase()}
+
+ )}
+
+
+
+ {f.name}
+
+ {f.role && (
+
{f.role}
+ )}
+
+
+
+ {f.bio && (
+
+ {f.bio}
+
+ )}
+
+ {/* optional links if provided */}
+ {(f.linkedin || f.url) && (
+
+ )}
+
+ ))}
+
+
+ );
+}
diff --git a/src/app/(public)/future/page.tsx b/src/app/(public)/future/page.tsx
index e5e8f2d..68c2ae0 100644
--- a/src/app/(public)/future/page.tsx
+++ b/src/app/(public)/future/page.tsx
@@ -1,17 +1,80 @@
-import Section from "../../components/Section";
-import { futurePlans } from "../../lib/data";
+// src/app/future/page.tsx
+import Section from "@/app/components/Section";
+import { futurePlans } from "@/app/lib/data";
export default function FuturePage() {
- return (
-
-
- {futurePlans.map((p, i) => (
-
-
{p.title}
- {p.details &&
{p.details}
}
-
- ))}
-
-
- );
-}
\ No newline at end of file
+ return (
+
+
+ {futurePlans.map((p: any, i: number) => (
+
+
+
+ {p.title}
+
+
+ {/* สถานะ (ถ้ามี) */}
+ {p.status && (
+
+ {p.status}
+
+ )}
+
+
+ {/* ไทม์ไลน์สั้น ๆ เช่น Q3 2025 (ถ้ามี) */}
+ {(p.quarter || p.when) && (
+
+ {p.quarter ?? p.when}
+
+ )}
+
+ {/* รายละเอียด */}
+ {p.details && (
+
+ {p.details}
+
+ )}
+
+ {/* แท็ก (ถ้ามี) */}
+ {Array.isArray(p.tags) && p.tags.length > 0 && (
+
+ {p.tags.map((t: string, idx: number) => (
+ -
+ {t}
+
+ ))}
+
+ )}
+
+ {/* ลิงก์อ่านเพิ่ม (ถ้ามี) */}
+ {p.href && (
+
+ )}
+
+ ))}
+
+
+ );
+}
diff --git a/src/app/(public)/milestones/page.tsx b/src/app/(public)/milestones/page.tsx
index 7128569..929cfe6 100644
--- a/src/app/(public)/milestones/page.tsx
+++ b/src/app/(public)/milestones/page.tsx
@@ -1,13 +1,61 @@
-import Section from "../../components/Section";
-import Timeline from "../../components/Timeline";
-import { milestones } from "../../lib/data";
+// src/app/milestones/page.tsx
+import Section from "@/app/components/Section";
+import Timeline from "@/app/components/Timeline";
+import { milestones } from "@/app/lib/data";
export default function MilestonesPage() {
- return (
-
- );
-}
\ No newline at end of file
+ const years = milestones.map((m: any) => Number(m.year)).filter(Boolean);
+ const minYear = years.length ? Math.min(...years) : null;
+ const maxYear = years.length ? Math.max(...years) : null;
+ const totalItems = milestones.reduce(
+ (acc: number, m: any) => acc + (Array.isArray(m.items) ? m.items.length : 0),
+ 0
+ );
+
+ return (
+
+ {/* meta stripe */}
+
+
+
+
Years
+
+ {minYear && maxYear ? `${minYear}–${maxYear}` : "—"}
+
+
+
+
Total Milestones
+
+ {totalItems || 0}
+
+
+
+
Entries
+
+ {milestones.length}
+
+
+
+
+
+ {/* timeline card */}
+
+
+ );
+}
diff --git a/src/app/(public)/performance/page.tsx b/src/app/(public)/performance/page.tsx
index 497557d..521f31e 100644
--- a/src/app/(public)/performance/page.tsx
+++ b/src/app/(public)/performance/page.tsx
@@ -1,39 +1,62 @@
+// src/app/performance/page.tsx
"use client";
-import Section from "../../components/Section";
-import { kpis } from "../../lib/data";
-import { useMemo } from "react";
+import Section from "@/app/components/Section";
+import {kpis} from "@/app/lib/data";
+import {useMemo} from "react";
export default function PerformancePage() {
- const rows = useMemo(() => kpis.map(k => ({
- year: k.year,
- sales: k.salesMillionBaht.toFixed(1),
- note: k.note ?? ""
- })), []);
+ const rows = useMemo(() => {
+ const fmt = new Intl.NumberFormat("en-US", {
+ minimumFractionDigits: 1,
+ maximumFractionDigits: 1,
+ });
- return (
-
-
-
-
-
- | Year |
- Sales (M ฿) |
- Notes |
-
-
-
- {rows.map((r) => (
-
- | {r.year} |
- {r.sales} |
- {r.note} |
-
- ))}
-
-
-
- * 2024: Strategic shift to profitability; profit +2.5%.
-
- );
-}
\ No newline at end of file
+ return kpis.map((k: any) => {
+ const n =
+ typeof k.salesMillionBaht === "number" ? k.salesMillionBaht : NaN;
+ return {
+ year: k.year,
+ sales: Number.isFinite(n) ? fmt.format(n) : "-",
+ note: k.note ?? "",
+ };
+ });
+ }, []);
+
+ return (
+
+
+
+
+
+
+ | Year |
+ Sales (M ฿) |
+ Notes |
+
+
+
+ {rows.map((r) => (
+
+ |
+ {r.year}
+ |
+ {r.sales} |
+ {r.note} |
+
+ ))}
+
+
+
+
+
+ {/* หมายเหตุจริงของคุณ—คงไว้ตามเดิมแต่เปลี่ยนสีให้เข้ากับพื้นขาว */}
+
+ * 2024: Strategic shift to profitability; profit +2.5%.
+
+
+ );
+}
diff --git a/src/app/(public)/products/page.tsx b/src/app/(public)/products/page.tsx
index beb1719..8ec3dbf 100644
--- a/src/app/(public)/products/page.tsx
+++ b/src/app/(public)/products/page.tsx
@@ -1,12 +1,52 @@
-import Section from "../../components/Section";
-import ProductGrid from "../../components/ProductGrid";
-import { products } from "../../lib/data";
+// src/app/products/page.tsx
+import Section from "@/app/components/Section";
+import ProductGrid from "@/app/components/ProductGrid";
+import {products} from "@/app/lib/data";
export default function ProductsPage() {
- return (
-
-
- Note: This is a catalog overview — images, shades, and details can be added later.
-
- );
-}
\ No newline at end of file
+ const hasProducts = Array.isArray(products) && products.length > 0;
+ const count = hasProducts ? products.length : 0;
+
+ return (
+
+
+ {/* top hairline */}
+
+
+ {/* header */}
+
+
+
+ Catalog
+
+
+ {count.toLocaleString()} Catelog{count === 1 ? "" : "s"}
+
+
+
+
+ {/* grid area */}
+
+ {hasProducts ? (
+
+ ) : (
+
+ Products will appear here.
+
+ )}
+
+
+ {/* bottom hairline */}
+
+
+
+ {/* note */}
+
+ Note: This is a catalog overview — images, shades, and details can be added later.
+
+
+ );
+}
diff --git a/src/app/components/Footer.tsx b/src/app/components/Footer.tsx
index c57d207..e3f8c34 100644
--- a/src/app/components/Footer.tsx
+++ b/src/app/components/Footer.tsx
@@ -1,17 +1,99 @@
+import Link from "next/link";
+
export default function Footer() {
- return (
-
- );
-}
\ No newline at end of file
+ const year = new Date().getFullYear();
+
+ return (
+
+ );
+}
diff --git a/src/app/components/Nav.tsx b/src/app/components/Nav.tsx
index ba2d6f9..7b50fc7 100644
--- a/src/app/components/Nav.tsx
+++ b/src/app/components/Nav.tsx
@@ -1,52 +1,137 @@
"use client";
import Link from "next/link";
-import { usePathname } from "next/navigation";
-import { useState } from "react";
+import {usePathname} from "next/navigation";
+import {useState} from "react";
const links = [
- { href: "/", label: "Home" },
- { href: "/about", label: "About" },
- { href: "/founders", label: "Founders" },
- { href: "/products", label: "Products" },
- { href: "/milestones", label: "Milestones" },
- { href: "/performance", label: "Performance" },
- { href: "/distribution", label: "Distribution" },
- { href: "/companies", label: "Group" },
- { href: "/future", label: "Future" },
- { href: "/contact", label: "Contact" },
+ {href: "/", label: "Home"},
+ {href: "/about", label: "About"},
+ {href: "/founders", label: "Founders"},
+ {href: "/products", label: "Products"},
+ {href: "/milestones", label: "Milestones"},
+ {href: "/performance", label: "Performance"},
+ {href: "/distribution", label: "Distribution"},
+ {href: "/companies", label: "Group"},
+ {href: "/future", label: "Future"},
+ {href: "/contact", label: "Contact"},
];
export default function Nav() {
- const pathname = usePathname();
- const [open, setOpen] = useState(false);
+ const pathname = usePathname();
+ const [open, setOpen] = useState(false);
- return (
-
- );
-}
\ No newline at end of file
+ return (
+
+ );
+}
diff --git a/src/app/components/ProductGrid.tsx b/src/app/components/ProductGrid.tsx
index 2da88c3..d551fa5 100644
--- a/src/app/components/ProductGrid.tsx
+++ b/src/app/components/ProductGrid.tsx
@@ -1,16 +1,68 @@
-import { Product } from "@/lib/data";
+// src/app/components/ProductGrid.tsx
+import type {Product} from "@/app/lib/data";
-export default function ProductGrid({ products }: { products: Product[] }) {
- return (
-
- {products.map((p, idx) => (
-
-
{p.category}
-
- {p.items.map((item, i) => - {item}
)}
-
+type Props = { products: Product[] };
+
+export default function ProductGrid({products}: Props) {
+ const has = Array.isArray(products) && products.length > 0;
+ if (!has) {
+ return (
+
+ Products will appear here.
+
+ );
+ }
+
+ return (
+
+ {products.map((p, idx) => {
+ const count = Array.isArray(p.items) ? p.items.length : 0;
+ return (
+
+ {/* hairline บน-ล่างแบบ editorial */}
+
+
+ {/* header */}
+
+
+ {p.category}
+
+
+ {count.toLocaleString()}
+
+
+
+ {/* list */}
+
+
+ {(p.items ?? []).map((item, i) => (
+ -
+
+ {item}
+
+
+ ))}
+
+
+
+
+
+ );
+ })}
- ))}
-
- );
-}
\ No newline at end of file
+ );
+}
diff --git a/src/app/components/Section.tsx b/src/app/components/Section.tsx
index a25512e..7f43353 100644
--- a/src/app/components/Section.tsx
+++ b/src/app/components/Section.tsx
@@ -1,15 +1,29 @@
-import { ReactNode } from "react";
+import {ReactNode} from "react";
-export default function Section({ id, title, subtitle, children }: { id?: string; title: string; subtitle?: string; children?: ReactNode }) {
- return (
-
-
-
-
{title}
- {subtitle &&
{subtitle}
}
-
- {children}
-
-
- );
-}
\ No newline at end of file
+export default function Section({
+ id,
+ title,
+ subtitle,
+ children,
+ }: {
+ id?: string;
+ title: string;
+ subtitle?: string;
+ children?: ReactNode;
+}) {
+ return (
+
+
+
+
{title}
+ {subtitle ? (
+
+ {subtitle}
+
+ ) : null}
+
+ {children}
+
+
+ );
+}
diff --git a/src/app/components/StatCard.tsx b/src/app/components/StatCard.tsx
index ad585f1..37b329d 100644
--- a/src/app/components/StatCard.tsx
+++ b/src/app/components/StatCard.tsx
@@ -1,9 +1,22 @@
-export default function StatCard({ title, value, note }: { title: string; value: string; note?: string }) {
- return (
-
-
{value}
-
{title}
- {note &&
{note}
}
-
- );
-}
\ No newline at end of file
+// src/app/components/StatCard.tsx
+type Props = {
+ title: string;
+ value: string;
+ note?: string;
+};
+
+export default function StatCard({title, value, note}: Props) {
+ return (
+
+
+ {value || "-"}
+
+
{title}
+ {note ?
{note}
: null}
+
+ );
+}
diff --git a/src/app/components/Timeline.tsx b/src/app/components/Timeline.tsx
index b238800..504759d 100644
--- a/src/app/components/Timeline.tsx
+++ b/src/app/components/Timeline.tsx
@@ -1,17 +1,63 @@
-import { Milestone } from "@/lib/data";
+import {Milestone} from "@/app/lib/data";
-export default function Timeline({ entries }: { entries: Milestone[] }) {
- return (
-
- {entries.map((ms, idx) => (
- -
-
-
{ms.year}
-
- {ms.items.map((it, i) => - {it}
)}
-
-
- ))}
-
- );
-}
\ No newline at end of file
+export default function Timeline({entries}: { entries: Milestone[] }) {
+ return (
+
+ {entries.map((ms, idx) => (
+ -
+ {/* ============ แถวบน: จุด + เส้นท่อนบน + ปี ============ */}
+
+ {/* เส้นท่อนบน: ยาวถึงขอบนอกของจุด (50% - 11px) */}
+ {idx > 0 && (
+
+ )}
+
+ {/* จุด */}
+
+
+
+ {/* ปี — อยู่แถวเดียวกับจุด เพื่อให้ “ตรงแนว” กันเสมอ */}
+
+
+ {ms.year}
+
+
+
+ {/* ============ แถวล่าง: เส้นท่อนล่าง + รายการ ============ */}
+ {/* ดันเส้นล่างขึ้นมา 11px ให้ชนขอบจุดพอดี */}
+
+ {idx < entries.length - 1 && (
+
+ )}
+
+
+
+
+ {ms.items.map((it, i) => (
+ -
+
+
{it}
+
+ ))}
+
+
+
+ ))}
+
+ );
+}
diff --git a/src/app/fonts.ts b/src/app/fonts.ts
index e69de29..e6d314d 100644
--- a/src/app/fonts.ts
+++ b/src/app/fonts.ts
@@ -0,0 +1,10 @@
+import localFont from "next/font/local";
+
+export const dbHeavent = localFont({
+ src: [
+ {path: "./font/dbheavent/DBHeavent-Regular.woff2", weight: "400", style: "normal"},
+ {path: "./font/dbheavent/DBHeavent-Bold.woff2", weight: "700", style: "normal"},
+ ],
+ display: "swap",
+ preload: true,
+});
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f7fa87e..7054f12 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,34 +1,27 @@
-import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import type {Metadata} from "next";
+import Nav from "@/app/components/Nav";
+import Footer from "@/app/components/Footer";
import "./globals.css";
-
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
+import {dbHeavent} from "./fonts";
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "Create Next App",
+ description: "Generated by create next app",
};
export default function RootLayout({
- children,
-}: Readonly<{
- children: React.ReactNode;
+ children,
+ }: Readonly<{
+ children: React.ReactNode;
}>) {
- return (
-
-
- {children}
-
-
- );
+ return (
+
+ {/* ✅ ต้องมี ไม่งั้น Next.js จะเตือน/พัง hydration */}
+
+
+
{children}
+
+
+
+ );
}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 295f8fd..9f9e99e 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,65 +1,198 @@
-import Image from "next/image";
+// src/app/page.tsx
+import Link from "next/link";
+import {company, milestones, products} from "@/app/lib/data";
-export default function Home() {
- return (
-
-
-
-
-
- To get started, edit the page.tsx file.
-
-
- Looking for a starting point or more instructions? Head over to{" "}
-
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
-
-
-
-
-
- );
+// แปลง milestones เป็นรายการเรียบ ๆ สำหรับโชว์หน้าโฮม (เอาแค่ 5 รายการแรก)
+function getHighlights() {
+ try {
+ const list = (milestones ?? [])
+ .flatMap((ms) => (ms.items ?? []).map((text) => ({year: ms.year, text})))
+ .slice(0, 5);
+ return list;
+ } catch {
+ return [];
+ }
+}
+
+export default function HomePage() {
+ const highlights = getHighlights();
+
+ const productLines = Array.isArray(products) ? products.length : 0;
+ const years = (milestones ?? [])
+ .map((m) => Number(m.year))
+ .filter((n) => Number.isFinite(n));
+ const minYear = years.length ? Math.min(...years) : null;
+ const maxYear = years.length ? Math.max(...years) : null;
+ const milestonesCount = (milestones ?? []).reduce(
+ (acc, m) => acc + (m.items ?? []).length,
+ 0
+ );
+
+ return (
+ <>
+ {/* ===== HERO: Split (ซ้ายข้อความ / ขวาสื่อ) — ขาว-แพง พร้อม hairline & media fallback ===== */}
+
+
+
+ {/* hairline บน */}
+
+
+ {/* Left: Text */}
+
+
+ AMREZ Group
+
+ Portfolio
+
+
+
+ KATHY · AMREZ
+
+
+ {company.tagline}
+
+
+
+
+ ติดต่อทีมงาน
+
+
+
+ {/* At a glance — จริงจากข้อมูล ไม่รก */}
+
+
+
+ Product Lines
+
+
+ {productLines}
+
+
+
+
+ Years
+
+
+ {minYear && maxYear ? `${minYear}–${maxYear}` : "—"}
+
+
+
+
+ Milestones
+
+
+ {milestonesCount.toLocaleString()}
+
+
+
+
+
+ {/* Right: Media (มี fallback ไม่ต้องพึ่งรูปก็ยังดูแพง) */}
+
+
+
+ {/* กรอบในแบบ editorial ให้มิติขึ้น */}
+
+
+
+ {/* hairline ล่าง */}
+
+
+
+
+
+ {/* ===== WHAT WE DO ===== */}
+
+
+
เราทำอะไร
+
+ {company.about}
+
+
+
+
+
Cosmetics
+
+ เครื่องสำอางสำหรับทุกวันและงานโชว์ เน้นการใช้งานจริง สีสม่ำเสมอ คุมฟินิชได้
+
+
+
+
Skincare
+
+ ทำความสะอาด กันแดด และเซรั่มเฉพาะจุด โฟกัสความสม่ำเสมอและความสบายผิว
+
+
+
+
Supplements
+
+ สูตรเรียบง่ายสำหรับโกลว์ พลังงานประจำวัน และสนับสนุนระบบทางเดินอาหาร
+
+
+
+
+
+
+ {/* ===== HIGHLIGHTS (list) ===== */}
+
+
+
Highlights
+
+ {highlights.length === 0 ? (
+
+ — เพิ่มรายการใน milestones เพื่อแสดงบนหน้าแรก —
+
+ ) : (
+
+ {highlights.map((h, i) => (
+ -
+
+
+ {h.year}
+
+
{h.text}
+
+
+ ))}
+
+ )}
+
+
+
+ ติดต่อเรา
+
+
+
+
+ >
+ );
}