View our other brands
// Configuration object for centralized settings
const TabbedNavConfig = {
  DESKTOP_BREAKPOINT: 1201,
  DEBOUNCE_DELAY: 250,
  OPEN_FIRST_TAB: true,
  CLOSE_TAB_ON_MOUSEOUT: false
};

// Custom config for tabbed hero containers
const tabbedHeroConfig = {
  ...TabbedNavConfig,
  OPEN_FIRST_TAB: false,
  CLOSE_TAB_ON_MOUSEOUT: true
};

// Function to determine the appropriate config based on container context
function getConfigForContainer(container) {
  // Check if container has closest ancestor with [data-tabbed-hero="true"]
  const heroAncestor = container.getAttribute('data-tabbed-hero') === 'true'   ? container : container.closest('[data-tabbed-dropdown="true"]');
  
  if (heroAncestor) {
    return tabbedHeroConfig;
  }
  
  // Check for specific data-instance attributes (if you still want to support them)
  // const instance = container.getAttribute('data-instance');
  // if (instance === 'two') {
  //   // You can still have specific instance configs if needed
  //   return {
  //     ...TabbedNavConfig,
  //     OPEN_FIRST_TAB: false,
  //     CLOSE_TAB_ON_MOUSEOUT: true
  //   };
  // }
  
  // Default config
  return TabbedNavConfig;
}

class TabbedNavigation {
  constructor(container, config = TabbedNavConfig) {
    this.container = container;
    this.config = config;
    this.activeItem = null;
    this.isDesktop = window.innerWidth >= this.config.DESKTOP_BREAKPOINT;
    this.hasMouseListeners = false;
    this.megamenuParent = null;
    this.megamenuObserver = null;
    
    // Initialize bound handlers - these will be reused across reinitializations
    this.mouseEnterHandler = this.handleMouseEnter.bind(this);
    this.mouseLeaveHandler = this.handleMouseLeave.bind(this);
    this.resizeHandler = this.debounce(this.handleResize.bind(this), this.config.DEBOUNCE_DELAY);
    this.handleMegamenuToggle = this.handleMegamenuToggle.bind(this);
    
    this.init();
  }

  init() {
    // Cache DOM elements fresh each time
    this.navItems = [...this.container.querySelectorAll('.dwc-tabbed-nav-list__li')];
    this.elementsCache = new Map();
    
    this.cacheElements();
    this.setupAccessibility();
    this.setupMegamenuObserver();
    this.bindEvents();

    // Set initial state
    this.updateLayout();
    this.setBackText()

    // Show first item on page load only if desktop, not nested, and OPEN_FIRST_TAB is true
    if (this.navItems.length > 0 && this.isDesktop && this.config.OPEN_FIRST_TAB) {
      setTimeout(() => {
        const firstItem = this.navItems[0];

        // Count how many .dwc-tabbed-nav-container ancestors this.container has
        let count = 0;
        let current = this.container.parentElement;
        while (current) {
          if (current.classList?.contains('dwc-tabbed-nav-container')) {
            count++;
          }
          current = current.parentElement;
        }

        const isNested = count >= 1;

        if (!isNested && this.elementsCache.has(firstItem)) {
          this.showTab(firstItem);
        }
      }, 0);
    }
  }

