1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
const TAU = Math.PI * 2
type orb = {
color: string
cx: number
cy: number
rx: number
ry: number
fx: number
fy: number
px: number
py: number
sw: number
sh: number
}
export type html = HTMLElement & { _orbs: orb[], _seed: number }
function initBlob(el: html) {
const colors = Array.from({ length: 8 }, (_, i) => el.getAttribute(`data-c${i + 1}`) || '#ffffff')
const seed = Math.random() * 1000
const orbs = colors.map((color, i) => ({
color,
cx: 15 + Math.random() * 70,
cy: 15 + Math.random() * 70,
rx: 8 + Math.random() * 14,
ry: 8 + Math.random() * 14,
fx: 0.018 + Math.random() * 0.022,
fy: 0.015 + Math.random() * 0.020,
px: Math.random() * TAU,
py: Math.random() * TAU,
sw: 70 + Math.random() * 30,
sh: 60 + Math.random() * 35,
}))
el._orbs = orbs
el._seed = seed
}
function renderBlob(el: html, t: number) {
const layers = el._orbs.map(o => {
const x = o.cx + Math.sin(TAU * o.fx * t + o.px) * o.rx
const y = o.cy + Math.cos(TAU * o.fy * t + o.py) * o.ry
return `radial-gradient(ellipse ${o.sw}% ${o.sh}% at ${x.toFixed(1)}% ${y.toFixed(1)}%, ${o.color} 0%, transparent 85%)`
})
const c0 = el._orbs[0]!.color, c1 = el._orbs[4]!.color
layers.push(`linear-gradient(135deg, ${c0} 0%, ${c1} 100%)`)
el.style.background = layers.join(',')
}
const blobs = Array.from(document.querySelectorAll('blob')) as html[]
blobs.forEach(initBlob)
let last = 0
function tick(ts: number) {
const t = ts / 250
if (ts - last >= 40) {
blobs.forEach(b => renderBlob(b, t))
last = ts
}
requestAnimationFrame(tick)
}
requestAnimationFrame(tick)
export function createBlob(seeds: [number, number, number, number, number, number, number, number]) {
function seedToColor(n: number) {
n = Math.abs(n) | 0
const r = (n * 1664525 + 1013904223) & 0xff
const g = (n * 22695477 + 1) & 0xff
const b = (n * 214013 + 2531011) & 0xff
return `#${[r, g, b].map(v => v.toString(16).padStart(2, '0')).join('')}`
}
seeds[4] = seeds[0]
seeds[5] = seeds[1]
seeds[6] = seeds[2]
seeds[7] = seeds[3]
const colors = seeds.map(seedToColor)
const blob = document.createElement('blob') as html
colors.forEach((c, i) => blob.setAttribute(`data-c${i + 1}`, c))
initBlob(blob)
blobs.push(blob)
return blob
}
export let id_list: Record<string, HTMLElement[]> = {}
export function pubkeyToBlobs(pubkey: string): [number, number, number, number, number, number, number, number] {
let bytes: Uint8Array
const b64 = pubkey.replace(/-/g, '+').replace(/_/g, '/')
const padded = b64.padEnd(b64.length + ((4 - (b64.length % 4)) % 4), '=')
bytes = Uint8Array.from(atob(padded), c => c.charCodeAt(0))
const step = Math.floor(bytes.length / 8)
return Array.from({ length: 8 }, (_, i) => bytes[i * step]!) as [number, number, number, number, number, number, number, number]
}
export const decoder = new TextDecoder()
export const encoder = new TextEncoder()
export function addStrings(left: string, right: string): string {
const l = encoder.encode(left)
const r = encoder.encode(right)
const result = new Uint8Array(Math.max(l.length, r.length))
for (let i = 0; i < result.length; i++) {
result[i] = (l[i] ?? 0) ^ (r[i] ?? 0)
}
return Array.from(result, b => b.toString(16).padStart(2, '0')).join('')
}
export function subtractStrings(value: string, key: string): string {
const v = new Uint8Array(value.match(/.{2}/g)!.map(b => parseInt(b, 16)))
const k = encoder.encode(key)
const result = new Uint8Array(Math.max(v.length, k.length))
for (let i = 0; i < result.length; i++) {
result[i] = (v[i] ?? 0) ^ (k[i] ?? 0)
}
return decoder.decode(result)
}
export let info: { keypair: { identity: string, cryptPrivate: string, signPrivate: string } } = {} as any
export let followed_chats: string[] = JSON.parse(localStorage.getItem('followed_chats') ?? '["main"]')
followed_chats = [...new Set(followed_chats)]
// , seeds: [number, number, number, number, number, number, number, number]
export function makeMessage(type: 'in' | 'out' | 'broadcast' | 'system', id: number, prefix: string, text: string, pubkey: string): HTMLElement {
const row = document.createElement('div')
row.className = 'msg-row'
const seeds: [number, number, number, number, number, number, number, number] = pubkey === '0' ? [27, 28, 32, 45, 49, 58, 91, 28] : pubkeyToBlobs(pubkey)
const blob = createBlob(seeds)
if (pubkey !== info?.keypair?.identity) blob.addEventListener('click', () => {
if (confirm('do you want to open a direct message with this user?')) {
const chat = `@${pubkey}`
if (!followed_chats.includes(chat)) {
followed_chats.push(chat)
globalThis.refreshChatList()
}
globalThis.switchChat(chat)
}
})
const msg = document.createElement(`m-${type}`)
msg.innerHTML = `<span class="msg-prefix">${prefix}</span> ${text}`
row.appendChild(blob)
row.appendChild(msg)
if (!id_list[pubkey]) id_list[pubkey] = [row]
else id_list[pubkey].push(row)
return row
}
|