document.addEventListener('DOMContentLoaded',function(){
  const toggle=document.querySelector('.jc-menu-toggle');
  const nav=document.querySelector('.jc-nav');
  if(toggle&&nav){toggle.addEventListener('click',function(){const open=nav.classList.toggle('is-open');toggle.setAttribute('aria-expanded',open?'true':'false');});}
  document.querySelectorAll('.jc-nav a').forEach(a=>a.addEventListener('click',()=>nav&&nav.classList.remove('is-open')));
});

document.addEventListener('DOMContentLoaded', function () {
  const pulseLayer = document.querySelector('.jc-hero-pulses');

  if (!pulseLayer) {
    return;
  }

  /*
   * Node positions mapped directly from the original
   * 1983 × 793 hero artwork.
   *
   * x and y are percentages of the source image.
   */
  const nodeMap = [
    { x: 48.311, y: 9.962 },
    { x: 52.597, y: 11.980 },
    { x: 60.414, y: 4.666 },
    { x: 61.321, y: 12.484 },
    { x: 63.137, y: 12.989 },
    { x: 67.827, y: 7.440 },
    { x: 69.188, y: 14.754 },
    { x: 72.819, y: 3.279 },
    { x: 74.332, y: 9.458 },
    { x: 77.811, y: 7.188 },
    { x: 77.912, y: 14.376 },
    { x: 82.905, y: 11.223 },

    { x: 45.436, y: 20.933 },
    { x: 51.437, y: 24.590 },
    { x: 56.884, y: 24.212 },
    { x: 60.615, y: 19.294 },
    { x: 63.238, y: 22.068 },
    { x: 66.919, y: 18.159 },
    { x: 69.995, y: 19.168 },
    { x: 72.769, y: 17.024 },
    { x: 78.366, y: 20.681 },
    { x: 80.081, y: 21.438 },
    { x: 83.006, y: 19.042 },

    { x: 47.857, y: 32.787 },
    { x: 51.488, y: 31.652 },
    { x: 55.572, y: 31.274 },
    { x: 60.615, y: 29.382 },
    // Removed - pulsed in the sunrise
    // { x: 66.667, y: 39.218 },

    { x: 44.478, y: 42.875 },
    { x: 51.589, y: 47.163 },
    { x: 51.689, y: 54.477 },
    { x: 57.085, y: 63.934 },
    { x: 63.893, y: 63.430 },
    { x: 67.524, y: 53.090 },
    { x: 72.920, y: 45.019 },
    { x: 80.182, y: 48.298 },
    { x: 82.905, y: 51.955 },

    { x: 41.049, y: 65.574 },
    { x: 52.395, y: 70.996 },
    { x: 57.085, y: 74.905 },
    { x: 60.767, y: 72.636 },
    { x: 63.540, y: 82.472 }
  ];

  const nodes = nodeMap.map(function (position, index) {
    const node = document.createElement('span');

    node.className = 'jc-pulse-node';
    node.dataset.nodeIndex = String(index);

    node.style.setProperty('--node-x', position.x + '%');
    node.style.setProperty('--node-y', position.y + '%');

    pulseLayer.appendChild(node);

    return node;
  });

  let lastNodeIndex = -1;
  let timeoutId = null;

  function getRandomNodeIndex() {
    if (nodes.length < 2) {
      return 0;
    }

    let nextIndex;

    do {
      nextIndex = Math.floor(Math.random() * nodes.length);
    } while (nextIndex === lastNodeIndex);

    lastNodeIndex = nextIndex;

    return nextIndex;
  }

  function activateNode(node, strong) {
    node.classList.remove('is-active', 'is-strong');

    /*
     * Force the browser to recognize the reset so the
     * animation can replay on the same element later.
     */
    void node.offsetWidth;

    node.classList.add(strong ? 'is-strong' : 'is-active');

    window.setTimeout(function () {
      node.classList.remove('is-active', 'is-strong');
    }, strong ? 3000 : 2500);
  }

  function scheduleNextPulse() {
    /*
     * Random delay between approximately 900ms and 2400ms.
     */
    const delay = 350 + Math.random() * 450;

    timeoutId = window.setTimeout(function () {
      const nodeIndex = getRandomNodeIndex();
      const node = nodes[nodeIndex];

      /*
       * Roughly one out of every eight pulses is stronger.
       */
      const strong = Math.random() < 0.35;

      activateNode(node, strong);
      scheduleNextPulse();
    }, delay);
  }

  /*
   * Begin with a gentle delay so the hero does not animate
   * immediately during the initial page render.
   */
  timeoutId = window.setTimeout(scheduleNextPulse, 100);

  /*
   * Stop unnecessary animation work when the page is hidden.
   */
  document.addEventListener('visibilitychange', function () {
    if (document.hidden) {
      window.clearTimeout(timeoutId);
      timeoutId = null;

      nodes.forEach(function (node) {
        node.classList.remove('is-active', 'is-strong');
      });

      return;
    }

    if (!timeoutId) {
      scheduleNextPulse();
    }
  });
});


