feat(header): add UtilityBar + Marquee + Navigation from v6-nav
- UtilityBar.astro (v6-utility): phone, clock, date, email from site settings Clock updated by fxClock() in src/lib/fx-animations.ts - Marquee.astro (v6-marquee): log ticker with 4 entries (animated horizontally) Content duplicated for seamless loop - Navigation.astro (v6-nav): REPLACED legacy. Adds 'บริการ' dropdown (4 services) + 'บทความ' link per plan round 2. Click-to-toggle on mobile. - src/data/nav.ts: single source of truth for mainLinks + servicesDropdown - fx-system.css: +27 lines for dropdown styles + active link underline (v6-nav originally had no dropdown — we added per spec) Refs: .hermes/plans/2026-06-13_124000-moreminimore-v7-5-migration.md Task 2.1-2.4
This commit is contained in:
30
src/components/Marquee.astro
Normal file
30
src/components/Marquee.astro
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
/**
|
||||
* MOREMINIMORE - Marquee (from v6-marquee)
|
||||
* Extracted from Desktop/moreminomore-mockup-v7-5.html lines 590-609
|
||||
*
|
||||
* Log ticker — animates horizontally (marquee 40s linear infinite in fx-system.css)
|
||||
* Content is hardcoded ticker entries (not dynamic) — same as v7-5 design
|
||||
*
|
||||
* Duplicate content to achieve seamless loop (track is rendered twice)
|
||||
*/
|
||||
const tickerEntries = [
|
||||
{ ts: '[2026-06-13]', text: '<span class="cmd">$</span> build/deploy <span class="ok">✓</span>' },
|
||||
{ ts: '[log]', text: 'Dataroot +373% Impression <span class="ok">✓</span>' },
|
||||
{ ts: '[2026-06-13]', text: '<span class="cmd">$</span> contact/free <em>30 min</em> <span class="ok">✓</span>' },
|
||||
{ ts: '[log]', text: '9 case studies, 0 fabricated <span class="ok">✓</span>' },
|
||||
];
|
||||
---
|
||||
|
||||
<div id="v6-marquee-inner" class="fx-marquee">
|
||||
<div class="fx-marquee-track">
|
||||
{/* First copy */}
|
||||
{tickerEntries.map((entry) => (
|
||||
<span><span class="ts">{entry.ts}</span> <Fragment set:html={entry.text} /></span>
|
||||
))}
|
||||
{/* Second copy (duplicated for seamless loop) */}
|
||||
{tickerEntries.map((entry) => (
|
||||
<span><span class="ts">{entry.ts}</span> <Fragment set:html={entry.text} /></span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,529 +1,82 @@
|
||||
---
|
||||
/**
|
||||
* MOREMINIMORE - NAVIGATION COMPONENT (LIGHT THEME)
|
||||
* Light bg + dark text nav links + white logo banner
|
||||
* MOREMINIMORE - NAVIGATION (from v6-nav, enhanced)
|
||||
* Extracted from Desktop/moreminomore-mockup-v7-5.html lines 662-684
|
||||
*
|
||||
* Per plan 2026-06-13 round 2:
|
||||
* - v6-nav structure: logo + menu + CTA
|
||||
* - Enhancement A: dropdown 'บริการ' (4 sub-items from servicesDropdown)
|
||||
* - Enhancement C: add 'บทความ' link (blog) — not in original v6-nav
|
||||
* - Social icons: not in v6-nav itself; they live in UtilityBar (per plan)
|
||||
*
|
||||
* Props:
|
||||
* - currentPath: string — for active link highlighting (optional, defaults to Astro.url.pathname)
|
||||
*/
|
||||
import { mainLinks, servicesDropdown } from '../data/nav';
|
||||
|
||||
const services = [
|
||||
{ label: 'Website Development', href: '/services/webdev' },
|
||||
{ label: 'Marketing Automation', href: '/services/marketing' },
|
||||
{ label: 'AI Automation', href: '/services/automation' },
|
||||
{ label: 'Tech Consult', href: '/services/ai-consult' },
|
||||
];
|
||||
interface Props {
|
||||
currentPath?: string;
|
||||
}
|
||||
|
||||
const { currentPath = Astro.url.pathname } = Astro.props;
|
||||
const isActive = (href: string) => {
|
||||
if (href === '/') return currentPath === '/';
|
||||
return currentPath.startsWith(href);
|
||||
};
|
||||
---
|
||||
|
||||
<header class="nav" id="nav">
|
||||
<div class="nav-container container">
|
||||
<!-- Logo: white-bg banner with the black-text logo (works on light/yellow) -->
|
||||
<a href="/" class="nav-logo">
|
||||
<div class="logo-banner">
|
||||
<img src="/images/logo-long-black.png" alt="MoreminiMore" class="logo-img" />
|
||||
</div>
|
||||
<nav id="v6-nav-inner" class="fx-nav">
|
||||
<div class="fx-nav-inner">
|
||||
<a href="/" class="fx-nav-logo">
|
||||
<span class="fx-nav-logo-text">MOREMI<em>ni</em>MORE</span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<nav class="nav-desktop">
|
||||
<a href="/" class="nav-link" data-magnetic>หน้าแรก</a>
|
||||
|
||||
<!-- Services with Submenu -->
|
||||
<div class="nav-dropdown">
|
||||
<a href="/services" class="nav-link nav-dropdown-trigger" data-magnetic>
|
||||
บริการ
|
||||
<svg class="dropdown-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M6 9l6 6 6-6"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div class="nav-dropdown-menu">
|
||||
{services.map(service => (
|
||||
<a href={service.href} class="dropdown-item">
|
||||
{service.label}
|
||||
<ul class="fx-nav-menu">
|
||||
{mainLinks.map((link) => (
|
||||
<li class={link.hasDropdown ? 'fx-nav-has-dropdown' : ''}>
|
||||
{link.hasDropdown ? (
|
||||
<>
|
||||
<a href={link.href} class:list={[isActive(link.href) && 'active']}>
|
||||
{link.label}
|
||||
</a>
|
||||
<ul class="fx-nav-dropdown">
|
||||
{servicesDropdown.map((service) => (
|
||||
<li>
|
||||
<a href={service.href} class:list={[isActive(service.href) && 'active']}>
|
||||
{service.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
<a href={link.href} class:list={[isActive(link.href) && 'active']}>
|
||||
{link.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="/portfolio" class="nav-link" data-magnetic>ผลงาน</a>
|
||||
<a href="/blog" class="nav-link" data-magnetic>บทความ</a>
|
||||
<a href="/about" class="nav-link" data-magnetic>เกี่ยวกับเรา</a>
|
||||
<a href="/contact" class="nav-link nav-cta" data-magnetic>ติดต่อเรา</a>
|
||||
</nav>
|
||||
|
||||
<!-- Mobile Menu Toggle (DARK icons on light bg) -->
|
||||
<button class="nav-toggle" id="nav-toggle" aria-label="Toggle menu">
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
<span class="toggle-line"></span>
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<a href="/contact" class="fx-nav-cta">ปรึกษาฟรี →</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Navigation Overlay (LIGHT bg) -->
|
||||
<div class="nav-mobile-overlay" id="nav-mobile">
|
||||
<nav class="nav-mobile-content">
|
||||
<a href="/" class="mobile-link">หน้าแรก</a>
|
||||
|
||||
<!-- Mobile Services with Submenu -->
|
||||
<div class="mobile-dropdown">
|
||||
<button class="mobile-dropdown-trigger">
|
||||
บริการ
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M6 9l6 6 6-6"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="mobile-dropdown-content">
|
||||
{services.map(service => (
|
||||
<a href={service.href} class="mobile-link mobile-sublink">{service.label}</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="/portfolio" class="mobile-link">ผลงาน</a>
|
||||
<a href="/blog" class="mobile-link">บทความ</a>
|
||||
<a href="/about" class="mobile-link">เกี่ยวกับเรา</a>
|
||||
<a href="/contact" class="mobile-link mobile-cta">ติดต่อเรา</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Scroll Progress Bar (yellow) -->
|
||||
<div class="scroll-progress" id="scroll-progress"></div>
|
||||
|
||||
<style>
|
||||
/* ============================================
|
||||
NAVIGATION BASE — LIGHT THEME
|
||||
============================================ */
|
||||
|
||||
.nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
padding: 16px 0;
|
||||
background: var(--color-white);
|
||||
border-bottom: 1px solid var(--color-gray-200);
|
||||
transition: all 0.3s var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.nav.scrolled {
|
||||
padding: 12px 0;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
CONTAINER
|
||||
============================================ */
|
||||
|
||||
.nav-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
LOGO BANNER
|
||||
============================================ */
|
||||
|
||||
.nav-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.logo-banner {
|
||||
background: var(--color-white);
|
||||
border-radius: 0 0 12px 12px;
|
||||
padding: 8px 16px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.logo-banner:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.nav.scrolled .logo-banner {
|
||||
border-radius: 0 0 8px 8px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.logo-img {
|
||||
height: 36px;
|
||||
width: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav.scrolled .logo-img {
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
DESKTOP NAVIGATION
|
||||
============================================ */
|
||||
|
||||
.nav-desktop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
position: relative;
|
||||
padding: 12px 16px;
|
||||
font-family: var(--font-display);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
color: var(--color-black);
|
||||
transition: color 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.nav-link:hover,
|
||||
.nav-link.active {
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.nav-cta {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-black) !important;
|
||||
border: 2px solid var(--color-primary);
|
||||
border-radius: var(--radius-md);
|
||||
margin-left: 16px;
|
||||
padding: 12px 24px;
|
||||
transition: all 0.3s var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.nav-cta:hover {
|
||||
background: var(--color-primary-dark);
|
||||
border-color: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(254, 212, 0, 0.3);
|
||||
}
|
||||
|
||||
.nav-cta::after { display: none; }
|
||||
|
||||
/* ============================================
|
||||
DROPDOWN — LIGHT CARD ON WHITE BG
|
||||
============================================ */
|
||||
|
||||
.nav-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-dropdown-trigger {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-dropdown:hover .dropdown-arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.nav-dropdown-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(10px);
|
||||
min-width: 220px;
|
||||
background: var(--color-white);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 12px 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s var(--ease-out-expo);
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.nav-dropdown:hover .nav-dropdown-menu {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: block;
|
||||
padding: 12px 24px;
|
||||
font-family: var(--font-display);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-700);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background: var(--color-bg-alt);
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
MOBILE TOGGLE — DARK lines (since nav is light)
|
||||
============================================ */
|
||||
|
||||
.nav-toggle {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
z-index: 1001;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.toggle-line {
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
background: var(--color-black);
|
||||
border-radius: 2px;
|
||||
transition: all 0.3s var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.nav-toggle.active .toggle-line:nth-child(1) {
|
||||
transform: rotate(45deg) translate(5px, 5px);
|
||||
}
|
||||
|
||||
.nav-toggle.active .toggle-line:nth-child(2) {
|
||||
opacity: 0;
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
|
||||
.nav-toggle.active .toggle-line:nth-child(3) {
|
||||
transform: rotate(-45deg) translate(5px, -5px);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
MOBILE OVERLAY — LIGHT THEME
|
||||
============================================ */
|
||||
|
||||
.nav-mobile-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--color-white);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.4s var(--ease-out-expo);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.nav-mobile-overlay.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.nav-mobile-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mobile-link {
|
||||
font-family: var(--font-display);
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
color: var(--color-black);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
padding: 14px 32px;
|
||||
transition: all 0.3s ease;
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-mobile-overlay.active .mobile-link {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.nav-mobile-overlay.active .mobile-link:nth-child(1) { transition-delay: 0.1s; }
|
||||
.nav-mobile-overlay.active .mobile-link:nth-child(2) { transition-delay: 0.15s; }
|
||||
.nav-mobile-overlay.active .mobile-link:nth-child(3) { transition-delay: 0.2s; }
|
||||
.nav-mobile-overlay.active .mobile-link:nth-child(4) { transition-delay: 0.25s; }
|
||||
.nav-mobile-overlay.active .mobile-link:nth-child(5) { transition-delay: 0.3s; }
|
||||
.nav-mobile-overlay.active .mobile-link:nth-child(6) { transition-delay: 0.35s; }
|
||||
|
||||
.mobile-link:hover {
|
||||
color: var(--color-primary-dark);
|
||||
transform: translateX(10px);
|
||||
}
|
||||
|
||||
.mobile-cta {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-black) !important;
|
||||
border: 2px solid var(--color-primary);
|
||||
border-radius: var(--radius-lg);
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
MOBILE DROPDOWN
|
||||
============================================ */
|
||||
|
||||
.mobile-dropdown {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mobile-dropdown-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 14px 32px;
|
||||
background: none;
|
||||
border: none;
|
||||
font-family: var(--font-display);
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
color: var(--color-black);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
.mobile-dropdown-trigger svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.mobile-dropdown.open .mobile-dropdown-trigger {
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.mobile-dropdown.open .mobile-dropdown-trigger svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.mobile-dropdown-content {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.4s var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.mobile-dropdown.open .mobile-dropdown-content {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.mobile-sublink {
|
||||
font-size: 20px !important;
|
||||
padding: 10px 32px !important;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.mobile-sublink:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
SCROLL PROGRESS
|
||||
============================================ */
|
||||
|
||||
.scroll-progress {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, var(--color-primary), #ffe066);
|
||||
width: 0;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
RESPONSIVE
|
||||
============================================ */
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.nav-desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-toggle {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.mobile-link {
|
||||
font-size: 24px;
|
||||
padding: 12px 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Navigation scroll effect
|
||||
const nav = document.getElementById('nav');
|
||||
const toggle = document.getElementById('nav-toggle');
|
||||
const mobileNav = document.getElementById('nav-mobile');
|
||||
const scrollProgress = document.getElementById('scroll-progress');
|
||||
// Mobile menu toggle — only relevant on small screens
|
||||
// Desktop uses hover for dropdown (handled by CSS)
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const nav = document.getElementById('v6-nav-inner');
|
||||
if (!nav) return;
|
||||
|
||||
// Scroll effect
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrollY = window.scrollY;
|
||||
|
||||
if (scrollY > 80) {
|
||||
nav?.classList.add('scrolled');
|
||||
} else {
|
||||
nav?.classList.remove('scrolled');
|
||||
}
|
||||
|
||||
// Scroll progress
|
||||
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||
const progress = (scrollY / docHeight) * 100;
|
||||
if (scrollProgress) {
|
||||
scrollProgress.style.width = `${progress}%`;
|
||||
}
|
||||
});
|
||||
|
||||
// Mobile menu toggle
|
||||
toggle?.addEventListener('click', () => {
|
||||
toggle.classList.toggle('active');
|
||||
mobileNav?.classList.toggle('active');
|
||||
document.body.style.overflow = mobileNav?.classList.contains('active') ? 'hidden' : '';
|
||||
});
|
||||
|
||||
// Mobile dropdown toggle
|
||||
document.querySelectorAll('.mobile-dropdown-trigger').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
btn.parentElement?.classList.toggle('open');
|
||||
});
|
||||
});
|
||||
|
||||
// Close mobile nav on link click
|
||||
document.querySelectorAll('.nav-mobile-overlay .mobile-link').forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
toggle?.classList.remove('active');
|
||||
mobileNav?.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
// Find all items with dropdowns and add click-to-toggle for mobile
|
||||
const dropdownItems = nav.querySelectorAll('.fx-nav-has-dropdown > a');
|
||||
dropdownItems.forEach((trigger) => {
|
||||
trigger.addEventListener('click', (e) => {
|
||||
if (window.innerWidth <= 768) {
|
||||
e.preventDefault();
|
||||
const parent = trigger.parentElement;
|
||||
if (parent) parent.classList.toggle('fx-nav-dropdown-open');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
30
src/components/UtilityBar.astro
Normal file
30
src/components/UtilityBar.astro
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
/**
|
||||
* MOREMINIMORE - UtilityBar (from v6-utility)
|
||||
* Extracted from Desktop/moreminomore-mockup-v7-5.html lines 571-589
|
||||
*
|
||||
* Top info bar — phone + clock + date + mode indicator + email
|
||||
* Phone/email pulled from src/content/settings/site.md (single source of truth)
|
||||
*
|
||||
* Clock/date are updated by fx-animations.ts → fxClock() (id="fx-time", id="fx-date")
|
||||
*/
|
||||
import { getEntry } from 'astro:content';
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
|
||||
const site = (await getEntry('settings', 'site')) as CollectionEntry<'settings'>;
|
||||
const phone = site?.data?.phone ?? '080-995-5945';
|
||||
const email = site?.data?.email ?? 'contact@moreminimore.com';
|
||||
---
|
||||
|
||||
<div id="v6-utility-inner" class="fx-utility-bar">
|
||||
<div class="fx-utility-bar-left">
|
||||
<span class="fx-utility-item">📞 <strong>{phone}</strong></span>
|
||||
<span class="fx-utility-item" id="fx-time">⏱ — : — : —</span>
|
||||
<span class="fx-utility-item" id="fx-date">📅 — — —</span>
|
||||
</div>
|
||||
<div class="fx-utility-bar-right">
|
||||
<span class="fx-mode-indicator">system</span>
|
||||
<span class="fx-theme-toggle" id="fx-theme-toggle">◐ auto</span>
|
||||
<a href={`mailto:${email}`} class="fx-utility-item" style="text-decoration:none">✉ {email}</a>
|
||||
</div>
|
||||
</div>
|
||||
43
src/data/nav.ts
Normal file
43
src/data/nav.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* MOREMINIMORE - Nav data (single source of truth)
|
||||
* Used by Navigation.astro and Footer.astro
|
||||
*
|
||||
* Per plan 2026-06-13 round 2:
|
||||
* - Main menu: 7 items including 'บทความ' (blog) + 'FAQ' + 'ติดต่อ'
|
||||
* - Services dropdown: 4 services (matched to content collection slugs)
|
||||
* - Social: facebook, line, linkedin (from settings collection at runtime)
|
||||
*
|
||||
* Slugs match src/content/services/*-new.mdx:
|
||||
* - ai-consult-new → /services/ai-consult
|
||||
* - marketing-new → /services/marketing
|
||||
* - automation-new → /services/automation
|
||||
* - webdev-new → /services/webdev
|
||||
*
|
||||
* Href uses /services/{slug-without-new} to match the [slug].astro route.
|
||||
*/
|
||||
export const mainLinks = [
|
||||
{ label: 'หน้าแรก', href: '/' },
|
||||
{ label: 'บริการ', href: '/services', hasDropdown: true },
|
||||
{ label: 'ผลงาน', href: '/portfolio' },
|
||||
{ label: 'บทความ', href: '/blog' },
|
||||
{ label: 'เกี่ยวกับ', href: '/about' },
|
||||
{ label: 'FAQ', href: '/faq' },
|
||||
{ label: 'ติดต่อ', href: '/contact' },
|
||||
];
|
||||
|
||||
export const servicesDropdown = [
|
||||
{ label: 'AI Consult', href: '/services/ai-consult' },
|
||||
{ label: 'Marketing Automation', href: '/services/marketing' },
|
||||
{ label: 'AI Automation', href: '/services/automation' },
|
||||
{ label: 'Website Development', href: '/services/webdev' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Social links are passed as props to the component
|
||||
* (because they come from the settings collection which is loaded async)
|
||||
*/
|
||||
export interface SocialLinks {
|
||||
facebook?: string;
|
||||
line?: string;
|
||||
linkedin?: string;
|
||||
}
|
||||
@@ -482,4 +482,30 @@ img{max-width:100%;display:block}
|
||||
.fx-btn.coral:hover{background:linear-gradient(135deg,var(--ink) 0%,var(--coral) 100%)}
|
||||
.fx-marquee-track{animation:none}
|
||||
.fx-faq-a::after{display:none}
|
||||
.fx-hero::after{display:none}
|
||||
.fx-hero::after{display:none}
|
||||
|
||||
/* ============================================
|
||||
NAV DROPDOWN ENHANCEMENT (added 2026-06-13, plan round 2)
|
||||
v6-nav didn't have a dropdown originally; we added
|
||||
'บริการ' dropdown per user spec. Active link styling too.
|
||||
============================================ */
|
||||
.fx-nav-menu{position:relative}
|
||||
.fx-nav-menu li{position:relative}
|
||||
.fx-nav-dropdown{position:absolute;top:100%;left:0;min-width:240px;background:var(--paper);border:1.5px solid var(--ink);box-shadow:4px 4px 0 var(--ink);list-style:none;padding:8px 0;margin:0;opacity:0;visibility:hidden;transform:translateY(-4px);transition:opacity 0.18s,transform 0.18s,visibility 0.18s;z-index:60}
|
||||
.fx-nav-has-dropdown:hover>.fx-nav-dropdown,
|
||||
.fx-nav-dropdown-open>.fx-nav-dropdown{opacity:1;visibility:visible;transform:translateY(0)}
|
||||
.fx-nav-dropdown li{margin:0}
|
||||
.fx-nav-dropdown a{display:block;padding:10px 18px;font:600 12px/1.3 'JetBrains Mono',monospace;text-transform:uppercase;letter-spacing:0.3px;color:var(--ink);text-decoration:none;transition:background 0.15s,color 0.15s}
|
||||
.fx-nav-dropdown a:hover{background:var(--brand-yellow);color:var(--ink)}
|
||||
.fx-nav-dropdown a.active{background:var(--coral);color:#FAFAFA}
|
||||
.fx-nav-menu a.active{color:var(--coral)}
|
||||
.fx-nav-menu a.active::after{content:'';display:block;height:2px;background:var(--coral);margin-top:4px}
|
||||
@media (max-width:768px){
|
||||
.fx-nav-inner{flex-direction:column;align-items:flex-start;padding:12px 16px}
|
||||
.fx-nav-menu{flex-direction:column;width:100%;gap:0}
|
||||
.fx-nav-menu>li{width:100%;border-bottom:1px solid var(--line-2)}
|
||||
.fx-nav-menu>li>a{display:block;padding:12px 0}
|
||||
.fx-nav-dropdown{position:static;box-shadow:none;border:none;background:var(--paper-2);opacity:1;visibility:visible;transform:none;max-height:0;overflow:hidden;padding:0;transition:max-height 0.25s,padding 0.25s}
|
||||
.fx-nav-dropdown-open>.fx-nav-dropdown{max-height:400px;padding:8px 0}
|
||||
.fx-nav-cta{margin-top:12px;align-self:flex-start}
|
||||
}
|
||||
Reference in New Issue
Block a user