/* update variables when header is on dark background*/
.on-dark-bg {
   --header-bg: var(--current-header-bg);
   --chevron-clr: white;
   --menu-item-clr: white;
   --menu-item-hover-bg: #172235;
   --menu-item-hover-clr: #bc92e6;
   --menu-toggle-clr: var(--menu-item-clr);
   --dropdown-content-border: solid 1px rgb(220 220 220 / 20%);
   --mobile-menu-bg: var(--header-bg);
 }

 .on-light-bg {
   --menu-item-clr: black;
   /* update variables when header is on light background*/
 }


 /* update variable when on dark or light background */
 .on-dark-bg,
 .on-light-bg {
   --overlay-header-bg-active: var(--current-header-bg);
   --dropdown-content-bg: var(--current-header-bg);
 }

 .on-video-section {
   /* update variables when header is on video section*/
   --menu-item-clr: white;
   --chevron-clr: white;
   --menu-item-hover-bg: rgb(0 0 0 / 30%);
   --menu-item-hover-clr: #bc92e6;
   --dropdown-content-bg: rgb(0 0 0 / 70%);
   --overlay-header-bg: rgb(0 0 0 / 25%);
   --overlay-header-bg-active: var(--dropdown-content-bg);
   --dropdown-content-border: solid 1px rgb(220 220 220 / 20%);
   --current-header-bg: rgb(0 0 0 / 14%) !important;
   --header-bg: rgb(0 0 0 / 70%);
   --menu-toggle-clr: white;
   --mobile-menu-bg: rgb(0 0 0 / 95%);
   --menu-cta-border: var(--dropdown-content-border)
 }


 .on-hero-section {
   /* update variables when header is on hero section*/
 }

 /* header bg blure when on video section */
 html:not(.dwc-mobile) .dwc-nest-header.on-video-section {
   backdrop-filter: blur(var(--overlay-header-blur));
 }

 /* overlay header border when on video section or dark background*/
 .dwc-nest-header:is(.on-dark-bg, .on-video-section)[data-overlay-header="true"] {
   border: 1px solid rgba(255, 255, 255, 0.2);
 }

 /* header bottom border when on dark background or video section*/
 .dwc-nest-header:is(.on-dark-bg, .on-video-section) {
   border-bottom: 1px solid rgba(255, 255, 255, 0.2);
 }


 /* logo color when on dark background*/
 .on-dark-bg .dwc-nest-menu__logo path {
   fill: white;
 }

 /* logo color when on light background*/
 .on-dark-bg .dwc-nest-menu__logo path {
   /*define svg fill when logo is on light background */
 }

 /* logo color when on video background*/
 .on-video-section .dwc-nest-menu__logo path {
   fill: white;
 }

/*custom */

.on-dark-bg .mega-menu-magic__menu-icon-large{
   background-color: var(--current-header-bg);
    fill: #bc92e6;
    color: #bc92e6;
}

.on-video-section .mega-menu-magic__menu-icon-large{
   background-color: var(--current-header-bg);
    fill: #bc92e6;
    color: #bc92e6;
}

.on-video-section .mega-menu-magic__dropdown-title,
.on-dark-bg .mega-menu-magic__dropdown-title{
    color: rgb(210 210 210);   
}
// Constants
const BREAKPOINT_DESKTOP = 0;
const DEBOUNCE_DELAY = 50;

// Configuration object for all header-related selectors
const HEADER_CONFIG = {
  // Main header element
  headerElement: '.dwc-nest-header',
  // Wrapper element selectors (can be classes, IDs, or data attributes)
  wrapperSelectors: [
    // '.dwc-nest-header__container'
  ],
  // Elements to monitor for header styling (can include any CSS selector)
  // Default monitors sections, but you can add footer, divs, or any element
  monitoredElements: 'section, [data-header-zone]',
  // Store original header background color
  originalHeaderBgColor: null
};