document.addEventListener('DOMContentLoaded', function () {
  const modalLayer = document.getElementById('jc-modal-layer');

  if (!modalLayer) {
    return;
  }

  const closeControls = modalLayer.querySelectorAll('[data-jc-modal-close]');
  const modals = modalLayer.querySelectorAll('.jc-modal');

  let activeModal = null;
  let lastTrigger = null;

  function getFocusableElements(container) {
    return Array.from(
      container.querySelectorAll(
        'a[href], button:not([disabled]), input:not([disabled]), ' +
        'select:not([disabled]), textarea:not([disabled]), ' +
        '[tabindex]:not([tabindex="-1"])'
      )
    );
  }

  function openModal(modalId, trigger) {
    const selectedModal = document.getElementById('jc-modal-' + modalId);

    if (!selectedModal) {
      return;
    }

    lastTrigger = trigger;
    activeModal = selectedModal;

    modals.forEach(function (modal) {
      modal.hidden = true;
    });

    modalLayer.hidden = false;
    selectedModal.hidden = false;

    document.body.classList.add('jc-modal-open');

    requestAnimationFrame(function () {
      const closeButton = selectedModal.querySelector('.jc-modal-close');

      if (closeButton) {
        closeButton.focus();
      } else {
        selectedModal.focus();
      }
    });
  }

  function closeModal() {
    if (!activeModal) {
      return;
    }

    modalLayer.hidden = true;
    activeModal.hidden = true;

    document.body.classList.remove('jc-modal-open');

    activeModal = null;

    if (lastTrigger) {
      lastTrigger.focus();
    }
  }

  /*
   * Use one delegated click handler instead of binding only to the
   * trigger elements found during initialization. This keeps modal
   * triggers reliable when WordPress changes markup placement or a
   * trigger is rendered later in the document.
   */
  document.addEventListener('click', function (event) {
    const trigger = event.target.closest('[data-jc-modal]');

    if (!trigger) {
      return;
    }

    const modalId = trigger.getAttribute('data-jc-modal');

    if (!modalId) {
      return;
    }

    event.preventDefault();
    event.stopPropagation();
    openModal(modalId, trigger);
  });

  closeControls.forEach(function (control) {
    control.addEventListener('click', closeModal);
  });

  document.addEventListener('keydown', function (event) {
    if (!activeModal) {
      return;
    }

    if (event.key === 'Escape') {
      event.preventDefault();
      closeModal();
      return;
    }

    if (event.key !== 'Tab') {
      return;
    }

    const focusableElements = getFocusableElements(activeModal);

    if (!focusableElements.length) {
      event.preventDefault();
      activeModal.focus();
      return;
    }

    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];

    if (event.shiftKey && document.activeElement === firstElement) {
      event.preventDefault();
      lastElement.focus();
    } else if (
      !event.shiftKey &&
      document.activeElement === lastElement
    ) {
      event.preventDefault();
      firstElement.focus();
    }
  });
});



/* ==========================================================
   JOURNEY — BLUEPRINT TIMELINE
   Add this block to the end of assets/js/theme.js
   ========================================================== */

