diff options
| author | 2026-02-27 23:37:44 +0330 | |
|---|---|---|
| committer | 2026-02-27 23:37:44 +0330 | |
| commit | 8ee3112ca09fd70a50c2159629b40700beb2d5c3 (patch) | |
| tree | 42a8d3e5d7898e9d084f77e1345be270be0f5969 /client/public/blobs.ts | |
| parent | small check (diff) | |
first client ver
Diffstat (limited to 'client/public/blobs.ts')
| -rw-r--r-- | client/public/blobs.ts | 154 |
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 |
