// Apex Universe: the live sandbox for apex-fusion.odyssey-works.io (section "Apex Universe"). // A star system drawn from public data: AP3X is the star; PRIME, Vector and Nexus are worlds; // PRIME's 333 stake pools are its asteroid belt; the staking reserve is a moon filled to what is // left; agents fly as a fleet round Vector; bridges are jump gates; Cardano, Base and BNB Chain // are distant worlds; partners are beacons on the outer belt; comets bring value home. // Figures come from universe/data.json (refreshed daily) with the embedded snapshot as fallback. import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; import { OutputPass } from 'three/addons/postprocessing/OutputPass.js'; const COL = { bg: 0x050b09, ink: 0xecf4ee, ink2: 0xbfd4c7, emerald: 0x4bb87a, emeraldSoft: 0x7fd5a1, pine: 0x077368, vermilion: 0xf25041, ember: 0xe0623e, ignition: 0xf2a03c, orange: 0xf27b50, plasma: 0xffe0b8, azure: 0x5ba8e0, violet: 0x8b7fd5, gold: 0xf0b90b, eye: 0xff2a1a, }; const hex = c => '#' + c.toString(16).padStart(6, '0'); const fmt = (v, d = 0) => v == null || isNaN(v) ? '—' : Number(v).toLocaleString('en-US', { minimumFractionDigits: d, maximumFractionDigits: d }); const M = (v, d = 1) => v == null || isNaN(v) ? '—' : Math.abs(v) >= 1e9 ? fmt(v / 1e9, 2) + 'B' : Math.abs(v) >= 1e6 ? fmt(v / 1e6, d) + 'M' : Math.abs(v) >= 1e3 ? fmt(v / 1e3, d) + 'k' : fmt(v, 0); const usd = v => v == null ? '—' : '$' + M(v); const esc = s => String(s ?? '').replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); // Blender (z up) -> three (y up), so the live scene and the film share one layout const B = (x, y, z) => new THREE.Vector3(x, z, -y); // partners on the outer belt, grouped by what they do (logo file, name, role, link) const PARTNERS = [ ['Research', COL.violet, [['well-typed', 'Well-Typed', 'Ouroboros and protocol engineering behind Vector', 'https://well-typed.com'], ['pnsol', 'PNSol', 'Network performance science', 'https://pnsol.com'], ['ensurable', 'Ensurable Systems', 'Formal methods and consensus research', 'https://ensurable.systems'], ['20squares', '20squares', 'Game theory and mechanism design', 'https://20squares.xyz'], ['txpipe', 'TxPipe', 'Open-source Cardano tooling; Vector validators', 'https://txpipe.io']]], ['Tooling & infrastructure', COL.azure, [['zeeve', 'Zeeve', 'Enterprise blockchain infrastructure', 'https://www.zeeve.io'], ['tachyon', 'Tachyon', 'Exchange and matching-engine technology', 'https://tachyon.ae'], ['mlabs', 'MLabs', 'High-assurance Haskell and Cardano engineering', 'https://mlabs.city'], ['anastasia-labs', 'Anastasia Labs', 'Smart-contract audits and development', 'https://anastasialabs.com']]], ['Wallets', COL.emerald, [['eternl', 'Eternl', 'Cardano light wallet with AP3X on PRIME', 'https://eternl.io'], ['gero', 'GeroWallet', 'Wallet for Cardano, Midnight and Apex Fusion', 'https://gerowallet.io'], ['route3', 'Route3', 'Mobile wallet listed for AP3X', 'https://www.route3.co']]], ['Studios', COL.violet, [['mvp-workshop', 'MVP Workshop', 'Web3 product studio', 'https://mvpworkshop.co'], ['thespian', 'Thespian', 'Software development and consulting', 'https://thespian.eu'], ['t-minus-one', 'T Minus One', 'Swiss IT consulting and engineering', 'https://tminusone.pro']]], ['Games & media', COL.orange, [['dega', 'DEGA', 'Tools for Web3 game developers', 'https://www.dega.org'], ['cornucopias', 'Cornucopias', 'Open-world MMO', 'https://www.cornucopias.io'], ['mandala', 'The Mandala Project', 'A Conjunction Studios project', 'https://www.enterthemandala.com'], ['the-fuse', 'The Fuse', 'Video-first Web3 news', 'https://th3fus3.com']]], ]; export async function mount(root, opts = {}) { const base = opts.base || 'universe/'; const $ = sel => root.querySelector(sel); const stage = $('.au-stage'); const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches; // ------------------------------------------------------------------ data let D = {}; try { D = JSON.parse(($('#au-data') || {}).textContent || '{}'); } catch (e) { D = {}; } try { const r = await fetch(base + 'data.json', { cache: 'no-cache' }); if (r.ok) D = await r.json(); } catch (e) { /* the embedded snapshot stands */ } const P = D.pools || {}, BR = D.bridges || {}, R = D.rewards || {}, A = D.agents || {}, MK = D.markets || {}; fillFigures(root, D); // ------------------------------------------------------------------ hero film const video = $('.au-hero-video'); if (video) { if (reduced) { video.removeAttribute('autoplay'); video.pause(); } root.querySelectorAll('[data-au="watch"]').forEach(b => b.addEventListener('click', () => { video.currentTime = 0; video.muted = true; video.play().catch(() => {}); (video.requestFullscreen || video.webkitRequestFullscreen || (() => {})).call(video); })); } root.querySelectorAll('[data-au="enter"]').forEach(b => b.addEventListener('click', () => { stage.scrollIntoView({ behavior: reduced ? 'auto' : 'smooth', block: 'center' }); boot(); })); // the 3D scene boots when the stage first comes into view (the hub stays light until then) let api = null; const io = new IntersectionObserver(es => { if (es.some(e => e.isIntersecting)) boot(); }, { rootMargin: '200px' }); io.observe(stage); function boot() { if (api) return; try { api = sandbox(); } catch (e) { console.warn('[Apex Universe] 3D unavailable:', e); stage.classList.add('au-nogl'); api = { destroy() {} }; } } // ------------------------------------------------------------------ the sandbox function sandbox() { const host = $('.au-canvas'); const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: 'high-performance' }); renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); renderer.setSize(host.clientWidth, host.clientHeight); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.0; renderer.localClippingEnabled = true; host.appendChild(renderer.domElement); const labels = new CSS2DRenderer(); labels.setSize(host.clientWidth, host.clientHeight); labels.domElement.className = 'au-labels3d'; host.appendChild(labels.domElement); const scene = new THREE.Scene(); scene.background = new THREE.Color(COL.bg); const camera = new THREE.PerspectiveCamera(40, host.clientWidth / host.clientHeight, 0.1, 2000); const HOME = { pos: B(5, -60, 29), at: B(3, 0, 0) }; camera.position.copy(HOME.pos); const controls = new OrbitControls(camera, renderer.domElement); controls.target.copy(HOME.at); controls.enableDamping = true; controls.dampingFactor = 0.06; controls.minDistance = 3; controls.maxDistance = 220; controls.autoRotate = !reduced; controls.autoRotateSpeed = 0.18; const composer = new EffectComposer(renderer); composer.addPass(new RenderPass(scene, camera)); const bloom = new UnrealBloomPass(new THREE.Vector2(host.clientWidth, host.clientHeight), 0.6, 0.45, 0.32); composer.addPass(bloom); composer.addPass(new OutputPass()); const loader = new THREE.TextureLoader(); const tex = (f, srgb = true) => { const t = loader.load(base + f); if (srgb) t.colorSpace = THREE.SRGBColorSpace; return t; }; const glowTex = (() => { const c = document.createElement('canvas'); c.width = c.height = 128; const g = c.getContext('2d'); const r = g.createRadialGradient(64, 64, 0, 64, 64, 64); r.addColorStop(0, 'rgba(255,255,255,1)'); r.addColorStop(.2, 'rgba(255,255,255,.7)'); r.addColorStop(.5, 'rgba(255,255,255,.14)'); r.addColorStop(1, 'rgba(255,255,255,0)'); g.fillStyle = r; g.fillRect(0, 0, 128, 128); const t = new THREE.CanvasTexture(c); t.colorSpace = THREE.SRGBColorSpace; return t; })(); const pickables = []; const groups = { orbits: [], lanes: [], beacons: [], labels: [], comets: [], ships: [] }; const addTo = (g, o) => { groups[g].push(o); return o; }; // sky: the Blender-rendered panorama when present, a procedural field otherwise loader.load(base + 'sky.jpg', t => { t.mapping = THREE.EquirectangularReflectionMapping; t.colorSpace = THREE.SRGBColorSpace; scene.background = t; }, undefined, () => { const n = 5000, a = new Float32Array(n * 3); for (let i = 0; i < n; i++) { const u = Math.random() * 2 - 1, t = Math.random() * Math.PI * 2, r = 700; const s = Math.sqrt(1 - u * u); a.set([r * s * Math.cos(t), r * u, r * s * Math.sin(t)], i * 3); } const g = new THREE.BufferGeometry(); g.setAttribute('position', new THREE.BufferAttribute(a, 3)); scene.add(new THREE.Points(g, new THREE.PointsMaterial({ color: 0xeaf2ff, size: 1.3, sizeAttenuation: false, transparent: true, opacity: .8 }))); }); scene.add(new THREE.AmbientLight(0x6f8f86, 0.35)); // ---------------------------------------------------------------- helpers const lineLoop = (r, color, opacity, center = new THREE.Vector3(), tilt = null, seg = 256) => { const pts = []; for (let i = 0; i <= seg; i++) { const t = i / seg * Math.PI * 2; pts.push(new THREE.Vector3(r * Math.cos(t), 0, r * Math.sin(t))); } const l = new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), new THREE.LineBasicMaterial({ color, transparent: true, opacity, depthWrite: false })); l.position.copy(center); if (tilt) l.rotation.copy(tilt); return l; }; const fresnel = (color, power = 3.0, strength = 1.4) => new THREE.ShaderMaterial({ uniforms: { c: { value: new THREE.Color(color) }, p: { value: power }, s: { value: strength } }, vertexShader: 'varying vec3 vN; varying vec3 vV; void main(){ vec4 mv = modelViewMatrix*vec4(position,1.); vN = normalize(normalMatrix*normal); vV = normalize(-mv.xyz); gl_Position = projectionMatrix*mv; }', fragmentShader: 'uniform vec3 c; uniform float p; uniform float s; varying vec3 vN; varying vec3 vV; void main(){ float f = pow(1.0-abs(dot(vN,vV)), p); gl_FragColor = vec4(c*s, f); }', transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.FrontSide, }); const noiseTex = (c1, c2, c3, seed = 1, bands = 0) => { const w = 512, h = 256, cv = document.createElement('canvas'); cv.width = w; cv.height = h; const g = cv.getContext('2d'); const img = g.createImageData(w, h); const A = new THREE.Color(c1), Bc = new THREE.Color(c2), C = new THREE.Color(c3); const n = (x, y) => { const s = Math.sin(x * 12.9898 + y * 78.233 + seed * 3.7) * 43758.5453; return s - Math.floor(s); }; const sm = (x, y) => { const xi = Math.floor(x), yi = Math.floor(y), xf = x - xi, yf = y - yi, u = xf * xf * (3 - 2 * xf), v = yf * yf * (3 - 2 * yf); return (n(xi, yi) * (1 - u) + n(xi + 1, yi) * u) * (1 - v) + (n(xi, yi + 1) * (1 - u) + n(xi + 1, yi + 1) * u) * v; }; for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) { let f = 0, a = .5, fr = 4 / w * 8; for (let o = 0; o < 5; o++) { f += a * sm(x * fr * (o + 1) * 1.9, (y * (bands ? .25 : 1)) * fr * (o + 1) * 1.9); a *= .5; } const col = f < .5 ? A.clone().lerp(Bc, f * 2) : Bc.clone().lerp(C, (f - .5) * 2); const i = (y * w + x) * 4; img.data[i] = col.r * 255; img.data[i + 1] = col.g * 255; img.data[i + 2] = col.b * 255; img.data[i + 3] = 255; } g.putImageData(img, 0, 0); const t = new THREE.CanvasTexture(cv); t.colorSpace = THREE.SRGBColorSpace; return t; }; const planetMat = (key, c1, c2, c3, seed, bands) => { const m = new THREE.MeshStandardMaterial({ map: noiseTex(c1, c2, c3, seed, bands), roughness: .85, metalness: 0 }); loader.load(base + 'tex/' + key + '.jpg', t => { t.colorSpace = THREE.SRGBColorSpace; m.map = t; m.needsUpdate = true; }, undefined, () => {}); return m; }; // logo badges: a dark disc, a thin coloured rim and the logo, drawn once to a canvas const imgCache = {}; const loadImg = f => imgCache[f] || (imgCache[f] = new Promise(res => { const im = new Image(); im.onload = () => res(im); im.onerror = () => res(null); im.src = base + 'logos/' + f + '.png'; })); const badgeTex = (logo, ring, bare = false) => { const c = document.createElement('canvas'); c.width = c.height = 192; const g = c.getContext('2d'); const t = new THREE.CanvasTexture(c); t.colorSpace = THREE.SRGBColorSpace; if (!bare) { g.beginPath(); g.arc(96, 96, 88, 0, Math.PI * 2); g.fillStyle = 'rgba(8,18,14,.9)'; g.fill(); g.lineWidth = 5; g.strokeStyle = hex(ring); g.stroke(); } loadImg(logo).then(im => { if (!im) return; const box = bare ? 184 : 118; const k = Math.min(box / im.width, box / im.height); const w = im.width * k, h = im.height * k; g.drawImage(im, 96 - w / 2, 96 - h / 2, w, h); t.needsUpdate = true; }); return t; }; const badge = (info, logo, ring, size, bare = false, group = null) => { const s = new THREE.Sprite(new THREE.SpriteMaterial({ map: badgeTex(logo, ring, bare), transparent: true, depthWrite: false, sizeAttenuation: false })); s.scale.set(size, size, 1); s.renderOrder = 10; s.userData.info = info; pickables.push(s); scene.add(s); if (group) addTo(group, s); return s; }; const label = (text, sub, obj, cls = '') => { const el = document.createElement('div'); el.className = 'au-l3 ' + cls; el.innerHTML = esc(text) + (sub ? `${esc(sub)}` : ''); const o = new CSS2DObject(el); o.center.set(0.5, -0.9); obj.add(o); addTo('labels', o); return o; }; const hit = (obj, info) => { obj.userData.info = info; pickables.push(obj); return obj; }; // ---------------------------------------------------------------- the star const STAR = B(0, 8, 0); const starMat = new THREE.ShaderMaterial({ uniforms: { t: { value: 0 } }, vertexShader: 'varying vec3 vP; varying vec3 vN; varying vec3 vV; void main(){ vP = position; vec4 mv = modelViewMatrix*vec4(position,1.); vN = normalize(normalMatrix*normal); vV = normalize(-mv.xyz); gl_Position = projectionMatrix*mv; }', fragmentShader: `uniform float t; varying vec3 vP; varying vec3 vN; varying vec3 vV; float h(vec3 p){ return fract(sin(dot(p, vec3(12.9898,78.233,45.164)))*43758.5453); } float n(vec3 p){ vec3 i=floor(p), f=fract(p); f=f*f*(3.-2.*f); return mix(mix(mix(h(i),h(i+vec3(1,0,0)),f.x),mix(h(i+vec3(0,1,0)),h(i+vec3(1,1,0)),f.x),f.y),mix(mix(h(i+vec3(0,0,1)),h(i+vec3(1,0,1)),f.x),mix(h(i+vec3(0,1,1)),h(i+vec3(1,1,1)),f.x),f.y),f.z); } void main(){ vec3 p = vP*1.6 + vec3(t*.05, t*.03, 0.); float f = n(p)*.5 + n(p*2.1)*.3 + n(p*4.3)*.2; vec3 col = mix(vec3(1.0,.55,.18), vec3(1.0,.93,.78), smoothstep(.35,.75,f)); float rim = pow(1.-abs(dot(vN,vV)), 2.); gl_FragColor = vec4(col*(2.2 + rim*1.5), 1.); }`, }); const star = hit(new THREE.Mesh(new THREE.SphereGeometry(2.4, 64, 32), starMat), { id: 'ap3x' }); star.position.copy(STAR); scene.add(star); const halo = new THREE.Sprite(new THREE.SpriteMaterial({ map: glowTex, color: COL.ignition, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, opacity: .9 })); halo.scale.set(18, 18, 1); halo.position.copy(STAR); scene.add(halo); const sun = new THREE.PointLight(0xffe8cc, 2600, 0, 1.6); sun.position.copy(STAR); scene.add(sun); // corona crown: the nine public allocations const crown = new THREE.Group(); crown.position.copy(STAR); crown.rotation.set(THREE.MathUtils.degToRad(-18), 0, THREE.MathUtils.degToRad(8)); scene.add(crown); let a0 = -Math.PI / 2; (D.genesis || []).forEach((g, i) => { const span = Math.PI * 2 * (g.pct || 0) / 100; const arc = new THREE.Mesh(new THREE.TorusGeometry(4.3, i ? .06 : .09, 8, 96, span - .05), new THREE.MeshBasicMaterial({ color: [COL.plasma, COL.ignition, COL.orange][i % 3] })); arc.rotation.z = a0 + .025; crown.add(arc); a0 += span; hit(arc, { id: 'alloc', name: g.label, fig: `${M(g.ap3x, 0)} AP3X · ${g.pct}%`, text: 'One of the nine allocations of the fixed 3B supply in the public tokenomics; unlocks are linear.' }); }); // ---------------------------------------------------------------- worlds on their orbits const W = { prime: { ang: 215, r: 15, size: 2.2, c: ['#0B3B2C', '#077368', '#7FD5A1'], atmo: COL.emerald, period: 600 }, vector: { ang: 262, r: 22, size: 1.6, c: ['#8A2416', '#F25041', '#FFE0B8'], atmo: COL.vermilion, period: 1000 }, nexus: { ang: 318, r: 29, size: 1.8, c: ['#3B1F06', '#F27B50', '#FFD9A0'], atmo: COL.ignition, period: 1500, bands: 1 }, }; Object.entries(W).forEach(([k, w], i) => { w.group = new THREE.Group(); scene.add(w.group); w.mesh = hit(new THREE.Mesh(new THREE.SphereGeometry(w.size, 64, 32), planetMat(k, ...w.c, i + 1, w.bands)), { id: k }); w.group.add(w.mesh); const atmo = new THREE.Mesh(new THREE.SphereGeometry(w.size * 1.07, 48, 24), fresnel(w.atmo, 3.2, 1.6)); w.group.add(atmo); addTo('orbits', lineLoop(w.r, w.atmo, .28, STAR)); scene.add(groups.orbits[groups.orbits.length - 1]); }); const worldPos = (k, t) => { const w = W[k]; const ang = THREE.MathUtils.degToRad(w.ang) + t * Math.PI * 2 / w.period; return STAR.clone().add(B(w.r * Math.cos(ang), w.r * Math.sin(ang), 0)); }; // PRIME's asteroid belt: every registered stake pool (glowing = carries stake, size ~ stake) const items = P.items || []; const smax = Math.max(...items.map(p => p.s), 1); const belt = new THREE.Group(); belt.rotation.set(THREE.MathUtils.degToRad(-18), 0, THREE.MathUtils.degToRad(-8)); W.prime.group.add(belt); const rockGeo = (() => { const g = new THREE.IcosahedronGeometry(1, 1); const p = g.attributes.position; for (let i = 0; i < p.count; i++) { const k = .75 + .45 * ((Math.sin(i * 91.7) * 43758.5) % 1 + 1) % 1; p.setXYZ(i, p.getX(i) * k, p.getY(i) * k, p.getZ(i) * k); } g.computeVertexNormals(); return g; })(); const live = items.filter(p => p.a), dark = items.filter(p => !p.a); const beltLive = new THREE.InstancedMesh(rockGeo, new THREE.MeshStandardMaterial({ color: 0x2a3a33, emissive: COL.emeraldSoft, emissiveIntensity: 1.3, roughness: .8 }), live.length); const beltDark = new THREE.InstancedMesh(rockGeo, new THREE.MeshStandardMaterial({ color: 0x3a403d, roughness: .95 }), Math.max(dark.length, 1)); const m4 = new THREE.Matrix4(), q = new THREE.Quaternion(), e = new THREE.Euler(), s3 = new THREE.Vector3(), v3 = new THREE.Vector3(); const golden = Math.PI * (3 - Math.sqrt(5)); let li = 0, di = 0; items.forEach((p, i) => { const r = 3.4 + 2.0 * ((i * 0.618034) % 1), t = i * golden * 7.3; v3.set(r * Math.cos(t), (((i * 0.381966) % 1) - .5) * .22, r * Math.sin(t)); e.set(i, i * 2, i * 3); q.setFromEuler(e); const sz = p.a ? .04 + .13 * Math.sqrt(p.s / smax) : .03; s3.set(sz, sz, sz); m4.compose(v3, q, s3); if (p.a) { beltLive.setMatrixAt(li, m4); beltLive.userData[li++] = p; } else { beltDark.setMatrixAt(di, m4); beltDark.userData[di++] = p; } }); beltDark.count = di; belt.add(beltLive, beltDark); hit(beltLive, { id: 'pool' }); hit(beltDark, { id: 'pool' }); [3.3, 5.5].forEach(r => belt.add(lineLoop(r, COL.emerald, .18))); // the Reserve moon: glass, filled to what is left of the staking reserve const MR = .72, fill = Math.max(0, Math.min(1, (R.reserve_left || 600e6) / 600e6)); const moon = new THREE.Group(); W.prime.group.add(moon); const glass = hit(new THREE.Mesh(new THREE.SphereGeometry(MR, 48, 24), fresnel(COL.ink, 2.2, 1.3)), { id: 'moon' }); moon.add(glass); let lo = -MR, hi = MR, hgt = 0; for (let k = 0; k < 40; k++) { hgt = (lo + hi) / 2; const cap = Math.PI * (MR + hgt) ** 2 * (3 * MR - (MR + hgt)) / 3 / (4 / 3 * Math.PI * MR ** 3); if (cap < fill) lo = hgt; else hi = hgt; } const clip = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0); const liquid = new THREE.Mesh(new THREE.SphereGeometry(MR * .94, 48, 24), new THREE.MeshBasicMaterial({ color: COL.emeraldSoft, transparent: true, opacity: .85, clippingPlanes: [clip] })); moon.add(liquid); const menis = new THREE.Mesh(new THREE.CircleGeometry(Math.sqrt(Math.max(0, (MR * .94) ** 2 - (hgt * .94) ** 2)), 48), new THREE.MeshBasicMaterial({ color: COL.emeraldSoft, transparent: true, opacity: .95, side: THREE.DoubleSide })); menis.rotation.x = -Math.PI / 2; menis.position.y = hgt * .94; moon.add(menis); const pulse = new THREE.Mesh(new THREE.RingGeometry(.9, 1.0, 64), new THREE.MeshBasicMaterial({ color: COL.emeraldSoft, transparent: true, opacity: 0, side: THREE.DoubleSide, depthWrite: false })); pulse.rotation.x = -Math.PI / 2; W.prime.group.add(pulse); // the Foundation's ring station and LBank's dock in PRIME orbit const hull = c => new THREE.MeshStandardMaterial({ color: c, metalness: .85, roughness: .3 }); const station = new THREE.Group(); W.prime.group.add(station); station.add(new THREE.Mesh(new THREE.TorusGeometry(.62, .05, 12, 64), hull(0xc9d6cf))); station.add(new THREE.Mesh(new THREE.CylinderGeometry(.14, .14, .5, 16), hull(0xc9d6cf))); for (let i = 0; i < 4; i++) { const sp = new THREE.Mesh(new THREE.BoxGeometry(.62, .03, .03), hull(0xc9d6cf)); sp.rotation.y = i * Math.PI / 4; station.add(sp); } station.add(new THREE.Mesh(new THREE.TorusGeometry(.66, .012, 6, 96), new THREE.MeshBasicMaterial({ color: COL.emeraldSoft }))); station.rotation.x = 1.2; hit(station, { id: 'foundation' }); const dock = new THREE.Group(); W.prime.group.add(dock); dock.add(new THREE.Mesh(new THREE.BoxGeometry(.9, .24, .34), hull(0x8fa39a))); [-1, 1].forEach(sx => { const pn = new THREE.Mesh(new THREE.BoxGeometry(.8, .02, .36), new THREE.MeshStandardMaterial({ color: 0x1c3a52, emissive: 0x0b2236, metalness: .4 })); pn.position.x = sx * .9; dock.add(pn); }); hit(dock, { id: 'lbank' }); // ---------------------------------------------------------------- gates const gate = (id, color, locked, size) => { const g = new THREE.Group(); scene.add(g); g.add(new THREE.Mesh(new THREE.TorusGeometry(size, .09, 12, 96), hull(0x7e918a))); for (let i = 0; i < 8; i++) { const c = new THREE.Mesh(new THREE.BoxGeometry(.16, .3, .26), hull(0x7e918a)); c.position.set(Math.cos(i * Math.PI / 4) * size * 1.06, Math.sin(i * Math.PI / 4) * size * 1.06, 0); c.rotation.z = i * Math.PI / 4; g.add(c); } g.add(new THREE.Mesh(new THREE.TorusGeometry(size * .97, .03, 8, 128), new THREE.MeshBasicMaterial({ color }))); const portalMat = new THREE.ShaderMaterial({ uniforms: { t: { value: 0 }, c: { value: new THREE.Color(color) } }, transparent: true, depthWrite: false, side: THREE.DoubleSide, blending: THREE.AdditiveBlending, vertexShader: 'varying vec2 vU; void main(){ vU = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }', fragmentShader: 'uniform float t; uniform vec3 c; varying vec2 vU; void main(){ vec2 p = vU-.5; float r = length(p)*2.; float a = atan(p.y,p.x); float s = .5+.5*sin(a*3.+r*9.-t*2.); float f = smoothstep(1.,.2,r)*(.35+.65*s); gl_FragColor = vec4(c*f*1.6, f*.85); }' }); const portal = new THREE.Mesh(new THREE.CircleGeometry(size * .95, 64), portalMat); g.add(portal); const core = new THREE.Mesh(new THREE.SphereGeometry(.1 + .4 * Math.sqrt(Math.max(locked || 0, 0) / 120e6), 24, 12), new THREE.MeshBasicMaterial({ color: COL.plasma })); g.add(core); hit(portal, { id }); return { g, portal, mat: portalMat }; }; const GATES = { reactor: gate('reactor', COL.plasma, BR.reactor, 1.25), layerzero: gate('layerzero', COL.ignition, BR.layerzero, .95), skyline: gate('skyline', COL.azure, BR.skyline, 1.1) }; GATES.skyline.g.position.copy(STAR.clone().add(B(-31, -8, 1.5))); // ---------------------------------------------------------------- distant worlds, venues as moons const FAR = { cardano: { pos: STAR.clone().add(B(-40, 10, -4)), size: 3.0, c: ['#0B1E4A', '#2C5CC5', '#CFE1FF'], atmo: COL.azure }, base: { pos: STAR.clone().add(B(39, 14, 5)), size: 2.8, c: ['#06123A', '#0052FF', '#9DB9FF'], atmo: COL.azure, bands: 1 }, bsc: { pos: STAR.clone().add(B(44, -6, -2)), size: 1.7, c: ['#3A2A00', '#F0B90B', '#FFF2B0'], atmo: COL.gold }, }; Object.entries(FAR).forEach(([k, w], i) => { w.mesh = hit(new THREE.Mesh(new THREE.SphereGeometry(w.size, 64, 32), planetMat(k, ...w.c, 7 + i, w.bands)), { id: k }); w.mesh.position.copy(w.pos); scene.add(w.mesh); const at = new THREE.Mesh(new THREE.SphereGeometry(w.size * 1.06, 48, 24), fresnel(w.atmo, 3.0, 1.4)); at.position.copy(w.pos); scene.add(at); }); const ringTilt = new THREE.Euler(THREE.MathUtils.degToRad(70 - 90), THREE.MathUtils.degToRad(12), 0); const depth = MK.base_depth_usd || 0; for (let k = 0; k < 9; k++) addTo('orbits', scene.add(lineLoop(3.7 + k * (0.06 + Math.sqrt(depth) / 9000), COL.azure, .35 + .04 * k, FAR.base.pos, ringTilt)) && scene.children[scene.children.length - 1]); // ---------------------------------------------------------------- Vector's fleet and the HAL 8 mothership const nShips = Math.min(A.identities || 36, 2000), nLive = A.production || 7; const shipGeo = new THREE.ConeGeometry(.05, .16, 3); shipGeo.rotateX(Math.PI / 2); const fleet = new THREE.InstancedMesh(shipGeo, new THREE.MeshStandardMaterial({ color: 0xb9c9c1, metalness: .7, roughness: .35, emissive: 0x223029 }), nShips); const orbitsF = []; for (let i = 0; i < nShips; i++) orbitsF.push({ r: 2.5 + 2.0 * ((i * 0.618034) % 1), ph: i * golden * 3.1, inc: THREE.MathUtils.degToRad(-25 + 50 * ((i * 0.381966) % 1)), node: i * .122, w: .9 / (2.5 + 2.0 * ((i * 0.618034) % 1)), live: i < nLive }); fleet.instanceColor = new THREE.InstancedBufferAttribute(new Float32Array(nShips * 3), 3); const cLive = new THREE.Color(COL.emeraldSoft).multiplyScalar(3), cShip = new THREE.Color(0xb9c9c1); orbitsF.forEach((o, i) => fleet.setColorAt(i, o.live ? cLive : cShip)); W.vector.group.add(fleet); hit(fleet, { id: 'fleet' }); addTo('ships', fleet); const ms = new THREE.Group(); W.vector.group.add(ms); const msb = new THREE.Mesh(new THREE.BoxGeometry(.5, .34, 2.4), hull(0xd5ddd9)); ms.add(msb); const msn = new THREE.Mesh(new THREE.ConeGeometry(.26, .8, 6), hull(0xd5ddd9)); msn.rotation.x = Math.PI / 2; msn.position.z = 1.55; ms.add(msn); [-1, 1].forEach(sx => { const wg = new THREE.Mesh(new THREE.BoxGeometry(.7, .06, .9), hull(0xd5ddd9)); wg.position.set(sx * .55, 0, -.6); ms.add(wg); const en = new THREE.Mesh(new THREE.SphereGeometry(.11, 16, 8), new THREE.MeshBasicMaterial({ color: 0xff3b24 })); en.position.set(sx * .18, 0, -1.3); ms.add(en); }); const msRing = new THREE.Mesh(new THREE.TorusGeometry(.72, .025, 8, 96), new THREE.MeshBasicMaterial({ color: COL.eye })); msRing.position.z = -.3; ms.add(msRing); const eye = new THREE.Mesh(new THREE.SphereGeometry(.14, 16, 8), new THREE.MeshBasicMaterial({ color: COL.eye })); eye.position.set(0, .2, 1.05); ms.add(eye); hit(msb, { id: 'hal8' }); ms.position.copy(B(-3.4, -3.0, 1.3)); ms.lookAt(new THREE.Vector3()); // ---------------------------------------------------------------- comets: value coming home const comets = []; Object.entries(BR.back_30d || {}).sort().forEach(([src, amount], i) => { const g = new THREE.Group(); scene.add(g); addTo('comets', g); const head = hit(new THREE.Mesh(new THREE.SphereGeometry(.18, 16, 8), new THREE.MeshBasicMaterial({ color: COL.plasma })), { id: 'comet', src, amount }); g.add(head); const tail = new THREE.Mesh(new THREE.ConeGeometry(.55, 5 + i * 1.5, 24, 1, true), new THREE.ShaderMaterial({ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, side: THREE.DoubleSide, uniforms: { c: { value: new THREE.Color(src === 'skyline' ? COL.azure : COL.plasma) } }, vertexShader: 'varying float vY; void main(){ vY = uv.y; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }', fragmentShader: 'uniform vec3 c; varying float vY; void main(){ gl_FragColor = vec4(c*1.4, pow(vY,1.6)*.8); }' })); tail.userData.len = 5 + i * 1.5; g.add(tail); comets.push({ g, tail, ph: i * 2.1, src }); }); // ---------------------------------------------------------------- the outer belt and its beacons { const n = 1600, arr = new Float32Array(n * 3); for (let i = 0; i < n; i++) { const t = Math.random() * Math.PI * 2, r = 47 + (Math.random() - .5) * 3; arr.set([STAR.x + r * Math.cos(t), (Math.random() - .5) * .8, STAR.z + r * Math.sin(t)], i * 3); } const g = new THREE.BufferGeometry(); g.setAttribute('position', new THREE.BufferAttribute(arr, 3)); scene.add(new THREE.Points(g, new THREE.PointsMaterial({ color: COL.ink2, size: .12, map: glowTex, transparent: true, depthWrite: false, opacity: .8 }))); } const beaconList = []; { const total = PARTNERS.reduce((a, g) => a + g[2].length, 0) + PARTNERS.length - 1; let slot = 0; PARTNERS.forEach(([grp, col, list]) => { list.forEach(([logo, name, role, link]) => { const t = THREE.MathUtils.degToRad(28 + (152 - 28) * slot / (total - 1)); const b = badge({ id: 'partner', name, role, link, grp, logo }, 'partners/' + logo, col, .034, false, 'beacons'); b.position.copy(STAR.clone().add(B(47 * Math.cos(t), 47 * Math.sin(t), 2.2))); beaconList.push(b); slot++; }); slot++; }); } // ---------------------------------------------------------------- badges on the named bodies const T1 = .05, T2 = .04, T3 = .03; const BADGES = []; const attach = (id, logo, ring, size, follow, offset, name, sub, bare = false) => { const b = badge({ id }, logo, ring, size, bare); BADGES.push({ b, follow, offset }); if (name) label(name, sub, b); return b; }; attach('ap3x', 'ap3x', COL.ignition, T1, () => STAR, B(0, 0, 5.4), 'AP3X', '3B · fixed'); attach('prime', 'prime', COL.emerald, T1, () => W.prime.group.position, B(0, 0, 2.9), 'PRIME', `${fmt(P.registered)} pools`); attach('vector', 'vector', COL.vermilion, T1, () => W.vector.group.position, B(0, 0, 4.6), 'Vector', `${fmt(A.identities)} agents`); attach('nexus', 'nexus', COL.ignition, T1, () => W.nexus.group.position, B(0, 0, 2.7), 'Nexus', 'EVM'); attach('hal8', 'hal8-eye', COL.eye, T2 * 1.3, () => W.vector.group.position.clone().add(ms.position), B(0, 0, 1.7), 'HAL 8', 'mothership', true); attach('foundation', 'apex-fusion', COL.emerald, T2, () => station.getWorldPosition(new THREE.Vector3()), B(0, 0, 1.2), 'Foundation', 'InFusion'); attach('lbank', 'lbank', COL.azure, T2, () => dock.getWorldPosition(new THREE.Vector3()), B(0, 0, 1.2), 'LBank', 'AP3X/USDT'); attach('moon', 'ap3x', COL.emeraldSoft, T3, () => moon.getWorldPosition(new THREE.Vector3()), B(0, 0, 1.3), 'Reserve', `${Math.round(fill * 100)}% full`); attach('reactor', 'apex-fusion-white', COL.plasma, T2, () => GATES.reactor.g.position, B(0, 0, 2.0), 'Reactor', `${M(BR.reactor)} locked`); attach('ethernal', 'partners/ethernal', COL.plasma, T3, () => GATES.reactor.g.position, B(1.8, 0.4, 2.2)); attach('layerzero', 'layerzero-white', COL.ignition, T2, () => GATES.layerzero.g.position, B(0, 0, 1.8), 'LayerZero', `${M(BR.layerzero)} locked`); attach('skyline', 'skyline', COL.azure, T2, () => GATES.skyline.g.position, B(0, 0, 2.0), 'Skyline', `${M(BR.skyline)} locked`); attach('cardano', 'cardano', COL.azure, T1, () => FAR.cardano.pos, B(0, 0, 4.2), 'Cardano', 'cAP3X'); attach('base', 'base', COL.azure, T1, () => FAR.base.pos, B(0, 0, 4.0), 'Base', 'bAP3X'); attach('bsc', 'bnb', COL.gold, T1, () => FAR.bsc.pos, B(0, 0, 2.6), 'BNB Chain', 'bnAP3X'); attach('usdc', 'usdc', COL.azure, T3, () => W.nexus.group.position, B(2.2, -1.6, 1.3)); attach('stargate', 'stargate', COL.ignition, T3, () => W.nexus.group.position, B(3.2, -0.4, 0.3)); attach('tenderly', 'partners/tenderly', COL.ignition, T3, () => W.nexus.group.position, B(-1.8, -2.2, 1.6)); attach('origintrail', 'origintrail', COL.vermilion, T3, () => W.vector.group.position, B(3.8, -1.6, 1.2)); attach('kash', 'kash', COL.vermilion, T3, () => W.vector.group.position, B(2.8, 2.6, -0.6)); [['aerodrome', 'aerodrome'], ['uniswap', 'uniswap'], ['quickswap', 'quickswap'], ['merkl', 'merkl']].forEach(([id, lg], i) => { const t = THREE.MathUtils.degToRad(200 + i * 42); attach('venue:' + id, lg, COL.azure, T3, () => FAR.base.pos, B(4.8 * Math.cos(t), 4.8 * Math.sin(t), -1.4 + i * .5)); }); [['cap3x', 'cap3x'], ['cstap3x', 'cstap3x'], ['minswap', 'minswap'], ['vyfi', 'partners/vyfi'], ['steelswap', 'partners/steelswap'], ['charli3', 'partners/charli3']].forEach(([id, lg], i) => { const t = THREE.MathUtils.degToRad(-60 + i * 36); attach('venue:' + id, lg, COL.azure, T3, () => FAR.cardano.pos, B(5.2 * Math.cos(t), 5.2 * Math.sin(t), -1 + i * .45)); }); // ---------------------------------------------------------------- lanes: particles on curves between moving ends const lanes = []; const lane = (id, color, value, ends, fixedN = null) => { const n = fixedN ?? (value > 0 ? Math.round(Math.max(14, Math.min(160, 14 + 30 * (Math.log10(value) - 3)))) : 0); const geo = new THREE.BufferGeometry(); const lg = new THREE.BufferGeometry(); const pts = new THREE.Points(geo, new THREE.PointsMaterial({ color, size: .34, map: glowTex, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending })); geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(Math.max(n, 1) * 3), 3)); const line = new THREE.Line(lg, new THREE.LineBasicMaterial({ color, transparent: true, opacity: .28, depthWrite: false })); scene.add(pts, line); addTo('lanes', pts); addTo('lanes', line); const L = { id, n, ends, pts, line, phase: Float32Array.from({ length: n }, (_, i) => (i + Math.random() * .6) / Math.max(n, 1)) }; lanes.push(L); return L; }; const out = BR.out_30d || {}; const vOf = k => W[k].group.position; lane('rewards', COL.emeraldSoft, R.per_epoch, () => { const m = moon.getWorldPosition(new THREE.Vector3()), p = vOf('prime'); return [m, m.clone().lerp(p, .3).add(new THREE.Vector3(0, 1.2, 0)), p.clone().add(m.clone().sub(p).normalize().multiplyScalar(3.8))]; }); lane('infusion', COL.emerald, 168e6, () => { const s = station.getWorldPosition(new THREE.Vector3()), p = vOf('prime'); return [s, s.clone().lerp(p, .4).add(new THREE.Vector3(0, .8, 0)), p.clone().add(s.clone().sub(p).normalize().multiplyScalar(4.2))]; }); lane('prime_reactor', COL.plasma, 5e5, () => [vOf('prime'), vOf('prime').clone().lerp(GATES.reactor.g.position, .5).add(new THREE.Vector3(0, 1.4, 0)), GATES.reactor.g.position]); lane('reactor_vector', COL.vermilion, (out.vector || 0) + 2e5, () => [GATES.reactor.g.position, GATES.reactor.g.position.clone().lerp(vOf('vector'), .5).add(new THREE.Vector3(0, 1, 0)), vOf('vector')]); lane('reactor_nexus', COL.ignition, out.nexus, () => [GATES.reactor.g.position, GATES.reactor.g.position.clone().lerp(vOf('nexus'), .5).add(new THREE.Vector3(0, 3.5, 0)), vOf('nexus')]); lane('nexus_lz', COL.ignition, 1e5, () => [vOf('nexus'), vOf('nexus').clone().lerp(GATES.layerzero.g.position, .5).add(new THREE.Vector3(0, .8, 0)), GATES.layerzero.g.position]); lane('lz_base', COL.ignition, BR.layerzero, () => [GATES.layerzero.g.position, GATES.layerzero.g.position.clone().lerp(FAR.base.pos, .5).add(new THREE.Vector3(0, 4, 0)), FAR.base.pos]); lane('lz_bsc', COL.gold, 0, () => [GATES.layerzero.g.position, GATES.layerzero.g.position.clone().lerp(FAR.bsc.pos, .5).add(new THREE.Vector3(0, 1.5, 0)), FAR.bsc.pos], 0); lane('prime_skyline', COL.azure, 3e5, () => [vOf('prime'), vOf('prime').clone().lerp(GATES.skyline.g.position, .5).add(new THREE.Vector3(0, 2, 0)), GATES.skyline.g.position]); lane('sky_cardano', COL.azure, out.cardano, () => [GATES.skyline.g.position, GATES.skyline.g.position.clone().lerp(FAR.cardano.pos, .5).add(new THREE.Vector3(0, 2, 0)), FAR.cardano.pos]); lane('serve', COL.eye, null, () => { const a = W.vector.group.position.clone().add(ms.position); return [a, a.clone().lerp(vOf('vector'), .5).add(new THREE.Vector3(0, 1.2, 0)), vOf('vector')]; }, 18); const curve = new THREE.CatmullRomCurve3([new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()]); // ---------------------------------------------------------------- the inspector and its catalogue const Pc = P.registered, Pa = P.active; const INFO = { ap3x: () => ({ name: 'AP3X', kind: 'The star', logo: 'ap3x', rows: [['Supply', '3,000,000,000 · fixed'], ['Staking reserve', `${M(R.reserve_left)} left`], ['Price', D.price_usd ? `$${fmt(D.price_usd, 4)}` : '—']], text: 'The native coin, minted only on PRIME. Its corona is the nine public allocations.', go: 'Markets' }), prime: () => ({ name: 'PRIME', kind: 'Anchor world', logo: 'prime', rows: [['Asteroid belt', `${Pc} pools · ${Pa} producing`], ['Mass in the belt', `${M(P.active_stake)} AP3X staked`], ['Reserve moon', `${M(R.reserve_left)} of 600M left`], ['Falls each epoch', `${M(R.per_epoch)} AP3X`], ['Network yield', R.yield_network ? `${(R.yield_network * 100).toFixed(1)}% a year` : '—']], text: 'A Cardano-derived proof-of-stake L1 and the only issuer of AP3X. Stake is never locked.', go: 'Staking' }), vector: () => ({ name: 'Vector', kind: 'Agent world', logo: 'vector', rows: [['Agent identities', fmt(A.identities)], ['Commercial agents', `${fmt(A.production)} (${fmt(A.via_hal8)} via HAL 8)`], ['Escrow transactions', fmt(A.escrow_txs)], ['Arrived in 30 days', `${M(out.vector)} AP3X`]], text: 'The settlement layer for AI agents: bonded escrow, staked reputation, disputes, signed receipts.', go: 'Agent Stack' }), nexus: () => ({ name: 'Nexus', kind: 'EVM world', logo: 'nexus', rows: [['Arrived in 30 days', `${M(out.nexus)} AP3X`], ['LayerZero lockbox', `${M(BR.layerzero)} AP3X`], ['Chain id', '9069']], text: 'The EVM chain with AP3X as gas; home of the Reputation System and the gateway to Base and BNB Chain.', go: 'Developers' }), moon: () => ({ name: 'Staking reserve', kind: 'The moon', logo: 'ap3x', rows: [['Still inside', `${M(R.reserve_left)} of 600M`], ['Paid last epoch', `${M(R.per_epoch)} AP3X`], ['Emitted so far', `${M(R.emitted)} AP3X`]], text: 'Filled to what is left. Each epoch a pulse of rewards falls into the belt.', go: 'Staking' }), pool: p => ({ name: p.t || 'Stake pool', kind: 'Asteroid · stake pool', logo: 'prime', rows: [['Stake', `${M(p.s)} AP3X`], ['Delegations', fmt(p.d)], ['Saturation', `${fmt(p.sat, 1)}%`], ['Blocks made', fmt(p.b)]], text: p.a ? 'Glowing: this pool carries stake and makes blocks.' : 'Dark rock: registered, without stake.', go: 'Staking' }), foundation: () => ({ name: 'Apex Fusion Foundation', kind: 'Ring station', logo: 'apex-fusion', rows: [['InFusion', '720k / 480k AP3X per pool'], ['By design', `${M((D.infusion || {}).max_ap3x, 0)} AP3X`], ['Share of supply', '13.33%']], text: 'The Swiss foundation that stewards the protocol and issues AP3X.', go: 'Staking' }), lbank: () => ({ name: 'LBank', kind: 'Trading dock', logo: 'lbank', rows: [['Pair', 'AP3X / USDT'], ['Last', MK.lbank ? `$${fmt(MK.lbank.last, 4)}` : '—'], ['Top-5 depth', MK.lbank ? usd(MK.lbank.depth_usd) : '—']], text: 'The centralised market; deposits arrive as native PRIME AP3X.', go: 'Exchanges' }), reactor: () => ({ name: 'Reactor', kind: 'Jump gate', logo: 'apex-fusion-white', rows: [['Locked on PRIME', `${M(BR.reactor)} AP3X`], ['To Nexus, 30 days', `${M(out.nexus)} AP3X`], ['To Vector, 30 days', `${M(out.vector)} AP3X`], ['Built by', 'Ethernal']], text: 'The native bridge between PRIME, Vector and Nexus.', go: 'Bridges' }), layerzero: () => ({ name: 'LayerZero', kind: 'Jump gate', logo: 'layerzero-white', rows: [['Lockbox on Nexus', `${M(BR.layerzero)} AP3X`], ['Out to', 'Base (bAP3X) · BNB Chain (bnAP3X)']], text: 'The OFT standard: every bAP3X and bnAP3X is minted against AP3X locked here.', go: 'Bridges' }), skyline: () => ({ name: 'Skyline', kind: 'Jump gate', logo: 'skyline', rows: [['Locked on PRIME', `${M(BR.skyline)} AP3X`], ['To Cardano, 30 days', `${M(out.cardano)} AP3X`], ['Built by', 'Ethernal']], text: 'Carries AP3X to Cardano as cAP3X, redeemable 1:1.', go: 'Bridges' }), hal8: () => ({ name: 'HAL 8', kind: 'Mothership', logo: 'hal8-eye', rows: [['Models on Vector', fmt(A.via_hal8)], ['Built', 'The Apex Fusion blockchain']], text: 'The Abu Dhabi AI lab whose inference platform serves models to agents on the Vector marketplace.', go: 'HAL 8' }), fleet: () => ({ name: 'Agents on Vector', kind: 'The fleet', logo: 'vector', rows: [['Identities', fmt(A.identities)], ['Commercial (lit)', fmt(A.production)], ['Escrow transactions', fmt(A.escrow_txs)], ['MCP tools', fmt(A.mcp_tools)]], text: (A.pilot || '') + '.', go: 'Agent Stack' }), cardano: () => ({ name: 'Cardano', kind: 'Distant world', logo: 'cardano', rows: [['Via', 'Skyline'], ['Forms', 'cAP3X · cstAP3X'], ['Pools', usd(MK.cardano_depth_usd)]], text: 'Reached through Skyline; staked through VyFi.', go: 'Bridges' }), base: () => ({ name: 'Base', kind: 'Distant world', logo: 'base', rows: [['bAP3X pools', usd(MK.base_depth_usd)], ['24h volume', usd(MK.base_vol24_usd)], ['Venues', 'Aerodrome · Uniswap · QuickSwap']], text: 'Home of bAP3X and its main DEX liquidity; its rings widen with depth.', go: 'Markets' }), bsc: () => ({ name: 'BNB Chain', kind: 'Distant world', logo: 'bnb', rows: [['Form', 'bnAP3X'], ['Liquidity', 'none yet']], text: 'The same OFT as bAP3X, deployed without a pool.', go: 'Exchanges' }), comet: o => ({ name: 'Value coming home', kind: 'Comet', logo: 'ap3x', rows: [['Released by', o.src === 'skyline' ? 'Skyline' : 'Reactor'], ['Last 30 days', `${M(o.amount)} AP3X`]], text: 'AP3X released back to PRIME from the other chains.', go: 'Bridges' }), alloc: o => ({ name: o.name, kind: 'Allocation', logo: 'ap3x', rows: [['Size', o.fig]], text: o.text, go: 'Overview' }), partner: o => ({ name: o.name, kind: o.grp, logo: 'partners/' + o.logo, rows: [], text: o.role, link: o.link }), }; const VENUES = { aerodrome: ['Aerodrome', 'bAP3X/USDC on Base'], uniswap: ['Uniswap', 'bAP3X/AERO v4 on Base'], quickswap: ['QuickSwap', 'bAP3X/cbBTC on Base'], merkl: ['Merkl', 'Incentives on the Base launch'], cap3x: ['cAP3X', 'AP3X on Cardano via Skyline'], cstap3x: ['cstAP3X', 'Staked cAP3X via VyFi'], minswap: ['Minswap', 'cAP3X/ADA'], vyfi: ['VyFinance', 'ADA/cstAP3X; the Bar'], steelswap: ['Steelswap', 'Cardano DEX aggregator'], charli3: ['Charli3', "Cardano's oracle"], usdc: ['USDC.e', 'Stargate USDC on Nexus'], stargate: ['Stargate', 'The stablecoin rail to Nexus'], tenderly: ['Tenderly', 'EVM developer platform on Nexus'], origintrail: ['OriginTrail', 'Holds the agent pilot knowledge graph'], kash: ['KASH', 'Vector utility token'], ethernal: ['Ethernal', 'Built Reactor and Skyline'] }; const infoFor = u => { if (!u) return null; if (u.id && u.id.startsWith('venue:')) { const k = u.id.slice(6); const [n, t] = VENUES[k] || [k, '']; return { name: n, kind: 'Venue', logo: k === 'vyfi' || k === 'steelswap' || k === 'charli3' ? 'partners/' + k : k, rows: [], text: t, go: 'Exchanges' }; } if (VENUES[u.id]) { const [n, t] = VENUES[u.id]; return { name: n, kind: 'Partner', logo: u.id === 'tenderly' || u.id === 'ethernal' ? 'partners/' + u.id : u.id, rows: [], text: t }; } const f = INFO[u.id]; return f ? f(u.pool || u) : null; }; const insp = $('.au-insp'); const showInsp = (u, pick) => { const i = infoFor(u); if (!i) return; insp.innerHTML = `
${esc(i.kind || '')}
${esc(i.name)}
` + (i.rows.length ? `
${i.rows.map(([k, v]) => `
${esc(k)}${esc(v)}
`).join('')}
` : '') + `

