File structure and key code explained
projects/sarawak-laksa/
├── index.html ← Main app (all content embedded)
├── css/
│ └── style.css ← All styles, variables, animations, responsive
├── js/
│ └── app.js ← Scroll nav, fade-in observer, smooth scroll
└── knowledge/
├── what-is-sarawak-laksa.md
├── ingredients.md
├── recipe.md
├── variations.md
├── where-to-eat.md
├── food-culture.md
└── people.md
index.html. The knowledge/ folder contains the Markdown source documents used to author the HTML content — they are not loaded at runtime.
Cards fade in as they enter the viewport using the IntersectionObserver API. Each observed element gets a fade-in class immediately, then visible once it enters the viewport.
const fadeEls = document.querySelectorAll(
'.ingredient-card, .variation-card, .stall-card, ' +
'.culture-item, .person-card, .recipe-phase'
);
fadeEls.forEach(el => el.classList.add('fade-in'));
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
fadeEls.forEach(el => observer.observe(el));
.fade-in {
opacity: 0;
transform: translateY(22px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.fade-in.visible {
opacity: 1;
transform: translateY(0);
}
The sticky navigation highlights the current section as the user scrolls. It tracks which section[id] is currently in view and adds an active class to the matching nav link.
const sections = document.querySelectorAll('section[id]');
const navLinks = document.querySelectorAll('.nav-links a');
function updateActiveNav() {
let current = '';
sections.forEach(section => {
const sectionTop = section.offsetTop - 100;
if (window.scrollY >= sectionTop) {
current = section.getAttribute('id');
}
});
navLinks.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === '#' + current) {
link.classList.add('active');
}
});
}
window.addEventListener('scroll', updateActiveNav, { passive: true });
The site uses CSS custom properties for a consistent colour scheme and spacing across all sections.
:root {
--brown: #5C3D2E;
--brown-light: #7A5243;
--cream: #FDF6EC;
--cream-mid: #F5ECD7;
--gold: #C8973E;
--gold-light: #E8B96A;
--text-dark: #2C1810;
--text-mid: #4A3728;
--text-light: #7A6558;
--section-light: #FDF6EC;
--section-mid: #F5ECD7;
--section-dark: #3D2415;
--radius: 8px;
--shadow-sm: 0 2px 8px rgba(0,0,0,0.08);
--shadow-md: 0 4px 16px rgba(0,0,0,0.12);
--transition: 0.3s ease;
}
Each section of the site was authored from a corresponding Markdown file in knowledge/. These files contain the raw facts, quotes, and structured data that were used to write the HTML content.
--- title: "Where to Eat Sarawak Laksa" tags: ["stalls", "kuching", "hawker", "halal"] --- ## Top Picks ### Chong Choon Cafe - Location: Jalan Wan Alwi, Kuching - Hours: 7:00am – 11:30am - Halal: No - One of Kuching's most celebrated laksa stalls. Queue starts at 7am. Usually sold out by 10am.