All files core.js

68.64% Statements 81/118
62.79% Branches 27/43
56.25% Functions 18/32
71.17% Lines 79/111

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277                32x 32x 32x 32x 32x 32x 32x 32x           4x 4x 2x 2x   2x 2x 2x           2x             4x 2x   2x       4x         4x 4x           1x           5x                               5x 5x 5x                                                 5x 5x       5x     5x 5x 5x 5x     5x     5x         5x 5x 5x 5x                           4x 4x     3x 3x 6x 5x         3x     3x 3x 3x     3x 3x       3x     3x 2x 2x           2x 2x 2x     2x             15x 15x 5x                                                           1x             4x 4x   4x   3x 3x 3x 3x     3x 1x       3x               3x 2x 2x             1x     1x          
/* CORE.JS - Core Application Initialization ============================================
   Copyright 2025, Mark Forscher */
/* ======================================================================================= */
 
import { Utils } from './utils.js';
 
export class Core {
  constructor() {
    this.preloaderProgress = 0;
    this.preloaderTargets = [];
    this.targetCompletionPercentage = 65;
    this.preloaderStartTime = null;
    this.minDisplayTime = 0;
    this.animationInterval = null;
    this.contentReady = false;
    this.displayedProgress = 0;
  }
 
  // Detect WebP browser support and add class to html element
  detectWebPSupport() {
    // Quick synchronous check using canvas
    const canvas = document.createElement('canvas');
    if (canvas.getContext && canvas.getContext('2d')) {
      canvas.width = canvas.height = 1;
      const supportsWebP = canvas.toDataURL('image/webp').indexOf('image/webp') === 0;
 
      if (!supportsWebP) {
        document.documentElement.classList.add('no-webp');
        console.warn('WebP not supported - using fallback images');
      } else E{
        document.documentElement.classList.add('webp');
      }
    } else {
      // Fallback for browsers without canvas support
      document.documentElement.classList.add('no-webp');
    }
  }
 
  // Bind global event listeners
  bindEvents() {
    // DOM content loaded
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', () => this.onDOMReady());
    } else {
      this.onDOMReady();
    }
 