document.addEventListener('DOMContentLoaded', function () {
  const timeline = document.querySelector('[data-jc-journey]');

  if (!timeline) {
    return;
  }

  const steps = Array.from(
    timeline.querySelectorAll('[data-journey-step]')
  );

  const objects = Array.from(
    timeline.querySelectorAll('.jc-journey-object')
  );

  const reducedMotion = window.matchMedia(
    '(prefers-reduced-motion: reduce)'
  ).matches;

  let hasPlayed = false;
  let rafId = null;

  /*
   * Prepare each external SVG so its internal paths can be
   * activated when the timeline reaches that milestone.
   */
  function prepareObject(objectElement) {
    function prepare() {
      try {
        const svg = objectElement.contentDocument
          ? objectElement.contentDocument.documentElement
          : null;

        if (svg) {
          svg.classList.add('jc-ready');
        }
      } catch (error) {
        /*
         * The static SVG still displays if the browser prevents
         * access to its document for any reason.
         */
      }
    }

    objectElement.addEventListener('load', prepare, { once:true });
    prepare();
  }

  objects.forEach(prepareObject);

  function activateInternalSvg(step) {
    const objectElement = step.querySelector('.jc-journey-object');

    if (!objectElement) {
      return;
    }

    try {
      const svg = objectElement.contentDocument
        ? objectElement.contentDocument.documentElement
        : null;

      if (!svg) {
        return;
      }

      svg.classList.remove('is-animated');

      /*
       * Force the SVG document to register the reset before
       * starting its one-time construction sequence.
       */
      void svg.getBoundingClientRect();

      svg.classList.add('is-animated');
    } catch (error) {
      /*
       * The parent milestone animation remains as a graceful
       * fallback if internal SVG access is unavailable.
       */
    }
  }

  function setProgress(progress) {
    timeline.style.setProperty(
      '--jc-journey-progress',
      String(Math.max(0, Math.min(1, progress)))
    );
  }

  function completeImmediately() {
    setProgress(1);

    steps.forEach(function (step) {
      step.classList.add('is-complete');
      activateInternalSvg(step);
    });

    timeline.classList.add('is-complete');
  }

  function playJourney() {
    if (hasPlayed) {
      return;
    }

    hasPlayed = true;

    if (reducedMotion) {
      completeImmediately();
      return;
    }

    timeline.classList.add('is-running');

    const totalDuration = window.innerWidth <= 800 ? 3800 : 5000;
    const iconLead = 0.055;
    const startTime = performance.now();
    const activated = new Set();

    function frame(now) {
      const elapsed = now - startTime;
      const rawProgress = Math.min(elapsed / totalDuration, 1);

      /*
       * Smooth, deliberate plotting motion without feeling slow.
       */
      const progress =
        rawProgress < .5
          ? 2 * rawProgress * rawProgress
          : 1 - Math.pow(-2 * rawProgress + 2, 2) / 2;

      setProgress(progress);

      steps.forEach(function (step, index) {
        const threshold =
          steps.length === 1
            ? 0
            : index / (steps.length - 1);

        if (!activated.has(index) && progress >= threshold) {
          activated.add(index);

          /*
           * Let the plotting point visibly make contact before
           * revealing the milestone.
           */
          window.setTimeout(function () {
            step.classList.add('is-active');
            activateInternalSvg(step);

            window.setTimeout(function () {
              step.classList.remove('is-active');
              step.classList.add('is-complete');
            }, index === steps.length - 1 ? 850 : 620);
          }, 30);
        }
      });

      if (rawProgress < 1) {
        rafId = window.requestAnimationFrame(frame);
        return;
      }

      setProgress(1);
      timeline.classList.remove('is-running');
      timeline.classList.add('is-complete');
      rafId = null;
    }

    rafId = window.requestAnimationFrame(frame);
  }

  /*
   * Use the same activation line as the fixed navigation. The Journey
   * begins only after it clears the compact header, while the bottom
   * margin lets the section enter naturally before playback starts.
   */
  const header = document.querySelector('.jc-site-header');
  const headerHeight = header ? header.offsetHeight : 50;
  const activationOffset = headerHeight + 30;

  if ('IntersectionObserver' in window) {
    const observer = new IntersectionObserver(
      function (entries) {
        entries.forEach(function (entry) {
          if (!entry.isIntersecting || hasPlayed) {
            return;
          }

          playJourney();
          observer.disconnect();
        });
      },
      {
        threshold:0,
        rootMargin:'-' + activationOffset + 'px 0px -12% 0px'
      }
    );

    observer.observe(timeline);
  } else {
    /* Graceful fallback for older browsers. */
    completeImmediately();
    hasPlayed = true;
  }
});