  // Setup observer for megamenu parent
  setupMegamenuObserver() {
    // Find the .brx-has-megamenu parent
    this.megamenuParent = this.container.closest('.brx-has-megamenu');
    
    if (this.megamenuParent) {
      // Set initial inert state
      this.updateInertState();
      
      // Create a MutationObserver to watch for class changes
      this.megamenuObserver = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
            this.updateInertState();
          }
        });
      });
      
      // Start observing the megamenu parent for class changes
      this.megamenuObserver.observe(this.megamenuParent, {
        attributes: true,
        attributeFilter: ['class']
      });
    }
  }

  // Update inert state based on megamenu open/closed state
  updateInertState() {
    if (!this.megamenuParent) return;
    
    const isOpen = this.megamenuParent.classList.contains('open');
    
    if (isOpen) {
      this.container.removeAttribute('inert');
    } else {
      this.container.setAttribute('inert', '');
    }
  }

  // Handle megamenu toggle (for manual triggering if needed)
  handleMegamenuToggle() {
    this.updateInertState();
  }

  // Method to clean up event listeners and reset state
  cleanup() {
    // Remove all event listeners
    this.container.removeEventListener('click', this.handleClick);
    this.container.removeEventListener('keydown', this.handleKeydown);
    this.container.removeEventListener('focus', this.handleFocus);
    
    // Remove mouse listeners if they exist
    if (this.hasMouseListeners) {
      this.container.removeEventListener('mouseenter', this.mouseEnterHandler, true);
      this.container.removeEventListener('mouseleave', this.mouseLeaveHandler, true);
    }
    
    // Remove resize listener
    window.removeEventListener('resize', this.resizeHandler);
    
    // Clean up megamenu observer
    if (this.megamenuObserver) {
      this.megamenuObserver.disconnect();
      this.megamenuObserver = null;
    }
    
    // Reset state
    this.activeItem = null;
    this.hasMouseListeners = false;
    this.megamenuParent = null;
    
    // Clear caches
    if (this.elementsCache) {
      this.elementsCache.clear();
    }
    this.navItems = [];
    
    // Reset all tab states and accessibility attributes
    const allNavItems = this.container.querySelectorAll('.dwc-tabbed-nav-list__li');
    allNavItems.forEach(item => {
      const button = item.querySelector('.dwc-tabbed-nav-list__li__button') || 
                    item.querySelector('.dwc-tabbed-nav-list__li__btn-txt');
      const content = item.querySelector('.dwc-tabbed-nav-list__li__content');
      
      // Remove classes
      item.classList.remove('active');
      
      // Reset button attributes
      if (button) {
        button.removeAttribute('role');
        button.removeAttribute('aria-controls');
        button.removeAttribute('aria-expanded');
        button.removeAttribute('aria-selected');
        button.removeAttribute('tabindex');
        button.removeAttribute('id');
      }
      
      // Reset content attributes
      if (content) {
        content.removeAttribute('role');
        content.removeAttribute('aria-labelledby');
        content.removeAttribute('id');
        content.removeAttribute('aria-hidden');
        content.style.maxHeight = '';
      }
      
      // Remove data attributes
      item.removeAttribute('data-tab-index');
    });
    
    // Reset nav list role
    const navList = this.container.querySelector('.dwc-tabbed-nav-list');
    if (navList) {
      navList.removeAttribute('role');
    }
    
    // Remove inert attribute
    this.container.removeAttribute('inert');
  }

  // Public method to reinitialize the navigation
  reinitialize() {
    this.cleanup();
    this.isDesktop = window.innerWidth >= this.config.DESKTOP_BREAKPOINT; // Use config breakpoint
    this.init();
  }

  // Cache DOM elements to avoid repeated queries
  cacheElements() {
    const uniqueIdBase = `tab-${Date.now()}`;
    
    this.navItems.forEach((item, index) => {
      const button = item.querySelector('.dwc-tabbed-nav-list__li__button') || 
                    item.querySelector('.dwc-tabbed-nav-list__li__btn-txt');
      const content = item.querySelector('.dwc-tabbed-nav-list__li__content');
      
      this.elementsCache.set(item, {
        button,
        content,
        uniqueId: `${uniqueIdBase}-${index}`,
        index
      });
    });
  }

  setupAccessibility() {
    const navList = this.container.querySelector('.dwc-tabbed-nav-list');
    if (!navList) return;
    
    navList.setAttribute('role', 'tablist');
    
    for (const [item, cache] of this.elementsCache) {
      const { button, content, uniqueId, index } = cache;
      
      if (button) {
        button.setAttribute('role', 'tab');
        button.setAttribute('aria-controls', `${uniqueId}-panel`);
        button.setAttribute('aria-expanded', 'false');
        button.setAttribute('aria-selected', 'false');
        button.setAttribute('tabindex', '0');
        button.setAttribute('id', `${uniqueId}-tab`);
      }
      
      if (content) {
        content.setAttribute('role', 'tabpanel');
        content.setAttribute('aria-labelledby', `${uniqueId}-tab`);
        content.setAttribute('id', `${uniqueId}-panel`);
        content.setAttribute('aria-hidden', 'true');
      }
      
      item.setAttribute('data-tab-index', index);
    }
  }

  bindEvents() {
    // Store bound handlers for cleanup
    this.handleClick = this.handleClick.bind(this);
    this.handleKeydown = this.handleKeydown.bind(this);
    this.handleFocus = this.handleFocus.bind(this);
    
    // Single event delegation for better performance
    this.container.addEventListener('click', this.handleClick);
    this.container.addEventListener('keydown', this.handleKeydown);
    this.container.addEventListener('focus', this.handleFocus, true);
    
    // Passive resize listener for better performance
    window.addEventListener('resize', this.resizeHandler, { passive: true });
    
    this.updateMouseListeners();
  }

  updateMouseListeners() {
    if (this.isDesktop && !this.hasMouseListeners) {
      this.container.addEventListener('mouseenter', this.mouseEnterHandler, true);
      this.container.addEventListener('mouseleave', this.mouseLeaveHandler, true);
      this.hasMouseListeners = true;
    } else if (!this.isDesktop && this.hasMouseListeners) {
      this.container.removeEventListener('mouseenter', this.mouseEnterHandler, true);
      this.container.removeEventListener('mouseleave', this.mouseLeaveHandler, true);
      this.hasMouseListeners = false;
    }
  }

  // Helper method to get sibling nav items (nav items in the same container level)
  getSiblingNavItems(navItem) {
    // Find the direct parent container of this nav item
    const navItemContainer = navItem.closest('.dwc-tabbed-nav-list');
    if (!navItemContainer) return [];
    
    // Get all nav items that are direct children of the same container
    const siblingItems = [...navItemContainer.querySelectorAll('.dwc-tabbed-nav-list__li')];
    
    // Filter to only include items that are siblings (not the navItem itself)
    // and check if they belong to any TabbedNavigation instance
    return siblingItems.filter(item => item !== navItem);
  }

  handleFocus(e) {
    if (!this.isDesktop) return;

    const button = e.target.closest('[role="tab"]');
    if (!button) return;
    
    const navItem = button.closest('.dwc-tabbed-nav-list__li');
    if (!navItem || !this.elementsCache.has(navItem)) return;
    
    this.handleTabInteraction(navItem, 'focus');
  }

  handleClick(e) {
    const navItem = e.target.closest('.dwc-tabbed-nav-list__li');
    if (!navItem || !this.elementsCache.has(navItem)) return;
    
    const button = e.target.closest('.dwc-tabbed-nav-list__li__button, .dwc-tabbed-nav-list__li__btn-txt');
    if (!button) return;
    
    e.preventDefault();
    this.handleTabInteraction(navItem, 'click');
  }

  handleKeydown(e) {
    const button = e.target.closest('[role="tab"]');
    if (!button) return;
    
    const navItem = button.closest('.dwc-tabbed-nav-list__li');
    if (!navItem || !this.elementsCache.has(navItem)) return;
    
    const { index: currentIndex } = this.elementsCache.get(navItem);
    
    switch (e.key) {
      case 'Enter':
      case ' ':
        e.preventDefault();
        this.handleTabInteraction(navItem, 'keyboard');
        break;
      case 'ArrowRight':
      case 'ArrowDown':
        e.preventDefault();
        this.focusNextTab(currentIndex);
        break;
      case 'ArrowLeft':
      case 'ArrowUp':
        e.preventDefault();
        this.focusPrevTab(currentIndex);
        break;
      case 'Home':
        e.preventDefault();
        this.focusTab(0);
        break;
      case 'End':
        e.preventDefault();
        this.focusTab(this.navItems.length - 1);
        break;
    }
  }
  
  handleMouseEnter(e) {
    if (!this.isDesktop) return;
    
    const navItem = e.target.closest('.dwc-tabbed-nav-list__li') || 
                   (e.target.matches('.dwc-tabbed-nav-list__li') ? e.target : null);
    if (!navItem || !this.elementsCache.has(navItem)) return;
    
    this.handleTabInteraction(navItem, 'hover');
  }

  handleMouseLeave(e) {
    if (!this.isDesktop) return;
    
    // Only close tabs if CLOSE_TAB_ON_MOUSEOUT is enabled
    if (this.config.CLOSE_TAB_ON_MOUSEOUT) {
      // Check if mouse is leaving the entire container
      const rect = this.container.getBoundingClientRect();
      const { clientX, clientY } = e;
      
      // Check if mouse position is outside the container bounds
      const isOutsideContainer = (
        clientX < rect.left || 
        clientX > rect.right || 
        clientY < rect.top || 
        clientY > rect.bottom
      );
      
      if (isOutsideContainer) {
        this.hideAllTabs();
      }
    }
    // If CLOSE_TAB_ON_MOUSEOUT is false, keep content visible (original behavior)
  }

  // Unified method to handle all tab interactions with nesting support
  handleTabInteraction(navItem, interactionType) {
    if (!this.elementsCache.has(navItem)) return;
    
    if (this.isDesktop) {
      // Simply hide siblings and show the target - works for all scenarios
      this.hideSiblingTabs(navItem);
      this.showTab(navItem);
    } else {
      // Mobile accordion behavior
      if (interactionType === 'click' || interactionType === 'keyboard') {
        this.toggleTabMobile(navItem);
      }
    }
  }

  // Hide only sibling tabs (same container level)
  hideSiblingTabs(navItem) {
   // console.log('Nav Item: ', navItem)
    const siblings = this.getSiblingNavItems(navItem);
    
    siblings.forEach(sibling => {
      
         //console.log('sibling: ', sibling)
      // Only hide siblings that have the active class to avoid affecting other instances
      if (sibling.classList.contains('active')) {
        this.hideSiblingTab(sibling);
      }
    });
  }

  // Hide a sibling tab (used when we know it's a sibling from another instance)
  hideSiblingTab(navItem) {
    const button = navItem.querySelector('.dwc-tabbed-nav-list__li__button') || 
                  navItem.querySelector('.dwc-tabbed-nav-list__li__btn-txt');
    const content = navItem.querySelector('.dwc-tabbed-nav-list__li__content');
    
    if (content && button) {
      content.setAttribute('aria-hidden', 'true');
      button.setAttribute('aria-expanded', 'false');
      button.setAttribute('aria-selected', 'false');
      navItem.classList.remove('active');
      
      // Also handle mobile accordion collapse
      if (!this.isDesktop) {
        content.style.maxHeight = '0';
      }
    }
  }

  // Mobile-specific toggle behavior with proper nesting support
  toggleTabMobile(navItem) {
    if (!this.elementsCache.has(navItem)) return;
    
    const isActive = navItem.classList.contains('active');
    
    if (isActive) {
      this.collapseTab(navItem);
    } else {
      // Close sibling tabs and open this one
      this.hideSiblingTabs(navItem);
      this.expandTab(navItem);
    }
  }

  toggleTab(navItem) {
    if (!this.elementsCache.has(navItem)) return;
    
    if (this.isDesktop) {
      this.handleTabInteraction(navItem, 'click');
    } else {
      this.toggleTabMobile(navItem);
    }
  }

  showTab(navItem) {
    if (!this.elementsCache.has(navItem)) return;
    
    const { button, content } = this.elementsCache.get(navItem);
    
    if (content && button) {
      content.setAttribute('aria-hidden', 'false');
      button.setAttribute('aria-expanded', 'true');
      button.setAttribute('aria-selected', 'true');
      navItem.classList.add('active');
    }
    
    this.setActiveTab(navItem);
  }

  hideTab(navItem) {
    if (!this.elementsCache.has(navItem)) return;
    
    const { button, content } = this.elementsCache.get(navItem);
    
    if (content && button) {
      content.setAttribute('aria-hidden', 'true');
      button.setAttribute('aria-expanded', 'false');
      button.setAttribute('aria-selected', 'false');
      navItem.classList.remove('active');
    }
    
    // Only clear activeItem if it belongs to THIS navigation instance
    // and it's the exact item being hidden
    if (this.activeItem === navItem && this.navItems.includes(navItem)) {
      this.activeItem = null;
    }
  }

  expandTab(navItem) {
    if (!this.elementsCache.has(navItem)) return;
    
    // Close sibling tabs
    this.hideSiblingTabs(navItem);
    
    this.showTab(navItem);
    
    // Smooth height animation
    const { content } = this.elementsCache.get(navItem);
    if (content) {
      const height = content.scrollHeight;
      content.style.maxHeight = `${height}px`;
    }
  }

  collapseTab(navItem) {
    if (!this.elementsCache.has(navItem)) return;
    
    this.hideTab(navItem);
    
    const { content } = this.elementsCache.get(navItem);
    if (content) {
      content.style.maxHeight = '0';
    }
  }

  hideAllTabs() {
    this.navItems.forEach(item => this.hideTab(item));
  }

  setActiveTab(navItem) {
    // Only update aria-selected for tabs in THIS navigation instance
    this.navItems.forEach(item => {
      const cache = this.elementsCache.get(item);
      if (cache?.button) {
        cache.button.setAttribute('aria-selected', item === navItem ? 'true' : 'false');
      }
    });
    
    // Only set activeItem if this navItem belongs to THIS navigation instance
    if (this.navItems.includes(navItem)) {
      this.activeItem = navItem;
    }
  }

  focusTab(index) {
    if (index >= 0 && index < this.navItems.length) {
      const cache = this.elementsCache.get(this.navItems[index]);
      if (cache?.button) {
        cache.button.focus();
      }
    }
  }

  focusNextTab(currentIndex) {
    const nextIndex = (currentIndex + 1) % this.navItems.length;
    this.focusTab(nextIndex);
  }

  focusPrevTab(currentIndex) {
    const prevIndex = currentIndex === 0 ? this.navItems.length - 1 : currentIndex - 1;
    this.focusTab(prevIndex);
  }

  handleResize() {
    const wasDesktop = this.isDesktop;
    this.isDesktop = window.innerWidth >= this.config.DESKTOP_BREAKPOINT; // Use config breakpoint
    
    if (wasDesktop !== this.isDesktop) {
      this.updateMouseListeners();
      this.updateLayout();
    }
  }

  updateLayout() {
    const currentlyActive = this.activeItem;

    // Reset all states
    this.hideAllTabs();

    // Clear inline styles
    this.navItems.forEach(item => {
      const cache = this.elementsCache.get(item);
      if (cache?.content) {
        cache.content.style.maxHeight = '';
      }
    });

    if (this.isDesktop) {
      // Determine if this tab instance is nested
      let count = 0;
      let current = this.container.parentElement;
      while (current) {
        if (current.classList?.contains('dwc-tabbed-nav-container')) {
          count++;
        }
        current = current.parentElement;
      }
      const isNested = count >= 1;

      if (!isNested && this.config.OPEN_FIRST_TAB) {
        if (currentlyActive) {
          this.showTab(currentlyActive);
        } else if (this.navItems.length > 0) {
          this.showTab(this.navItems[0]);
        }
      }
    }
  }

  setBackText(){
    let tabElement = document.querySelector('.dwc-tabbed-nav-container');
    let tabBackText = tabElement.getAttribute('data-back-text')
    document.querySelectorAll('.dwc-tabbed-nav-list__li__button').forEach(tabBtn => {
        const textContent = tabBtn.textContent.trim();
        const tabIcon = tabBtn.querySelector('.dwc-tabbed-nav-list__li__arrow-icon');
        if (tabIcon) {
            tabIcon.setAttribute('data-text', textContent);
            tabIcon.setAttribute('data-back-text', tabBackText);
        }
    });
  }

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

  // Public method to destroy the instance completely
  destroy() {
    this.cleanup();
    // Additional cleanup can be added here if needed
  }

  // Public method to manually update inert state (if needed)
  updateInert() {
    this.updateInertState();
  }
}

