File size: 9,861 Bytes
1652593 2917b05 1652593 2917b05 1652593 2917b05 1652593 2917b05 1652593 2917b05 1652593 2917b05 1652593 2917b05 1652593 2917b05 1652593 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
/**
* Main Application Logic
* Handles routing, data fetching, and component interaction.
*/
// Data Mockups (Simulating Public API response)
const servicesData = [
{
id: 1,
title: "Tree Removal",
desc: "Complete removal of hazardous or unwanted trees using advanced rigging techniques to protect your property.",
icon: "log-out",
image: "http://static.photos/nature/640x360/1"
},
{
id: 2,
title: "Stump Grinding",
desc: "Eliminate tripping hazards and improve curb appeal by grinding stumps below ground level.",
icon: "disc",
image: "http://static.photos/nature/640x360/2"
},
{
id: 3,
title: "Tree Pruning",
desc: "Selective branch removal to improve structure, health, and aesthetics of your trees.",
icon: "scissors",
image: "http://static.photos/nature/640x360/3"
},
{
id: 4,
title: "Emergency Storm Care",
desc: "24/7 rapid response for fallen trees or branches threatening your home or power lines.",
icon: "alert-triangle",
image: "http://static.photos/nature/640x360/4"
},
{
id: 5,
title: "Land Clearing",
desc: "Preparing lots for construction or renovation by removing vegetation efficiently.",
icon: "map",
image: "http://static.photos/nature/640x360/5"
},
{
id: 6,
title: "Cabling & Bracing",
desc: "Installing support systems to preserve structurally weak trees and extend their lifespan.",
icon: "anchor",
image: "http://static.photos/nature/640x360/6"
}
];
const projectsData = [
{ title: "Oak Tree Removal", location: "Downtown", img: "http://static.photos/nature/640x360/10" },
{ title: "Storm Damage Cleanup", location: "Westside", img: "http://static.photos/nature/640x360/11" },
{ title: "Stump Grinding Project", location: "Hillside", img: "http://static.photos/nature/640x360/12" },
{ title: "Palm Tree Trimming", location: "Beach Blvd", img: "http://static.photos/nature/640x360/13" },
{ title: "Hazardous Limb Removal", location: "Suburbia", img: "http://static.photos/nature/640x360/14" },
{ title: "Commercial Clearing", location: "Industrial Park", img: "http://static.photos/nature/640x360/15" },
];
class Router {
constructor() {
this.routes = ['home', 'services', 'projects', 'contact'];
this.init();
}
init() {
// Handle initial load based on hash or default to home
const hash = window.location.hash.replace('#', '') || 'home';
this.navigate(hash);
// Handle browser back/forward buttons
window.addEventListener('popstate', (event) => {
if(event.state && event.state.page) {
this.renderPage(event.state.page);
}
});
}
navigate(pageId) {
// Update URL hash without reload
if (window.location.hash !== `#${pageId}`) {
history.pushState({ page: pageId }, null, `#${pageId}`);
}
this.renderPage(pageId);
}
renderPage(pageId) {
// Hide all sections
document.querySelectorAll('.page-section').forEach(section => {
section.classList.remove('active');
});
// Show target section
const target = document.getElementById(pageId);
if (target) {
target.classList.add('active');
window.scrollTo(0, 0);
} else {
// Fallback to home
document.getElementById('home').classList.add('active');
}
// Update Nav Active State (works inside Shadow DOM via querySelector logic below)
this.updateNavState(pageId);
// Load specific page data if needed
if (pageId === 'services') this.loadServices();
if (pageId === 'projects') this.loadProjects();
// Close mobile menu if open
this.closeMobileMenu();
}
updateNavState(activeId) {
// Since header is shadow DOM, we need to access it specifically
const header = document.querySelector('custom-header');
if (header && header.shadowRoot) {
const links = header.shadowRoot.querySelectorAll('.nav-link');
links.forEach(link => {
if (link.getAttribute('data-target') === activeId) {
link.classList.add('active');
} else {
link.classList.remove('active');
}
});
}
}
closeMobileMenu() {
const header = document.querySelector('custom-header');
if (header && header.shadowRoot) {
const mobileMenu = header.shadowRoot.querySelector('.mobile-menu');
if (mobileMenu) {
mobileMenu.classList.remove('open');
}
}
}
async loadServices() {
const grid = document.getElementById('services-grid');
if (!grid) return;
// Check if already loaded to prevent re-render
if (grid.children.length > 1 && !grid.querySelector('.spinner')) return;
// Simulate API delay
grid.innerHTML = '<div class="col-span-full flex justify-center py-10"><div class="spinner"></div></div>';
setTimeout(() => {
grid.innerHTML = servicesData.map(service => `
<div class="bg-white rounded-xl shadow-lg overflow-hidden hover:shadow-2xl transition duration-300 group flex flex-col">
<div class="h-48 overflow-hidden relative">
<img src="${service.image}" alt="${service.title}" class="w-full h-full object-cover group-hover:scale-110 transition duration-500">
<div class="absolute inset-0 bg-black/20 group-hover:bg-black/10 transition"></div>
</div>
<div class="p-8 flex-grow flex flex-col">
<div class="w-12 h-12 bg-primary-100 rounded-lg flex items-center justify-center text-primary-600 mb-4">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
${this.getIconPath(service.icon)}
</svg>
</div>
<h4 class="text-xl font-bold mb-2">${service.title}</h4>
<p class="text-gray-600 mb-4 flex-grow">${service.desc}</p>
<button onclick="window.router.navigate('contact')" class="text-secondary-600 font-bold hover:text-secondary-800 inline-flex items-center self-start cursor-pointer bg-transparent border-0 p-0">
Request Service
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="ml-1"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
</button>
</div>
</div>
`).join('');
}, 600);
}
getIconPath(iconName) {
const icons = {
'log-out': '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>',
'disc': '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/>',
'scissors': '<circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><line x1="20" y1="4" x2="8.12" y2="15.88"/><line x1="14.47" y1="14.48" x2="20" y2="20"/><line x1="8.12" y1="8.12" x2="12" y2="12"/>',
'alert-triangle': '<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>',
'map': '<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/>',
'anchor': '<circle cx="12" cy="5" r="3"/><line x1="12" y1="22" x2="12" y2="8"/><path d="M5 12H2a10 10 0 0 0 20 0h-3"/>'
};
return icons[iconName] || icons['log-out'];
}
loadProjects() {
const grid = document.getElementById('gallery-grid');
if (!grid) return;
if (grid.children.length > 0) return;
const html = projectsData.map(proj => `
<div class="relative group overflow-hidden rounded-lg cursor-pointer aspect-[4/3]">
<img src="${proj.img}" alt="${proj.title}" class="w-full h-full object-cover transition duration-500 group-hover:scale-110">
<div class="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent opacity-0 group-hover:opacity-100 transition duration-300 flex flex-col justify-end p-6">
<h4 class="text-white font-bold text-xl">${proj.title}</h4>
<p class="text-gray-300 text-sm flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-1"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
${proj.location}
</p>
</div>
</div>
`).join('');
grid.innerHTML = html;
}
}
// Initialize Router when DOM is ready
let router;
document.addEventListener('DOMContentLoaded', () => {
router = new Router();
// Expose to window for onclick handlers
window.router = router;
}); |