document.querySelectorAll('.jc-project-card').forEach(function(card){

    const link = card.querySelector('.jc-project-link');

    if(!link) return;

    card.style.cursor = 'pointer';

    card.addEventListener('click',function(e){

        if(e.target.closest('a')){
            return;
        }

        window.location = link.href;

    });

});
/* ==========================================================
   FIXED NAVIGATION — SCROLL BLUR STATE
   Added July 13, 2026
   ========================================================== */

document.addEventListener('DOMContentLoaded', function () {
  const header = document.querySelector('.jc-site-header');

  if (!header) {
    return;
  }

  let ticking = false;

  function updateHeaderState() {
    header.classList.toggle('is-scrolled', window.scrollY > 24);
    ticking = false;
  }

  function requestHeaderUpdate() {
    if (ticking) {
      return;
    }

    ticking = true;
    window.requestAnimationFrame(updateHeaderState);
  }

  updateHeaderState();
  window.addEventListener('scroll', requestHeaderUpdate, { passive: true });
});


/* ==========================================================
   BLUEPRINT NAVIGATION — ACTIVE SECTION
   Scroll-away logic retained from the working build
   ========================================================== */

document.addEventListener('DOMContentLoaded', function () {
  const navLinks = Array.from(
    document.querySelectorAll('.jc-nav a[href*="#"]')
  );

  const items = navLinks
    .map(function (link) {
      let hash = '';

      try {
        hash = new URL(link.href, window.location.href).hash;
      } catch (error) {
        return null;
      }

      const section = hash ? document.querySelector(hash) : null;
      return section ? { link:link, section:section } : null;
    })
    .filter(Boolean);

  if (!items.length) {
    return;
  }

  let ticking = false;
  let activeLink = null;

  function setActiveNavigation(item) {
    if (!item || item.link === activeLink) {
      return;
    }

    activeLink = item.link;

    navLinks.forEach(function (link) {
      const isActive = link === activeLink;
      link.classList.toggle('is-active', isActive);

      if (isActive) {
        link.setAttribute('aria-current', 'location');
      } else {
        link.removeAttribute('aria-current');
      }
    });
  }

  function updateActiveNavigation() {
    const header = document.querySelector('.jc-site-header');
    const headerHeight = header ? header.offsetHeight : 50;

    /*
     * This is the scroll-away activation line. A section remains active
     * until the next section crosses 30px below the fixed header.
     * It is also mirrored by the Journey observer rootMargin above.
     */
    const activationPoint = headerHeight + 30;
    let activeItem = items[0];

    items.forEach(function (item) {
      const rect = item.section.getBoundingClientRect();

      if (rect.top <= activationPoint) {
        activeItem = item;
      }
    });

    /* Ensure the final section owns the active state at page bottom. */
    const nearPageBottom =
      window.innerHeight + window.scrollY >=
      document.documentElement.scrollHeight - 2;

    if (nearPageBottom) {
      activeItem = items[items.length - 1];
    }

    setActiveNavigation(activeItem);
    ticking = false;
  }

  function requestNavigationUpdate() {
    if (ticking) {
      return;
    }

    ticking = true;
    window.requestAnimationFrame(updateActiveNavigation);
  }

  items.forEach(function (item) {
    item.link.addEventListener('click', function () {
      setActiveNavigation(item);
    });
  });

  updateActiveNavigation();

  window.addEventListener('scroll', requestNavigationUpdate, {
    passive:true
  });

  window.addEventListener('resize', requestNavigationUpdate);
});