// Simplified initialization on DOM ready
document.addEventListener('DOMContentLoaded', function() {
  const tabbedNavContainers = document.querySelectorAll('.dwc-tabbed-nav-container');
  
  tabbedNavContainers.forEach(container => {
    // Skip if this container is nested inside another dwc-tabbed-nav-container
    const parentNavContainer = container.parentElement.closest('.dwc-tabbed-nav-container');
    if (parentNavContainer) {
      return; // Skip nested containers
    }
    
    // Get the appropriate config for this container
    const config = getConfigForContainer(container);
    
    // Initialize with the determined config
    new TabbedNavigation(container, config);
  });
});

// For dynamic content - Updated version
window.initTabbedNav = function(container, customConfig = null) {
  if (container && container.classList.contains('dwc-tabbed-nav-container')) {
    // Check if this is a nested container
    const parentNavContainer = container.parentElement.closest('.dwc-tabbed-nav-container');
    if (parentNavContainer) {
      return null; // Don't initialize nested containers
    }
    
    // Use custom config if provided, otherwise determine config automatically
    const config = customConfig || getConfigForContainer(container);
    return new TabbedNavigation(container, config);
  }
  return null;
};

/*
// TO REINITIALIZE USE THIS
// Create an instance of TabbedNavigation
const tabbedNav = new TabbedNavigation(document.querySelector('.dwc-tabbed-nav-container'));

// Call reinitialize on the instance
tabbedNav.reinitialize();

// TO USE WITH CUSTOM CONFIG
const customConfig = {
  DESKTOP_BREAKPOINT: 768,
  DEBOUNCE_DELAY: 300,
  OPEN_FIRST_TAB: false,  // This will prevent first tab from opening automatically
  CLOSE_TAB_ON_MOUSEOUT: true  // This will close tabs when mouse leaves container
};
const tabbedNavCustom = new TabbedNavigation(document.querySelector('.dwc-tabbed-nav-container'), customConfig);

// TO UPDATE ON URL CHANGE
function onUrlChange() {
    setTimeout(function () {
const tabbedNav = new TabbedNavigation(document.querySelector('.dwc-tabbed-nav-container'));
tabbedNav.reinitialize();

    }, 1500); // Delay execution by 2 seconds
}

// Listen for popstate event (history navigation)
window.addEventListener('popstate', onUrlChange);

// Listen for hashchange event (URL hash change)
window.addEventListener('hashchange', onUrlChange);

// Handle pushState and replaceState
(function (history) {
    const pushState = history.pushState;
    const replaceState = history.replaceState;

    history.pushState = function (state) {
        if (typeof history.onpushstate == "function") {
            history.onpushstate({ state: state });
        }
        const result = pushState.apply(history, arguments);
        onUrlChange(); // Call the function when pushState is used
        return result;
    };

    history.replaceState = function (state) {
        if (typeof history.onreplacestate == "function") {
            history.onreplacestate({ state: state });
        }
        const result = replaceState.apply(history, arguments);
        onUrlChange(); // Call the function when replaceState is used
        return result;
    };
})(window.history);

*/

