a demonstration replicated social networking web app built with anproto
wiredove.net/
social
ed25519
protocols
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 {}