aboutsummaryrefslogtreecommitdiff
path: root/client/public/blobs.ts
blob: fcb4ec0137fd0942e65f2321f8ec71261db79b94 (plain) (blame)
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
}