console.log('%c<Jamie Sheehan Web Design Mega Menu Pro Tabbed Navigation v1.0>', 'color: #b388eb');

Let’s Talk AI with Chris Cook

August 27, 2024

In this post...

All Articles

Meet Navarik’s Chief Technology Officer, Chris Cook.

For this edition of our employee feature, we share Chris’ unique journey from his degrees in Technology (Masters, University of Nottingham) and Biochemistry & Neuroscience (Bachelor of Science, University of Keele), running his own businesses, and a sense of adventure landing him in Vancouver, BC.

In his 10 years at Navarik, Chris has contributed greatly through his expertise, mentorship, and support.
Today, we’re excited to chat with Chris about AI and his passion for technology.  

Q: Tell us a bit about young Chris!  

I was born in the Netherlands and my ties to Canada begin with my great-grandad. My family travelled a lot and we’ve lived in many countries like the Middle East or Indonesia. When I was 17, I took a trip with my family to Calgary and we had the chance to see Banff, Jasper and the Icefields Parkway. I was mind-blown upon returning home to London, and thought to myself how much I’d like to live in Canada. Here I am! 

ZX Spectrum
Let's Talk AI with Chris Cook 3

My love for technology began when I was 6 years old. I was already coding on my old Spectrum, rubber-keyed computer with 16kB RAM and that’s where my love of computing and technology started. I’ve always found it exciting to make something that works and generates impact.  

