/* 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

Traditional Cardio vs. Connected Cardio: Is Engagement Worth the Cost?

Connected cardio can add energy, variety, and engagement to your gym floor, but it also comes with added costs, subscriptions, and technology needs. Here’s how traditional and connected cardio compare, where each works best, and why the right strategy may include a thoughtful mix of both.

fitness equipment in use

Every facility manager who calls us for a cardio refresh eventually asks the same question: Is connected equipment worth the extra investment, or should we stick with traditional cardio? The answer isn’t as simple as choosing what’s newest or what’s cheapest. It depends on what you need your cardio floor to accomplish.

Traditional equipment may be the right choice if your priority is reliable, straightforward equipment that gets the job done. Connected cardio, on the other hand, can add features like live and on-demand workouts, progress tracking, personalized experiences, and other tools designed to increase engagement.

So the real question isn’t “connected or traditional?” It’s “what do you want your cardio investment to do for your facility?” That’s the question worth answering before you decide where to spend your budget.

Key Takeaways

  • Connected equipment tends to drive the greatest engagement among younger, tech-comfortable users and those who already expect a digital fitness experience.
  • Connected cardio can cost more over time, with added expenses for subscriptions, networking, and service. 
  • Traditional cardio remains the more dependable choice for facilities with tight budgets, heavy equipment turnover, or limited onsite technical support.
  • Connected equipment pays off when paired with reliable wifi and someone responsible for keeping content and software current.
  • The strongest cardio floors we design mix connected pieces in high-visibility spots with dependable traditional units to handle exerciser volume.

What’s the Real Difference Between Traditional and Connected Cardio?

Traditional cardio equipment covers treadmills, bikes, ellipticals, and rowers built around a simple console: heart rate, time, distance, calories, maybe a preset program or two. 

Meanwhile, connected cardio adds a layer of software on top of the underlying machine, turning it into a hub for streaming classes, leaderboards, biometric tracking, and app syncing. Popular commercial-grade brands include Peloton and Precor.

Here’s how the two typically compare on the floor:

  • Content: Traditional equipment offers built-in programs at best. Connected equipment streams live and on-demand classes, often with instructor-led coaching.
  • Data: Traditional consoles track session basics. Connected systems track workout history over time, sync with wearables, and feed data back to exercisers through an app.
  • Community: Traditional equipment is a solo experience. Connected equipment adds leaderboards, group challenges, and sometimes social features tied to a broader online ecosystem.
  • Infrastructure needs: Traditional cardio needs power and floor space. Connected cardio needs these as well as reliable wifi, ongoing software updates, and often a subscription account per unit.
  • Learning curve: Traditional equipment is often universal, while connected equipment may be harder for some exercisers to set up and get started. 

Neither is objectively better designed. They’re built to solve different problems, which is important to consider if you are trying to compare price tags side by side.

How Much More Does Connected Cardio Actually Cost?

The upfront price difference is only part of the picture. When we walk clients through a full cardio budget, these are the line items that tend to get overlooked:

  • Service and maintenance. Connected consoles have more that can go wrong, from software glitches to screen failures, and service contracts for connected equipment tend to run higher than for traditional consoles. This is where it helps to partner with reputable brands, like  Peloton and Precor.
  • Network infrastructure. If your facility doesn’t already have commercial-grade wifi that can support multiple streaming devices at once, that’s a separate upfront project and an ongoing cost. However, having commercial-grade wifi is also becoming a common expectation in facilities. This upgrade could help clear up front desk complaints, minimize exerciser inconveniences, and even help you expand other benefits (from wireless access control to smart recovery bays).
  • Content and subscription fees. Connected equipment often requires a subscription to unlock classes and programming, which adds an ongoing cost. However, you may be able to balance the budget by trimming any extra cardio classes that only have one or two regular attendees, giving these exercisers a connected alternative they may even prefer.
  • Staff training. Someone on your team needs to understand the software well enough to troubleshoot it, update it, and help exercisers get set up. While this is a time cost, it can often be handled by your on-site staff without increasing payroll. Additionally, many of the exercisers attracted to connected fitness equipment are familiar with popular brands (or have the tech-savvy skills to sort it out).

To be fair, traditional equipment has its own hidden costs. Basic consoles fail too, and a facility full of plain treadmills doesn’t do much to differentiate itself from the gym down the street.

Does Connected Equipment Actually Improve Engagement?

This is where a lot of the marketing noise around connected fitness gets ahead of reality. Engagement isn’t just a feeling—it’s measurable through session frequency, workout length, repeat visits, and retention.

What we’ve observed across the facilities we’ve helped design and equip:

  • Connected equipment tends to extend average session length, particularly when class content is fresh, and instructors are engaging.
  • It performs best as a draw for undecided or lapsed exercisers, giving them a reason to try the gym instead of a home workout app.
  • Connected equipment helps facilities add more classes and increase variety without expanding payroll. For example, you would not need to hire someone new to teach a spin class, as Peloton bikes (and their subscriptions) include various instructors. Exercisers also benefit from getting to choose the instructor, workout, and pace that meets them where they are at, creating an elevated overall experience that can drive engagement (and potentially limit front desk complaints).
  • It tends to underperform when the wifi is unreliable, the content library goes stale, or nobody on staff promotes it. In those cases, the expensive machine becomes a very nice-looking treadmill that nobody streams anything on.
  • Traditional equipment doesn’t drive the same excitement, but it also doesn’t disappoint. A well-maintained traditional cardio floor sees consistent, dependable use year over year without much intervention.

Engagement is real, but it’s not automatic. It has to be supported in your operations.

Which Gyms Need Connected Cardio?

Connected equipment tends to earn its keep fastest in facilities where differentiation and exerciser experience are part of the business model, including:

Who Should Stick with Traditional Cardio?

Connected cardio isn’t the right call everywhere, and there’s no shame in choosing dependable over flashy:

  • K-12 schools, where budget constraints and simplicity usually win out
  • High-turnover public recreation centers where equipment takes heavy, unsupervised use
  • Sports performance and training facilities where athletes want function over entertainment
  • Active aging facilities, where exerciser populations tend to prefer simple, tech-free interfaces
  • Facilities without reliable network infrastructure
  • Any budget-conscious project where the funds are better spent on strength equipment, flooring, or facility layout

How Traditional and Connected Cardio Work Together

A thoughtful mix of traditional and connected cardio equipment can give your facility the best of both worlds.

Connected equipment can add value and engagement, but it doesn’t need to make up your entire cardio floor. While digital features are becoming more common, many exercisers still prefer reliable, straightforward equipment that simply gets the job done.

The right balance ultimately depends on your audience and facility. For example, many hospitality gyms benefit from primarily traditional equipment that’s simple to use and appeals to a wide range of exercisers. On the other hand, a luxury, high-end hotel may benefit from more connected cardio to meet guest expectations for the latest technology and features.

How Do You Decide What’s Right for Your Facility?

Before locking in a cardio equipment plan, it helps to speak with a gym design professional on how to appeal to your exerciser audience and future-proof your cardio floor. You may also consider a few guiding questions, including:

  • What’s your current wifi and network capacity, and can it handle streaming across multiple machines at once?
  • Who on your team will own troubleshooting and content updates for connected equipment?
  • What does your exerciser population look like, and how tech-comfortable are they?
  • What’s your realistic budget beyond the purchase price, including subscriptions and service contracts?
  • What are competing facilities in your area offering, and does that matter to the people you’re trying to attract or retain?

Make Your Gym’s Cardio Visions a Reality with ASF

If you’re planning a cardio equipment refresh and want a second opinion on what will actually work for your space and your exercisers, ASF is here for you. 

We proudly offer the services you need to build the cardio floor of your dreams, including gym design and planning, equipment delivery and installation, maintenance and repair, and leasing or financing

You can view our work or contact us to start the conversation today.

Frequently Asked Questions

Connected cardio equipment includes a touchscreen or app-based console that streams live and on-demand classes, tracks workout history over time, and often syncs with wearables or a broader fitness app. Traditional cardio equipment tracks the basics, like time, distance, and heart rate, without any of that software layer.

Plan for more than just the equipment premium. Factor in per-unit content subscriptions, higher-tier service contracts, and any network upgrades your facility needs to support streaming across multiple machines. Those ongoing costs usually add up to more than the upfront price difference over the life of the equipment.

Both, depending on the exerciser. Some are drawn in by classes and leaderboards and will use a facility more often because of them. Others just want a treadmill that works every time they walk up to it. A cardio floor that only serves one of those groups is leaving engagement on the table.

Yes, and in our experience it’s usually the better strategy. Placing a handful of connected units in a high-visibility area while filling out the rest of the floor with dependable traditional machines tends to outperform an all-connected or all-traditional approach, both on cost and on usage.

At minimum, commercial-grade wifi that can handle multiple devices streaming video at once without dropping. Facilities that skip this step are the ones most likely to see connected equipment sit unused, since a class that buffers or disconnects mid-workout gets abandoned quickly.

Peloton is built entirely around the connected model, with bikes and rowers centered on live and on-demand classes. Precor offers connected options within its Experience Series while also making strong traditional cardio equipment, which is part of why we recommend pairing brands based on what each facility needs rather than picking one brand for the whole floor. Other connected brands include Aviron, FORME, and Your Reformer, with equipment and details outlined here for more information. 

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