/* app-sections-bottom.jsx — Modules, Integrations, UseCases, Proof, CTA form, Footer */

/* ---------------- MODULES ---------------- */
function Modules({ tweaks }) {
  const variant = tweaks.moduleStyle; // 'grid' | 'list' | 'compact'
  const expandable = variant !== 'compact';
  const [open, setOpen] = useState(() => new Set());
  const toggle = (i) => {
    if (!expandable) return;
    setOpen((prev) => {
      const n = new Set(prev);
      n.has(i) ? n.delete(i) : n.add(i);
      return n;
    });
  };
  return (
    <section className="sec" id="product">
      <div className="container">
        <div className="sec-head-row">
          <div className="section-head">
            <h2 className="h-sec">Everything your quality system needs, and more.</h2>
            <p className="lead" style={{ marginTop: 16 }}>A growing suite of connected modules, all validated, all on Atlassian.</p>
          </div>
        </div>
        <div className={'mod-wrap ' + variant}>
          {window.MODULES.map((m, i) => {
            const [bg, fg] = window.TINT[m.tint];
            const isOpen = open.has(i);
            return (
              <article
                key={m.title}
                className={'module reveal d' + i % 3 + (isOpen ? ' open' : '')}
                onClick={() => toggle(i)}
                role={expandable ? 'button' : undefined}
                tabIndex={expandable ? 0 : undefined}
                onKeyDown={(e) => {if (expandable && (e.key === 'Enter' || e.key === ' ')) {e.preventDefault();toggle(i);}}}>
                
                <div className="m-top">
                  <span className={'m-ico' + (m.tile ? ' m-ico-tile' : '')} style={m.tile ? {} : { background: bg, color: fg }}>
                    {m.tile ? <img src={asset('assets/modules/' + m.tile + '.png')} alt="" style={{ objectFit: "scale-down", width: "32px", height: "32px" }} /> : <Ico name={m.icon} />}
                  </span>
                  <h3>{m.title}</h3>
                </div>
                <p className="m-blurb">{m.blurb}</p>
                {expandable &&
                <>
                    <div className="m-more"><div>
                      <ul className="m-feats">
                        {m.feats.map((f) => <li key={f}><Ico name="Check" /> <span>{f}</span></li>)}
                      </ul>
                    </div></div>
                    <span className="m-toggle">{isOpen ? 'Show less' : 'What\u2019s inside'} <Ico name="ChevronDown" /></span>
                  </>
                }
              </article>);

          })}
        </div>
        <div className="mod-kit reveal" style={{ backgroundColor: "rgb(245, 250, 240)" }}>
          <span className="mk-illo">
            <svg viewBox="0 0 64 92" fill="none" aria-hidden="true">
              <rect x="16" y="6" width="40" height="52" rx="6" fill="#CDEFDD" />
              <rect x="8" y="18" width="40" height="52" rx="6" fill="#fff" stroke="#2FB874" strokeWidth="2" />
              <path d="M16 31h22M16 39h22M16 47h13" stroke="#2FB874" strokeWidth="2.4" strokeLinecap="round" />
              <circle cx="45" cy="64" r="13" fill="#2FB874" />
              <path d="m39 64 4 4 7-8" stroke="#fff" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
          </span>
          <div className="mk-copy">
            <h3>Matching process documentation with every app</h3>
            <p>Matching SOPs, Work Instructions and templates ship with each app — so startups can stand up a compliant quality system from day one, then tailor it as they grow.</p>
          </div>
        </div>
      </div>
    </section>);

}

/* ---------------- INTEGRATIONS ---------------- */
function useTravelingHighlight(count, ms) {
  const [idx, setIdx] = useState(0);
  useEffect(() => {
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const id = setInterval(() => setIdx((i) => (i + 1) % count), ms);
    return () => clearInterval(id);
  }, [count, ms]);
  return idx;
}

function ToolChip({ t, lit, className }) {
  return (
    <div className={'tool' + (lit ? ' lit' : '') + (className ? ' ' + className : '')}>
      <span className="t-ico" style={{ background: t.q ? 'var(--brand-deep)' : t.c }}>
        {t.q ? <QMark style={{ width: 13, height: 15, color: '#fff' }} /> : t.l}
      </span>
      {t.nm}
    </div>);

}

function IntegrationFlow() {
  const flat = [];
  window.FLOW.forEach((col, ci) => col.tools.forEach((t, ti) => flat.push({ ci, ti })));
  const idx = useTravelingHighlight(flat.length, 950);
  const litKey = flat[idx] ? flat[idx].ci + '-' + flat[idx].ti : '';
  return (
    <>
      <div className="flow">
        {window.FLOW.map((col, ci) =>
        <div key={col.stage} className={'flow-col' + (col.dest ? ' dest' : '')}>
            <div className="fc-stage"><span className="n">{col.n}</span>{col.stage}</div>
            {col.tools.map((t, ti) => <ToolChip key={t.nm} t={t} lit={litKey === ci + '-' + ti} />)}
            {ci < window.FLOW.length - 1 && <Ico name="ArrowRight" className="flow-arrow" />}
          </div>
        )}
      </div>
      <p className="flow-caption">Every tool in your workflow <b>feeds compliance evidence to Qity</b> — automatically.</p>
    </>);

}