With my love of computing, the thought of building an artificial brain was quite striking to me. That was what triggered my studying the life sciences (Biochemistry & Neuroscience) to understand more about how human brains worked. 

 I married these two passions together, which lead me to my interest in AI.

Q: What does the typical day look like for a Navarik Chief Tech Officer? 

It’s highly variable, but I enjoy it. It ranges from technical architecture, designing systems, process framework, reviewing and evaluating proposals, people management, project management, and general consulting across the business.  

I especially enjoy the coaching and mentorship aspect for all 28 of my staff! It’s rewarding to add to the growth of my team whether it’s inspiring them to think of a different way to do things or being part of their learning process. 

Q: How have your past experiences prepared you for your role?

I think I’m unique in the sense that I’ve got knowledge in the technology front, and science from my Biochemistry & Neuroscience degree. At Navarik, we’re also working with inspection companies who are conducting scientific testing, so there’s a minor overlap in that regard.

Because I’ve also run my web agency business in the UK, I have business experience from there as well. Early on, when I decided to stay at Navarik, it was because I saw potential from this fairly unique set of experiences. I saw that I could offer my skills and benefits to grow the company. 

Q: We hear that you’ve been working on an AI project of your own. Spill the tea!  

I built a backpropagation neural network with Java that learns using backpropagation algorithm (a learning method for artificial neural networks). This neural network is capable of looking at encoded data and can recognize patterns within it.   