// Unified section type configuration
// Add new data attributes here - they will be automatically mapped to header classes
const SECTION_TYPES = {
  'data-hero': 'on-hero-section',
  'data-video': 'on-video-section',
  'data-custom': 'on-custom-section',
  'data-sticky': 'on-media-section'
  // Add new: 'data-feature': 'on-feature-section'
};

// Configuration for excluded sections
const EXCLUSION_CONFIG = {
  // Data attribute that identifies sections to exclude from header styling
  sectionAttribute: 'data-exclude-header'
};

// Text color classes
const TEXT_CLASSES = {
  light: 'on-dark-bg',
  dark: 'on-light-bg'
};

// Generate classes to remove dynamically from SECTION_TYPES and text classes
const ALL_CLASSES_TO_REMOVE = [
  // Add all header classes from section types
  ...Object.values(SECTION_TYPES),
  // Add text color classes
  TEXT_CLASSES.light,
  TEXT_CLASSES.dark
];

// Performance optimization: Cache DOM elements and computed values
const CACHE = {
  headerElement: null,
  wrapperElement: null,
  sections: [],
  sectionsData: new Map(),
  colorCache: new Map(),
  tempColorElement: null,
  lastKnownSection: null,
  isUpdating: false,
  pendingColorUpdate: false
};

// Initialize cache
function initializeCache() {
  // Cache header elements
  CACHE.headerElement = document.querySelector(HEADER_CONFIG.headerElement);
  
  // Try each wrapper selector until one is found
  for (const selector of HEADER_CONFIG.wrapperSelectors) {
    CACHE.wrapperElement = document.querySelector(selector);
    if (CACHE.wrapperElement) break;
  }
  
  // Cache sections and their positions
  CACHE.sections = Array.from(document.querySelectorAll(HEADER_CONFIG.monitoredElements));
  
  // Create reusable element for color calculations
  CACHE.tempColorElement = document.createElement('div');
  CACHE.tempColorElement.style.position = 'absolute';
  CACHE.tempColorElement.style.visibility = 'hidden';
  CACHE.tempColorElement.style.pointerEvents = 'none';
  document.body.appendChild(CACHE.tempColorElement);
  
  // Cache section metadata
  updateSectionsCache();
}

// Update sections cache (call when DOM changes)
function updateSectionsCache() {
  CACHE.sections = Array.from(document.querySelectorAll(HEADER_CONFIG.monitoredElements));
  CACHE.sectionsData.clear();
  
  CACHE.sections.forEach((section, index) => {
    const sectionData = {
      index,
      isExcluded: section.hasAttribute(EXCLUSION_CONFIG.sectionAttribute)
    };
    
    // Dynamically check for all section types defined in SECTION_TYPES
    Object.keys(SECTION_TYPES).forEach(attribute => {
      sectionData[attribute] = section.hasAttribute(attribute);
    });
    
    CACHE.sectionsData.set(section, sectionData);
  });
}

// Function to remove all section classes from :root only
function removeAllSectionClasses() {
  const rootElement = document.documentElement;
  
  // Remove classes from :root only
  rootElement.classList.remove(...ALL_CLASSES_TO_REMOVE);
}

// Function to update the CSS variable on :root
function updateRootBackgroundVariable(color) {
  document.documentElement.style.setProperty('--current-header-bg', color);
}

// Function to apply classes to :root only
function applyClassesToRoot(headerClass, textClass) {
  const rootElement = document.documentElement;
  
  // Apply header class to :root only
  if (headerClass) {
    rootElement.classList.add(headerClass);
  }
  
  // Apply text class to :root only
  if (textClass) {
    rootElement.classList.add(textClass);
  }
}

// Optimized color conversion using cached temp element
function getRGBValues(color) {
  const cacheKey = color;
  if (CACHE.colorCache.has(cacheKey)) {
    return CACHE.colorCache.get(cacheKey);
  }
  
  CACHE.tempColorElement.style.color = color;
  const computedColor = window.getComputedStyle(CACHE.tempColorElement).color;
  
  // Extract RGB values
  const match = computedColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
  const result = match ? {
    r: parseInt(match[1]),
    g: parseInt(match[2]),
    b: parseInt(match[3])
  } : { r: 255, g: 255, b: 255 };
  
  // Cache the result
  CACHE.colorCache.set(cacheKey, result);
  return result;
}

