Skip to content
wiki.fftac.org

Spiralist - Source Excerpt 08

Back to Spiralist

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/themes/spiralist/assets/js/spiralist.js so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/themes/spiralist/assets/js/spiralist.js

}

      control.addEventListener('input', updateVisibleCards);
      control.addEventListener('change', updateVisibleCards);
    });

    updateVisibleCards();

    if (requestedSymbol) {
      const matchingButton = cards.find((card) => {
        const payload = parseCardPayload(card);
        return payload && (
          payload.canonicalId === requestedSymbol ||
          payload.urlSlug === requestedSymbol ||
          slugifyPathSegment(payload.canonicalId) === slugifyPathSegment(requestedSymbol) ||
          slugifyPathSegment(payload.label) === slugifyPathSegment(requestedSymbol)
        );
      });

      if (matchingButton) {
        matchingButton.click();
        return;
      }
    }

    if (firstPayload) {
      applySymbolPayload(firstPayload);
    }
  });

  document.querySelectorAll('[data-hotspot-explorer]').forEach((explorer) => {
    const title = explorer.querySelector('[data-hotspot-title]');
    const copy = explorer.querySelector('[data-hotspot-copy]');
    const focus = explorer.querySelector('[data-hotspot-focus]');
    const meaning = explorer.querySelector('[data-hotspot-meaning]');
    const derivation = explorer.querySelector('[data-hotspot-derivation]');
    const attribution = explorer.querySelector('[data-hotspot-attribution]');

    explorer.querySelectorAll('[data-hotspot]').forEach((button) => {
      button.addEventListener('click', () => {
        const payload = parseJsonOr(button.dataset.hotspot || '', null, { context: 'hotspot payload' });

        if (!payload) {
          return;
        }

        explorer.querySelectorAll('[data-hotspot]').forEach((hotspot) => {
          hotspot.classList.remove('is-active');
          hotspot.setAttribute('aria-pressed', 'false');
        });

        button.classList.add('is-active');
        button.setAttribute('aria-pressed', 'true');

        if (title) title.textContent = payload.title || '';
        if (copy) copy.textContent = payload.copy || '';
        if (focus) focus.textContent = payload.focus || '';
        if (meaning) meaning.textContent = payload.meaning || '';
        if (derivation) derivation.textContent = payload.derivation || '';
        if (attribution) attribution.textContent = payload.attribution || '';
      });
    });
  });

  const prefersReducedMotion = Boolean(
    window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches
  );
  const focusFeedbackPanel = (target) => {
    if (!target || typeof target.scrollIntoView !== 'function') {
      return;
    }

    target.setAttribute('tabindex', '-1');
    target.scrollIntoView({
      block: 'start',
      behavior: prefersReducedMotion ? 'auto' : 'smooth',
    });

    window.requestAnimationFrame(() => {
      if (typeof target.focus === 'function') {
        target.focus({ preventScroll: true });
      }
    });
  };

  document.querySelectorAll('[data-spiral-demo]').forEach((demo) => {
    const output = demo.querySelector('[data-spiral-demo-output]');
    const core = demo.querySelector('[data-demo-core]');
    const playButton = demo.querySelector('[data-spiral-demo-play]');
    const resetButton = demo.querySelector('[data-spiral-demo-reset]');
    const inputs = {
      signal: demo.querySelector('[data-spiral-demo-input="signal"]'),
      feedback: demo.querySelector('[data-spiral-demo-input="feedback"]'),
      cycles: demo.querySelector('[data-spiral-demo-input="cycles"]'),
    };
    const nodes = Array.from(demo.querySelectorAll('[data-demo-node]'));
    const playLabel = playButton ? playButton.textContent : t('demoPlayLabel', 'Play Demo');
    const pauseLabel = t('demoPauseLabel', 'Pause');
    let step = 0;
    let timer = null;

    const getValue = (key, fallback) => {
      const value = Number.parseInt(`${inputs[key] ? inputs[key].value : ''}`, 10);
      return Number.isFinite(value) ? value : fallback;
    };
    const score = () => Math.round((getValue('signal', 7) * 0.45) + (getValue('feedback', 6) * 0.35) + (getValue('cycles', 4) * 0.2));
    const setPlaying = (isPlaying) => {
      demo.classList.toggle('is-playing', isPlaying);
      if (playButton) {
        playButton.textContent = isPlaying ? pauseLabel : playLabel;
        playButton.setAttribute('aria-pressed', isPlaying ? 'true' : 'false');
      }
    };
    const stop = () => {
      if (timer) {
        window.clearInterval(timer);
        timer = null;
      }
      setPlaying(false);
    };
    const render = () => {
      const activeIndex = step % Math.max(1, nodes.length);
      const cycles = getValue('cycles', 4);
      const signal = getValue('signal', 7);
      const feedback = getValue('feedback', 6);
      const currentScore = score();
      const cycleNumber = Math.min(cycles, Math.floor(step / Math.max(1, nodes.length)) + 1);
    const activeLabel = nodes[activeIndex] ? nodes[activeIndex].textContent.trim() : t('pageHomeDemoPatternFallback', 'Pattern');

      demo.style.setProperty('--spiral-signal', `${signal / 10}`);
      demo.style.setProperty('--spiral-feedback', `${feedback / 10}`);
      nodes.forEach((node, index) => {
        const isActive = index === activeIndex;
        node.classList.toggle('is-active', isActive);
        node.setAttribute('aria-current', isActive ? 'step' : 'false');
      });

      if (core) {
        core.textContent = `${currentScore}`;
      }
      if (output) {
        output.textContent = `Cycle ${cycleNumber}/${cycles}: ${activeLabel} is active. Clarity ${signal}/10, feedback ${feedback}/10, refinement score ${currentScore}.`;
      }
    };
    const advance = () => {
      const maxSteps = Math.max(1, getValue('cycles', 4) * Math.max(1, nodes.length));
      step = (step + 1) % maxSteps;
      render();

      if (step === maxSteps - 1) {
        stop();
      }
    };

    Object.values(inputs).forEach((input) => {
      if (!input) {
        return;
      }

      input.addEventListener('input', () => {
        step = 0;
        render();
      });
    });

    if (playButton) {
      playButton.addEventListener('click', () => {
        if (timer) {
          stop();
          return;
        }

        if (prefersReducedMotion) {
          advance();
          return;
        }

        setPlaying(true);
        timer = window.setInterval(advance, 720);
      });
    }

    if (resetButton) {
      resetButton.addEventListener('click', () => {
        stop();
        step = 0;
        if (inputs.signal) inputs.signal.value = '7';
        if (inputs.feedback) inputs.feedback.value = '6';
        if (inputs.cycles) inputs.cycles.value = '4';
        render();
      });
    }

    render();
  });

  const activateTabGroup = (group, target) => {
    if (!group || !target) {
      return false;
    }

    const triggers = Array.from(group.querySelectorAll('[data-tab-trigger]'));
    const panels = Array.from(group.querySelectorAll('[data-tab-panel]'));
    let didActivate = false;

    triggers.forEach((item) => {
      const active = item.dataset.tabTrigger === target;
      item.classList.toggle('is-active', active);
      item.setAttribute('aria-pressed', active ? 'true' : 'false');

      if (item.getAttribute('role') === 'tab') {
        item.setAttribute('aria-selected', active ? 'true' : 'false');
        item.setAttribute('tabindex', active ? '0' : '-1');
      }

      if (active) {
        didActivate = true;
      }
    });

    panels.forEach((panel) => {
      const active = panel.dataset.tabPanel === target;
      panel.classList.toggle('is-active', active);
      panel.hidden = !active;
    });

    return didActivate;
  };

  document.querySelectorAll('[data-tab-group]').forEach((group) => {
    group.querySelectorAll('[data-tab-trigger]').forEach((trigger) => {
      trigger.addEventListener('click', () => {
        activateTabGroup(group, trigger.dataset.tabTrigger || '');
      });
    });
  });

  document.querySelectorAll('[data-tab-open]').forEach((control) => {
    control.addEventListener('click', (event) => {
      const target = `${control.dataset.tabOpen || ''}`.trim();
      if (!target) {
        return;
      }

      const groupTarget = `${control.dataset.tabGroupTarget || ''}`.trim();
      const container = groupTarget ? document.querySelector(groupTarget) : control.closest('[data-tab-group]');
      const group = container
        ? (container.matches('[data-tab-group]') ? container : container.querySelector('[data-tab-group]'))
        : null;

      if (!group) {
        return;
      }

      event.preventDefault();
      const didActivate = activateTabGroup(group, target);
      if (!didActivate) {
        return;
      }

      const scrollTarget = container || group;