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 = {} 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 = `${prefix} ${text}` row.appendChild(blob) row.appendChild(msg) if (!id_list[pubkey]) id_list[pubkey] = [row] else id_list[pubkey].push(row) return row }