PageSourceSearch

https://www.wmaviation.com/web/components.js

js wmaviation.com collected 2026-10-10 00:58:29 UTC 14,887 bytes, 409 lines download raw bytes

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.