feat: enhance header and mobile navigation with smooth scrolling and active link highlighting
This commit is contained in:
@@ -9,11 +9,11 @@
|
|||||||
|
|
||||||
<div class="flex flex-col items-start w-full">
|
<div class="flex flex-col items-start w-full">
|
||||||
<!-- 3 cards row -->
|
<!-- 3 cards row -->
|
||||||
<div class="flex flex-col lg:flex-row lg:gap-[24px] lg:h-[477px] lg:items-center gap-6 w-full shrink-0">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 w-full">
|
||||||
|
|
||||||
<!-- Card 1: Clear Interface -->
|
<!-- Card 1: Clear Interface -->
|
||||||
<div class="bg-gradient-to-b from-[#fef0eb] to-white flex flex-col gap-[36px] lg:h-full items-center overflow-clip pb-[36px] px-[36px] rounded-tl-[30px] rounded-tr-[30px] shrink-0 w-full lg:w-[320px]">
|
<div class="bg-gradient-to-b from-[#fef0eb] to-white flex flex-col gap-[36px] items-center overflow-clip pb-[36px] px-[36px] rounded-tl-[30px] rounded-tr-[30px] w-full">
|
||||||
<div class="relative h-[232px] shrink-0 w-full lg:w-[320px]">
|
<div class="relative h-[232px] shrink-0 w-full">
|
||||||
<div class="absolute inset-0 overflow-hidden pointer-events-none">
|
<div class="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
<img alt="Clear interface screenshot" class="absolute h-[298.5%] left-0 max-w-none top-[-58.58%] w-full" src="/assets/exp-card-1.png" />
|
<img alt="Clear interface screenshot" class="absolute h-[298.5%] left-0 max-w-none top-[-58.58%] w-full" src="/assets/exp-card-1.png" />
|
||||||
</div>
|
</div>
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Card 2: Smooth Navigation -->
|
<!-- Card 2: Smooth Navigation -->
|
||||||
<div class="bg-gradient-to-b from-[#fef0eb] to-white flex flex-col gap-[36px] lg:h-full items-center overflow-clip pb-[36px] px-[36px] rounded-tl-[30px] rounded-tr-[30px] shrink-0 w-full lg:w-[320px]">
|
<div class="bg-gradient-to-b from-[#fef0eb] to-white flex flex-col gap-[36px] items-center overflow-clip pb-[36px] px-[36px] rounded-tl-[30px] rounded-tr-[30px] w-full">
|
||||||
<div class="bg-[#ffeddf] flex flex-col items-start shrink-0 w-full lg:w-[320px]">
|
<div class="bg-[#ffeddf] flex flex-col items-start w-full">
|
||||||
<div class="relative h-[232px] shrink-0 w-full">
|
<div class="relative h-[232px] shrink-0 w-full">
|
||||||
<div class="absolute inset-0 overflow-hidden pointer-events-none">
|
<div class="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
<img alt="Smooth navigation screenshot" class="absolute h-[411.28%] left-[-5.95%] max-w-none top-[-335.6%] w-[137.79%]" src="/assets/exp-card-2.png" />
|
<img alt="Smooth navigation screenshot" class="absolute h-[411.28%] left-[-5.95%] max-w-none top-[-335.6%] w-[137.79%]" src="/assets/exp-card-2.png" />
|
||||||
@@ -40,8 +40,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Card 3: Refined Visual Design -->
|
<!-- Card 3: Refined Visual Design -->
|
||||||
<div class="bg-gradient-to-b from-[#fef0eb] to-white flex flex-col gap-[36px] lg:h-full items-center overflow-clip pb-[36px] px-[36px] rounded-tl-[30px] rounded-tr-[30px] shrink-0 w-full lg:w-[320px]">
|
<div class="bg-gradient-to-b from-[#fef0eb] to-white flex flex-col gap-[36px] items-center overflow-clip pb-[36px] px-[36px] rounded-tl-[30px] rounded-tr-[30px] w-full">
|
||||||
<div class="bg-[#ffeddf] flex flex-col h-[232px] items-start overflow-clip shrink-0 w-full lg:w-[320px]">
|
<div class="bg-[#ffeddf] flex flex-col h-[232px] items-start overflow-clip w-full">
|
||||||
<div class="flex-1 min-h-0 relative w-full">
|
<div class="flex-1 min-h-0 relative w-full">
|
||||||
<div class="absolute inset-0 overflow-hidden pointer-events-none">
|
<div class="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
<img alt="Refined visual design screenshot" class="absolute h-[298.5%] left-0 max-w-none top-[-99.23%] w-full" src="/assets/exp-card-3.png" />
|
<img alt="Refined visual design screenshot" class="absolute h-[298.5%] left-0 max-w-none top-[-99.23%] w-full" src="/assets/exp-card-3.png" />
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ const logoWordmark = "/assets/header-logo-wordmark.svg";
|
|||||||
const globeIcon = "/assets/header-globe.svg";
|
const globeIcon = "/assets/header-globe.svg";
|
||||||
---
|
---
|
||||||
|
|
||||||
<header class="bg-white border-b border-[#e3d9d1] w-full flex items-center justify-center h-[72px] sticky top-0 z-50">
|
<header id="site-header" class="bg-white border-b border-[#e3d9d1] w-full sticky top-0 z-50">
|
||||||
<div class="flex items-center w-full max-w-[1280px] mx-auto px-4 lg:px-6">
|
<!-- Main bar -->
|
||||||
|
<div class="flex items-center w-full max-w-[1280px] mx-auto px-4 lg:px-6 h-[72px] gap-6">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<div class="flex flex-1 items-center min-w-0">
|
<div class="flex flex-1 items-center min-w-0">
|
||||||
<a href="/" class="relative h-[42px] w-[143px] shrink-0 block">
|
<a href="/" class="relative h-[42px] w-[143px] shrink-0 block">
|
||||||
@@ -18,24 +19,98 @@ const globeIcon = "/assets/header-globe.svg";
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Nav -->
|
<!-- Desktop nav -->
|
||||||
<nav class="hidden lg:flex items-center gap-[32px] shrink-0">
|
<nav class="hidden lg:flex items-center gap-[32px] shrink-0" aria-label="Main navigation">
|
||||||
<a href="#" class="font-semibold text-[14px] text-[#f28a4b] tracking-[-0.09px] whitespace-nowrap leading-normal">Home</a>
|
<a href="#hero" class="font-semibold text-[14px] text-[#f28a4b] tracking-[-0.09px] whitespace-nowrap leading-normal" data-nav-link>Home</a>
|
||||||
<a href="#features" class="font-semibold text-[14px] text-[#7a726d] tracking-[-0.09px] whitespace-nowrap leading-normal hover:text-[#f28a4b] transition-colors">Features</a>
|
<a href="#features" class="font-semibold text-[14px] text-[#7a726d] tracking-[-0.09px] whitespace-nowrap leading-normal hover:text-[#f28a4b] transition-colors" data-nav-link>Features</a>
|
||||||
<a href="#experience" class="font-semibold text-[14px] text-[#7a726d] tracking-[-0.09px] whitespace-nowrap leading-normal hover:text-[#f28a4b] transition-colors">Experience</a>
|
<a href="#experience" class="font-semibold text-[14px] text-[#7a726d] tracking-[-0.09px] whitespace-nowrap leading-normal hover:text-[#f28a4b] transition-colors" data-nav-link>Experience</a>
|
||||||
<a href="#use-cases" class="font-semibold text-[14px] text-[#7a726d] tracking-[-0.09px] whitespace-nowrap leading-normal hover:text-[#f28a4b] transition-colors">Use Cases</a>
|
<a href="#use-cases" class="font-semibold text-[14px] text-[#7a726d] tracking-[-0.09px] whitespace-nowrap leading-normal hover:text-[#f28a4b] transition-colors" data-nav-link>Use Cases</a>
|
||||||
<a href="#reliability" class="font-semibold text-[14px] text-[#7a726d] tracking-[-0.09px] whitespace-nowrap leading-normal hover:text-[#f28a4b] transition-colors">Reliability</a>
|
<a href="#reliability" class="font-semibold text-[14px] text-[#7a726d] tracking-[-0.09px] whitespace-nowrap leading-normal hover:text-[#f28a4b] transition-colors" data-nav-link>Reliability</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div class="flex flex-1 gap-[12px] items-center justify-end min-w-0">
|
<div class="flex flex-1 gap-[12px] items-center justify-end min-w-0">
|
||||||
|
<!-- Language button (desktop only) -->
|
||||||
<button class="hidden lg:flex border border-[rgba(46,42,40,0.3)] gap-[8px] h-[43px] items-center justify-center pl-[12px] pr-[16px] rounded-[17px] shrink-0 hover:border-[#f28a4b] transition-colors">
|
<button class="hidden lg:flex border border-[rgba(46,42,40,0.3)] gap-[8px] h-[43px] items-center justify-center pl-[12px] pr-[16px] rounded-[17px] shrink-0 hover:border-[#f28a4b] transition-colors">
|
||||||
<img alt="Language" class="block size-[26px] object-contain" src={globeIcon} />
|
<img alt="Language" class="block size-[26px] object-contain" src={globeIcon} />
|
||||||
<span class="font-semibold text-[14px] text-[#2e2a28] tracking-[-0.09px] whitespace-nowrap leading-[14px]">EN</span>
|
<span class="font-semibold text-[14px] text-[#2e2a28] tracking-[-0.09px] whitespace-nowrap leading-[14px]">EN</span>
|
||||||
</button>
|
</button>
|
||||||
<a href="#download" class="bg-[#f28a4b] flex items-center justify-center px-[22px] py-[13px] rounded-[17px] shrink-0 hover:bg-[#e07a3b] transition-colors">
|
<!-- Download button (desktop only) -->
|
||||||
|
<a href="#download" class="hidden lg:flex bg-[#f28a4b] items-center justify-center px-[22px] py-[13px] rounded-[17px] shrink-0 hover:bg-[#e07a3b] transition-colors">
|
||||||
<span class="font-bold text-[14px] text-white leading-normal whitespace-nowrap">Download</span>
|
<span class="font-bold text-[14px] text-white leading-normal whitespace-nowrap">Download</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<!-- Hamburger toggle (mobile only) -->
|
||||||
|
<button
|
||||||
|
id="menu-toggle"
|
||||||
|
class="lg:hidden flex flex-col justify-center gap-[5px] w-[40px] h-[40px] p-[8px] shrink-0"
|
||||||
|
type="button"
|
||||||
|
aria-label="Open menu"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="mobile-nav"
|
||||||
|
>
|
||||||
|
<span id="bar-1" class="block w-full h-[2px] bg-[#2e2a28] rounded-sm transition-all duration-[240ms] origin-center"></span>
|
||||||
|
<span id="bar-2" class="block w-full h-[2px] bg-[#2e2a28] rounded-sm transition-all duration-[240ms]"></span>
|
||||||
|
<span id="bar-3" class="block w-full h-[2px] bg-[#2e2a28] rounded-sm transition-all duration-[240ms] origin-center"></span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile nav drawer -->
|
||||||
|
<div id="mobile-nav" class="hidden lg:!hidden border-t border-[#e3d9d1] bg-white" aria-hidden="true">
|
||||||
|
<ul class="flex flex-col px-6 py-4 list-none m-0 p-0 px-6 py-4">
|
||||||
|
<li><a href="#hero" class="block py-4 font-semibold text-[16px] text-[#2e2a28] border-b border-[#e3d9d1]" data-nav-link>Home</a></li>
|
||||||
|
<li><a href="#features" class="block py-4 font-semibold text-[16px] text-[#2e2a28] border-b border-[#e3d9d1]" data-nav-link>Features</a></li>
|
||||||
|
<li><a href="#experience" class="block py-4 font-semibold text-[16px] text-[#2e2a28] border-b border-[#e3d9d1]" data-nav-link>Experience</a></li>
|
||||||
|
<li><a href="#use-cases" class="block py-4 font-semibold text-[16px] text-[#2e2a28] border-b border-[#e3d9d1]" data-nav-link>Use Cases</a></li>
|
||||||
|
<li><a href="#reliability" class="block py-4 font-semibold text-[16px] text-[#2e2a28]" data-nav-link>Reliability</a></li>
|
||||||
|
<li class="mt-4 mb-2">
|
||||||
|
<a href="#download" class="block text-center bg-[#f28a4b] text-white font-bold text-[15px] py-[14px] px-6 rounded-[17px] hover:bg-[#e07a3b] transition-colors">
|
||||||
|
Download
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const toggle = document.getElementById('menu-toggle') as HTMLButtonElement;
|
||||||
|
const nav = document.getElementById('mobile-nav') as HTMLDivElement;
|
||||||
|
const bar1 = document.getElementById('bar-1') as HTMLSpanElement;
|
||||||
|
const bar2 = document.getElementById('bar-2') as HTMLSpanElement;
|
||||||
|
const bar3 = document.getElementById('bar-3') as HTMLSpanElement;
|
||||||
|
|
||||||
|
function openMenu() {
|
||||||
|
toggle.setAttribute('aria-expanded', 'true');
|
||||||
|
toggle.setAttribute('aria-label', 'Close menu');
|
||||||
|
nav.classList.remove('hidden');
|
||||||
|
nav.removeAttribute('aria-hidden');
|
||||||
|
// Animate to X
|
||||||
|
bar1.style.transform = 'translateY(7px) rotate(45deg)';
|
||||||
|
bar2.style.opacity = '0';
|
||||||
|
bar3.style.transform = 'translateY(-7px) rotate(-45deg)';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMenu() {
|
||||||
|
toggle.setAttribute('aria-expanded', 'false');
|
||||||
|
toggle.setAttribute('aria-label', 'Open menu');
|
||||||
|
nav.classList.add('hidden');
|
||||||
|
nav.setAttribute('aria-hidden', 'true');
|
||||||
|
// Reset bars
|
||||||
|
bar1.style.transform = '';
|
||||||
|
bar2.style.opacity = '';
|
||||||
|
bar3.style.transform = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle.addEventListener('click', () => {
|
||||||
|
toggle.getAttribute('aria-expanded') === 'true' ? closeMenu() : openMenu();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close when any nav link is clicked
|
||||||
|
nav.querySelectorAll('a').forEach(a => a.addEventListener('click', closeMenu));
|
||||||
|
|
||||||
|
// Close when resized past mobile breakpoint
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
if (window.innerWidth >= 1024) closeMenu();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const heroBg = "/assets/hero-bg.png";
|
|||||||
const phoneMockup = "/assets/hero-phone.png";
|
const phoneMockup = "/assets/hero-phone.png";
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="relative w-full flex items-start justify-center min-h-[600px] lg:h-[891px] overflow-hidden">
|
<section id="hero" class="relative w-full flex items-start justify-center min-h-[600px] lg:h-[891px] overflow-hidden">
|
||||||
<img alt="" class="absolute inset-0 max-w-none object-cover size-full pointer-events-none" src={heroBg} />
|
<img alt="" class="absolute inset-0 max-w-none object-cover size-full pointer-events-none" src={heroBg} />
|
||||||
|
|
||||||
<div class="relative flex flex-col-reverse lg:flex-row gap-[40px] items-start w-full max-w-[1280px] mx-auto px-4 lg:px-0 h-full">
|
<div class="relative flex flex-col-reverse lg:flex-row gap-[40px] items-start w-full max-w-[1280px] mx-auto px-4 lg:px-0 h-full">
|
||||||
|
|||||||
@@ -16,9 +16,49 @@ const { title = 'Talk Pro — One User. Multiple Worlds.' } = Astro.props
|
|||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-surface font-sans overflow-x-hidden">
|
<body class="bg-surface font-sans overflow-x-hidden">
|
||||||
<slot />
|
<slot />
|
||||||
|
<script>
|
||||||
|
(() => {
|
||||||
|
const header = document.getElementById('site-header');
|
||||||
|
const getOffset = () => header ? header.offsetHeight : 0;
|
||||||
|
|
||||||
|
// Smooth scroll with header offset
|
||||||
|
document.querySelectorAll('a[href^="#"]').forEach(link => {
|
||||||
|
link.addEventListener('click', e => {
|
||||||
|
const href = link.getAttribute('href');
|
||||||
|
if (!href || href === '#') return;
|
||||||
|
const target = document.querySelector(href);
|
||||||
|
if (!target) return;
|
||||||
|
e.preventDefault();
|
||||||
|
const top = target.getBoundingClientRect().top + window.scrollY - getOffset();
|
||||||
|
window.scrollTo({ top, behavior: 'smooth' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Active nav highlighting via IntersectionObserver
|
||||||
|
const navLinks = document.querySelectorAll('[data-nav-link]');
|
||||||
|
const sections = Array.from(navLinks)
|
||||||
|
.map(l => document.querySelector(l.getAttribute('href') ?? ''))
|
||||||
|
.filter(Boolean) as Element[];
|
||||||
|
|
||||||
|
if ('IntersectionObserver' in window && sections.length) {
|
||||||
|
const observer = new IntersectionObserver(entries => {
|
||||||
|
const visible = entries
|
||||||
|
.filter(e => e.isIntersecting)
|
||||||
|
.sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
|
||||||
|
if (!visible) return;
|
||||||
|
navLinks.forEach(link => {
|
||||||
|
const active = link.getAttribute('href') === `#${visible.target.id}`;
|
||||||
|
link.classList.toggle('!text-[#f28a4b]', active);
|
||||||
|
link.classList.toggle('text-[#7a726d]', !active);
|
||||||
|
});
|
||||||
|
}, { rootMargin: '-30% 0px -60% 0px', threshold: 0 });
|
||||||
|
sections.forEach(s => observer.observe(s));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user