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>