    // Window load event
    window.addEventListener('load', () => this.onWindowLoad());
  }
 
  // DOM ready handler
  onDOMReady() {
    this.completePreloaderTarget('dom');
    this.trackAboveFoldImageLoading();
  }
 
  // Window load handler
  onWindowLoad() {
    // Fallback: complete above-fold images if not already done
    this.completePreloaderTarget('above-fold-images');
  }
 
  // Track above-the-fold image loading progress
  trackAboveFoldImageLoading() {
    // Focus on images that are likely above the fold or critical
    const aboveFoldImages = Array.from(document.querySelectorAll('img')).filter(img => {
      const rect = img.getBoundingClientRect();
      const windowHeight = window.innerHeight;
 
      // Include images that are:
      // 1. Currently visible or within 200px of viewport
      // 2. Have critical classes/attributes
      // 3. Are in hero/header sections
      return (
        rect.top < windowHeight + 200 ||
        img.closest('header, .hero, .masthead, .intro, .page-intro') ||
        img.classList.contains('hero', 'featured', 'priority') ||
        img.hasAttribute('loading') && img.getAttribute('loading') === 'eager'
      );
    });
 
    Eif (aboveFoldImages.length === 0) {
      this.completePreloaderTarget('above-fold-images');
      return;
    }
 
    let loadedImages = 0;
    const totalImages = aboveFoldImages.length;
 
    const checkImageProgress = () => {
      loadedImages++;
      if (loadedImages >= totalImages) {
        this.completePreloaderTarget('above-fold-images');
      }
    };
 
    aboveFoldImages.forEach(img => {
      if (img.complete) {
        checkImageProgress();
      } else {
        img.addEventListener('load', checkImageProgress);
        img.addEventListener('error', checkImageProgress); // Count errors as "loaded"
      }
    });
  }
 
  // Minimal preloader with smooth number animation
  initPreloader() {
    this.preloaderProgress = 0;
    this.preloaderTargets = [
      { name: 'dom', weight: 25, completed: false },
      { name: 'above-fold-images', weight: 40, completed: false }
    ];
    this.targetCompletionPercentage = 65; // Complete at 65% instead of 100%
 
    // Initialize preloader display
    this.preloaderStartTime = Date.now();
    this.minDisplayTime = 0; // No artificial delay
    this.animationInterval = null;
    this.contentReady = false;
 
    // Prevent body scrolling during preloader
    document.body.style.overflow = 'hidden';
 
    // Start progress tracking and animation
    this.startPreloaderAnimation();
  }
 
  // Start smooth number animation
  startPreloaderAnimation() {
    const percentageText = Utils.$1('#preloader-percentage');
    Eif (!percentageText) {
      setTimeout(() => this.startPreloaderAnimation(), 100);
      return;
    }
 
    this.displayedProgress = 0;
    this.updatePreloaderDisplay();
 
    // Quick increment animation - updates every 50ms
    this.animationInterval = setInterval(() => {
      this.updatePreloaderDisplay();
    }, 50);
  }
 
  // Update the displayed percentage with smooth animation
  updatePreloaderDisplay() {
    const percentageText = Utils.$1('#preloader-percentage');
    if (!percentageText) return;
 
    // Calculate target progress based on completed targets
    let actualProgress = 0;
    this.preloaderTargets.forEach(target => {
      if (target.completed) {
        actualProgress += target.weight;
      }
    });
 
    // Scale progress to show 0-100% but complete at our target percentage
    const scaledTargetProgress = Math.min((actualProgress / this.targetCompletionPercentage) * 100, 100);
 
    // Animate towards target with quick increments
    Eif (this.displayedProgress < scaledTargetProgress) {
      const increment = Math.max(1, Math.ceil((scaledTargetProgress - this.displayedProgress) / 8));
      this.displayedProgress = Math.min(this.displayedProgress + increment, scaledTargetProgress);
 
      // Add updating class for micro-animation
      percentageText.classList.add('updating');
      setTimeout(() => percentageText.classList.remove('updating'), 50);
    }
 
    // Update display
    percentageText.textContent = `${Math.round(this.displayedProgress)}%`;
 
    // Complete loading when we reach our target or 100%
    if (actualProgress >= this.targetCompletionPercentage || this.displayedProgress >= 100) {
      clearInterval(this.animationInterval);
      this.completePreloaderAnimation();
    }
  }
 
  // Complete preloader with slide-up animation
  completePreloaderAnimation() {
    const percentageText = Utils.$1('#preloader-percentage');
    Eif (percentageText) {
      percentageText.classList.add('slide-up');
    }
 
    setTimeout(() => {
      this.hidePreloader();
    }, 100); // Match slide-up animation duration
  }
 
  // Mark a preloader target as completed
  completePreloaderTarget(targetName) {
    const target = this.preloaderTargets.find(t => t.name === targetName);
    if (target && !target.completed) {
      target.completed = true;
      // Animation will automatically pick up the change
    }
  }
 
  // Hide preloader and start content loading sequence
  hidePreloader() {
    const preloader = Utils.$1('#preloader');
    if (!preloader) return;
 
    // Ensure minimum display time for smooth UX
    const elapsed = Date.now() - this.preloaderStartTime;
    const remainingTime = Math.max(0, this.minDisplayTime - elapsed);
 
    setTimeout(() => {
      preloader.classList.add('loading-complete');
 
      setTimeout(() => {
        preloader.style.display = 'none';
        document.body.style.overflow = 'visible';
 
        // Start content loading sequence
        this.startContentLoadingSequence();
      }, 200); // Match CSS transition duration
    }, remainingTime);
  }
 
  // Start the content loading sequence after preloader
  startContentLoadingSequence() {
    // Restore body scrolling
    document.body.style.overflow = 'visible';
 
    // The existing fade-in system will handle viewport-based animations
  }
 
  // Show preloader for page transitions
  showPreloaderForTransition() {
    const preloader = Utils.$1('#preloader');
    const percentageText = Utils.$1('#preloader-percentage');
 
    if (preloader) {
      // Reset classes and display
      preloader.classList.remove('loading-complete', 'page-transition-out');
      preloader.style.display = 'block';
      preloader.style.opacity = '0';
      document.body.style.overflow = 'hidden';
 
      // Hide percentage for transitions
      if (percentageText) {
        percentageText.style.opacity = '0';
      }
 
      // Quick fade in
      setTimeout(() => {
        preloader.classList.add('page-transition-in');
      }, 10);
    }
  }
 
  // Detect hover capability
  detectHoverCapability() {
    window.addEventListener('mouseover', function onFirstHover() {
      window.USER_CAN_HOVER = true;
      window.removeEventListener('mouseover', onFirstHover);
    }, { once: true });
  }
 
  // Navigate with page transition animation
  navigateWithPreloader(href) {
    // Show transition preloader
    this.showPreloaderForTransition();
 
    // Navigate after fade-in completes
    setTimeout(() => {
      window.location.href = href;
    }, 200); // Match transition timing
  }
}