6.4 KiB
Context
Step 002 produced a BaseLayout.astro that imports Nav.astro and Footer.astro stubs — both render nothing. The navigation bar is the first visible UI element needed on every page. It must:
- Render correctly on a Midnight (
#102022) dark background - Work at build time (Astro SSG — no server runtime)
- Derive the current locale from the URL path (no runtime props)
- Use zero client-side framework code
Available assets confirmed:
src/assets/logos/logo-wide.svg— full "qumo" wordmark, paths filled with#161616(near-black — needs override)src/assets/icons/wum-arrow.svg— chevron arrow, alreadystroke="#f3f3f3"(Snow — correct for dark nav)
Goals / Non-Goals
Goals:
- Fully functional
Nav.astroreplacing the empty stub - Logo visible on Midnight background
- Three nav links + language switcher + Connect CTA
- Scroll-triggered background transition (transparent → Midnight)
- Mobile hamburger menu with open/close behavior
- All text strings from
en.json/nl.json
Non-Goals:
- Animated underline or hover effects beyond Tailwind utilities
- Active page highlighting (deferred — pages don't exist yet to test against)
- Dropdown sub-menus (not on staging site)
- Separate
LanguageSwitcher.astrocomponent (simple enough to inline)
Decisions
1. Logo: inline SVG with fill override
Decision: Inline the logo-wide.svg SVG directly in Nav.astro. Change all fill="#161616" attributes to fill="currentColor". Set class="text-snow" on the <svg> element so currentColor resolves to Snow (#F3F3F3).
Rationale: The logo is a small, purely decorative SVG — inlining it avoids an extra HTTP request and allows CSS-controlled coloring without filters. fill="currentColor" + Tailwind text color is the idiomatic approach. An <img> tag cannot be recolored without CSS filters, which look worse.
Alternative considered: <Image> component with a separate white-fill SVG asset. Rejected: requires maintaining two copies of the same logo.
2. Locale detection: Astro.url at build time
Decision: In Nav.astro, derive locale from Astro.url.pathname:
const isNl = Astro.url.pathname.startsWith('/nl');
const locale = isNl ? 'nl' : 'en';
const alternatePath = isNl
? Astro.url.pathname.replace(/^\/nl/, '') || '/'
: '/nl' + Astro.url.pathname;
Pass locale to getContent() for nav strings. Use alternatePath for the language switcher link.
Rationale: Astro SSG renders each page at build time with its own Astro.url. This approach is zero-JS, works for every route without configuration, and the prefix rule (/nl/... ↔ /...) holds for all routes on this site. Confirmed with user during explore session (Option B).
Alternative considered: Prop threading from BaseLayout (Option A). Rejected by user — more verbose, non-standard for nav components.
3. Scroll behavior: window scroll event + CSS class toggle
Decision: A <script> tag in Nav adds/removes a scrolled class on the <nav> element when window.scrollY > 20. Tailwind's data-scrolled attribute or a direct class toggle drives the style change.
Implementation approach — use a data-scrolled attribute to avoid class name conflicts:
const nav = document.getElementById('main-nav');
window.addEventListener('scroll', () => {
nav.dataset.scrolled = window.scrollY > 20 ? 'true' : 'false';
}, { passive: true });
CSS: nav starts bg-transparent, transitions to bg-midnight when data-scrolled="true". Use Tailwind's arbitrary variant: data-[scrolled=true]:bg-midnight.
Rationale: Simple, no dependencies, no layout shift. passive: true avoids scroll jank. The data-* attribute approach keeps the JS–CSS contract explicit and avoids collisions with Tailwind's JIT-generated class names.
Alternative considered: IntersectionObserver on a sentinel element. Unnecessary complexity for this use case — scroll position threshold is simpler and more predictable.
4. Mobile menu: vanilla JS toggle with CSS transition
Decision: The hamburger button toggles aria-expanded on itself and a corresponding data-open attribute on the mobile menu panel. CSS handles the visual transition (translate or max-height). A <script> tag wires up the toggle.
<button id="menu-toggle" aria-expanded="false" aria-controls="mobile-menu">...</button>
<div id="mobile-menu" class="... -translate-y-full data-[open]:translate-y-0 transition-transform">
...
</div>
Rationale: aria-expanded is the correct ARIA pattern for disclosure widgets. The data-open attribute drives CSS state without needing to manage class lists. translate transitions are GPU-accelerated and smooth.
5. Nav content: nav key in en.json / nl.json
Decision: Add a top-level "nav" key to both content files:
"nav": {
"links": [
{ "label": "Services", "href": "/#services" },
{ "label": "AI Launchpad", "href": "/ai-launchpad" },
{ "label": "About", "href": "/about" }
],
"cta": "Connect",
"langSwitch": { "en": "EN", "nl": "NL" }
}
NL links have the same hrefs with /nl prefix where needed; CTA becomes "Verbind".
Rationale: Consistent with the meta key established in step 002. Keeps all user-visible text out of component markup.
Note on NL link hrefs: The /#services anchor on the EN homepage becomes /nl#services in NL (no slash before #). The href values in nl.json must reflect this.
Risks / Trade-offs
- Logo SVG inlining grows component size → The logo SVG is ~1.5KB. Acceptable for a component rendered on every page; Astro will inline it into each page's HTML at build time. If the logo is ever animated or reused frequently, extract to a dedicated
Logo.astrocomponent. - Scroll behavior flickers on page load → On slow connections the nav may briefly show transparent before JS runs. Mitigation: set
data-scrolled="false"as the default HTML attribute so no flash occurs on initial render. - Mobile menu accessibility →
aria-expandedandaria-controlsare set, but focus trapping inside the open menu is not implemented in this step. Acceptable for now — full accessibility audit is a later concern. - NL href construction → The
alternatePathlogic strips the/nlprefix. If a page path ever starts with/nlfor a reason other than locale (e.g./nl-tech), it would be incorrectly treated as Dutch. This site has no such routes, so it's safe — but document the assumption.