${esc(i.text || '')}

` + (pick ? 'Fly there' : '') + (i.go ? `Open ${esc(i.go)} →` : '') + (i.link ? `Visit →` : '') + '
'; insp.classList.add('on'); insp.querySelector('.au-x').onclick = () => insp.classList.remove('on'); const fb = insp.querySelector('[data-au="fly"]'); if (fb) fb.onclick = () => flyTo(pick); const ob = insp.querySelector('[data-au="open"]'); if (ob) ob.onclick = () => openSection(i.go); const cr = $('.au-crumb'); if (cr) cr.innerHTML = `AP3X system › ${esc(i.name)}`; }; const openSection = name => { const el = [...document.querySelectorAll('.nav-list button.nav-item')].find(n => (n.querySelector('.label') || n).textContent.trim().toLowerCase() === name.toLowerCase()); if (el) { el.click(); window.scrollTo({ top: 0 }); } }; // ---------------------------------------------------------------- picking, hover, fly-to const ray = new THREE.Raycaster(), mouse = new THREE.Vector2(); const tip = $('.au-tip'); const pick = ev => { const r = renderer.domElement.getBoundingClientRect(); mouse.set(((ev.clientX - r.left) / r.width) * 2 - 1, -((ev.clientY - r.top) / r.height) * 2 + 1); ray.setFromCamera(mouse, camera); const h = ray.intersectObjects(pickables.filter(o => o.visible !== false), true)[0]; if (!h) return null; const o = h.object; if ((o === beltLive || o === beltDark) && h.instanceId != null) return { u: { id: 'pool', pool: o.userData[h.instanceId] }, obj: o, point: h.point }; let n = o; while (n && !(n.userData && n.userData.info)) n = n.parent; return n ? { u: n.userData.info, obj: n, point: h.point } : null; }; renderer.domElement.addEventListener('pointermove', ev => { const h = pick(ev); renderer.domElement.style.cursor = h ? 'pointer' : 'grab'; if (!tip) return; const i = h && infoFor(h.u); if (!i) { tip.hidden = true; return; } tip.innerHTML = `${esc(i.name)}${esc(i.kind || '')}${i.rows[0] ? ' · ' + esc(i.rows[0][1]) : ''}`; const r = stage.getBoundingClientRect(); tip.style.left = (ev.clientX - r.left + 14) + 'px'; tip.style.top = (ev.clientY - r.top + 10) + 'px'; tip.hidden = false; }); renderer.domElement.addEventListener('pointerleave', () => { if (tip) tip.hidden = true; }); let downAt = 0; renderer.domElement.addEventListener('pointerdown', () => { downAt = performance.now(); controls.autoRotate = false; stopTour(); }); renderer.domElement.addEventListener('click', ev => { if (performance.now() - downAt > 300) return; const h = pick(ev); if (h) { showInsp(h.u, h.point); flyTo(h.point); } }); let anim = null; const ease = t => t < .5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; const flyTo = (point, dist = 9, ms = 1400) => { if (!point) return; const dir = camera.position.clone().sub(point).normalize(); const to = point.clone().add(dir.multiplyScalar(dist)).add(new THREE.Vector3(0, dist * .3, 0)); go(to, point.clone(), ms); }; const go = (pos, at, ms) => { if (reduced) { camera.position.copy(pos); controls.target.copy(at); return; } anim = { p0: camera.position.clone(), t0: controls.target.clone(), p1: pos, t1: at, s: performance.now(), ms }; }; // ---------------------------------------------------------------- HUD: toggles, time, search, tour root.querySelectorAll('.au-toggles .pill').forEach(p => p.addEventListener('click', () => { p.classList.toggle('on'); const on = p.classList.contains('on'); const g = groups[p.dataset.g] || []; g.forEach(o => { o.visible = on; if (o.element) o.element.style.display = on ? '' : 'none'; }); })); let speed = reduced ? 0 : 1, simT = 0; root.querySelectorAll('.au-time [data-sp]').forEach(b => b.addEventListener('click', () => { speed = Number(b.dataset.sp); root.querySelectorAll('.au-time [data-sp]').forEach(x => x.classList.toggle('on', x === b)); })); const ep = $('.au-epoch'), bar = $('.au-bar i'); const epochInfo = () => { const start = Date.UTC(2026, 8, 25, 17, 40), len = 5 * 864e5; const n = Math.floor((Date.now() - start) / len); const into = (Date.now() - start) / len - n; return { epoch: 173 + n, day: Math.floor(into * 5) + 1, frac: into }; }; const tickEpoch = () => { const e = epochInfo(); if (ep) ep.textContent = `Epoch ${e.epoch} · day ${e.day} of 5`; if (bar) bar.style.width = (e.frac * 100).toFixed(1) + '%'; }; tickEpoch(); const epTimer = setInterval(tickEpoch, 60000); const search = $('.au-search input'); if (search) { const names = { ap3x: 'AP3X', prime: 'PRIME', vector: 'Vector', nexus: 'Nexus', moon: 'Staking reserve', foundation: 'Apex Fusion Foundation', lbank: 'LBank', reactor: 'Reactor', layerzero: 'LayerZero', skyline: 'Skyline', hal8: 'HAL 8', fleet: 'Agents', cardano: 'Cardano', base: 'Base', bsc: 'BNB Chain' }; const dl = document.createElement('datalist'); dl.id = 'au-names'; dl.innerHTML = [...Object.values(names), ...PARTNERS.flatMap(g => g[2].map(x => x[1])), ...(P.items || []).slice(0, 400).map(p => p.t)].filter(Boolean).map(n => `