// Function to check if a background color is transparent or empty
function isBackgroundTransparent(color) {
  if (!color) return true;
  
  const transparentValues = [
    'transparent',
    'rgba(0, 0, 0, 0)',
    'rgba(0,0,0,0)',
    'initial',
    'inherit',
    ''
  ];
  
  return transparentValues.includes(color.toLowerCase().replace(/\s/g, ''));
}

// Function to determine if a color is dark or light
function isColorDark(color) {
  if (color === 'transparent' || color === 'rgba(0, 0, 0, 0)') {
    return false;
  }
  
  const rgb = getRGBValues(color);
  const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
  return luminance < 0.5;
}

// Optimized section detection using cached data
function getCurrentSection() {
  if (!CACHE.headerElement || CACHE.sections.length === 0) return null;
  
  const headerHeight = CACHE.headerElement.offsetHeight;
  
  // Quick check if we're still in the same section
  if (CACHE.lastKnownSection) {
    const rect = CACHE.lastKnownSection.getBoundingClientRect();
    if (rect.top <= headerHeight && rect.bottom > headerHeight) {
      return CACHE.lastKnownSection;
    }
  }
  
  // Binary search would be ideal here, but simple loop is sufficient for most cases
  for (const section of CACHE.sections) {
    const rect = section.getBoundingClientRect();
    if (rect.top <= headerHeight && rect.bottom > headerHeight) {
      CACHE.lastKnownSection = section;
      return section;
    }
  }
  
  const firstSection = CACHE.sections[0] || null;
  CACHE.lastKnownSection = firstSection;
  return firstSection;
}

// Function to get computed background color with caching
function getComputedBackgroundColor(element) {
  const cacheKey = element;
  if (CACHE.colorCache.has(cacheKey)) {
    return CACHE.colorCache.get(cacheKey);
  }
  
  const bgColor = window.getComputedStyle(element).backgroundColor;
  CACHE.colorCache.set(cacheKey, bgColor);
  return bgColor;
}

// Function to initialize original header background color
function initializeOriginalHeaderBgColor() {
  if (CACHE.headerElement) {
    HEADER_CONFIG.originalHeaderBgColor = window.getComputedStyle(CACHE.headerElement).backgroundColor;
  }
}

// Function to determine header style configuration based on section
function determineHeaderConfig(section) {
  if (!section) return null;
  
  const sectionData = CACHE.sectionsData.get(section);
  if (!sectionData) return null;
  
  // If section is excluded, return null to skip styling
  if (sectionData.isExcluded) {
    return null;
  }
  
  // Dynamically check for any section type defined in SECTION_TYPES
  for (const [attribute, headerClass] of Object.entries(SECTION_TYPES)) {
    if (sectionData[attribute]) {
      return {
        backgroundColor: null,
        headerClass: headerClass,
        needsWhiteText: null,
        isSpecialSection: true
      };
    }
  }
  
  // Get the actual background color of the section
  const sectionBgColor = getComputedBackgroundColor(section);
  
  if (isBackgroundTransparent(sectionBgColor)) {
    return {
      backgroundColor: HEADER_CONFIG.originalHeaderBgColor || 'rgb(255, 255, 255)',
      headerClass: null,
      needsWhiteText: isColorDark(HEADER_CONFIG.originalHeaderBgColor || 'rgb(255, 255, 255)'),
      isSpecialSection: false
    };
  }
  
  const needsWhiteText = isColorDark(sectionBgColor);
  
  return {
    backgroundColor: sectionBgColor,
    headerClass: null,
    needsWhiteText: needsWhiteText,
    isSpecialSection: false
  };
}

