diff --git a/app.js b/app.js index 693b41d..03825bc 100644 --- a/app.js +++ b/app.js @@ -689,6 +689,168 @@ async function loadSovereigntyStatus() { loadSovereigntyStatus(); +// === SOVEREIGNTY TECH LANDSCAPE === +// Gemini Deep Research findings — 6 tech category panels in an outer ring. +// Toggle with [L]. Panels visible by default. + +const LANDSCAPE_RADIUS = 13.5; +const LANDSCAPE_Y = 2.2; +const LANDSCAPE_FLOAT_AMP = 0.18; +const LANDSCAPE_FLOAT_SPEED = 0.14; + +const landscapeGroup = new THREE.Group(); +scene.add(landscapeGroup); + +/** @type {Array<{sprite: THREE.Sprite, floatPhase: number}>} */ +const landscapeSprites = []; + +let landscapeVisible = true; + +/** + * Creates a canvas texture for a sovereignty tech landscape category panel. + * @param {{ name: string, color: string, score: number, technologies: string[] }} cat + * @returns {THREE.CanvasTexture} + */ +function createLandscapeTexture(cat) { + const W = 360, H = 220; + const canvas = document.createElement('canvas'); + canvas.width = W; + canvas.height = H; + const ctx = canvas.getContext('2d'); + const col = cat.color; + + // Dark background + ctx.fillStyle = 'rgba(0, 6, 18, 0.90)'; + ctx.fillRect(0, 0, W, H); + + // Outer border + ctx.strokeStyle = col; + ctx.lineWidth = 2; + ctx.strokeRect(1, 1, W - 2, H - 2); + + // Inner faint border + ctx.strokeStyle = col; + ctx.lineWidth = 1; + ctx.globalAlpha = 0.25; + ctx.strokeRect(4, 4, W - 8, H - 8); + ctx.globalAlpha = 1.0; + + // Header background tint + ctx.fillStyle = col + '18'; + ctx.fillRect(1, 1, W - 2, 38); + + // Category name + ctx.font = 'bold 22px "Courier New", monospace'; + ctx.fillStyle = '#ffffff'; + ctx.textAlign = 'left'; + ctx.fillText(cat.name.toUpperCase(), 14, 28); + + // Score label + ctx.font = '9px "Courier New", monospace'; + ctx.fillStyle = '#445566'; + ctx.fillText('SOVEREIGNTY SCORE', 14, 50); + + // Score bar background + const barX = 14, barY = 54, barW = W - 28, barH = 9; + ctx.fillStyle = 'rgba(255,255,255,0.06)'; + ctx.fillRect(barX, barY, barW, barH); + + // Score bar fill + ctx.fillStyle = col; + ctx.globalAlpha = 0.72; + ctx.fillRect(barX, barY, (cat.score / 100) * barW, barH); + ctx.globalAlpha = 1.0; + + // Score value + ctx.font = 'bold 12px "Courier New", monospace'; + ctx.fillStyle = col; + ctx.textAlign = 'right'; + ctx.fillText(`${cat.score}%`, W - 14, 62); + ctx.textAlign = 'left'; + + // Separator + ctx.strokeStyle = '#1a3a6a'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(14, 72); + ctx.lineTo(W - 14, 72); + ctx.stroke(); + + // Tech label + ctx.font = '9px "Courier New", monospace'; + ctx.fillStyle = '#445566'; + ctx.fillText('KEY TECHNOLOGIES', 14, 86); + + // Technology list + const maxTech = Math.min(cat.technologies.length, 5); + for (let i = 0; i < maxTech; i++) { + ctx.font = '12px "Courier New", monospace'; + ctx.fillStyle = i === 0 ? '#ddeeff' : '#8899bb'; + ctx.fillText('\u203a ' + cat.technologies[i], 14, 104 + i * 22); + } + + return new THREE.CanvasTexture(canvas); +} + +/** + * Builds landscape panel sprites from loaded data. + * @param {{ categories: Array<{ id: string, name: string, color: string, score: number, technologies: string[] }> }} data + */ +function buildLandscapePanels(data) { + while (landscapeGroup.children.length) landscapeGroup.remove(landscapeGroup.children[0]); + landscapeSprites.length = 0; + + const n = data.categories.length; + data.categories.forEach((cat, i) => { + const angle = (i / n) * Math.PI * 2; + const x = Math.cos(angle) * LANDSCAPE_RADIUS; + const z = Math.sin(angle) * LANDSCAPE_RADIUS; + + const texture = createLandscapeTexture(cat); + const material = new THREE.SpriteMaterial({ + map: texture, + transparent: true, + opacity: 0.88, + depthWrite: false, + }); + const sprite = new THREE.Sprite(material); + sprite.scale.set(6.0, 3.65, 1); + sprite.position.set(x, LANDSCAPE_Y, z); + landscapeGroup.add(sprite); + landscapeSprites.push({ sprite, floatPhase: (i / n) * Math.PI * 2 }); + }); +} + +async function loadLandscape() { + try { + const res = await fetch('./sovereignty-landscape.json'); + if (!res.ok) throw new Error('not found'); + const data = await res.json(); + buildLandscapePanels(data); + } catch { + buildLandscapePanels({ + categories: [ + { id: 'identity', name: 'Identity', color: '#4af0c0', score: 90, technologies: ['Nostr / NIP-01', 'Bitcoin Keys', 'NIP-07 Signing', 'DIDs', 'Taproot Assets'] }, + { id: 'money', name: 'Money', color: '#ffd700', score: 95, technologies: ['Bitcoin L1', 'Lightning / L402', 'Fedimint', 'Taproot', 'Ordinals / Runes'] }, + { id: 'compute', name: 'Compute', color: '#7b5cff', score: 72, technologies: ['Ollama (local LLM)', 'llama.cpp / LM Studio', 'Browser WASM ML', 'Edge Inference', 'Claude API'] }, + { id: 'comms', name: 'Comms', color: '#ff6644', score: 80, technologies: ['Nostr Relays', 'Matrix Protocol', 'Tor Network', 'SimpleX', 'Session'] }, + { id: 'storage', name: 'Storage', color: '#44aaff', score: 68, technologies: ['Nostr Blossom / NIP-96', 'IPFS / Arweave', 'Self-hosted MinIO', 'BTCPay Server', 'Umbrel'] }, + { id: 'governance', name: 'Governance', color: '#ff44aa', score: 62, technologies: ['Multi-sig (Miniscript)', 'Taproot Scripts', 'Bitcoin Covenants', 'Ark Protocol', 'Cashu Mints'] }, + ] + }); + } +} + +loadLandscape(); + +// Toggle landscape visibility with [L] +document.addEventListener('keydown', (e) => { + if ((e.key === 'l' || e.key === 'L') && !e.metaKey && !e.ctrlKey) { + landscapeVisible = !landscapeVisible; + landscapeGroup.visible = landscapeVisible; + } +}); + // === RUNE RING === // 12 Elder Futhark rune sprites in a slow-orbiting ring around the center platform. @@ -884,6 +1046,11 @@ function animate() { rune.sprite.material.opacity = 0.65 + Math.sin(elapsed * 1.2 + rune.floatPhase) * 0.2; } + // Animate sovereignty tech landscape panels — gentle hover float + for (const { sprite, floatPhase } of landscapeSprites) { + sprite.position.y = LANDSCAPE_Y + Math.sin(elapsed * LANDSCAPE_FLOAT_SPEED + floatPhase) * LANDSCAPE_FLOAT_AMP; + } + composer.render(); } diff --git a/sovereignty-landscape.json b/sovereignty-landscape.json new file mode 100644 index 0000000..5b5d410 --- /dev/null +++ b/sovereignty-landscape.json @@ -0,0 +1,49 @@ +{ + "version": "2026-03", + "research": "Gemini Deep Research", + "summary": "Comprehensive sovereignty tech landscape across identity, money, compute, communications, storage, and governance layers.", + "categories": [ + { + "id": "identity", + "name": "Identity", + "color": "#4af0c0", + "score": 90, + "technologies": ["Nostr / NIP-01", "Bitcoin Keys", "NIP-07 Signing", "DIDs", "Taproot Assets"] + }, + { + "id": "money", + "name": "Money", + "color": "#ffd700", + "score": 95, + "technologies": ["Bitcoin L1", "Lightning / L402", "Fedimint", "Taproot", "Ordinals / Runes"] + }, + { + "id": "compute", + "name": "Compute", + "color": "#7b5cff", + "score": 72, + "technologies": ["Ollama (local LLM)", "llama.cpp / LM Studio", "Browser WASM ML", "Edge Inference", "Claude API"] + }, + { + "id": "comms", + "name": "Comms", + "color": "#ff6644", + "score": 80, + "technologies": ["Nostr Relays", "Matrix Protocol", "Tor Network", "SimpleX", "Session"] + }, + { + "id": "storage", + "name": "Storage", + "color": "#44aaff", + "score": 68, + "technologies": ["Nostr Blossom / NIP-96", "IPFS / Arweave", "Self-hosted MinIO", "BTCPay Server", "Umbrel"] + }, + { + "id": "governance", + "name": "Governance", + "color": "#ff44aa", + "score": 62, + "technologies": ["Multi-sig (Miniscript)", "Taproot Scripts", "Bitcoin Covenants", "Ark Protocol", "Cashu Mints"] + } + ] +}