refactor: Format site files for 11ty

- Convert HTML pages into NJK templates
- Add front matter to NJK templates
- Modularize monolithic stylesheet
This commit is contained in:
2025-08-18 23:28:46 -05:00
parent c9c5b89736
commit de71b2d663
89 changed files with 4471 additions and 989 deletions

178
_site/scripts/cart.js Normal file
View File

@@ -0,0 +1,178 @@
document.addEventListener('DOMContentLoaded', () => {
let cart = JSON.parse(sessionStorage.getItem('cart')) || [];
// Get modal and cart-related elements
const modal = document.getElementById('cart-modal');
const closeModal = document.querySelector('.close-modal');
const cartItemsContainer = document.getElementById('cart-items');
const cartTotalContainer = document.getElementById('cart-total');
const clearCartModalButton = document.getElementById('clear-cart-modal-btn');
const processOrderModalButton = document.getElementById('process-order-modal-btn');
// Custom confirmation modals for clear cart and process order
const confirmationModal = document.getElementById('confirmation-modal');
const confirmationMessage = document.getElementById('confirmation-message');
const confirmationCloseButton = document.getElementById('confirmation-close-btn');
const confirmationConfirmButton = document.getElementById('confirmation-confirm-btn');
// Buttons
const shoppingCartButton = document.getElementById('shopping-cart');
const cartDetailsButton = document.getElementById('cart-details');
// Custom alert modal for item added
const itemAddedModal = document.getElementById('item-added-modal');
const itemAddedMessage = document.getElementById('item-added-message');
const itemAddedClose = document.getElementById('item-added-close');
// Final confirmation modal for success messages
const finalConfirmationModal = document.getElementById('final-confirmation-modal');
const finalConfirmationMessage = document.getElementById('final-confirmation-message');
const finalConfirmationCloseButton = document.getElementById('final-confirmation-close-btn');
// Function to render the cart
function renderCart() {
cart = JSON.parse(sessionStorage.getItem('cart')) || [];
cartItemsContainer.innerHTML = ''; // Clear existing items
let total = 0;
if (cart.length === 0) {
cartItemsContainer.innerHTML = '<li>Your cart is empty.</li>';
cartTotalContainer.textContent = '';
return;
}
cart.forEach(item => {
const itemTotal = item.price * item.quantity;
total += itemTotal;
const listItem = document.createElement('li');
listItem.textContent = `${item.name} x ${item.quantity} - $${itemTotal.toFixed(2)}`;
cartItemsContainer.appendChild(listItem);
});
cartTotalContainer.textContent = `Subtotal: $${total.toFixed(2)}`;
}
// Show cart modal with current cart content
function showModal() {
renderCart(); // Always render the latest cart
modal.style.display = 'flex'; // Show the cart modal
}
// Show confirmation modal for actions (clear or process order)
function showConfirmationModal(message, action) {
confirmationMessage.textContent = message;
confirmationModal.style.display = 'flex'; // Show the confirmation modal
// Confirm action on the "Confirm" button
confirmationConfirmButton.onclick = () => {
action(); // Execute the passed action (clear cart, process order)
confirmationModal.style.display = 'none'; // Close the confirmation modal after confirming
};
// Close confirmation modal without performing any action
confirmationCloseButton.onclick = () => {
confirmationModal.style.display = 'none';
};
}
// Show final confirmation modal with success message
function showFinalConfirmationModal(message) {
finalConfirmationMessage.textContent = message;
finalConfirmationModal.style.display = 'flex'; // Show the final confirmation modal
finalConfirmationCloseButton.onclick = () => {
finalConfirmationModal.style.display = 'none'; // Close final confirmation modal
};
}
// Show custom modal when item is added to cart
function showItemAddedModal(message) {
itemAddedMessage.textContent = message; // Set message dynamically
itemAddedModal.style.display = 'flex'; // Show the modal
itemAddedClose.addEventListener('click', () => {
itemAddedModal.style.display = 'none'; // Close the modal
});
}
// Attach event listeners to both cart buttons
[shoppingCartButton, cartDetailsButton].forEach(button => {
if (button) {
button.addEventListener('click', showModal); // Open modal when clicked
}
});
// Close modal when clicking outside of it
window.addEventListener('click', event => {
if (event.target === modal) {
modal.style.display = 'none';
}
});
// Close the modal by clicking the close button (for cart modal)
if (closeModal) {
closeModal.addEventListener('click', () => {
modal.style.display = 'none';
});
}
// Add to cart functionality
const addToCartButtons = document.querySelectorAll('.add-to-cart-btn');
addToCartButtons.forEach(button => {
button.addEventListener('click', () => {
const productId = parseInt(button.dataset.productId, 10);
const product = products.find(item => item.id === productId);
if (product) {
const existingProduct = cart.find(item => item.id === productId);
if (existingProduct) {
existingProduct.quantity++;
} else {
cart.push({ ...product, quantity: 1 });
}
sessionStorage.setItem('cart', JSON.stringify(cart)); // Save cart to session storage
showItemAddedModal(`${product.name} has been added to the cart!`); // Show custom modal
}
});
});
// Event listener for Clear Cart button in the modal
if (clearCartModalButton) {
clearCartModalButton.addEventListener('click', () => {
showConfirmationModal('Are you sure you want to clear the cart?', () => {
sessionStorage.removeItem('cart');
cart = [];
renderCart(); // Re-render cart after clearing it
showFinalConfirmationModal('Cart has been cleared!'); // Show final confirmation for cart clearing
});
});
}
// Event listener for Process Order button in the modal
if (processOrderModalButton) {
processOrderModalButton.addEventListener('click', () => {
if (cart.length === 0) {
showItemAddedModal('Your cart is empty. Please add items to the cart before processing the order.');
return;
}
showConfirmationModal('Are you sure you want to process your order?', () => {
const receiptNumber = Math.floor(Math.random() * 1000000); // Generate receipt number
showFinalConfirmationModal(`Thank you for your order!\nYour receipt number is ${receiptNumber}.`);
sessionStorage.removeItem('cart');
cart = [];
renderCart(); // Re-render cart after processing the order
});
});
}
});
const allCloseButtons = document.querySelectorAll('.close-modal');
allCloseButtons.forEach(button => {
button.addEventListener('click', () => {
// Close the parent modal of the button clicked
const modal = button.closest('.modal');
modal.style.display = 'none';
});
});