// Function to apply styles (background color to header, classes to :root only)
function applyHeaderStyles(config) {
  if (!CACHE.headerElement || !config) return;
  
  const elementsToStyle = [CACHE.headerElement, CACHE.wrapperElement].filter(Boolean);
  
  // Use requestAnimationFrame for smooth visual updates
  requestAnimationFrame(() => {
    // First, remove all section classes and text classes from :root only
    removeAllSectionClasses();
    
    // Update the root CSS variable with background color
    if (config.backgroundColor !== null) {
      const appliedBgColor = config.backgroundColor || '';
      updateRootBackgroundVariable(appliedBgColor);
    } else {
      updateRootBackgroundVariable('');
    }
    
    // Determine text class to apply
    const textClass = config.needsWhiteText !== null 
      ? (config.needsWhiteText ? TEXT_CLASSES.light : TEXT_CLASSES.dark)
      : null;
    
    // Apply classes to :root only
    applyClassesToRoot(config.headerClass, textClass);
    
    // Handle background color on header elements only
    elementsToStyle.forEach(element => {
      if (config.backgroundColor !== null) {
        const appliedBgColor = config.backgroundColor || '';
        element.style.backgroundColor = appliedBgColor;
      } else {
        element.style.backgroundColor = '';
      }
    });
    
    // For special sections, allow CSS to apply first, then get computed background color
    if (config.isSpecialSection && config.headerClass) {
      // Use setTimeout(0) to ensure CSS has been applied, but batch multiple calls
      if (!CACHE.pendingColorUpdate) {
        CACHE.pendingColorUpdate = true;
        setTimeout(() => {
          // Process all elements that need color updates in this batch
          elementsToStyle.forEach(el => {
            // Check if element has any of the section classes (though they're not applied anymore)
            const hasSectionClass = Object.values(SECTION_TYPES).some(className => 
              el.classList.contains(className)
            );
            
            if (hasSectionClass) {
              const computedBgColor = getComputedBackgroundColor(el);
              updateRootBackgroundVariable(computedBgColor);
            }
          });
          CACHE.pendingColorUpdate = false;
        }, 50);
      }
    }
    
    CACHE.isUpdating = false;
  });
}

// Throttled update function using requestAnimationFrame
function updateHeaderStyles() {
  // Prevent multiple updates in the same frame
  if (CACHE.isUpdating) return;
  
  // Apply only on screens larger than the breakpoint
  if (window.innerWidth <= BREAKPOINT_DESKTOP) {
    return;
  }
  
  CACHE.isUpdating = true;
  
  const currentSection = getCurrentSection();
  if (!currentSection) {
    CACHE.isUpdating = false;
    return;
  }
  
  const headerConfig = determineHeaderConfig(currentSection);
  if (!headerConfig) {
    CACHE.isUpdating = false;
    return;
  }
  
  applyHeaderStyles(headerConfig);
}

// Debounce function for resize events (less frequent)
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// RAF-based throttle for scroll events
let rafId = null;
function throttleWithRAF(func) {
  return function(...args) {
    if (rafId) return;
    
    rafId = requestAnimationFrame(() => {
      func.apply(this, args);
      rafId = null;
    });
  };
}

// Initialize everything
function initialize() {
  initializeCache();
  initializeOriginalHeaderBgColor();
  updateHeaderStyles();
}

// Cleanup function
function cleanup() {
  if (CACHE.tempColorElement && CACHE.tempColorElement.parentNode) {
    CACHE.tempColorElement.parentNode.removeChild(CACHE.tempColorElement);
  }
  if (rafId) {
    cancelAnimationFrame(rafId);
  }
}

// Initialize on page load
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', initialize);
} else {
  initialize();
}

// Create optimized event handlers
const throttledUpdateHeaderStyles = throttleWithRAF(updateHeaderStyles);
const debouncedUpdateHeaderStyles = debounce(() => {
  updateSectionsCache(); // Refresh cache on resize
  updateHeaderStyles();
}, DEBOUNCE_DELAY);

// Add event listeners with passive option for better performance
window.addEventListener('scroll', throttledUpdateHeaderStyles, { passive: true });
window.addEventListener('resize', debouncedUpdateHeaderStyles, { passive: true });

