a demonstration replicated social networking web app built with anproto wiredove.net/
social ed25519 protocols
at master 358 lines 12 kB view raw
1// Disabled: reverted to apds.visual buffer from previous implementation. 2/* 3const cache = new Map() 4 5const hashSeed = (value) => { 6 let h = 2166136261 7 for (let i = 0; i < value.length; i += 1) { 8 h ^= value.charCodeAt(i) 9 h = Math.imul(h, 16777619) 10 } 11 return h >>> 0 12} 13 14const mulberry32 = (seed) => () => { 15 let t = (seed += 0x6D2B79F5) 16 t = Math.imul(t ^ (t >>> 15), t | 1) 17 t ^= t + Math.imul(t ^ (t >>> 7), t | 61) 18 return ((t ^ (t >>> 14)) >>> 0) / 4294967296 19} 20 21const decodeKeyBytes = (key) => { 22 if (!key) { return [] } 23 try { 24 const cleaned = String(key).trim() 25 if (typeof atob === 'function') { 26 const binary = atob(cleaned) 27 return Array.from(binary, (ch) => ch.charCodeAt(0)) 28 } 29 } catch {} 30 return Array.from(String(key), (ch) => ch.charCodeAt(0)) 31} 32 33const buildWorld = (key, size) => { 34 const seed = hashSeed(String(key || '')) 35 const rand = mulberry32(seed) 36 const bytes = decodeKeyBytes(key) 37 const hues = [] 38 const total = Math.max(6, Math.min(12, bytes.length)) 39 for (let i = 0; i < total; i += 1) { 40 const value = bytes[i % bytes.length] || Math.floor(rand() * 256) 41 hues.push(Math.floor((value / 255) * 360)) 42 } 43 44 const canvas = document.createElement('canvas') 45 const canvasSize = Math.round(size * 3.2) 46 canvas.width = canvasSize 47 canvas.height = canvasSize 48 const ctx = canvas.getContext('2d') 49 50 const bg = ctx.createLinearGradient(0, 0, canvasSize, canvasSize) 51 bg.addColorStop(0, `hsl(${hues[0]}, 45%, 8%)`) 52 bg.addColorStop(1, `hsl(${hues[1] || hues[0]}, 55%, 14%)`) 53 ctx.fillStyle = bg 54 ctx.fillRect(0, 0, canvasSize, canvasSize) 55 56 const starCount = Math.floor(canvasSize * canvasSize / 900) 57 for (let i = 0; i < starCount; i += 1) { 58 const x = rand() * canvasSize 59 const y = rand() * canvasSize 60 const r = 0.3 + rand() * 1.2 61 const a = 0.2 + rand() * 0.6 62 ctx.fillStyle = `rgba(255, 255, 255, ${a})` 63 ctx.beginPath() 64 ctx.arc(x, y, r, 0, Math.PI * 2) 65 ctx.fill() 66 } 67 68 const radius = canvasSize * (0.52 + rand() * 0.08) 69 const cx = canvasSize * (0.5 + (rand() - 0.5) * 0.08) 70 const cy = canvasSize * (0.5 + (rand() - 0.5) * 0.08) 71 72 const lightAngle = rand() * Math.PI * 2 73 const lightOffsetX = Math.cos(lightAngle) * radius * 0.45 74 const lightOffsetY = Math.sin(lightAngle) * radius * 0.45 75 const surface = ctx.createRadialGradient( 76 cx + lightOffsetX, 77 cy + lightOffsetY, 78 radius * 0.18, 79 cx, 80 cy, 81 radius 82 ) 83 const surfaceStops = [ 84 `hsl(${hues[2] || hues[0]}, 95%, 75%)`, 85 `hsl(${hues[3] || hues[1]}, 90%, 62%)`, 86 `hsl(${hues[4] || hues[2]}, 85%, 48%)`, 87 `hsl(${hues[5] || hues[3]}, 80%, 34%)`, 88 ] 89 surface.addColorStop(0, surfaceStops[0]) 90 surface.addColorStop(0.38, surfaceStops[1]) 91 surface.addColorStop(0.68, surfaceStops[2]) 92 surface.addColorStop(1, surfaceStops[3]) 93 94 ctx.fillStyle = surface 95 ctx.beginPath() 96 ctx.arc(cx, cy, radius, 0, Math.PI * 2) 97 ctx.fill() 98 99 ctx.save() 100 ctx.globalCompositeOperation = 'source-atop' 101 const textureLumps = 140 102 for (let i = 0; i < textureLumps; i += 1) { 103 const px = cx + (rand() - 0.5) * radius * 2 104 const py = cy + (rand() - 0.5) * radius * 2 105 const pr = radius * (0.03 + rand() * 0.12) 106 const ph = hues[(i * 7 + 1) % hues.length] 107 const alpha = 0.08 + rand() * 0.18 108 const lump = ctx.createRadialGradient(px, py, pr * 0.1, px, py, pr) 109 lump.addColorStop(0, `hsla(${ph}, 85%, 60%, ${alpha})`) 110 lump.addColorStop(1, `hsla(${ph}, 85%, 30%, 0)`) 111 ctx.fillStyle = lump 112 ctx.beginPath() 113 ctx.arc(px, py, pr, 0, Math.PI * 2) 114 ctx.fill() 115 } 116 ctx.restore() 117 118 ctx.save() 119 ctx.globalCompositeOperation = 'source-atop' 120 const cracks = 12 121 for (let i = 0; i < cracks; i += 1) { 122 const startAngle = rand() * Math.PI * 2 123 const length = radius * (0.6 + rand() * 0.5) 124 const sx = cx + Math.cos(startAngle) * radius * 0.1 125 const sy = cy + Math.sin(startAngle) * radius * 0.1 126 const ex = sx + Math.cos(startAngle + (rand() - 0.5) * 0.8) * length 127 const ey = sy + Math.sin(startAngle + (rand() - 0.5) * 0.8) * length 128 const c1x = sx + (rand() - 0.5) * radius * 0.6 129 const c1y = sy + (rand() - 0.5) * radius * 0.6 130 const c2x = ex + (rand() - 0.5) * radius * 0.6 131 const c2y = ey + (rand() - 0.5) * radius * 0.6 132 const ch = hues[(i * 5 + 4) % hues.length] 133 ctx.strokeStyle = `hsla(${ch}, 90%, 70%, 0.16)` 134 ctx.lineWidth = radius * (0.006 + rand() * 0.008) 135 ctx.lineCap = 'round' 136 ctx.beginPath() 137 ctx.moveTo(sx, sy) 138 ctx.bezierCurveTo(c1x, c1y, c2x, c2y, ex, ey) 139 ctx.stroke() 140 } 141 ctx.restore() 142 143 ctx.save() 144 ctx.globalCompositeOperation = 'source-atop' 145 const patchCount = 3 + Math.floor(rand() * 4) 146 for (let i = 0; i < patchCount; i += 1) { 147 const px = cx + (rand() - 0.5) * radius * 1.1 148 const py = cy + (rand() - 0.5) * radius * 1.1 149 const pr = radius * (0.22 + rand() * 0.35) 150 const ph = hues[(i * 3 + 1) % hues.length] 151 const ph2 = hues[(i * 3 + 2) % hues.length] 152 const patchGrad = ctx.createRadialGradient(px, py, pr * 0.1, px, py, pr) 153 patchGrad.addColorStop(0, `hsla(${ph}, 90%, 62%, 0.35)`) 154 patchGrad.addColorStop(1, `hsla(${ph2}, 75%, 35%, 0)`) 155 ctx.fillStyle = patchGrad 156 ctx.beginPath() 157 ctx.arc(px, py, pr, 0, Math.PI * 2) 158 ctx.fill() 159 } 160 ctx.restore() 161 162 // Intentionally left without horizontal bands to avoid streaks. 163 164 ctx.save() 165 ctx.globalCompositeOperation = 'source-atop' 166 const terminator = ctx.createRadialGradient( 167 cx + radius * 0.55, 168 cy + radius * 0.1, 169 radius * 0.2, 170 cx + radius * 0.6, 171 cy, 172 radius * 1.1 173 ) 174 terminator.addColorStop(0, 'rgba(0, 0, 0, 0)') 175 terminator.addColorStop(1, 'rgba(0, 0, 0, 0.65)') 176 ctx.fillStyle = terminator 177 ctx.fillRect(0, 0, canvasSize, canvasSize) 178 ctx.restore() 179 180 ctx.save() 181 ctx.globalCompositeOperation = 'source-atop' 182 const auroraCount = 2 + Math.floor(rand() * 2) 183 for (let i = 0; i < auroraCount; i += 1) { 184 const arcRadius = radius * (0.75 + rand() * 0.2) 185 const arcWidth = radius * (0.18 + rand() * 0.12) 186 const arcAngle = (rand() * 0.6 - 0.3) 187 const arcX = cx + Math.cos(arcAngle) * radius * 0.15 188 const arcY = cy - radius * (0.35 + rand() * 0.2) 189 const auroraGrad = ctx.createRadialGradient( 190 arcX, 191 arcY, 192 arcRadius * 0.2, 193 arcX, 194 arcY, 195 arcRadius 196 ) 197 const ah1 = hues[(i * 3 + 2) % hues.length] 198 const ah2 = hues[(i * 3 + 3) % hues.length] 199 auroraGrad.addColorStop(0, `hsla(${ah1}, 95%, 78%, 0.6)`) 200 auroraGrad.addColorStop(1, `hsla(${ah2}, 95%, 68%, 0)`) 201 ctx.strokeStyle = auroraGrad 202 ctx.lineWidth = arcWidth 203 ctx.beginPath() 204 ctx.arc(arcX, arcY, arcRadius, Math.PI * 0.05, Math.PI * 0.95) 205 ctx.stroke() 206 207 const auroraGrad2 = ctx.createRadialGradient( 208 arcX + radius * 0.06, 209 arcY + radius * 0.04, 210 arcRadius * 0.15, 211 arcX + radius * 0.06, 212 arcY + radius * 0.04, 213 arcRadius * 0.85 214 ) 215 const ah3 = hues[(i * 3 + 4) % hues.length] 216 const ah4 = hues[(i * 3 + 5) % hues.length] 217 auroraGrad2.addColorStop(0, `hsla(${ah3}, 98%, 82%, 0.55)`) 218 auroraGrad2.addColorStop(1, `hsla(${ah4}, 95%, 70%, 0)`) 219 ctx.strokeStyle = auroraGrad2 220 ctx.lineWidth = arcWidth * 0.7 221 ctx.beginPath() 222 ctx.arc(arcX + radius * 0.05, arcY + radius * 0.03, arcRadius * 0.92, Math.PI * 0.1, Math.PI * 0.9) 223 ctx.stroke() 224 } 225 ctx.restore() 226 227 ctx.save() 228 ctx.globalCompositeOperation = 'screen' 229 const focusCount = 2 + Math.floor(rand() * 2) 230 for (let i = 0; i < focusCount; i += 1) { 231 const fx = cx + (rand() - 0.5) * radius * 0.6 232 const fy = cy + (rand() - 0.5) * radius * 0.6 233 const fr = radius * (0.2 + rand() * 0.2) 234 const fh = hues[(i * 4 + 4) % hues.length] 235 const highlight = ctx.createRadialGradient(fx, fy, fr * 0.1, fx, fy, fr) 236 highlight.addColorStop(0, `hsla(${fh}, 95%, 85%, 0.55)`) 237 highlight.addColorStop(1, 'rgba(255, 255, 255, 0)') 238 ctx.fillStyle = highlight 239 ctx.beginPath() 240 ctx.arc(fx, fy, fr, 0, Math.PI * 2) 241 ctx.fill() 242 } 243 ctx.restore() 244 245 ctx.save() 246 ctx.globalCompositeOperation = 'screen' 247 const flareHue = hues[(hues.length - 2 + Math.floor(rand() * 3)) % hues.length] 248 const flareCenterX = cx + (rand() - 0.5) * radius * 0.9 249 const flareCenterY = cy + (rand() - 0.5) * radius * 0.9 250 const flareRadius = radius * (0.35 + rand() * 0.2) 251 const flareCore = ctx.createRadialGradient( 252 flareCenterX, 253 flareCenterY, 254 flareRadius * 0.1, 255 flareCenterX, 256 flareCenterY, 257 flareRadius 258 ) 259 flareCore.addColorStop(0, `hsla(${flareHue}, 98%, 88%, 0.7)`) 260 flareCore.addColorStop(0.5, `hsla(${flareHue}, 95%, 80%, 0.4)`) 261 flareCore.addColorStop(1, 'rgba(255, 255, 255, 0)') 262 ctx.fillStyle = flareCore 263 ctx.beginPath() 264 ctx.arc(flareCenterX, flareCenterY, flareRadius, 0, Math.PI * 2) 265 ctx.fill() 266 267 ctx.restore() 268 269 ctx.save() 270 ctx.globalCompositeOperation = 'source-atop' 271 const riverCount = 3 + Math.floor(rand() * 3) 272 for (let i = 0; i < riverCount; i += 1) { 273 const startAngle = rand() * Math.PI * 2 274 const endAngle = startAngle + (rand() * 1.2 - 0.6) 275 const startR = radius * (0.1 + rand() * 0.6) 276 const endR = radius * (0.1 + rand() * 0.6) 277 const sx = cx + Math.cos(startAngle) * startR 278 const sy = cy + Math.sin(startAngle) * startR 279 const ex = cx + Math.cos(endAngle) * endR 280 const ey = cy + Math.sin(endAngle) * endR 281 const c1x = cx + (rand() - 0.5) * radius * 0.8 282 const c1y = cy + (rand() - 0.5) * radius * 0.8 283 const c2x = cx + (rand() - 0.5) * radius * 0.8 284 const c2y = cy + (rand() - 0.5) * radius * 0.8 285 const riverHue = hues[(i * 5 + 2) % hues.length] 286 const riverHue2 = hues[(i * 5 + 3) % hues.length] 287 const riverGrad = ctx.createLinearGradient(sx, sy, ex, ey) 288 riverGrad.addColorStop(0, `hsla(${riverHue}, 95%, 78%, 0.25)`) 289 riverGrad.addColorStop(0.5, `hsla(${riverHue2}, 98%, 82%, 0.35)`) 290 riverGrad.addColorStop(1, `hsla(${riverHue}, 90%, 68%, 0.2)`) 291 ctx.strokeStyle = riverGrad 292 ctx.lineWidth = radius * (0.015 + rand() * 0.02) 293 ctx.lineCap = 'round' 294 ctx.beginPath() 295 ctx.moveTo(sx, sy) 296 ctx.bezierCurveTo(c1x, c1y, c2x, c2y, ex, ey) 297 ctx.stroke() 298 } 299 ctx.restore() 300 301 ctx.save() 302 ctx.strokeStyle = `hsla(${hues[6] || hues[2]}, 95%, 80%, 0.7)` 303 ctx.lineWidth = Math.max(1, canvasSize * 0.02) 304 ctx.shadowBlur = canvasSize * 0.08 305 ctx.shadowColor = `hsla(${hues[7] || hues[3]}, 95%, 80%, 0.7)` 306 ctx.beginPath() 307 ctx.arc(cx, cy, radius * 1.01, 0, Math.PI * 2) 308 ctx.stroke() 309 ctx.restore() 310 311 const bloomCanvas = document.createElement('canvas') 312 bloomCanvas.width = canvasSize 313 bloomCanvas.height = canvasSize 314 const bloomCtx = bloomCanvas.getContext('2d') 315 bloomCtx.drawImage(canvas, 0, 0) 316 ctx.save() 317 ctx.globalCompositeOperation = 'screen' 318 ctx.globalAlpha = 0.35 319 ctx.filter = `blur(${canvasSize * 0.02}px)` 320 ctx.drawImage(bloomCanvas, 0, 0) 321 ctx.filter = 'none' 322 ctx.restore() 323 324 ctx.save() 325 ctx.globalAlpha = 0.04 326 ctx.fillStyle = '#000' 327 const noiseCount = Math.floor(canvasSize * canvasSize / 120) 328 for (let i = 0; i < noiseCount; i += 1) { 329 const x = rand() * canvasSize 330 const y = rand() * canvasSize 331 ctx.fillRect(x, y, 1, 1) 332 } 333 ctx.restore() 334 335 const output = document.createElement('canvas') 336 output.width = size 337 output.height = size 338 const outCtx = output.getContext('2d') 339 outCtx.drawImage(canvas, 0, 0, canvasSize, canvasSize, 0, 0, size, size) 340 return output.toDataURL() 341} 342 343export const visual = async (key, size = 256) => { 344 const cacheKey = `${key || ''}:${size}` 345 let dataUrl = cache.get(cacheKey) 346 if (!dataUrl) { 347 dataUrl = buildWorld(key, size) 348 cache.set(cacheKey, dataUrl) 349 } 350 const img = new Image() 351 img.src = dataUrl 352 img.width = size 353 img.height = size 354 return img 355} 356 357*/ 358export {}