93
_site/scripts/feedback.js Normal file
View File

@@ -0,0 +1,93 @@
document.addEventListener('DOMContentLoaded', () => {
const feedbackForm = document.querySelector('#feedback-form form');
const nameInput = document.getElementById('name');
const emailInput = document.getElementById('email');
const phoneInput = document.getElementById('tel');
const messageInput = document.getElementById('message');
const alertBox = document.getElementById('custom-alert');
const alertMessage = document.getElementById('alert-message');
const alertOkButton = document.getElementById('alert-ok-button');
const alertCloseButton = document.getElementById('alert-close-btn');
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; // Email validation regex
const phoneRegex = /^[0-9]{10,15}$/; // Phone validation regex (10-15 digits)
const nameRegex = /^[a-zA-Z\s]{2,}$/; // Name validation regex (at least 2 letters)
const sqlInjectionRegex = /[;'"\\]/; // Prevent SQL injection-like characters
// Form submission handler
feedbackForm.addEventListener('submit', (event) => {
event.preventDefault(); // Prevent form submission and page refresh
const name = nameInput.value.trim();
const email = emailInput.value.trim();
const phone = phoneInput.value.trim();
const message = messageInput.value.trim();
// Validate each field
if (!nameRegex.test(name)) {
showCustomAlert('Please enter a valid name (letters and spaces only).');
return;
}
if (!emailRegex.test(email)) {
showCustomAlert('Please enter a valid email address.');
return;
}
if (!phoneRegex.test(phone)) {
showCustomAlert('Please enter a valid phone number (10-15 digits).');
return;
}
if (sqlInjectionRegex.test(message)) {
showCustomAlert('Your message contains invalid characters.');
return;
}
// Save feedback to localStorage
const feedbackData = {
name,
email,
phone,
message,
timestamp: new Date().toISOString(),
};
// Retrieve existing feedback data or initialize an empty array
const feedbackList = JSON.parse(localStorage.getItem('feedbackList')) || [];
feedbackList.push(feedbackData); // Add new feedback to the list
localStorage.setItem('feedbackList', JSON.stringify(feedbackList)); // Save updated list to localStorage
console.log('Feedback saved to localStorage:', feedbackList);
// Show success message
showCustomAlert('Thank you for your feedback! We appreciate hearing from you.');
feedbackForm.reset(); // Clear the input fields after submission
});
// Function to show the custom alert modal
function showCustomAlert(message) {
alertMessage.textContent = message; // Set modal message
alertBox.classList.remove('hidden'); // Display the modal
alertBox.style.display = 'flex'; // Ensure the modal is visible
}
// Function to close the custom alert modal
function closeCustomAlert() {
alertBox.classList.add('hidden'); // Hide the modal
alertBox.style.display = 'none'; // Ensure it's hidden
}
// Close modal on "OK" button click
alertOkButton.addEventListener('click', closeCustomAlert);
// Close modal on "X" button click
alertCloseButton.addEventListener('click', closeCustomAlert);
// Close modal when clicking outside the modal content
window.addEventListener('click', (event) => {
if (event.target === alertBox) {
closeCustomAlert();
}
});
});

View File

@@ -0,0 +1,130 @@
// Gallery Carousel .js
const products = {
trees: [
{ id: 1, name: "Crepe Mertyl Tree", description: "Beautiful Crepe Mertyl tree.", price: 55.00, image: "images/trees/crepe-mertyl.jpg" },
{ id: 2, name: "Silver Birch Tree", description: "Sturdy Silver Birch tree.", price: 45.00, image: "images/trees/silver-birch.jpg" },
{ id: 3, name: "Apple Tree", description: "Fruitful apple tree.", price: 35.00, image: "images/trees/apple-tree.jpg" },
{ id: 4, name: "Potted Spruce Tree", description: "Miniature Spruce in a white planter.", price: 35.00, image: "images/trees/potted-spruce.jpg" }
],
indoor: [
{ id: 5, name: "Aloe Plant", description: "Thrives with 6-8 hours of direct sunlight a day.", price: 15.00, image: "images/indoor/aloe-plant.jpg" },
{ id: 6, name: "Peperonia Plant", description: "Low-maintenance and perfect for spaces with lots of natural light.", price: 12.50, image: "images/indoor/peperonia.jpg" },
{ id: 7, name: "String-of-Pearls Plant", description: "Best placed in east-facing windows or on shaded patios and balconies.", price: 20.00, image: "images/indoor/string-of-pearls.jpg" },
{ id: 12, name: "Venus Fly Trap", description: "Perfect for young botanists or your fly problem!", price: 12.50, image: "images/indoor/venus-fly-trap.jpg"}
],
tools: [
{ id: 8, name: "Watering Cans", description: "Galvanized aluminum watering cans (x2).", price: 25.00, image: "images/tools/watering-cans.jpg" },
{ id: 9, name: "Potting Soil", description: "Premium-blend of nutrient-rich potting soil - 10 lb bag.", price: 8.00, image: "images/tools/potting-soil.png" },
{ id: 10, name: "Bird House", description: "Handmade wooden bird house for hanging or mounting.", price: 11.00, image: "images/tools/birdhouse.jpg" },
{ id: 11, name: "Gardening Tools", description: "Wooden handled gardening tools.", price: 20.00, image: "images/tools/gardening-tools.jpg"}
]
};
document.addEventListener('DOMContentLoaded', () => {
console.log("DOM fully loaded and parsed"); // Check if the DOM is ready
const productCarousel = document.getElementById('product-carousel');
const scrollLeftButton = document.getElementById('scroll-left');
const scrollRightButton = document.getElementById('scroll-right');
const categoryButtons = document.querySelectorAll('.cat-btn');
// Scroll button functionality
scrollLeftButton.addEventListener('click', () => {
productCarousel.scrollBy({ left: -300, behavior: 'smooth' });
});
scrollRightButton.addEventListener('click', () => {
productCarousel.scrollBy({ left: 300, behavior: 'smooth' });
});
// Map buttons to categories
categoryButtons.forEach(button => {
button.addEventListener('click', (event) => {
event.preventDefault(); // Prevent default navigation
const url = new URL(button.href); // Parse the URL
const category = url.searchParams.get('category'); // Get category from the query parameter
updateProductList(category); // Update the product list
});
});
// Load default category or the one from the current URL
const currentUrl = new URL(window.location.href);
const defaultCategory = currentUrl.searchParams.get('category') || 'trees';
updateProductList(defaultCategory);
});
function updateScrollButtons() {
const scrollLeftButton = document.getElementById('scroll-left');
const scrollRightButton = document.getElementById('scroll-right');
const productCarousel = document.getElementById('product-carousel');
scrollLeftButton.style.display = productCarousel.scrollLeft === 0 ? 'none' : 'block';
scrollRightButton.style.display =
productCarousel.scrollWidth - productCarousel.clientWidth === productCarousel.scrollLeft
? 'none'
: 'block';
}
function updateProductList(category) {
const productList = document.getElementById("product-list");
if (!productList) {
console.error("Element with ID 'product-list' not found in the DOM.");
return;
}
productList.innerHTML = ""; // Clear existing products
console.log(`Updating product list for category: ${category}`); // Debugging line
const selectedProducts = products[category];
if (selectedProducts) {
selectedProducts.forEach(product => {
const productCard = document.createElement("div");
productCard.className = "product-card";
console.log(`Creating product card for: ${product.name}`); // Debugging line
productCard.innerHTML = `
<img src="${product.image}" alt="${product.name}">
<h3>${product.name}</h3>
<p>${product.description}</p>
<p>$${product.price}</p>
<button class="add-to-cart-btn" data-product-id="${product.id}">Add to Cart</button>
`;
productList.appendChild(productCard);
// Add event listener for "Add to Cart" button
const addToCartButton = productCard.querySelector('.add-to-cart-btn');
addToCartButton.addEventListener('click', () => {
addToCart(product.id);
});
});
} else {
console.error(`No products found for category: ${category}`);
}
}
function addToCart(productId) {
const product = findProductById(productId);
if (product) {
let cart = JSON.parse(sessionStorage.getItem('cart')) || [];
const existingProduct = cart.find(item => item.id === productId);
if (existingProduct) {
existingProduct.quantity++;
} else {
cart.push({ ...product, quantity: 1 });
}
sessionStorage.setItem('cart', JSON.stringify(cart));
alert(`${product.name} has been added to your cart!`);
}
}
function findProductById(productId) {
for (let category in products) {
const product = products[category].find(item => item.id === productId);
if (product) return product;
}
return null;
}

View File

@@ -0,0 +1,50 @@
document.addEventListener('DOMContentLoaded', () => {
const newsletterForm = document.getElementById('newsletter-form');
const emailInput = document.getElementById('email-input');
const alertBox = document.getElementById('custom-alert');
const alertMessage = document.getElementById('alert-message');
const alertOkButton = document.getElementById('alert-ok-button');
const alertCloseButton = document.getElementById('alert-close-btn');
// Form submission handler
newsletterForm.addEventListener('submit', (event) => {
event.preventDefault(); // Prevent form from submitting and refreshing the page
const email = emailInput.value.trim();
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
// Check if email is valid
if (!emailRegex.test(email)) {
showCustomAlert('Please enter a valid email address.');
return;
}
// Show success message
showCustomAlert(`Thank you for subscribing, ${email}!`);
newsletterForm.reset(); // Clear the input field after submission
});
// Function to show the custom alert modal
function showCustomAlert(message) {
alertMessage.textContent = message; // Set the alert message
alertBox.classList.remove('hidden'); // Show the alert modal
alertBox.style.display = 'flex'; // Ensure the modal is displayed
// Close the modal when the "OK" button is clicked
alertOkButton.addEventListener('click', closeCustomAlert);
alertCloseButton.addEventListener('click', closeCustomAlert);
}
// Function to hide the custom alert modal
function closeCustomAlert() {
alertBox.classList.add('hidden'); // Hide the alert modal
alertBox.style.display = 'none'; // Ensure the modal is hidden
}
// Close the modal if clicking outside the modal content
window.addEventListener('click', (event) => {
if (event.target === alertBox) {
closeCustomAlert();
}
});
});

126
_site/scripts/script.js Normal file
View File

@@ -0,0 +1,126 @@
document.addEventListener("DOMContentLoaded", () => {
// ================== DEMO NOTICE MODAL ==================
if (!sessionStorage.getItem("demoNoticeAcknowledged")) {
let modal = document.createElement("div");
modal.id = "demoModal";
modal.style.position = "fixed";
modal.style.left = "0";
modal.style.top = "0";
modal.style.width = "100%";
modal.style.height = "100%";
modal.style.background = "rgba(0, 0, 0, 0.7)";
modal.style.display = "flex";
modal.style.alignItems = "center";
modal.style.justifyContent = "center";
modal.style.zIndex = "1000";
let modalContent = document.createElement("div");
modalContent.style.background = "white";
modalContent.style.padding = "20px";
modalContent.style.borderRadius = "10px";
modalContent.style.textAlign = "center";
modalContent.style.width = "80%";
modalContent.style.maxWidth = "400px";
let heading = document.createElement("h2");
heading.innerText = "Demo Notice";
let message = document.createElement("p");
message.innerText =
"This site is for demonstration purposes only and is not a real e-commerce store. No purchases can be made.";
let closeButton = document.createElement("button");
closeButton.innerText = "I Understand";
closeButton.style.padding = "10px 20px";
closeButton.style.marginTop = "15px";
closeButton.style.border = "none";
closeButton.style.background = "#007bff";
closeButton.style.color = "white";
closeButton.style.fontSize = "16px";
closeButton.style.cursor = "pointer";
closeButton.style.borderRadius = "5px";
closeButton.addEventListener("click", function () {
modal.style.display = "none";
sessionStorage.setItem("demoNoticeAcknowledged", "true");
});
modalContent.appendChild(heading);
modalContent.appendChild(message);
modalContent.appendChild(closeButton);
modal.appendChild(modalContent);
document.body.appendChild(modal);
}
// ================== SHOPPER PERKS CAROUSEL ==================
const perks = document.querySelectorAll(".perk-item");
let currentIndexPerks = 0;
function showNextPerk() {
perks.forEach(perk => {
perk.classList.remove("visible");
perk.style.display = "none";
});
perks[currentIndexPerks].classList.add("visible");
perks[currentIndexPerks].style.display = "block";
currentIndexPerks = (currentIndexPerks + 1) % perks.length;
}
showNextPerk();
setInterval(showNextPerk, 2250);
// ================== CUSTOMER HIGHLIGHTS CAROUSEL ==================
const testimonials = document.querySelectorAll(".testimonial");
let currentIndexTestimonials = 0;
function showNextTestimonial() {
testimonials.forEach(testimonial => {
testimonial.classList.remove("visible");
testimonial.style.display = "none";
});
testimonials[currentIndexTestimonials].classList.add("visible");
testimonials[currentIndexTestimonials].style.display = "block";
currentIndexTestimonials = (currentIndexTestimonials + 1) % testimonials.length;
}
showNextTestimonial();
setInterval(showNextTestimonial, 3500);
// ================== FEATURED ITEMS CAROUSEL ==================
const featuredItems = document.querySelectorAll("#featured-images .featured-item");
let currentIndex = 0;
function showNextItem() {
featuredItems[currentIndex].classList.remove("active");
currentIndex = (currentIndex + 1) % featuredItems.length;
featuredItems[currentIndex].classList.add("active");
}
featuredItems[currentIndex].classList.add("active");
setInterval(showNextItem, 3000);
// ================== UPDATED FEATURED ITEMS CAROUSEL ==================
let currentIndexFeatured = 0;
function updateFeaturedImages() {
featuredItems.forEach((item, index) => {
const text = item.closest('a');
if (text) text.style.display = (index === currentIndexFeatured ? "block" : "none");
const image = item;
image.style.display = (index === currentIndexFeatured ? "block" : "none");
});
}
function showNextFeatured() {
currentIndexFeatured = (currentIndexFeatured + 1) % featuredItems.length;
updateFeaturedImages();
}
updateFeaturedImages();
setInterval(showNextFeatured, 3000);
});