// Cleanup on page unload
window.addEventListener('beforeunload', cleanup);

Case studies

Precor vs. Peloton: Commercial Cardio Compared

Choosing between Precor and Peloton isn’t about picking one, it’s about building the right cardio experience. Precor delivers the durability, variety, and long-term performance gyms rely on, while Peloton brings the energy, content, and motivation that keeps members coming back.

modern and clean gym set up

It is no secret that the fitness equipment landscape has changed. Today’s exercisers expect reliable, high-end equipment alongside experiences that motivate them, keep them engaged, and make them want to come back tomorrow. Gym owners and managers are faced with the question: “How do we build a future-proof cardio floor that meets every exerciser where they are?” The ideal solution often includes a combination of Precor and Peloton equipment. 

gym equipment in use

Let’s explore these brands, which equipment mix is right for your gym, and how they can work together to give your exercisers the best of both worlds. 

Key Takeaways

  • Precor is known for reliability. With 40+ years of commercial manufacturing behind it, Precor delivers the durability, variety, and resale value that facilities depend on.
  • Peloton drives engagement like nothing else. Its connected fitness platform (live classes, leaderboards, world-class instructors) keeps exercisers motivated and coming back.
  • You don’t have to choose. Precor and Peloton serve different but complementary purposes. The strongest cardio floors include both.
  • The right mix depends on your facility. Factors like member demographics, facility type, budget, and floor layout all play a role in finding the ideal balance.

Precor Overview

Precor has been setting the standard in commercial fitness for more than 40 years. Across the globe, hotels, universities, corporate wellness centers, and other sectors have trusted Precor, often in part because they have earned a strong reputation for reliability. 

Built on a foundation of biomechanical precision and commercial-grade engineering, Precor delivers long-term reliability and uncompromising design.

gym in use

Peloton Overview

Peloton came along and changed what exercisers expected from a cardio workout. By combining premium hardware with immersive instructor-led content and a passionate global community, Peloton essentially invented the interactive fitness experience as we know it today. For those driven by live classes, leaderboards, and real-time performance data, Peloton delivers an unmatched personalized and connected fitness experience.

Gym set up

What’s in the Lineup? Equipment Breakdown

Put Peloton and Precor together, and you’ve got the full picture of what modern commercial cardio can be.

Precor equipment covers virtually every cardio modality a facility needs:

  • Treadmills
  • Ellipticals
  • AMTs (Adaptive Motion Trainers)
  • Bikes
  • Climbers
gym in use

Precor equipment is intuitive and engineered specifically for commercial environments, meaning it is built to handle high traffic and heavy use over many years.

Peloton’s new commercial lineup centers on its signature connected equipment, perfect for withstanding the demands of commercial spaces:

  • Peloton Bike+ Pro
  • Peloton Tread+ Pro
  • Peloton Row+ Pro
peloton gym line

Each Peloton product is built around a screen-forward experience, putting thousands of live and on-demand classes directly in front of your exercisers every time they step on the equipment.

Where Precor Excels

Reliability you can count on. Precor equipment is designed from the ground up for commercial use, which means higher uptime, fewer service calls, and machines that keep performing under constant daily use. For busy facilities, that dependability is everything.

Lower total cost of ownership. Commercial-grade construction means less maintenance over time. Fewer repairs, less downtime, and a longer useful life all add up to real savings across the life of your equipment.

Intuitive, exerciser-first design philosophy. Precor prioritizes designs that are instinctive by nature, removing friction so exercisers can focus on what is most important to them.

fitness equipment in use

Something for everyone. With treadmills, ellipticals, AMTs, bikes, and climbers all under one brand, Precor helps you build a diverse cardio floor. That variety matters when you’re serving exercisers with different goals, fitness levels, and mobility needs.

Strong resale and trade-in value. Cardio equipment typically turns over every three to five years as technology evolves. When it comes time for a refresh, Precor holds its value better than any other commercial cardio brand. 