function IntegrationTrace({ organic, wide }) {
  const ref = useRef(null);
  const VB = wide ? window.TRACE_VB_W : window.TRACE_VB;
  const TOOLS = wide ? window.TRACE_TOOLS_W : window.TRACE_TOOLS;
  const NODES = wide ? window.TRACE_NODES_W : window.TRACE_NODES;
  const amp = (organic || 0) / 100 * 48;
  const rnd = (s) => {const x = Math.sin(s) * 10000;return x - Math.floor(x);};
  const jit = (id, bx, by) => {
    if (!amp) return { x: bx, y: by };
    const s = [...id].reduce((a, c) => a + c.charCodeAt(0), 0);
    return { x: bx + (rnd(s) * 2 - 1) * amp, y: by + (rnd(s * 1.7 + 11) * 2 - 1) * amp };
  };
  const byId = {};
  TOOLS.forEach((t) => byId[t.id] = t);
  NODES.forEach((n) => byId[n.id] = { ...n, ...jit(n.id, n.x, n.y) });
  const edges = window.TRACE_EDGES;
  const SEQ = ['complaint', 'nonconformity', 'capa', 'risk', 'requirement', 'specification', 'test', 'document', 'release', 'change', 'stakeholder', 'bom', 'file'];
  const hi = useTravelingHighlight(SEQ.length, 1500);
  const activeId = SEQ[hi];
  useEffect(() => {
    const graph = ref.current;if (!graph) return;
    const stage = graph.parentElement;if (!stage) return;
    // only when the stage is horizontally scrollable (mobile)
    if (stage.scrollWidth - stage.clientWidth < 8) return;
    const node = byId[activeId];if (!node) return;
    const nodeX = node.x / VB.w * graph.clientWidth;
    // a lit node expands its box rightward to reveal the label (~box left -24px, right +220px),
    // so frame the whole expanded box — not just the anchor — or the label clips at the edges.
    const margin = 16;
    const boxL = nodeX - 24;
    const boxR = nodeX + 220;
    const view = stage.clientWidth;
    const maxScroll = stage.scrollWidth - view;
    let target;
    if (boxR - boxL >= view - margin * 2) {
      target = boxL - margin; // box wider than viewport: pin its left edge in view
    } else {
      target = (boxL + boxR) / 2 - view / 2; // center the full box span
    }
    target = Math.max(0, Math.min(maxScroll, target));
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {stage.scrollLeft = target;return;}
    const from = stage.scrollLeft;
    const dist = target - from;
    if (Math.abs(dist) < 2) return;
    const dur = 300;let t0 = null,raf = 0;
    const ease = (x) => 1 - Math.pow(1 - x, 3);
    const step = (ts) => {
      if (t0 === null) t0 = ts;
      const k = Math.min(1, (ts - t0) / dur);
      stage.scrollLeft = from + dist * ease(k);
      if (k < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [activeId]);
  useEffect(() => {
    const el = ref.current;if (!el) return;
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {el.style.setProperty('--p', 1);return;}
    el.style.setProperty('--p', 0);
    let raf = 0;
    const upd = () => {
      raf = 0;
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || 800;
      const start = vh * 0.95,end = vh * 0.42;
      const p = Math.max(0, Math.min(1, (start - r.top) / (start - end)));
      el.style.setProperty('--p', p.toFixed(3));
    };
    const onScroll = () => {if (!raf) raf = requestAnimationFrame(upd);};
    upd();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => {window.removeEventListener('scroll', onScroll);window.removeEventListener('resize', onScroll);cancelAnimationFrame(raf);};
  }, []);
  const pathOf = (a, b) => {
    const mx = (a.x + b.x) / 2;
    return 'M ' + a.x + ' ' + a.y + ' C ' + mx + ' ' + a.y + ', ' + mx + ' ' + b.y + ', ' + b.x + ' ' + b.y;
  };
  return (
    <>
      <div className={'tgraph' + (wide ? ' wide' : '')} ref={ref}>
        <svg className="tedges" viewBox={'0 0 ' + VB.w + ' ' + VB.h} preserveAspectRatio="xMidYMid meet" aria-hidden="true">
          <defs>
            <linearGradient id="rovoA" x1="0" y1="0" x2="1" y2="0">
              <stop offset="0%" stopColor="#00B8D9" /><stop offset="34%" stopColor="#1D7AFC" /><stop offset="67%" stopColor="#8270DB" /><stop offset="100%" stopColor="#DA62AC" />
            </linearGradient>
            <linearGradient id="rovoB" x1="0" y1="0" x2="1" y2="1">
              <stop offset="0%" stopColor="#2ABB7F" /><stop offset="40%" stopColor="#00B8D9" /><stop offset="100%" stopColor="#1D7AFC" />
            </linearGradient>
            <linearGradient id="rovoC" x1="0" y1="0" x2="1" y2="0">
              <stop offset="0%" stopColor="#DA62AC" /><stop offset="45%" stopColor="#8270DB" /><stop offset="100%" stopColor="#FF8F73" />
            </linearGradient>
          </defs>
          {edges.map(([f, t], i) => <path key={'b' + i} className="tedge" d={pathOf(byId[f], byId[t])} pathLength="100" />)}
          {edges.map(([f, t], i) => {
            const grad = ['url(#rovoA)', 'url(#rovoB)', 'url(#rovoC)'][i % 3];
            const glow = window.ROVO[i % window.ROVO.length];
            const on = f === activeId || t === activeId;
            return (
              <path
                key={'c' + i}
                className={'tflow' + (on ? ' on' : '')}
                d={pathOf(byId[f], byId[t])}
                pathLength="100"
                style={{ stroke: grad, color: glow, animationDelay: (-(i * 0.31 % 2)).toFixed(2) + 's', animationDuration: (1.5 + i % 5 * 0.22).toFixed(2) + 's' }} />);

          })}
        </svg>

        {TOOLS.map((t, i) =>
        <div key={t.id} className="tg-tool" style={{ left: byId[t.id].x / VB.w * 100 + '%', top: byId[t.id].y / VB.h * 100 + '%', '--i': i }}>
            {t.img ?
          <img className="tg-chip tg-chip-img" src={asset('assets/tools/' + t.img + '.webp')} alt={t.nm} /> :
          <span className="tg-chip" style={{ background: t.c }}>{t.l}</span>}
          </div>
        )}

        {NODES.map((n, i) =>
        <div key={n.id} className={'tg-node' + (n.lucide ? ' is-generic' : '') + (n.id === activeId ? ' lit' : '')} style={{ left: byId[n.id].x / VB.w * 100 + '%', top: byId[n.id].y / VB.h * 100 + '%', '--c': n.c, '--i': i }}>
            <span className="tg-box">
              <span className="tg-ico">
                {n.icon ? <img src={asset('assets/worktypes/' + n.icon + '.png')} alt="" /> : <Ico name={n.lucide} />}
              </span>
              <span className="tg-label">{n.label}</span>
            </span>
          </div>
        )}
      </div>
      <div className="ngraph-mobile"><IntegrationGrid /></div>
    </>);

}

function IntegrationOrbit({ size = 100, labels = false }) {
  const ref = useRef(null);
  const VB = { w: 820, h: 520 };
  const C = { x: 410, y: 306 }; // sphere centered; tools sit just above
  const R = 340 * (size / 100); // sphere radius
  const TOOLS = window.TRACE_TOOLS;
  const NODES = window.ORBIT_NODES;
  const N = NODES.length;

  const toolSet = useRef(null);
  if (!toolSet.current) toolSet.current = new Set(TOOLS.map((t) => t.id));
  // tools feed Requirement + Specification directly (lines run from each tool's
  // logo in the box); the rest is the traceability spine between work types.
  const TOOL_FEED = [['claude', 'requirement'], ['claude', 'standard'], ['altium', 'specification'], ['figma', 'specification'], ['aikido', 'vulnerability'], ['solidworks', 'file'], ['github', 'test'], ['bitbucket', 'test']];
  const SPINE = [['complaint', 'nonconformity'], ['nonconformity', 'capa'], ['capa', 'risk'], ['risk', 'requirement'], ['requirement', 'specification'], ['specification', 'test'], ['test', 'change'], ['change', 'batch']];
  const ORBIT_EDGES = [...TOOL_FEED, ...SPINE];

  // base unit positions on a sphere (fibonacci), decorrelated from array order
  // (stride coprime with N) so connected work types spread around the globe.
  const base = useRef(null);
  if (!base.current || base.current.length !== N) {
    const gold = Math.PI * (3 - Math.sqrt(5)),arr = new Array(N);
    for (let i = 0; i < N; i++) {
      const s = i * 19 % N;
      const yy = 1 - s / (N - 1) * 2;
      const rad = Math.sqrt(Math.max(0, 1 - yy * yy));
      const th = gold * s;
      arr[i] = { x: Math.cos(th) * rad, y: yy, z: Math.sin(th) * rad };
    }
    base.current = arr;
  }
  const idIndex = {};
  NODES.forEach((n, i) => idIndex[n.id] = i);

  // scroll reveal --p
  useEffect(() => {
    const el = ref.current;if (!el) return;
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {el.style.setProperty('--p', 1);return;}
    el.style.setProperty('--p', 0);
    let raf = 0;
    const upd = () => {
      raf = 0;
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || 800;
      const start = vh * 0.95,end = vh * 0.42;
      const p = Math.max(0, Math.min(1, (start - r.top) / (start - end)));
      el.style.setProperty('--p', p.toFixed(3));
    };
    const onScroll = () => {if (!raf) raf = requestAnimationFrame(upd);};
    upd();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => {window.removeEventListener('scroll', onScroll);window.removeEventListener('resize', onScroll);cancelAnimationFrame(raf);};
  }, []);

  const pathOf = (a, b) => {
    const mx = (a.x + b.x) / 2,my = (a.y + b.y) / 2;
    const cx = mx + (C.x - mx) * 0.18,cy = my + (C.y - my) * 0.18;
    return 'M ' + a.x.toFixed(1) + ' ' + a.y.toFixed(1) + ' Q ' + cx.toFixed(1) + ' ' + cy.toFixed(1) + ', ' + b.x.toFixed(1) + ' ' + b.y.toFixed(1);
  };
  const wirePath = '';

  const nodeEls = useRef([]);
  const lastActive = useRef([]);
  const baseEls = useRef([]);
  const flowEls = useRef([]);
  const scaleRef = useRef(1);
  const toolXY = useRef({});

  // VB units -> screen px + tool-logo positions (so lines start at each tool)
  useEffect(() => {
    const el = ref.current;if (!el) return;
    const measure = () => {
      const r = el.getBoundingClientRect();
      scaleRef.current = r.width / VB.w;
      const logos = el.querySelectorAll('.orbit-tool-logo');
      const xy = {};
      logos.forEach((lg, i) => {
        const id = TOOLS[i] && TOOLS[i].id;if (!id) return;
        const lr = lg.getBoundingClientRect();
        xy[id] = { x: (lr.left + lr.width / 2 - r.left) / r.width * VB.w, y: (lr.top + lr.height / 2 - r.top) / r.height * VB.h };
      });
      toolXY.current = xy;
    };
    measure();
    const ro = new ResizeObserver(measure);ro.observe(el);
    return () => ro.disconnect();
  }, []);

  // 3D rotation about a tilted vertical axis; depth drives scale + opacity. Work
  // types reveal one-by-one to demonstrate the scale of what the system manages.
  useEffect(() => {
    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    const period = 46000;
    const phi = 0.34,cphi = Math.cos(phi),sphi = Math.sin(phi);
    const tSet = toolSet.current;
    const pos = new Array(N);
    const lastZ = new Array(N).fill(-1);
    const firedState = new Array(N).fill(-1);
    const firedNow = new Array(N).fill(0);
    const fireT = new Array(ORBIT_EDGES.length).fill(-1e9);
    const FIRE_GAP = 1500,FIRE_DUR = 1500;
    const srcLast = {}; // source id -> last fire ts, so each source takes turns
    let lastFire = -1e9,raf = 0;
    const frame = (ts) => {
      const theta = ts % period / period * Math.PI * 2;
      const ct = Math.cos(theta),st = Math.sin(theta),sc = scaleRef.current;
      // all work types are present from the start; depth drives scale + opacity
      for (let i = 0; i < N; i++) {
        const b = base.current[i];
        const x1 = b.x * ct + b.z * st,z1 = -b.x * st + b.z * ct,y1 = b.y;
        const y2 = y1 * cphi - z1 * sphi,z2 = y1 * sphi + z1 * cphi;
        const front = (z2 + 1) / 2,persp = 1 + 0.16 * z2;
        const px = C.x + R * x1 * persp,py = C.y + R * y2 * persp;
        pos[i] = { px, py, front, on: true };
        const el = nodeEls.current[i];if (!el) continue;
        const s = 0.5 + 0.5 * front;
        el.style.transform = 'translate(' + (px * sc).toFixed(1) + 'px,' + (py * sc).toFixed(1) + 'px) translate(-50%,-50%) scale(' + s.toFixed(3) + ')';
        el.style.opacity = (0.16 + 0.84 * Math.pow(front, 1.3)).toFixed(3);
        const zb = 200 + Math.round(front * 40) * 15;
        if (zb !== lastZ[i]) {el.style.zIndex = zb;lastZ[i] = zb;}
      }
      // pathways fire one at a time, taking turns by source: among edges whose
      // target faces front, pick the one whose source fired longest ago. This
      // gives every tool an equal turn (Claude's two feeds no longer double its
      // rate) and avoids the same icon firing twice in a row.
      for (let i = 0; i < N; i++) firedNow[i] = 0;
      if (ts - lastFire >= FIRE_GAP) {
        let best = -1,bestT = Infinity;
        for (let cand = 0; cand < ORBIT_EDGES.length; cand++) {
          const cf = ORBIT_EDGES[cand][0],cP = pos[idIndex[ORBIT_EDGES[cand][1]]];
          const srcFront = tSet.has(cf) ? 1 : pos[idIndex[cf]] ? pos[idIndex[cf]].front : 0;
          if (cP && cP.front > 0.58 && srcFront > 0.5) {
            const lt = srcLast[cf] == null ? -1e9 : srcLast[cf];
            if (lt < bestT) {bestT = lt;best = cand;}
          }
        }
        if (best >= 0) {fireT[best] = ts;lastFire = ts;srcLast[ORBIT_EDGES[best][0]] = ts;}
      }
      for (let k = 0; k < ORBIT_EDGES.length; k++) {
        const baseEl = baseEls.current[k],flowEl = flowEls.current[k];
        if (!baseEl || !flowEl) continue;
        const age = ts - fireT[k];
        if (age < 0 || age > FIRE_DUR) {baseEl.style.opacity = '0';flowEl.style.opacity = '0';continue;}
        const f = ORBIT_EDGES[k][0],ti = idIndex[ORBIT_EDGES[k][1]],Pto = pos[ti];
        if (!Pto) {baseEl.style.opacity = '0';flowEl.style.opacity = '0';continue;}
        let fx, fy;
        if (tSet.has(f)) {const o = toolXY.current[f];if (!o) {baseEl.style.opacity = '0';flowEl.style.opacity = '0';continue;}fx = o.x;fy = o.y;} else
        {const Pf = pos[idIndex[f]];if (!Pf) {baseEl.style.opacity = '0';flowEl.style.opacity = '0';continue;}fx = Pf.px;fy = Pf.py;}
        let env = 1;
        if (age < 220) env = age / 220;else
        if (age > FIRE_DUR - 450) env = Math.max(0, (FIRE_DUR - age) / 450);
        const d = pathOf({ x: fx, y: fy }, { x: Pto.px, y: Pto.py });
        baseEl.setAttribute('d', d);flowEl.setAttribute('d', d);
        baseEl.style.opacity = (env * 0.26).toFixed(3);flowEl.style.opacity = env.toFixed(3);
        if (env > 0.5) firedNow[ti] = 1;
      }
      for (let i = 0; i < N; i++) {
        const el = nodeEls.current[i];if (!el) continue;
        const left = pos[i].px > C.x ? 1 : 0,key = firedNow[i] << 1 | left;
        if (firedState[i] !== key) {el.classList.toggle('fired', !!firedNow[i]);el.classList.toggle('lbl-left', !!left);firedState[i] = key;}
      }
    };
    if (reduce) {frame(2000);return;}
    const tick = (ts) => {frame(ts);raf = requestAnimationFrame(tick);};
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [size, N]);

  return (
    <>
      <div className={'tgraph orbit' + (labels ? ' show-labels' : '')} ref={ref}>
        <svg className="tedges" viewBox={'0 0 ' + VB.w + ' ' + VB.h} preserveAspectRatio="xMidYMid meet" aria-hidden="true">
          <defs>
            <linearGradient id="rovoA" x1="0" y1="0" x2="1" y2="0">
              <stop offset="0%" stopColor="#00B8D9" /><stop offset="34%" stopColor="#1D7AFC" /><stop offset="67%" stopColor="#8270DB" /><stop offset="100%" stopColor="#DA62AC" />
            </linearGradient>
            <linearGradient id="rovoB" x1="0" y1="0" x2="1" y2="1">
              <stop offset="0%" stopColor="#2ABB7F" /><stop offset="40%" stopColor="#00B8D9" /><stop offset="100%" stopColor="#1D7AFC" />
            </linearGradient>
            <linearGradient id="rovoC" x1="0" y1="0" x2="1" y2="0">
              <stop offset="0%" stopColor="#DA62AC" /><stop offset="45%" stopColor="#8270DB" /><stop offset="100%" stopColor="#FF8F73" />
            </linearGradient>
            <linearGradient id="rovoD" x1="0" y1="0" x2="1" y2="1">
              <stop offset="0%" stopColor="#00B8D9" /><stop offset="24%" stopColor="#1D7AFC" /><stop offset="48%" stopColor="#8270DB" /><stop offset="72%" stopColor="#DA62AC" /><stop offset="100%" stopColor="#FF8F73" />
            </linearGradient>
          </defs>
          {ORBIT_EDGES.map((e, i) =>
          <path key={'b' + i} className="tedge" ref={(el) => baseEls.current[i] = el} style={{ opacity: 0 }} pathLength="100" />
          )}
          {ORBIT_EDGES.map((e, i) =>
          <path
            key={'c' + i}
            className="tflow orbit-flow"
            ref={(el) => flowEls.current[i] = el}
            pathLength="100"
            style={{ stroke: 'url(#rovoD)', color: window.ROVO[i % window.ROVO.length], opacity: 0, animationDelay: (-(i * 0.31 % 2)).toFixed(2) + 's', animationDuration: (1.3 + i % 5 * 0.2).toFixed(2) + 's' }} />
          )}
        </svg>

        <div className="orbit-tools">
          <div className="orbit-tools-grid">
            {Array.from({ length: 2 }).map((_, i) =>
            <div key={'gl' + i} className="orbit-tool-row"><span className="orbit-tool-ghost" /></div>
            )}
            {TOOLS.map((t) =>
            <div key={t.id} className="orbit-tool-row">
                {t.img ?
              <img className="orbit-tool-logo" src={asset('assets/tools/' + t.img + '.webp')} alt={t.nm} /> :
              <span className="orbit-tool-logo lt" style={{ background: t.c }}>{t.l}</span>}
              </div>
            )}
            {Array.from({ length: 2 }).map((_, i) =>
            <div key={'gr' + i} className="orbit-tool-row"><span className="orbit-tool-ghost" /></div>
            )}
          </div>
        </div>

        <img className="orbit-core" src={asset('assets/jira.png')} alt="Jira" style={{ left: C.x / VB.w * 100 + '%', top: C.y / VB.h * 100 + '%', width: R * 0.5 / VB.w * 100 + '%' }} />
        <div className="orb-layer">
          {NODES.map((n, i) =>
          <div key={n.id} className="orb-item" ref={(el) => nodeEls.current[i] = el}>
              {n.icon ?
            <img src={asset('assets/worktypes/' + n.icon + '.png')} alt="" /> :
            <span className="orb-generic"><Ico name={n.lucide} /></span>}
              <span className="orb-label">{n.label}</span>
            </div>
          )}
        </div>
      </div>
      <div className="ngraph-mobile"><IntegrationGrid /></div>
    </>);

}

function IntegrationGrid() {
  return (
    <div className="intg-grid">
      {window.INTG_GROUPS.map((g) =>
      <div key={g.cat} className="intg-card">
          <p className="intg-cat">{g.cat}</p>
          {g.tools.map((t) => <ToolChip key={t.nm} t={t} lit={false} />)}
        </div>
      )}
    </div>);

}

function Integrations({ tweaks }) {
  const orbit = tweaks && tweaks.intgLayout === 'Jira orbit';
  return (
    <section className="sec" id="compliance" style={{ backgroundColor: "rgb(245, 250, 240)" }}>
      <div className="container">
        <div className="section-head center" style={{ textAlign: 'center' }}>
          <h2 className="h-sec">Integrates with <span style={{ color: '#46dd8f' }}>design tools</span>.<br />Everything else lives in <span style={{ color: '#46dd8f' }}>Jira</span>.</h2>
          <p className="lead" style={{ marginTop: 16, marginInline: 'auto' }}>Design tools, source control, CI pipelines, security scanners, all flow into Jira. Source code becomes a source of truth.

          </p>
        </div>
        <div className="intg-stage reveal">
          <IntegrationOrbit size={48} />
        </div>
      </div>
    </section>);

}

/* ---------------- USE CASES ---------------- */
function Shot({ shot, shotLabel, focal }) {
  const pan = focal === 'top right' ? '-19%' : '0%';
  return (
    <div className="window uc-shot">
      <div className="shot-wrap shot-tall"><img src={asset(shot)} alt={shotLabel} loading="lazy" style={{ '--pan': pan }} /></div>
    </div>);

}

function UCVisual({ kind, shot, shotLabel, focal }) {
  if (kind === 'shot') {
    return <Shot shot={shot} shotLabel={shotLabel} focal={focal} />;
  }
  if (kind === 'placeholder') {
    return (
      <div className="window uc-shot">
        <div className="shot-wrap shot-tall uc-shot-empty">
          <span className="uce-ico"><Ico name="Layers" /></span>
          <div className="uce-title">{shotLabel}</div>
          <div className="uce-sub">Gantt charts, dashboards &amp; management summaries — add this product screenshot and it drops straight in.</div>
        </div>
      </div>);

  }
  if (kind === 'ba') {
    return (
      <div className="ba">
        <div className="ba-card before">
          <div className="ba-h"><Ico name="FileX" /> Before — scattered &amp; manual</div>
          <div className="ba-files">
            <span className="ba-file x"><Ico name="FileText" /> Traceability_v7_FINAL.xlsx</span>
            <span className="ba-file x"><Ico name="FileText" /> SOP-draft (2).docx</span>
            <span className="ba-file x"><Ico name="FileText" /> audit_evidence.zip</span>
            <span className="ba-file x"><Ico name="FileText" /> emails / SharePoint</span>
          </div>
        </div>
        <div className="ba-card after">
          <div className="ba-h"><Ico name="CheckCircle" /> After — generated from Jira</div>
          <div className="ba-after-flow">
            <span className="ba-step"><Ico name="Workflow" /> Team works in Jira</span>
            <Ico name="ArrowRight" className="ba-arrow" />
            <span className="ba-step"><Ico name="Layers" /> Live traceability</span>
            <Ico name="ArrowRight" className="ba-arrow" />
            <span className="ba-step"><QMark style={{ width: 14, height: 16, color: 'var(--brand-deep)' }} /> Audit-ready</span>
          </div>
        </div>
      </div>);

  }
  if (kind === 'flow') {
    return (
      <div className="ba">
        {[
        { i: 'Ruler', c: 'B', t: 'Figma · Altium · Solidworks', s: 'Design artifacts linked' },
        { i: 'GitBranch', c: 'P', t: 'GitHub · CI/CD', s: 'Commits, builds & tests' },
        { i: 'Sparkles', c: 'G', t: 'Rovo agents', s: 'Specs & test docs drafted' }].
        map((r) => {
          const [bg, fg] = window.TINT[r.c];
          return (
            <div key={r.t} className="ba-card" style={{ background: '#fff', borderColor: 'var(--line)', display: 'flex', alignItems: 'center', gap: 14 }}>
              <span className="m-ico" style={{ width: 40, height: 40, borderRadius: 11, display: 'grid', placeItems: 'center', background: bg, color: fg, flex: 'none' }}><Ico name={r.i} /></span>
              <div><div style={{ fontWeight: 700, color: 'var(--navy)' }}>{r.t}</div><div style={{ fontSize: 13, color: 'var(--muted)' }}>{r.s}</div></div>
              <span className="loz loz-green" style={{ marginLeft: 'auto' }}>Synced</span>
            </div>);

        })}
      </div>);

  }
  // samd
  return (
    <div className="ba">
      <div className="ba-card" style={{ background: '#0B1B3A', borderColor: 'rgba(255,255,255,.08)' }}>
        <div className="ba-h" style={{ color: 'var(--brand-bright)' }}><Ico name="GitBranch" /> git commit · sensor-driver</div>
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 12.5, color: 'rgba(233,239,250,.82)', lineHeight: 1.7 }}>
          <div>+ feat: ISO 14971 risk control</div>
          <div style={{ color: 'rgba(233,239,250,.5)' }}>commit a1f9c2 · pushed to main</div>
        </div>
      </div>
      <div className="ba-after-flow" style={{ justifyContent: 'center' }}><Ico name="ArrowDown" className="ba-arrow" /></div>
      <div className="ba-card after">
        <div className="ba-h"><Ico name="CheckCircle" /> Auto-generated in Qity</div>
        <div className="ba-files">
          <span className="ba-file"><Ico name="FileCheck" style={{ color: 'var(--brand-deep)' }} /> Design doc</span>
          <span className="ba-file"><Ico name="ClipboardCheck" style={{ color: 'var(--brand-deep)' }} /> Test report</span>
          <span className="ba-file"><Ico name="Shield" style={{ color: 'var(--brand-deep)' }} /> SBOM</span>
          <span className="ba-file"><Ico name="AlertTriangle" style={{ color: 'var(--brand-deep)' }} /> Vuln scan</span>
        </div>
      </div>
    </div>);

}

function UseCases() {
  const [active, setActive] = useState(0);
  const [dir, setDir] = useState(1);
  const pick = (i) => {setDir(i >= active ? 1 : -1);setActive(i);};
  const uc = window.USECASES[active];
  return (
    <section className="sec" id="usecases" style={{ backgroundColor: "#f5faf0" }}>
      <div className="container">
        <div className="section-head">
          <h2 className="h-sec">Quality for everyone.<br /><span style={{ color: '#46dd8f' }}>Zero silos.</span></h2>
        </div>
        <div className="uc-box">
        <div className="uc-tabs" style={{ '--active': active, '--count': window.USECASES.length, padding: "6px", backgroundColor: "rgb(235, 240, 225)", borderWidth: "0px" }}>
          <span className="uc-tab-ind" aria-hidden="true"></span>
          {window.USECASES.map((u, i) => <button key={u.id} className={'uc-tab' + (i === active ? ' active' : '')} onClick={() => pick(i)}>
              <Ico name={u.tabIcon} /> {u.tab}
            </button>
            )}
        </div>
        <div className="uc-panel" key={uc.id} style={{ '--uc-dir': dir }}>
          <div className="uc-copy">
            {uc.prob && <div className="uc-prob">{uc.prob}</div>}
            <h3>{uc.title}</h3>
            <p className="uc-body">{uc.body}</p>
          </div>
          <div className="uc-visual"><UCVisual kind={uc.visual} shot={uc.shot} shotLabel={uc.shotLabel} focal={uc.focal} /></div>
        </div>
        </div>
      </div>
    </section>);

}

/* ---------------- PROOF ---------------- */
function KitStrip() {
  return null;
}

function Proof() {
  const items = window.TESTIMONIALS;
  const [i, setI] = useState(0);
  const many = items.length > 1;
  const t = items[i];
  const vpRef = useRef(null);
  const [m, setM] = useState({ vw: 0, slideW: 0 });
  useEffect(() => {
    const el = vpRef.current;if (!el) return;
    const measure = () => {
      const vw = el.clientWidth;
      const slideW = Math.min(620, Math.round(vw * (many ? 0.74 : 0.92)));
      setM({ vw, slideW });
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(el);
    return () => ro.disconnect();
  }, [many]);
  const GAP = 30;
  const go = (d) => setI((p) => (p + d + items.length) % items.length);
  const activePos = many ? i + 1 : i;
  // wrap-around clones so peeks are cyclic (last shows left of first, first shows right of last)
  const display = many ? [items[items.length - 1]].concat(items, [items[0]]) : items;
  const offset = m.vw ? (m.vw - m.slideW) / 2 - activePos * (m.slideW + GAP) : 0;
  return (
    <section className="sec" id="customers" style={{ backgroundColor: "rgb(245, 250, 240)" }}>
      <div className="container">
        <div className="section-head proof-head">
          <div>
            <h2 className="h-sec">Don't take our word for it</h2>
          </div>
          <div className="proof-nav">
            <button className="proof-arrow" onClick={() => go(-1)} disabled={!many} aria-label="Previous testimonial"><Ico name="ChevronLeft" /></button>
            <button className="proof-arrow" onClick={() => go(1)} disabled={!many} aria-label="Next testimonial"><Ico name="ChevronRight" /></button>
          </div>
        </div>

        <div className="tcar reveal" ref={vpRef}>
          <div
            className="tcar-track"
            style={{
              gap: GAP + 'px',
              justifyContent: many ? 'flex-start' : 'center',
              transform: many ? 'translateX(' + offset + 'px)' : 'none' }}>

            {display.map((d, p) => {
              const logical = many ? p === 0 ? items.length - 1 : p === display.length - 1 ? 0 : p - 1 : p;
              const active = p === activePos;
              return (
                <article
                  key={p}
                  className={'quote-card tcard' + (active ? ' is-active' : '')}
                  style={{ width: (m.slideW || 0) + 'px', backgroundColor: "rgb(255, 255, 255)" }}
                  aria-hidden={!active}
                  onClick={() => !active && setI(logical)}>

                  <span className="q-mark"><Ico name="Quote" /></span>
                  <blockquote>"{d.quote}"</blockquote>
                  <div className="q-author">
                    {d.avatar ?
                    <img className="q-ava" src={asset(d.avatar)} alt={d.name} /> :
                    <span className="q-ava q-ava-i" style={{ background: d.tint || 'var(--brand-deep)' }}>{d.name.split(' ').map((w) => w[0]).slice(0, 2).join('')}</span>}
                    <div>
                      <div className="q-name">{d.name}</div>
                      <div className="q-role">{d.role}</div>
                    </div>
                  </div>
                </article>);

            })}
          </div>
        </div>
      </div>
    </section>);

}

/* ---------------- CTA + DEMO FORM ---------------- */
const DEMO_HREF = 'mailto:info@qity.be?subject=Demo%20request';

function DemoForm() {
  return (
    <div className="form-card" style={{ backgroundColor: 'rgb(245, 250, 240)', display: 'flex', flexDirection: 'column', gap: 20, alignItems: 'flex-start' }}>
      <h3 className="display">Book your demo</h3>
      <p style={{ fontSize: 16, color: 'var(--muted)', lineHeight: 1.65 }}>
        Send us a message and our team will get back to you within one business day to schedule a demo and walk through pricing.
      </p>
      <a href={DEMO_HREF} className="btn btn-brand btn-lg btn-block" style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8, textDecoration: 'none' }}>
        Contact us <Ico name="ArrowRight" />
      </a>
    </div>
  );
}

/* ---------------- FAQ ---------------- */
function FaqRow({ f, isOpen, onClick }) {
  return (
    <div className={'faq-item' + (isOpen ? ' open' : '')}>
      <button className="faq-q" onClick={onClick} aria-expanded={isOpen}>
        <span>{f.q}</span>
        <Ico name="ChevronDown" />
      </button>
      <div className="faq-a-wrap"><div className="faq-a-inner">{f.a.split('\n').map((para, k) => <p key={k}>{para}</p>)}</div></div>
    </div>);

}

function FAQ({ tweaks }) {
  const mode = (tweaks && tweaks.faqStyle) || 'List';
  const FAQS = window.FAQS;

  const [open, setOpen] = useState(-1); // List: one at a time
  const [openSet, setOpenSet] = useState(() => new Set()); // Two columns + Expand all
  const [sel, setSel] = useState(0); // Side-by-side selection

  const toggleSet = (i) => setOpenSet((s) => {
    const n = new Set(s);
    n.has(i) ? n.delete(i) : n.add(i);
    return n;
  });
  const allOpen = openSet.size >= FAQS.length;
  const toggleAll = () => setOpenSet(allOpen ? new Set() : new Set(FAQS.map((_, i) => i)));

  let body;

  if (mode === 'Two columns') {
    const cols = [[], []];
    FAQS.forEach((f, i) => cols[i % 2].push(i)); // interleave for even column heights
    body = (
      <div className="faq-cols">
        {cols.map((idxs, c) =>
        <div className="faq-list" key={c}>
            {idxs.map((i) => <FaqRow key={i} f={FAQS[i]} isOpen={openSet.has(i)} onClick={() => toggleSet(i)} />)}
          </div>
        )}
      </div>);

  } else if (mode === 'Expand all') {
    body = (
      <div className="faq-narrow">
        <div className="faq-toolbar">
          <span className="faq-count">{FAQS.length} questions</span>
          <button className="faq-allbtn" onClick={toggleAll} aria-expanded={allOpen}>
            <span>{allOpen ? 'Collapse all' : 'Expand all'}</span>
            <Ico name="ChevronDown" className={allOpen ? 'flip' : ''} />
          </button>
        </div>
        <div className="faq-list">
          {FAQS.map((f, i) => <FaqRow key={i} f={f} isOpen={openSet.has(i)} onClick={() => toggleSet(i)} />)}
        </div>
      </div>);

  } else if (mode === 'Side-by-side') {
    body = (
      <div className="faq-split">
        <div className="faq-split-nav" role="tablist">
          {FAQS.map((f, i) =>
          <button key={i} className={'faq-navq' + (sel === i ? ' active' : '')}
          onClick={() => setSel(i)} role="tab" aria-selected={sel === i}>
              <span>{f.q}</span>
              <Ico name="ChevronRight" />
            </button>
          )}
        </div>
        <div className="faq-split-panel">
          <div className="faq-panel-card" key={sel}>
            <h3 className="faq-panel-q">{FAQS[sel].q}</h3>
            {FAQS[sel].a.split('\n').map((para, k) => <p key={k}>{para}</p>)}
          </div>
        </div>
      </div>);

  } else {
    body = (
      <div className="faq-narrow">
        <div className="faq-list">
          {FAQS.map((f, i) => <FaqRow key={i} f={f} isOpen={open === i} onClick={() => setOpen(open === i ? -1 : i)} />)}
        </div>
      </div>);

  }

  return (
    <section className="sec faq-sec" id="faq">
      <div className="container">
        <div className="section-head faq-head">
          <h2 className="h-sec" style={{ marginTop: 12 }}>Got questions?</h2>
        </div>
        {body}
      </div>
    </section>);

}

function CTA({ formRef }) {
  return (
    <section className="sec cta-sec" id="demo" ref={formRef}>
      <div className="container cta-grid">
        <div className="cta-copy">
          <h2 className="h-sec">Ready to embed quality into your workflow?</h2>
          <p className="lead">Book a demo to find out how Qity and Atlassian can accelerate your team.</p>
        </div>
        <DemoForm />
      </div>
    </section>);

}

/* ---------------- CERTIFICATIONS ---------------- */
function Certs({ tweaks }) {
  const light = tweaks && tweaks.certsTheme === 'Light';
  return (
    <section className={'certs-band ' + (light ? 'certs-light' : 'certs-dark')} style={{ backgroundColor: light ? '#f5faf0' : '#091e42' }}>
      <div className="container">
        <p className="certs-label"><span style={{ color: light ? 'var(--brand-deep)' : '#46dd8f' }}>Built for regulated industries</span></p>
        <div className="certs-row">
          {window.STANDARDS.map((s) =>
          <span key={s.label} className="cert">
              {s.img ? <img className={'cert-logo' + (s.img === 'eu' ? ' cert-logo-eu' : '')} src={asset('assets/certs/' + s.img + '.png')} alt="" /> : <Ico name={s.icon || 'Shield'} />}
              <span>{s.label}</span>
            </span>
          )}
        </div>
      </div>
    </section>);

}

/* ---------------- FOOTER ---------------- */
function Footer() {
  return (
    <footer className="footer footer-min">
      <div className="container">
        <div className="footer-bottom">
          <div style={{ display: 'flex', gap: 18, alignItems: 'center', flexWrap: 'wrap' }}>
            <span>© 2026 Qity. All rights reserved.</span>
            <a href="#" style={{ color: 'inherit' }}>Privacy</a>
          </div>
          <div className="footer-social">
            <a href="https://www.linkedin.com/company/qity/" target="_blank" rel="noopener noreferrer" aria-label="LinkedIn"><Ico name="Linkedin" /></a>
          </div>
        </div>
      </div>
    </footer>);

}

Object.assign(window, { Modules, Integrations, UseCases, Proof, FAQ, Certs, CTA, Footer, DemoForm });