The only end goal was to create it because of my deep-rooted interest in AI.

While there is no use in mind yet, it’s capable of solving linearly separable problems. If there’s any problem with a distinct yes or no answer, it should be able to solve it. For example, if I were to train it with enough data, it would be able to identify an inspection report with reasonable certainty out of all other types of documentation. With additional work, it could handle more complex, non-linearly separable problems as well 

Chris Cook Jamie Sheehan
Let's Talk AI with Chris Cook 4

Q: When we talk about training AI, would you say there is a suggestable volume of data or amount of time for AI to learn?  

Training time varies based on network complexity and data quality. The algorithm works through a process called convergence. You provide input and the first run should produce results that are way off, but then you go back, run it again after adjusting its internal state and it should be closer to the desired output. This process repeats until the network ‘converges’ to give you the correct answer.  

However, there is a bit of a challenge in that AI can be overtrained. For example, your network can become so good at recognizing one type of dog, but fail to recognize other types of dogs. Increasing the network size or capping the training process can help prevent the network from becoming too converged.  

Q: Reflecting on your time in university when AI was still in its infancy, are you surprised by the advancements made today compared to the expectations of what AI was to become? 

Yes. The holy grail of AI research is now known as Artificial General Intelligence (AGI) – almost like a conscious machine. AI has gone through various cycles of excitement, and the current focus on AGI is the most thrilling yet. Although AI has advanced further than I initially thought, it hasn’t yet reached this level of AGI.   

