1/** 2 * WMA Aviation Central Component System 3 * All reusable components in one place for consistency 4 */ 5 6// Typography Components 7const typography = { 8 // Page titles (36px) 9 pageTitle: (text, extraClasses = '') => 10 `<h1 class="text-4xl font-bold text-wma-navy ${extraClasses}">${text}</h1>`, 11 12 // Section headings (30px) 13 sectionHeading: (text, extraClasses = '') => 14 `<h2 class="text-3xl font-bold text-wma-navy ${extraClasses}">${text}</h2>`, 15 16 // Subheadings (20px) 17 subheading: (text, extraClasses = '') => 18 `<h3 class="text-xl font-semibold text-wma-navy ${extraClasses}">${text}</h3>`, 19 20 // Body text (16px) 21 bodyText: (text, extraClasses = '') => 22 `<p class="text-base text-gray-700 leading-relaxed ${extraClasses}">${text}</p>`, 23 24 // Small text (14px) 25 smallText: (text, extraClasses = '') => 26 `<p class="text-sm text-gray-600 ${extraClasses}">${text}</p>`, 27 28 // Gold accent heading 29 goldHeading: (text, extraClasses = '') => 30 `<h3 class="text-xl font-bold text-wma-gold ${extraClasses}">${text}</h3>` 31}; 32 33// Card Components 34const cards = { 35 // Standard info card 36 infoCard: (title, content, icon = null) => ` 37 <div class="bg-white p-6 rounded-lg shadow-lg"> 38 ${icon ? `<div class="mb-4">${icon}</div>` : ''} 39 ${typography.subheading(title, 'mb-4')} 40 ${typography.bodyText(content)} 41 </div> 42 `, 43 44 // Feature card with icon 45 featureCard: (title, content, iconSvg) => ` 46 <div class="text-center"> 47 <div class="h-40 bg-gray-100 rounded-lg mb-4 flex items-center justify-center"> 48 ${iconSvg} 49 </div> 50 ${typography.subheading(title, 'mb-3')} 51 ${typography.bodyText(content, 'text-gray-600')} 52 </div> 53 `, 54 55 // Service card with gradient icon 56 serviceCard: (title, content, iconPath) => ` 57 <div class="bg-white p-6 rounded-lg shadow-lg hover:shadow-xl transition-shadow duration-300"> 58 <div class="w-16 h-16 bg-gradient-to-br from-wma-gold to-yellow-500 rounded-xl flex items-center justify-center mb-4 shadow-lg"> 59 <svg class="w-8 h-8 text-wma-navy" fill="currentColor" viewBox="0 0 24 24"> 60 ${iconPath} 61 </svg> 62 </div> 63 ${typography.subheading(title, 'mb-3')} 64 ${typography.bodyText(content)} 65 </div> 66 `, 67 68 // Stat card 69 statCard: (stat, label, description = '') => ` 70 <div class="text-center"> 71 <div class="text-4xl font-bold text-wma-navy mb-2">${stat}</div> 72 <div class="text-xl font-semibold text-wma-gold mb-2">${label}</div> 73 ${description ? typography.smallText(description) : ''} 74 </div> 75 `, 76 77 // Certification card 78 certCard: (logo, title, subtitle, description) => ` 79 <div class="bg-white p-6 rounded-lg shadow-md text-center"> 80 <div class="mb-4"> 81 <div class="text-3xl font-bold text-wma-navy">${logo}</div> 82 <div class="text-sm text-gray-600">${subtitle}</div> 83 </div> 84 <h3 class="font-semibold text-wma-navy">${title}</h3> 85 ${typography.smallText(description, 'mt-1')} 86 </div> 87 ` 88}; 89 90// Section Components 91const sections = { 92 // Standard content section 93 contentSection: (content, bgColor = 'bg-white', padding = 'py-20') => ` 94 <section class="${padding} ${bgColor}"> 95 <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> 96 ${content} 97 </div> 98 </section> 99 `, 100 101 // Centered CTA section 102 ctaSection: (title, content, buttons, bgColor = 'bg-wma-navy') => ` 103 <section class="py-12 ${bgColor} text-center ${bgColor.includes('navy') ? 'text-white' : ''}"> 104 <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8"> 105 ${title} 106 ${content ? `<div class="mb-6">${content}</div>` : ''} 107 <div class="flex flex-col sm:flex-row justify-center items-center space-y-4 sm:sp
107ace-y-0 sm:space-x-6"> 108 ${buttons} 109 </div> 110 </div> 111 </section> 112 `, 113 114 // Two column section 115 twoColumnSection: (leftContent, rightContent, reverse = false) => ` 116 <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 items-center ${reverse ? 'lg:flex-row-reverse' : ''}"> 117 <div>${leftContent}</div> 118 <div>${rightContent}</div> 119 </div> 120 `, 121 122 // Grid section 123 gridSection: (items, columns = 3) => ` 124 <div class="grid grid-cols-1 md:grid-cols-${columns} gap-8"> 125 ${items.join('')} 126 </div> 127 ` 128}; 129 130// Button Components 131const buttons = { 132 // Primary CTA button 133 primaryButton: (text, href, icon = '') => ` 134 <a href="${href}" class="bg-wma-gold hover:bg-yellow-500 text-wma-navy transform hover:scale-105 px-6 py-3 rounded-md font-bold transition-all duration-300 shadow-lg hover:shadow-xl inline-flex items-center justify-center gap-2"> 135 ${icon}${text} 136 </a> 137 `, 138 139 // Secondary button 140 secondaryButton: (text, href, icon = '') => ` 141 <a href="${href}" class="bg-wma-navy hover:bg-gray-700 text-white px-6 py-3 rounded-md font-semibold transition-colors inline-flex items-center justify-center gap-2"> 142 ${icon}${text} 143 </a> 144 `, 145 146 // Ghost button 147 ghostButton: (text, href, icon = '') => ` 148 <a href="${href}" class="border-2 border-wma-gold text-wma-gold hover:bg-wma-gold hover:text-wma-navy px-6 py-3 rounded-md font-semibold transition-all duration-300 inline-flex items-center justify-center gap-2"> 149 ${icon}${text} 150 </a> 151 ` 152}; 153 154// List Components 155const lists = { 156 // Bullet list with gold dots 157 bulletList: (items) => ` 158 <div class="space-y-4"> 159 ${items.map(item => ` 160 <div class="flex items-start space-x-3"> 161 <div class="bg-wma-gold w-3 h-3 rounded-full mt-1 flex-shrink-0"></div> 162 ${typography.bodyText(item)} 163 </div> 164 `).join('')} 165 </div> 166 `, 167 168 // Icon list 169 iconList: (items) => ` 170 <ul class="space-y-4"> 171 ${items.map(item => ` 172 <li class="flex items-center space-x-3"> 173 <svg class="w-5 h-5 text-wma-gold flex-shrink-0" fill="currentColor" viewBox="0 0 20 20"> 174 <path d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"/> 175 </svg> 176 ${typography.bodyText(item)} 177 </li> 178 `).join('')} 179 </ul> 180 ` 181}; 182 183// Image Components 184const images = { 185 // Rounded image with shadow 186 roundedImage: (src, alt, extraClasses = '') => 187 `<img src="${src}" alt="${alt}" class="w-full rounded-lg shadow-lg ${extraClasses}">`, 188 189 // Cover image 190 coverImage: (src, alt, height = 'h-80') => 191 `<img src="${src}" alt="${alt}" class="w-full ${height} object-cover rounded-lg shadow-lg">`, 192 193 // Logo image 194 logoImage: (src, alt, height = 'h-12') => 195 `<img src="${src}" alt="${alt}" class="${height} w-auto object-contain">`, 196 197 // Fleet aircraft image with toggle functionality 198 fleetImage: (aircraftCode, defaultView = 'exterior', extraClasses = '') => ` 199 <img id="${aircraftCode}-image" 200 alt="${aircraftCode} Aircraft" 201 class="w-full h-full object-cover object-center transition-opacity duration-300 ${extraClasses}"> 202 `, 203 204 // Image with buttons for toggling views 205 imageWithToggle: (imageId, buttonConfigs, extraClasses = '') => ` 206 <div class="relative"> 207 <img id="${imageId}" class="w-full h-full object-cover ${extraClasses}"> 208 <div class="absolute top-2 right-2 space-x-1"> 209 ${buttonConfigs.map(config => 210 `<button onclick="${config.onClick}" class="bg-wma-gold text-wma-navy px-2 py-1 rounded text-sm hover:bg-yellow-400 transition-colors">${config.label}</button>` 211 ).join('')} 212 </div> 213 </div> 214 ` 215}; 216 217// Container Components 218const containers = { 219 // Standard container 220 container: (content, maxWidth = 'max-w-7xl') => 221 `<div class="${maxWidth} mx-auto px-4 sm:px-6 lg:px-8">${content}</div>`, 222 223 // Centered container 224 centeredContainer: (content, maxWidth = 'max-w-4xl') => 225 `<div class="${maxWidth} mx-auto px-4 sm:px-6 lg:px-8 text-center">${content}</div>`, 226 227 // Card container 228 cardContainer: (content, bgColor = 'bg-white', padding = 'p-8') => 229 `<div class="${bgColor} ${padding} rounded-lg shadow-lg">${content}</div>` 230}; 231 232// Layout Components 233const layouts = { 234 // Page header 235 pageHeader: (title, subtitle = '') => ` 236 <div class="text-center mb-12"> 237 ${typography.pageTitle(title, 'mb-6')} 238 ${subtitle ? typography.bodyText(subtitle, 'text-xl text-gray-600 max-w-3xl mx-auto') : ''} 239 </div> 240 `, 241 242 // Section header 243 sectionHeader: (title, subtitle = '') => ` 244 <div class="text-center mb-12"> 245 ${typography.sectionHeading(title, 'mb-6')} 246 ${subtitle ? typography.bodyText(subtitle, 'text-xl text-gray-600 max-w-3xl mx-auto') : ''} 247 </div> 248 ` 249}; 250 251// Iframe Components 252const iframes = { 253 // JetInsight Quote Request Iframe 254 jetInsightQuote: (iframeId = 'quote-iframe', height = '750px', extraClasse
254s = '') => ` 255 <iframe 256 id="${iframeId}" 257 src="https://client.jetinsight.com/embed/cef08f29-e698-4765-8d82-24af0ec91933/Web-Request" 258 class="w-full border-0 rounded-lg shadow-lg ${extraClasses}" 259 style="height: ${height}; min-height: ${height};" 260 title="Request Quote Form" 261 frameborder="0" 262 scrolling="no"> 263 </iframe> 264 `, 265 266 // JetInsight Deals/Empty Legs Iframe 267 jetInsightDeals: (iframeId = 'jetinsight-iframe', extraClasses = '') => ` 268 <iframe 269 id="${iframeId}" 270 src="https://client.jetinsight.com/embed/cef08f29-e698-4765-8d82-24af0ec91933/deals" 271 class="w-full border-0 rounded-lg shadow-lg ${extraClasses}" 272 style="height: 800px; min-height: 800px;" 273 title="Empty Legs Deals" 274 frameborder="0" 275 scrolling="no"> 276 </iframe> 277 `, 278 279 // Initialize iframe message listeners for dynamic height adjustment 280 initializeMessageListeners: () => { 281 // Only add listener once 282 if (window.__wmaIframeListenerInitialized) return; 283 window.__wmaIframeListenerInitialized = true; 284 285 window.addEventListener('message', function(e) { 286 // Check if the message is from JetInsight 287 if (e.origin === 'https://client.jetinsight.com' && e.data) { 288 if (typeof e.data.height === 'number' || typeof e.data.frameHeight === 'number') { 289 // Try to find iframe by common IDs 290 const iframeIds = ['quote-iframe', 'jetinsight-iframe']; 291 292 for (const id of iframeIds) { 293 const iframe = document.getElementById(id); 294 if (iframe) { 295 const newHeight = (e.data.height || e.data.frameHeight) + 'px'; 296 iframe.style.height = newHeight; 297 iframe.style.minHeight = newHeight; 298 } 299 } 300 } 301 } 302 }, false); 303 } 304}; 305 306// Export all components 307const wmaComponents = { 308 typography, 309 cards, 310 sections, 311 buttons, 312 lists, 313 images, 314 containers, 315 layouts, 316 iframes 317}; 318 319// Helper function to render components 320function renderComponent(componentPath, ...args) { 321 const path = componentPath.split('.'); 322 let component = wmaComponents; 323 324 for (const key of path) { 325 component = component[key]; 326 if (!component) { 327 console.error(`Component ${componentPath} not found`); 328 return ''; 329 } 330 } 331 332 return typeof component === 'function' ? component(...args) : component; 333} 334 335// Utility Functions 336const utils = { 337 // Toggle aircraft photo view (exterior/interior) 338 toggleAircraftPhoto: (aircraftCode, viewType) => { 339 const imageElement = document.getElementById(aircraftCode + '-image'); 340 if (imageElement && window.getFleetImage) { 341 const newSrc = window.getFleetImage(aircraftCode, viewType); 342 if (newSrc) { 343 imageElement.src = newSrc; 344 } 345 } 346 }, 347 348 // Initialize page-specific functionality 349 initializePage: (pageName) => { 350 // Load header 351 if (window.loadHeader) { 352 window.loadHeader(pageName); 353 } 354 355 // Initialize image mapping 356 if (window.initializeImageMapping) { 357 window.initializeImageMapping(); 358 } 359 360 // Initialize iframe listeners 361 if (wmaComponents.iframes && wmaComponents.iframes.initializeMessageListeners) { 362 wmaComponents.iframes.initializeMessageListeners(); 363 } 364 }, 365 366 // Generic form handler 367 handleFormSubmission: async (formElement, submitUrl, successCallback, errorCallback) => { 368 try { 369 const formData = new FormData(formElement); 370 const response = await fetch(submitUrl, { 371 method: 'POST', 372 body: formData 373 }); 374 375 if (response.ok) { 376 if (successCallback) successCallback(response); 377 } else { 378 if (errorCallback) errorCallback(response); 379 } 380 } catch (error) { 381 if (errorCallback) errorCallback(error); 382 } 383 }, 384 385 // Smooth scroll to element 386 scrollToElement: (elementId) => { 387 const element = document.getElementById(elementId); 388 if (element) { 389 element.scrollIntoView({ behavior: 'smooth' }); 390 } 391 }, 392 393 // Initialize common event listeners 394 initializeCommonListeners: () => { 395 // Add global click handlers for smooth scrolling 396 document.addEventListener('click', function(e) { 397 if (e.target.matches('a[href^="#"]')) { 398 e.preventDefault(); 399 const targetId = e.target.getAttribute('href').substring(1); 400 utils.scrollToElement(targetId); 401 } 402 }); 403 } 404}; 405 406// Make components and utilities globally available 407window.wmaComponents = wmaComponents; 408window.renderComponent = renderComponent; 409window.wmaUtils = utils;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.