feat(footer+layout): v6-footer + mount UtilityBar/Marquee/Nav/Footer in Base.astro

- Footer.astro (v6-footer): REPLACED legacy 439-line version. 4-col sitemap
  bound to site settings (phone, email, line, facebook, linkedin) +
  servicesDropdown + company links. Logo uses /images/logo-long-black.png
  (local, was hardcoded to dataroot CDN in v7-5).
- Base.astro: mount <UtilityBar /> + <Marquee /> + <Navigation /> + <Footer />
  around <slot />. Nav receives currentPath for active link highlight.
  Animation init now runs BOTH initAnimations (legacy bento) and fxInit (v7-5).
- SWEEP: removed duplicate <Navigation /> / <Footer /> + their imports
  from 11 page files. Idempotent script via execute_code.

Verified: all 9 pages return 200, header/footer render exactly once each.

Refs: .hermes/plans/2026-06-13_124000-moreminimore-v7-5-migration.md Task 3.1-3.2
This commit is contained in:
Kunthawat Greethong
2026-06-13 17:50:10 +07:00
parent 1f859921cb
commit b586464b5c
13 changed files with 83 additions and 494 deletions

View File

@@ -1,439 +1,82 @@
---
/**
* MOREMINIMORE - FOOTER COMPONENT (LIGHT THEME)
* White bg + dark text + black-text logo (matches new light theme)
* MOREMINIMORE - FOOTER (from v6-footer)
* Extracted from Desktop/moreminomore-mockup-v7-5.html lines 1516-1565
*
* 4-col sitemap: brand + services + company + contact
* Data bound from site settings + nav data (single source of truth)
*/
import Icon from './Icon.astro';
import { getEntry } from 'astro:content';
import type { CollectionEntry } from 'astro:content';
import { servicesDropdown, mainLinks } from '../data/nav';
const site = (await getEntry('settings', 'site')) as CollectionEntry<'settings'>;
const data = site?.data;
const currentYear = new Date().getFullYear();
const navLinks = [
{ label: 'หน้าแรก', href: '/' },
{ label: 'บริการ', href: '/services' },
{ label: 'ผลงาน', href: '/portfolio' },
{ label: 'บทความ', href: '/blog' },
{ label: 'ติดต่อ', href: '/contact' },
];
// Company links (subset of mainLinks — exclude "หน้าแรก" and dropdown items)
const companyLinks = mainLinks
.filter((l) => !l.hasDropdown && l.href !== '/')
.map((l) => ({ label: l.label, href: l.href }));
const serviceLinks = [
{ label: 'พัฒนาเว็บไซต์', href: '/services/webdev' },
{ label: 'Marketing Automation', href: '/services/marketing' },
{ label: 'AI Automation', href: '/services/automation' },
{ label: 'Tech Consult', href: '/services/ai-consult' },
// Contact links — built from settings
const contactLinks = [
{ label: 'ปรึกษาฟรี 30 นาที', href: '/contact' },
{ label: data?.email ?? 'contact@moreminimore.com', href: `mailto:${data?.email ?? 'contact@moreminimore.com'}`, isEmail: true },
{ label: 'LINE: ' + (data?.line_id ?? '@moreminimore'), href: data?.line ?? '#', isExternal: true },
{ label: data?.phone ?? '080-995-5945', href: `tel:${(data?.phone ?? '080-995-5945').replace(/-/g, '')}` },
];
---
<footer class="footer">
<div class="container">
<div class="footer-grid">
<!-- Brand Column -->
<div class="footer-brand">
<a href="/" class="footer-logo">
<div class="logo-banner">
<img src="/images/logo-long-black.png" alt="MoreminiMore" class="logo-img" />
</div>
</a>
<p class="footer-tagline">
รับทำเว็บไซต์ SEO AI Chatbot<br/>
สำหรับธุรกิจไทย
</p>
<!-- Social Links -->
<div class="footer-social">
<a href="https://www.facebook.com/moreminimore" target="_blank" rel="noopener" class="social-btn" aria-label="Facebook">
<img src="/icons/social/facebook.svg" alt="Facebook" />
</a>
<a href="https://line.me/ti/p/~@539hdlul" target="_blank" rel="noopener" class="social-btn" aria-label="LINE">
<img src="/icons/social/line.svg" alt="LINE" />
</a>
<a href="https://www.linkedin.com/company/moreminimore" target="_blank" rel="noopener" class="social-btn" aria-label="LinkedIn">
<img src="/icons/social/linkedin.svg" alt="LinkedIn" />
</a>
</div>
</div>
<!-- Navigation Links -->
<div class="footer-col">
<h4 class="footer-title">ลิงก์</h4>
<ul class="footer-links">
{navLinks.map(link => (
<li>
<a href={link.href} class="footer-link">
<span class="link-arrow">→</span>
{link.label}
</a>
</li>
))}
</ul>
</div>
<!-- Services -->
<div class="footer-col">
<h4 class="footer-title">บริการ</h4>
<ul class="footer-links">
{serviceLinks.map(link => (
<li>
<a href={link.href} class="footer-link">
<span class="link-arrow">→</span>
{link.label}
</a>
</li>
))}
</ul>
</div>
<!-- Contact -->
<div class="footer-col footer-contact">
<h4 class="footer-title">ติดต่อเรา</h4>
<div class="contact-item">
<span class="contact-icon"><Icon name="phone" size={14} /></span>
<a href="tel:0809955945">080-995-5945</a>
</div>
<div class="contact-item">
<span class="contact-icon"><Icon name="mail" size={14} /></span>
<a href="mailto:contact@moreminimore.com">contact@moreminimore.com</a>
</div>
<div class="contact-item">
<span class="contact-icon"><Icon name="mapPin" size={14} /></span>
<span>สมุทรสาคร, ประเทศไทย</span>
</div>
<a href="/contact" class="btn btn-primary footer-cta">
ปรึกษาฟรี
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</div>
<footer id="v6-footer-inner" class="fx-footer fx-reveal">
<div class="fx-footer-grid">
<div class="fx-footer-col">
<a href="/" class="fx-footer-logo">
<img src="/images/logo-long-black.png" alt="MOREMINIMORE" class="fx-footer-logo-img" />
</a>
<p class="fx-footer-tagline">ช่วยธุรกิจไทยเพิ่มกำไร ด้วย AI + Marketing + Automation</p>
</div>
<!-- Footer Bottom -->
<div class="footer-bottom">
<div class="footer-legal">
<p>&copy; {currentYear} MoreminiMore. สงวนลิขสิทธิ์.</p>
</div>
<div class="footer-links-bottom">
<a href="/privacy">นโยบายความเป็นส่วนตัว</a>
<a href="/terms">เงื่อนไขการใช้บริการ</a>
</div>
<div class="fx-footer-col">
<h4>SERVICES</h4>
<ul>
{servicesDropdown.map((s) => (
<li><a href={s.href}>{s.label}</a></li>
))}
</ul>
</div>
<div class="fx-footer-col">
<h4>COMPANY</h4>
<ul>
{companyLinks.map((l) => (
<li><a href={l.href}>{l.label}</a></li>
))}
</ul>
</div>
<div class="fx-footer-col">
<h4>CONTACT</h4>
<ul>
{contactLinks.map((l) => (
<li>
<a href={l.href} target={l.isExternal ? '_blank' : undefined} rel={l.isExternal ? 'noopener' : undefined}>
{l.label}
</a>
</li>
))}
{data?.facebook && (
<li><a href={data.facebook} target="_blank" rel="noopener">Facebook</a></li>
)}
{data?.linkedin && (
<li><a href={data.linkedin} target="_blank" rel="noopener">LinkedIn</a></li>
)}
</ul>
</div>
</div>
<div class="fx-footer-bottom">
<span>© {currentYear} MOREMINIMORE</span>
<span>built with <em>Kanit</em> + Itim + JetBrains Mono</span>
</div>
</footer>
<!-- Back to Top Button (yellow on light bg) -->
<button class="back-to-top" id="back-to-top" aria-label="กลับไปด้านบน">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
<path d="M12 19V5M5 12l7-7 7 7"/>
</svg>
</button>
<style>
/* ============================================
FOOTER BASE — LIGHT THEME
============================================ */
.footer {
position: relative;
background: var(--color-white);
color: var(--color-black);
padding: 100px 0 40px;
border-top: 1px solid var(--color-gray-200);
}
/* ============================================
GRID
============================================ */
.footer-grid {
display: grid;
grid-template-columns: 1.5fr 1fr 1fr 1.5fr;
gap: 60px;
padding-bottom: 60px;
position: relative;
z-index: 1;
}
/* ============================================
BRAND - LOGO BANNER (light)
============================================ */
.footer-logo {
display: inline-flex;
margin-bottom: 24px;
}
.logo-banner {
background: var(--color-white);
border-radius: 0 16px 16px 0;
padding: 10px 20px 10px 16px;
}
.logo-img {
height: 32px;
width: auto;
display: block;
}
.footer-tagline {
font-size: 15px;
line-height: 1.7;
color: var(--color-gray-600);
margin-bottom: 32px;
}
/* Social Links */
.footer-social {
display: flex;
gap: 12px;
}
.social-btn {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
background: var(--color-bg-soft);
border: 1px solid var(--color-gray-200);
border-radius: 50%;
transition: all 0.3s var(--ease-out-expo);
}
.social-btn img {
width: 24px;
height: 24px;
object-fit: contain;
}
.social-btn:hover {
background: var(--color-primary);
border-color: var(--color-primary);
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(254, 212, 0, 0.3);
}
/* ============================================
COLUMNS
============================================ */
.footer-title {
font-family: var(--font-display);
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 3px;
color: var(--color-black);
margin-bottom: 24px;
}
.footer-links {
list-style: none;
display: flex;
flex-direction: column;
gap: 12px;
}
.footer-link {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 15px;
color: var(--color-gray-600);
transition: all 0.3s ease;
}
.link-arrow {
opacity: 0;
transform: translateX(-10px);
transition: all 0.3s ease;
color: var(--color-primary);
}
.footer-link:hover {
color: var(--color-black);
transform: translateX(8px);
}
.footer-link:hover .link-arrow {
opacity: 1;
transform: translateX(0);
}
/* ============================================
CONTACT
============================================ */
.footer-contact {
display: flex;
flex-direction: column;
}
.contact-item {
display: flex;
align-items: center;
gap: 12px;
font-size: 15px;
color: var(--color-gray-600);
margin-bottom: 12px;
}
.contact-icon {
font-size: 18px;
}
.contact-item a:hover {
color: var(--color-primary-dark);
}
.footer-cta {
margin-top: auto;
align-self: flex-start;
}
.footer-cta svg {
width: 20px;
height: 20px;
transition: transform 0.3s ease;
}
.footer-cta:hover svg {
transform: translateX(6px);
}
/* ============================================
BOTTOM
============================================ */
.footer-bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 32px;
border-top: 1px solid var(--color-gray-200);
position: relative;
z-index: 1;
}
.footer-legal p {
font-size: 14px;
color: var(--color-gray-500);
}
.footer-links-bottom {
display: flex;
gap: 24px;
}
.footer-links-bottom a {
font-size: 14px;
color: var(--color-gray-500);
transition: color 0.3s ease;
}
.footer-links-bottom a:hover {
color: var(--color-black);
}
/* ============================================
BACK TO TOP
============================================ */
.back-to-top {
position: fixed;
bottom: 32px;
right: 32px;
width: 56px;
height: 56px;
background: var(--color-primary);
color: var(--color-black);
border: none;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
transform: translateY(20px) scale(0.8);
transition: all 0.3s var(--ease-out-expo);
z-index: 100;
box-shadow: 0 8px 30px rgba(254, 212, 0, 0.4);
}
.back-to-top.visible {
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
}
.back-to-top:hover {
transform: translateY(-4px) scale(1.05);
box-shadow: 0 12px 40px rgba(254, 212, 0, 0.5);
}
.back-to-top svg {
width: 24px;
height: 24px;
}
/* ============================================
RESPONSIVE
============================================ */
@media (max-width: 1024px) {
.footer-grid {
grid-template-columns: repeat(2, 1fr);
gap: 48px;
}
.footer-brand {
grid-column: span 2;
}
}
@media (max-width: 640px) {
.footer {
padding: 60px 0 32px;
}
.footer-grid {
grid-template-columns: 1fr;
gap: 40px;
}
.footer-brand {
grid-column: span 1;
}
.footer-bottom {
flex-direction: column;
gap: 16px;
text-align: center;
}
.back-to-top {
bottom: 20px;
right: 20px;
width: 48px;
height: 48px;
}
}
</style>
<script>
// Back to top functionality
const backToTop = document.getElementById('back-to-top');
window.addEventListener('scroll', () => {
if (window.scrollY > 600) {
backToTop?.classList.add('visible');
} else {
backToTop?.classList.remove('visible');
}
});
backToTop?.addEventListener('click', () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
</script>

View File

@@ -1,6 +1,10 @@
---
import "../styles/global.css";
import "../styles/fx-system.css";
import UtilityBar from "../components/UtilityBar.astro";
import Marquee from "../components/Marquee.astro";
import Navigation from "../components/Navigation.astro";
import Footer from "../components/Footer.astro";
interface Props {
title: string;
@@ -35,14 +39,22 @@ const {
<title>{title}</title>
</head>
<body>
<UtilityBar />
<Marquee />
<Navigation currentPath={Astro.url.pathname} />
<slot />
<!-- Global animation init: runs on every page after DOM ready.
Animations are SSR-safe (no-op on server) and respect
prefers-reduced-motion via the .reveal/etc CSS rules. -->
<Footer />
<!-- Global animations: initAnimations (legacy bento) + fxInit (v7-5 fx-*) -->
<script>
import { initAnimations } from '../lib/animations';
const start = () => initAnimations();
import { fxInit } from '../lib/fx-animations';
const start = () => {
initAnimations();
fxInit();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else {

View File

@@ -1,7 +1,5 @@
---
import Base from '../layouts/Base.astro';
import Navigation from '../components/Navigation.astro';
import Footer from '../components/Footer.astro';
import PageHero from '../components/PageHero.astro';
import BentoGrid from '../components/BentoGrid.astro';
import BentoTile from '../components/BentoTile.astro';
@@ -14,8 +12,6 @@ const { Content } = await render(about);
---
<Base title="เกี่ยวกับเรา | MoreminiMore | รับทำเว็บไซต์ SEO AI Chatbot">
<Navigation />
<PageHero
badge={about.data.hero_badge}
title="เริ่มจากตรงนี้"
@@ -222,8 +218,6 @@ const { Content } = await render(about);
</div>
</div>
</section>
<Footer />
</Base>
<style>

View File

@@ -1,7 +1,5 @@
---
import Base from '../../layouts/Base.astro';
import Navigation from '../../components/Navigation.astro';
import Footer from '../../components/Footer.astro';
import PageHero from '../../components/PageHero.astro';
import BentoGrid from '../../components/BentoGrid.astro';
import BentoTile from '../../components/BentoTile.astro';
@@ -45,8 +43,6 @@ function surfaceFor(i: number) {
---
<Base title={`${post.data.title} | MoreminiMore`}>
<Navigation />
<PageHero
badge={post.data.category}
title={post.data.title}
@@ -129,8 +125,6 @@ function surfaceFor(i: number) {
</div>
</section>
)}
<Footer />
</Base>
<style>

View File

@@ -1,7 +1,5 @@
---
import Base from '../../layouts/Base.astro';
import Navigation from '../../components/Navigation.astro';
import Footer from '../../components/Footer.astro';
import PageHero from '../../components/PageHero.astro';
import BentoGrid from '../../components/BentoGrid.astro';
import BentoTile from '../../components/BentoTile.astro';
@@ -19,8 +17,6 @@ function surfaceFor(i: number) {
---
<Base title="บทความ | MoreminiMore - รับทำเว็บไซต์ SEO AI Chatbot">
<Navigation />
<PageHero
badge="บทความ"
title="ความรู้ด้านดิจิทัล"
@@ -111,8 +107,6 @@ function surfaceFor(i: number) {
</div>
</div>
</section>
<Footer />
</Base>
<style>

View File

@@ -1,7 +1,5 @@
---
import Base from '../layouts/Base.astro';
import Navigation from '../components/Navigation.astro';
import Footer from '../components/Footer.astro';
import PageHero from '../components/PageHero.astro';
import Icon from '../components/Icon.astro';
import BentoGrid from '../components/BentoGrid.astro';
@@ -21,8 +19,6 @@ const serviceOptions = [
---
<Base title="ติดต่อเรา | MoreminiMore | รับทำเว็บไซต์ SEO AI Chatbot">
<Navigation />
<PageHero
badge="30 นาที · ไม่มีค่าใช้จ่าย · ไม่มี commitment"
title="คุยกับ คนจริง ๆ ไม่ใช่ Bot"
@@ -226,8 +222,6 @@ const serviceOptions = [
</div>
</div>
</section>
<Footer />
</Base>
<style>

View File

@@ -1,7 +1,5 @@
---
import Base from '../layouts/Base.astro';
import Navigation from '../components/Navigation.astro';
import Footer from '../components/Footer.astro';
import PageHero from '../components/PageHero.astro';
import BentoGrid from '../components/BentoGrid.astro';
import BentoTile from '../components/BentoTile.astro';
@@ -65,8 +63,6 @@ groupedFaq.forEach((group, gIdx) => {
---
<Base title="คำถามที่พบบ่อย | MoreminiMore | รับทำเว็บไซต์ SEO AI Chatbot">
<Navigation />
<PageHero
badge="FAQ"
title="คำถามที่ลูกค้าถามบ่อยที่สุด"
@@ -165,8 +161,6 @@ groupedFaq.forEach((group, gIdx) => {
</div>
</div>
</section>
<Footer />
</Base>
<style>

View File

@@ -1,7 +1,5 @@
---
import Base from '../layouts/Base.astro';
import Navigation from '../components/Navigation.astro';
import Footer from '../components/Footer.astro';
import Hero from '../components/Hero.astro';
import BentoGrid from '../components/BentoGrid.astro';
import BentoTile from '../components/BentoTile.astro';
@@ -78,8 +76,6 @@ const featuredPortfolio = portfolio.filter(p =>
---
<Base title="MoreminiMore - ที่ปรึกษาเว็บ การตลาด และ AI สำหรับ SME ไทย">
<Navigation />
<Hero
badge="Moreminimore"
title="เราจะช่วยคุณเพิ่มกำไร"
@@ -289,8 +285,6 @@ const featuredPortfolio = portfolio.filter(p =>
</div>
</div>
</section>
<Footer />
</Base>
<style>

View File

@@ -1,7 +1,5 @@
---
import Base from '../layouts/Base.astro';
import Navigation from '../components/Navigation.astro';
import Footer from '../components/Footer.astro';
import PageHero from '../components/PageHero.astro';
import PortfolioCard from '../components/PortfolioCard.astro';
import Icon from '../components/Icon.astro';
@@ -25,8 +23,6 @@ const serviceFilters = [
---
<Base title="ผลงาน | MoreminiMore | รับทำเว็บไซต์ SEO AI Chatbot">
<Navigation />
<PageHero
badge="9 โปรเจกต์ · 5 อุตสาหกรรม · ผลงานจริงทุกชิ้น"
title="เราส่งมอบให้ใคร มาบ้างแล้วบ้าง"
@@ -115,8 +111,6 @@ const serviceFilters = [
</div>
</div>
</section>
<Footer />
</Base>
<script>

View File

@@ -1,7 +1,5 @@
---
import Base from '../layouts/Base.astro';
import Navigation from '../components/Navigation.astro';
import Footer from '../components/Footer.astro';
import PageHero from '../components/PageHero.astro';
import BentoGrid from '../components/BentoGrid.astro';
import BentoTile from '../components/BentoTile.astro';
@@ -9,8 +7,6 @@ import DecoOrb from '../components/DecoOrb.astro';
---
<Base title="นโยบายความเป็นส่วนตัว | MoreminiMore - รับทำเว็บไซต์ SEO AI Chatbot">
<Navigation />
<PageHero
badge="กฎหมาย"
title="นโยบายความเป็นส่วนตัว"
@@ -76,8 +72,6 @@ import DecoOrb from '../components/DecoOrb.astro';
</BentoGrid>
</div>
</section>
<Footer />
</Base>
<style>

View File

@@ -1,7 +1,5 @@
---
import Base from '../../layouts/Base.astro';
import Navigation from '../../components/Navigation.astro';
import Footer from '../../components/Footer.astro';
import BentoGrid from '../../components/BentoGrid.astro';
import BentoTile from '../../components/BentoTile.astro';
import DecoOrb from '../../components/DecoOrb.astro';
@@ -325,8 +323,6 @@ const featureList = data.features || data.services || [];
---
<Base title={`${service.data.title} | MoreminiMore`}>
<Navigation />
<!-- HERO -->
<section class="service-hero section-bento">
<DecoOrb color="yellow" size="500px" speed={0.4} position={{ top: '-150px', right: '-100px' }} opacity={0.3} blur="80px" />
@@ -626,8 +622,6 @@ const featureList = data.features || data.services || [];
</div>
</div>
</section>
<Footer />
</Base>
<style>

View File

@@ -1,7 +1,5 @@
---
import Base from '../../layouts/Base.astro';
import Navigation from '../../components/Navigation.astro';
import Footer from '../../components/Footer.astro';
import PageHero from '../../components/PageHero.astro';
import BentoGrid from '../../components/BentoGrid.astro';
import BentoTile from '../../components/BentoTile.astro';
@@ -15,8 +13,6 @@ const serviceSurfaces = ['yellow', 'purple-soft', 'mint', 'soft', 'teal', 'coral
---
<Base title="บริการ | MoreminiMore | รับทำเว็บไซต์ SEO AI Chatbot">
<Navigation />
<PageHero
badge="บริการ 4 ด้าน · เริ่มต้น 15,000 บาท"
title="โซลูชัน AI 4 ด้าน ที่คุณเลือกได้ตามงบ"
@@ -152,8 +148,6 @@ const serviceSurfaces = ['yellow', 'purple-soft', 'mint', 'soft', 'teal', 'coral
</div>
</div>
</section>
<Footer />
</Base>
<style>

View File

@@ -1,7 +1,5 @@
---
import Base from '../layouts/Base.astro';
import Navigation from '../components/Navigation.astro';
import Footer from '../components/Footer.astro';
import PageHero from '../components/PageHero.astro';
import BentoGrid from '../components/BentoGrid.astro';
import BentoTile from '../components/BentoTile.astro';
@@ -9,8 +7,6 @@ import DecoOrb from '../components/DecoOrb.astro';
---
<Base title="เงื่อนไขการให้บริการ | MoreminiMore - รับทำเว็บไซต์ SEO AI Chatbot">
<Navigation />
<PageHero
badge="กฎหมาย"
title="เงื่อนไขการให้บริการ"
@@ -83,8 +79,6 @@ import DecoOrb from '../components/DecoOrb.astro';
</BentoGrid>
</div>
</section>
<Footer />
</Base>
<style>