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

Everything your quality system needs, and more.

A growing suite of connected modules, all validated, all on Atlassian.

{window.MODULES.map((m, i) => { const [bg, fg] = window.TINT[m.tint]; const isOpen = open.has(i); return (
toggle(i)} role={expandable ? 'button' : undefined} tabIndex={expandable ? 0 : undefined} onKeyDown={(e) => {if (expandable && (e.key === 'Enter' || e.key === ' ')) {e.preventDefault();toggle(i);}}}>
{m.tile ? : }

{m.title}

{m.blurb}

{expandable && <>
    {m.feats.map((f) =>
  • {f}
  • )}
{isOpen ? 'Show less' : 'What\u2019s inside'} }
); })}

Matching process documentation with every app

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.

); } /* ---------------- 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 (
{t.q ? : t.l} {t.nm}
); } 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 ( <>
{window.FLOW.map((col, ci) =>
{col.n}{col.stage}
{col.tools.map((t, ti) => )} {ci < window.FLOW.length - 1 && }
)}

Every tool in your workflow feeds compliance evidence to Qity — automatically.

); } 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 ( <>
{TOOLS.map((t, i) =>
{t.img ? {t.nm} : {t.l}}
)} {NODES.map((n, i) =>
{n.icon ? : } {n.label}
)}
); } 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 ( <>
{Array.from({ length: 2 }).map((_, i) =>
)} {TOOLS.map((t) =>
{t.img ? {t.nm} : {t.l}}
)} {Array.from({ length: 2 }).map((_, i) =>
)}
Jira
{NODES.map((n, i) =>
nodeEls.current[i] = el}> {n.icon ? : } {n.label}
)}
); } function IntegrationGrid() { return (
{window.INTG_GROUPS.map((g) =>

{g.cat}

{g.tools.map((t) => )}
)}
); } function Integrations({ tweaks }) { const orbit = tweaks && tweaks.intgLayout === 'Jira orbit'; return (

Integrates with design tools.
Everything else lives in Jira.

Design tools, source control, CI pipelines, security scanners, all flow into Jira. Source code becomes a source of truth.

); } /* ---------------- USE CASES ---------------- */ function Shot({ shot, shotLabel, focal }) { const pan = focal === 'top right' ? '-19%' : '0%'; return (
{shotLabel}
); } function UCVisual({ kind, shot, shotLabel, focal }) { if (kind === 'shot') { return ; } if (kind === 'placeholder') { return (
{shotLabel}
Gantt charts, dashboards & management summaries — add this product screenshot and it drops straight in.
); } if (kind === 'ba') { return (
Before — scattered & manual
Traceability_v7_FINAL.xlsx SOP-draft (2).docx audit_evidence.zip emails / SharePoint
After — generated from Jira
Team works in Jira Live traceability Audit-ready
); } if (kind === 'flow') { return (
{[ { 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 (
{r.t}
{r.s}
Synced
); })}
); } // samd return (
git commit · sensor-driver
+ feat: ISO 14971 risk control
commit a1f9c2 · pushed to main
Auto-generated in Qity
Design doc Test report SBOM Vuln scan
); } 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 (

Quality for everyone.
Zero silos.

{window.USECASES.map((u, i) => )}
{uc.prob &&
{uc.prob}
}

{uc.title}

{uc.body}

); } /* ---------------- 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 (

Don't take our word for it

{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 (
!active && setI(logical)}>
"{d.quote}"
{d.avatar ? {d.name} : {d.name.split(' ').map((w) => w[0]).slice(0, 2).join('')}}
{d.name}
{d.role}
); })}
); } /* ---------------- CTA + DEMO FORM ---------------- */ const DEMO_HREF = 'mailto:info@qity.be?subject=Demo%20request'; function DemoForm() { return (

Book your demo

Send us a message and our team will get back to you within one business day to schedule a demo and walk through pricing.

Contact us
); } /* ---------------- FAQ ---------------- */ function FaqRow({ f, isOpen, onClick }) { return (
{f.a.split('\n').map((para, k) =>

{para}

)}
); } 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 = (
{cols.map((idxs, c) =>
{idxs.map((i) => toggleSet(i)} />)}
)}
); } else if (mode === 'Expand all') { body = (
{FAQS.length} questions
{FAQS.map((f, i) => toggleSet(i)} />)}
); } else if (mode === 'Side-by-side') { body = (
{FAQS.map((f, i) => )}

{FAQS[sel].q}

{FAQS[sel].a.split('\n').map((para, k) =>

{para}

)}
); } else { body = (
{FAQS.map((f, i) => setOpen(open === i ? -1 : i)} />)}
); } return (

Got questions?

{body}
); } function CTA({ formRef }) { return (

Ready to embed quality into your workflow?

Book a demo to find out how Qity and Atlassian can accelerate your team.

); } /* ---------------- CERTIFICATIONS ---------------- */ function Certs({ tweaks }) { const light = tweaks && tweaks.certsTheme === 'Light'; return (

Built for regulated industries

{window.STANDARDS.map((s) => {s.img ? : } {s.label} )}
); } /* ---------------- FOOTER ---------------- */ function Footer() { return ( ); } Object.assign(window, { Modules, Integrations, UseCases, Proof, FAQ, Certs, CTA, Footer, DemoForm });