aboutsummaryrefslogtreecommitdiff
path: root/client/public/blobs.ts
diff options
context:
space:
mode:
authorGravatar Moon Flower <mforoud86@gmail.com> 2026-02-27 23:37:44 +0330
committerGravatar Moon Flower <mforoud86@gmail.com> 2026-02-27 23:37:44 +0330
commit8ee3112ca09fd70a50c2159629b40700beb2d5c3 (patch)
tree42a8d3e5d7898e9d084f77e1345be270be0f5969 /client/public/blobs.ts
parentsmall check (diff)
first client ver
Diffstat (limited to 'client/public/blobs.ts')
-rw-r--r--client/public/blobs.ts154
1 files changed, 154 insertions, 0 deletions
diff --git a/client/public/blobs.ts b/client/public/blobs.ts
new file mode 100644
index 0000000..5ba99e9
--- /dev/null
+++ b/client/public/blobs.ts
@@ -0,0 +1,154 @@
+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('')}`
+ }
+
+ 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)
+ 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)
+ //@ts-expect-error
+ globalThis.refreshChatList()
+ }
+ //@ts-expect-error
+ 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
+} \ No newline at end of file