Where Peloton Excels

Engagement that drives retention. Peloton’s platform (world-class instructors, live classes, a global member community) turns cardio from something exercisers feel like they have to do into something they genuinely look forward to. That kind of motivation is one of the most effective retention tools a gym can offer.

A brand the masses already love. Peloton’s consumer recognition is unmatched in the fitness industry. Putting Peloton on your floor sends a clear message: this facility invests in a premium experience. For many prospective gym-goers, that’s a deciding factor.

Gym set up

Performance data that keeps people pushing. Live metrics, output tracking, and competitive leaderboards give Peloton users a level of accountability that keeps them coming back, and working harder when they get there. For data-driven exercisers, this is the standard they expect.

A real differentiator. In a crowded market, Peloton helps your gym stand out. It creates an experience exercisers talk about, share, and seek out.

Why Precor and Peloton Work Better Together

Think of Precor and Peloton as two layers of a complete cardio strategy. They are not competing choices, but complementary ones.

modern gym
  • Precor anchors the floor with variety, durability, and equipment that serves every type of exerciser, day in and day out
  • Peloton adds the energy layer, the content, community, and brand recognition that keeps exercisers engaged and excited
  • Together, they cover the full spectrum: low-impact options and high-intensity experiences, long-term reliability and cutting-edge technology

No single brand can deliver all of that on its own.

Finding the Right Mix for Your Facility

The ideal balance of Precor and Peloton will look different depending on your facility. A few things worth thinking through:

gym rendering
  • Facility type: Hotels and corporate wellness centers often use a mix of Precor and Peloton brands; however, they may favor Precor when faced with limited space or budget thanks to its reliability and variety across a broad user base. Boutique gyms, country clubs, and other fitness-forward facilities may lean more into Peloton to create a signature experience.
  • Member demographics: Who are your exercisers, and what motivates them? Tech-savvy exercisers will flock toPeloton’s content platform, and people from all backgrounds can benefit from Precor’s range of intuitive training options.
  • Space and layout: A well-designed floor can integrate both brands seamlessly. Many facilities create a dedicated connected fitness zone for Peloton while Precor fills out the broader cardio floor.

Finding the Right Mix of Cardio for Your Gym

The strongest commercial cardio floors are built around the needs of their exercisers across brands. Precor and Peloton each bring something distinct to the table, and when paired together, they create an environment that serves every type of exerciser while protecting your investment for years to come.

At ASF, we help you design the right mix for your space, your exercisers, and your goals. From comprehensive gym planning, equipment installation, customization, financing, and maintenance, our team is here to help. Contact us to get started today!

Frequently Asked Questions

Can I have both Precor and Peloton equipment in the same facility? 

Absolutely, and in most cases, we’d recommend it. Precor and Peloton complement each other well. Precor covers your cardio floor with variety and durability, while Peloton creates a premium, content-driven zone that appeals to a different kind of exerciser. Many of our most successful facility installs feature both brands side by side.

Is Peloton content included with the commercial equipment purchase? 

Peloton’s commercial offering operates on a subscription model for access to its full content library. Your ASF rep can walk you through the current pricing and package options to help you factor that into your budget planning.

How often does cardio equipment need to be replaced? 

Most facilities plan to refresh their cardio equipment every three to five years as technology evolves and wear accumulates. This is one area where Precor really shines. It holds its resale and trade-in value better than other commercial cardio brands, which helps offset the cost of your next equipment cycle. Read our complete guide to fitness equipment replacement here

How do I figure out the right balance of Precor vs. Peloton for my space? 

That’s exactly what we’re here for. The right mix depends on your facility type, member demographics, available floor space, and budget. Reach out to the ASF team, and we’ll work through it with you. No generic recommendations, just a plan built around your specific needs.

Ready to start your next project? Reach out to the Advantage team today.

Our services

We are ready to support your facility in every phase.

Partner Brands

Explore our collection of partner brands.

All Partner Brands
Case Studies

Examples of our work.

All Case Studies