### Clone Repository Source: https://github.com/vijay-kumar2001/grilli-brand-website/blob/main/README.md Command to clone the project repository from GitHub. ```bash git clone https://github.com/vijay-kumar2001/grilli-brand-website.git ``` -------------------------------- ### Project Folder Structure Source: https://github.com/vijay-kumar2001/grilli-brand-website/blob/main/README.md The directory layout for the Grilli brand website project. ```text GRILLI-BRAND-WEBSITE/ │ ├── Assets/ │ ├── CSS/ │ │ └── style.css │ │ │ ├── Images/ │ │ │ ├── JS/ │ │ └── script.js │ │ │ └── Screenshots/ │ ├── Images/ │ └── Videos/ │ ├── Videos/ │ ├── opening.mp4 │ └── opening.webm │ ├── favicon.svg ├── index.html ├── LEARNINGS.md └── README.md ``` -------------------------------- ### Initialize Hero Image Slider Source: https://context7.com/vijay-kumar2001/grilli-brand-website/llms.txt Manages automatic background image cycling with manual override controls that reset the auto-play timer. ```javascript // Hero image slider initialization (starts after video intro) setTimeout(heroImgHandler, 3100); function heroImgHandler() { let index = 0, intervalHandler; let imgTags = document.querySelectorAll("[data-backgroundImages]"); function showBg(index) { imgTags.forEach(img => img.classList.remove("active")); imgTags[index].classList.add("active"); } function autoChangeBg() { index = ((index + 1) % imgTags.length); showBg(index); } intervalHandler = setInterval(autoChangeBg, 5000); // Manual navigation with auto-reset let prevBtn = document.querySelector("[data-bgImgChangerPrev]"); let nextBtn = document.querySelector("[data-bgImgChangerNext]"); prevBtn.addEventListener("click", () => { index = index - 1 < 0 ? imgTags.length - 1 : index - 1; showBg(index); clearInterval(intervalHandler); intervalHandler = setInterval(autoChangeBg, 5000); }); } ``` -------------------------------- ### Define CSS Design System Variables Source: https://context7.com/vijay-kumar2001/grilli-brand-website/llms.txt Configures global design tokens for colors, typography, and spacing, along with CSS transitions for navigation visibility. ```css :root { /* Brand colors */ --gold-crayola: #e4c590; --quick-silver: hsla(0, 0%, 65%, 1); --smoky-black-1: hsla(40, 12%, 5%, 1); --eerie-black-1: #161718; --white: hsla(0, 0%, 100%, 1); --white-alpha-20: hsla(0, 0%, 100%, 0.2); /* Fluid typography with clamp() */ --fontSize-display-1: calc(1.3rem + 6.7vw); --fontSize-headline-1: calc(2rem + 2.5vw); --fontSize-title-1: calc(1.6rem + 1.2vw); --fontSize-body-2: 1.6rem; /* Spacing and transitions */ --section-space: 70px; --transition-1: 250ms ease; --transition-2: 500ms ease; --radius-24: 24px; --shadow-1: 0px 0px 25px 0px hsla(0, 0%, 0%, 0.25); } /* Auto-hide navigation with CSS transitions */ nav { position: fixed; z-index: 4; transition: transform 0.4s cubic-bezier(0, 0, 0.2, 1), opacity 0.4s cubic-bezier(0, 0, 0.2, 1); } nav.hide { transform: translateY(-100%); opacity: 0; } ``` -------------------------------- ### Implement GSAP About Section Mask Animation Source: https://context7.com/vijay-kumar2001/grilli-brand-website/llms.txt Uses ScrollTrigger to pin the about section and sequence opacity and mask transformations. Requires GSAP and ScrollTrigger plugins to be loaded. ```javascript function aboutAnimation() { let start = window.matchMedia("(max-width:1023px)").matches ? "top top" : "top+=40 top"; const maskedAnimation = gsap.timeline({ scrollTrigger: { trigger: '.about-us', start: start, end: 'bottom center', scrub: 1.5, // Smooth scrubbing pin: true, // Pin section during animation invalidateOnRefresh: true, toggleActions: "play none none reverse" } }); // Sequential animation timeline maskedAnimation.to(".will-fade", { opacity: 0, stagger: 0.2, ease: "power1.inOut" }); maskedAnimation.to(".underImg", { scale: 1.3, maskPosition: "center", maskSize: "400%", duration: 1, ease: 'power1.inOut' }); maskedAnimation.to(".will-appear", { duration: 0.5, opacity: 1, ease: "power1.inOut", stagger: 0.6 }); } ``` -------------------------------- ### Implement Mobile Navigation Menu Source: https://context7.com/vijay-kumar2001/grilli-brand-website/llms.txt Handles menu visibility, body scroll locking, and touch-based swipe gesture detection for closing the navigation. ```javascript // Menu open/close with scroll lock function menuOpen() { menuLogo.classList.add("menu-open"); menuItems.forEach((item) => { item.classList.add("menu-open") }); navOpen.classList.add("menu-open"); nav.classList.remove("hide"); document.body.style.overflow = 'hidden'; // Lock scroll menuItemStagger(); // Trigger stagger animation } function menuClose() { menuLogo.classList.remove("menu-open"); menuItems.forEach((item) => { item.classList.remove("menu-open") }); navOpen.classList.remove("menu-open"); document.body.style.overflow = ''; // Unlock scroll } // Swipe gesture detection for mobile function swipeToClose() { const swipeThresholdX = 50; let touchStartX = 0, touchEndX = 0; document.addEventListener("touchstart", (e) => { touchStartX = e.changedTouches[0].screenX; }); document.addEventListener("touchend", (e) => { touchEndX = e.changedTouches[0].screenX; const deltaX = touchStartX - touchEndX; if (Math.abs(deltaX) > swipeThresholdX) { deltaX > 0 ? menuClose() : menuOpen(); } }); } ``` -------------------------------- ### Create History-Aware Modal System Source: https://context7.com/vijay-kumar2001/grilli-brand-website/llms.txt Integrates modals with the browser History API to allow closing via the back button. Requires GSAP for entrance and exit animations. ```javascript let ModalOpened = false, FakeEntriesCount = 0; // Push fake history entry when modal opens function pushFakeEntry(state = null) { history.pushState(state, ""); FakeEntriesCount++; } // Pop fake entry when closing via X button function popFakeEntryIfAny() { if (FakeEntriesCount > 0) { FakeEntriesCount--; history.back(); } } function openModal(targetModal) { modalContainer.style.display = "flex"; targetModal.style.display = "flex"; ModalOpened = true; pushFakeEntry({ modal: true }); lockScroll(); // GSAP entrance animation gsap.fromTo(modalContainer, { autoAlpha: 0, scale: 0.9 }, { autoAlpha: 1, scale: 1, duration: 0.4, ease: "power2.out" } ); gsap.fromTo(targetModal, { autoAlpha: 0, y: 30 }, { autoAlpha: 1, y: 0, duration: 0.4, ease: "power2.out", delay: 0.1 } ); } function closeModal() { gsap.to(modalContainer, { autoAlpha: 0, scale: 0.9, duration: 0.3, ease: "power2.in", onComplete: () => { unLockScroll(); IsManualClose = true; popFakeEntryIfAny(); ModalOpened = false; } }); } // Back button handler for modals window.addEventListener("popstate", (eventObj) => { if (IsManualClose) { IsManualClose = false; return; } if (ModalOpened) { gsap.to(modalContainer, { autoAlpha: 0, scale: 0.9, duration: 0.3, ease: "power2.in", onComplete: () => { unLockScroll(); ModalOpened = false; } }); FakeEntriesCount = Math.max(0, FakeEntriesCount - 1); } }); ``` -------------------------------- ### Manage Hero Video Playback and Transitions Source: https://context7.com/vijay-kumar2001/grilli-brand-website/llms.txt Handles video playback with scroll locking and triggers a GSAP fade-out transition before the video ends. Relies on lockScroll and unLockScroll helper functions. ```javascript function heroVideoHandler() { let videoContainer = document.querySelector("#opener"); let heroVideo = document.querySelector(".heroVideo"); let header = document.querySelector("header"); let fadeDuration = 0.8; let fadeStarted = false; lockScroll(); // Prevent scrolling during video heroVideo.addEventListener("timeupdate", () => { // Start fade before video ends for smooth transition if (!fadeStarted && heroVideo.currentTime > heroVideo.duration - fadeDuration) { fadeStarted = true; let mainStarter = gsap.timeline(); mainStarter.to(videoContainer, { opacity: 0, duration: 0.8, ease: "power1.inOut" }); mainStarter.from(header, { opacity: 0, duration: 0.5, delay: -0.3 }); } }); heroVideo.addEventListener("ended", () => { unLockScroll(); videoContainer.style.display = "none"; }); } function lockScroll() { document.body.style.overflow = "hidden"; } function unLockScroll() { document.body.style.overflow = ""; } ``` -------------------------------- ### Handle Asynchronous Form Submission Source: https://context7.com/vijay-kumar2001/grilli-brand-website/llms.txt Manages form submission via Fetch API with support for loading states and modal feedback. Expects the form to have valid action and method attributes. ```javascript function handleFormSubmission() { let forms = document.querySelectorAll(".reservationForm"); forms.forEach(form => form.addEventListener("submit", (e) => handleFormManually(e, form))); async function handleFormManually(eventObj, form) { eventObj.preventDefault(); let formData = new FormData(form); preShowModal(); // Show loading state try { const response = await fetch(form.action, { method: form.method, body: formData, headers: { "Accept": "application/json" } }); if (response.ok) { showModal("success"); // Message: "Your table request has been received!" } else { showModal("failure"); // Message: "Oops! Something went wrong." } } catch { showModal("error"); // Message: "Looks like there's a connection issue." } } } ``` -------------------------------- ### Validate Forms with Regex Source: https://context7.com/vijay-kumar2001/grilli-brand-website/llms.txt Implements real-time validation for name, phone, and email fields using regex patterns. Requires specific CSS classes for error styling and sibling elements for error messages. ```javascript const errorMessages = { name: "Please enter a valid name (letters only, min 2 characters).", phoneNo: "Please enter a valid 10-digit phone number.", email: "Please enter a valid email address." }; const regexes = { name: /^[A-Za-z\s'-]{2,}$/, phoneNo: /^[0-9]{10}$/, email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ }; function validateForm(formSelector) { const form = document.querySelector(formSelector); const inputFields = [ { selector: ".customerName", type: "name" }, { selector: ".customerPhoneNo", type: "phoneNo" }, { selector: ".customerEmail", type: "email" } ]; const blurredFields = new WeakMap(); inputFields.forEach(({ selector, type }) => { form.querySelectorAll(selector).forEach(field => { const errorElement = field.nextElementSibling; field.addEventListener("blur", () => { blurredFields.set(field, true); const isValid = regexes[type].test(field.value.trim()); if (!isValid) { field.classList.add("error"); errorElement.textContent = errorMessages[type]; } else { field.classList.remove("error"); errorElement.textContent = ""; } }); field.addEventListener("input", () => { if (!blurredFields.get(field)) return; const isValid = regexes[type].test(field.value.trim()); field.classList.toggle("error", !isValid); field.classList.toggle("success", isValid); }); }); }); } validateForm("#modalForm"); validateForm("#sectionForm"); ``` -------------------------------- ### Configure Auto-Hide Navigation Bar Source: https://context7.com/vijay-kumar2001/grilli-brand-website/llms.txt Toggles navigation visibility based on scroll direction and window resize events. ```javascript function hideNav() { let navHeight = nav.offsetHeight; let lastScrollPosY = 0; window.addEventListener("resize", () => { navHeight = nav.offsetHeight; }); window.addEventListener("scroll", () => { if (window.scrollY > navHeight && window.scrollY > lastScrollPosY) { navHidden = 1; nav.classList.add("hide"); // Slide up and fade out } else if (window.scrollY < lastScrollPosY) { navHidden = 0; nav.classList.remove("hide"); // Slide down and fade in } lastScrollPosY = window.scrollY; }); } ``` -------------------------------- ### Implement Navigation Active State with IntersectionObserver Source: https://context7.com/vijay-kumar2001/grilli-brand-website/llms.txt Uses IntersectionObserver to toggle an 'active' class on navigation links based on the section currently centered in the viewport. ```javascript function navigateUsingNavLinks() { let links = document.querySelectorAll(".nav-bar-items"); let sections = document.querySelectorAll("section"); let sectionOptions = { root: null, rootMargin: "-50% 0px -50% 0px", // Trigger at center of viewport threshold: 0 }; let sectionObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { let sectionId = entry.target.getAttribute("id"); links.forEach(link => { let linkId = link.getAttribute("data-id"); link.classList.toggle("active", sectionId === linkId); }); } }); }, sectionOptions); sections.forEach(section => sectionObserver.observe(section)); links.forEach(link => link.addEventListener("click", menuClose)); } ``` -------------------------------- ### Apply Scroll-Based Card Hover Effect Source: https://context7.com/vijay-kumar2001/grilli-brand-website/llms.txt Calculates the card closest to the viewport center and applies a 'hovered' class. Designed for mobile devices to simulate hover states during scroll. ```javascript function cardHoverEffectOnMobile() { let cards = document.querySelectorAll(".cards"); function updateActiveCard() { let viewportCenter = window.innerHeight / 2; let closestCard = null; let closestDistance = viewportCenter / 2; cards.forEach(card => { let rect = card.getBoundingClientRect(); let cardCenter = rect.top + rect.height / 2; let distance = Math.abs(cardCenter - viewportCenter); if (distance < closestDistance) { closestDistance = distance; closestCard = card; } }); cards.forEach(card => card.classList.remove("hovered")); if (closestCard) closestCard.classList.add("hovered"); } window.addEventListener("scroll", updateActiveCard); window.addEventListener("resize", updateActiveCard); updateActiveCard(); } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.