/** Build / parse client-side share landing query (no server). */ export type PortraitSharePayload = { type: 'portrait' title: string line: string keywords: string[] } export type RelationSharePayload = { type: 'relation' me: string other: string diff: string keywords: string[] } export type SharePayload = PortraitSharePayload | RelationSharePayload export function buildSharePath(payload: SharePayload): string { const q = new URLSearchParams() q.set('type', payload.type) if (payload.type === 'portrait') { if (payload.title) q.set('t', payload.title) if (payload.line) q.set('l', payload.line) if (payload.keywords.length) q.set('k', payload.keywords.slice(0, 6).join(',')) } else { if (payload.me) q.set('me', payload.me) if (payload.other) q.set('ot', payload.other) if (payload.diff) q.set('d', payload.diff) if (payload.keywords.length) q.set('k', payload.keywords.slice(0, 6).join(',')) } return `/share?${q.toString()}` } export function parseShareQuery(query: Record): SharePayload | null { const type = String(query.type || '') const keywords = String(query.k || '') .split(',') .map((s) => s.trim()) .filter(Boolean) if (type === 'portrait') { const title = String(query.t || '') const line = String(query.l || '') if (!title && !line && !keywords.length) return null return { type: 'portrait', title, line, keywords } } if (type === 'relation') { const me = String(query.me || '') const other = String(query.ot || '') const diff = String(query.d || '') if (!me && !other && !diff) return null return { type: 'relation', me, other, diff, keywords } } return null } export async function copyText(text: string): Promise { try { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text) return true } } catch { /* fall through */ } try { const ta = document.createElement('textarea') ta.value = text ta.style.position = 'fixed' ta.style.left = '-9999px' document.body.appendChild(ta) ta.select() const ok = document.execCommand('copy') document.body.removeChild(ta) return ok } catch { return false } }