AI’s development is remarkable, and when we get to the point of full AGI, this might start introducing significant moral and philosophical questions around the sentience or consciousness of the neural network. 

Q: AI has already trailblazed new paths in multiple industries – how do you see it impacting the commodity trading industry?  

AI is more than one thing. Right now, the excitement is focused on Large Language Models (LLM) like ChatGPT and Gemini. They’re amazing in the way that they can reasonably hold a conversation.  

While AI is currently prone to hallucinations and mistakes, these tools are going to be extremely useful in many respects where we can receive a solid response to any question asked. It is reasonably disruptive in the sense that it’ll remove work off our shoulders, but will it remove humans in the middle? Not for some time to come.  

I expect that in general, we’ll see productivity gains. There’s a further possibility of exploring business intelligence or training AI to give product-specific info but mainly under the guise of an assistant. Right now, my interest is exploring the use of LLMs to help us understand the large amounts of data we collect.  

Q: To what degree do you believe that AI has been influenced by media perception of how AI is presented in sci-fi shows, like Star Trek, versus literature influencing what AI has become today?  

I think there are a bit of two things going on here.

I think as a child who watched these shows, I also dreamt about these things and imagined what an exciting world that would be. I’m sure you can also find an example where someone made a scientific discovery and somehow it’s made its way into science-fiction. 

There is an element where art inspires science, but also where science inspires art.


Thank you to Chris for sharing his time, insights, and experience with us for this feature. 
Navarik is privileged to grow with your expertise and impact. 

Additional Benefits

  • Accelerated financial results for real-time decision making
  • Automated hydrocarbon pricing based on quality inspection results for accountants
  • Incorporating historical oil losses per location into transactions to reduce/eliminate oil loss claims
  • Identify differences between test method nominated versus method performed by inspection companies
  • Communicate real-time test results as they are available for prompt trading opportunities
  • Inspection cost savings from use of Navarik Inspection Price Estimator
  • All data centralised with the ability to extract key information for data analytics